Commits

Anonymous committed 851d824

(no commit message)

Comments (0)

Files changed (21)

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.lispdev.console</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

.settings/org.eclipse.jdt.core.prefs

+#Fri Jan 01 16:47:04 CST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6

META-INF/MANIFEST.MF

+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Console
+Bundle-SymbolicName: org.lispdev.console
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.lispdev.console.Activator
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.jface.text;bundle-version="3.5.1",
+ org.eclipse.ui.editors;bundle-version="3.5.0"
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Export-Package: org.lispdev.console
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .

src/org/lispdev/console/Activator.java

+package org.lispdev.console;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+	// The plug-in ID
+	public static final String PLUGIN_ID = "org.lispdev.console";
+
+	// The shared instance
+	private static Activator plugin;
+	
+	/**
+	 * The constructor
+	 */
+	public Activator() {
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+	 */
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		plugin = this;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+	 */
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/**
+	 * Returns the shared instance
+	 *
+	 * @return the shared instance
+	 */
+	public static Activator getDefault() {
+		return plugin;
+	}
+
+}

src/org/lispdev/console/ClearConsoleAction.java

+package org.lispdev.console;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+
+public class ClearConsoleAction extends Action
+{
+  private LispConsole repl;
+  
+  public ClearConsoleAction(LispConsole repl)
+  {
+    this.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages()
+        .getImageDescriptor(ISharedImages.IMG_ETOOL_CLEAR));
+    this.setDisabledImageDescriptor(PlatformUI.getWorkbench().getSharedImages()
+        .getImageDescriptor(ISharedImages.IMG_ETOOL_CLEAR_DISABLED));
+    this.setToolTipText("Clear Repl");
+    this.repl = repl;
+  }
+  
+  public void run()
+  {
+    repl.clear();
+    repl.startEdit();
+  }
+}

src/org/lispdev/console/ConsoleAutoEdit.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+import org.eclipse.jface.text.DocumentCommand;
+import org.eclipse.jface.text.IAutoEditStrategy;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * @author sk
+ *
+ */
+public class ConsoleAutoEdit implements IAutoEditStrategy
+{
+  private LispConsole repl; 
+
+  public ConsoleAutoEdit(LispConsole repl)
+  {
+    this.repl = repl;
+  }
+  
+  /* (non-Javadoc)
+   * @see org.eclipse.jface.text.IAutoEditStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
+   */
+  public void customizeDocumentCommand(IDocument d, DocumentCommand c)
+  {
+    repl.logTrace("DocCommand = {"+c.length+","+c.offset+","+c.text+"}", 10);
+    if( repl == null ) return;
+    if( !repl.getEditModeFlag() )
+    {
+      //repl.logWarning("Called Auto Edit command when repl is in read-only mode");
+      return;
+    }
+
+    Point sel = repl.getSelectedRange();
+    if( sel.x < repl.getEditOffset() )
+    {
+      sel.y -= repl.getEditOffset() - sel.x;
+      sel.x = repl.getEditOffset();
+    }
+    if(sel.y > 0)
+    {
+      //extend selection to cover overlapping read-only
+      //remove read-only (without text)
+      repl.logTrace("Sel = "+sel.y, 10);
+      Point selnew = repl.computeExpandedEditSelection();
+      if( selnew != null )
+      {
+        repl.toDeletePartitions(selnew);
+        c.offset = selnew.x;
+        c.length = selnew.y - selnew.x;
+      }
+    }
+    else
+    {
+      if( c.offset < repl.getEditOffset() )
+      {
+        repl.logTrace("ReplAutoEdit.customizeDocumentCommand: " +
+            "move carret from read-only to start of edit region",5);
+        c.offset = repl.getEditOffset();
+      }
+      PartitionData pd = repl.getReadOnlyPartition(c.offset,LispConsole.NONE); 
+      if( pd != null )
+      {
+        c.offset = repl.getEditOffset() + pd.start + pd.length;
+      }        
+    }
+  }
+
+}

src/org/lispdev/console/ConsoleConfiguration.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+import org.eclipse.jface.text.IAutoEditStrategy;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
+
+public class ConsoleConfiguration extends TextSourceViewerConfiguration
+{
+  private LispConsole repl;
+  
+  public ConsoleConfiguration(LispConsole r)
+  {
+    repl = r;
+  }
+  
+  public IAutoEditStrategy[] getAutoEditStrategies(ISourceViewer sourceViewer,
+      String contentType)
+  {
+    return new IAutoEditStrategy[]{new ConsoleAutoEdit(repl)};
+  }
+
+
+}

src/org/lispdev/console/ConsoleEchoListener.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.events.VerifyEvent;
+
+/**
+ * @author sk
+ *
+ */
+public class ConsoleEchoListener implements IConsoleInputListener
+{
+  private LispConsole repl;
+  public ConsoleEchoListener(LispConsole repl)
+  {
+    this.repl = repl;
+  }
+  
+  /* (non-Javadoc)
+   * @see org.lispdev.views.repl.IReplInputListener
+   */
+  public void run(String msg, int offset, PartitionData pd, VerifyEvent e)
+  {
+    e.doit = false;
+    if( offset < repl.getEditOffset() ) // append read-only to repl
+    {
+      repl.insertPartInEdit(repl.getDocument().getLength(), msg, pd);
+    }
+    else
+    {
+      repl.stopEdit();
+      String str = "Printed: \""+msg+"\", in context: \""+pd.context
+        +"\", with id "+String.valueOf(pd.id);
+      StyleRange pr = new StyleRange();
+      pr.start = 0;
+      pr.length = "Printed: ".length();
+      pr.fontStyle = SWT.BOLD;
+      StyleRange cn = new StyleRange();
+      cn.start = "Printed: \"".length()+msg.length()+"\", in ".length();
+      cn.length = "context: ".length();
+      cn.fontStyle = SWT.BOLD;
+      repl.appendText(str, 
+          new PartitionData(0,str.length(),"echo_context","0",
+              new StyleRange[]{pr,cn}),true);
+      repl.startEdit();      
+    }
+  }
+  
+}

src/org/lispdev/console/ConsoleEnterTrigger.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+import org.eclipse.swt.events.VerifyEvent;
+
+/**
+ * @author sk
+ *
+ */
+public class ConsoleEnterTrigger extends ConsoleInputTrigger
+{
+  private int stateMask;
+  
+  /**
+   * @param r - repl to connect with
+   * @param stateMask - one of the following: SWT.NONE, or combination of
+   * SWT.ALT, SWT.CTRL, SWT.SHIFT (combination performed using |, example:
+   * SWT.ALT | SWT.CTRL
+   */
+  public ConsoleEnterTrigger(LispConsole repl, int stateMask, int partitionResolutionFlag)
+  {
+    super(repl,partitionResolutionFlag);
+    this.stateMask = stateMask;
+  }
+
+  /* (non-Javadoc)
+   * @see org.lispdev.views.repl.ReplInputTrigger#check(org.eclipse.swt.events.VerifyEvent)
+   */
+  @Override
+  protected boolean check(VerifyEvent event)
+  {
+    return (event.stateMask == stateMask &&
+        (event.keyCode == '\r' || event.keyCode == '\n'));
+  }
+
+}

src/org/lispdev/console/ConsoleInputTrigger.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+import java.util.ArrayList;
+
+import org.eclipse.swt.custom.VerifyKeyListener;
+import org.eclipse.swt.events.VerifyEvent;
+
+/**
+ * @author sk
+ *
+ */
+public abstract class ConsoleInputTrigger implements VerifyKeyListener
+{
+  private LispConsole repl;
+  private int partitionResolutionFlag;
+  protected LispConsole getRepl()
+  {
+    return repl;
+  }
+
+  private ArrayList<IConsoleInputListener> listeners;
+  
+  public ConsoleInputTrigger(LispConsole repl, int partitionResolutionFlag)
+  {
+    this.repl = repl;
+    this.partitionResolutionFlag = partitionResolutionFlag;
+  }
+  
+  public void addInputListener(IConsoleInputListener rl)
+  {
+    if(listeners == null)
+    {
+      listeners = new ArrayList<IConsoleInputListener>();      
+    }
+    listeners.add(rl);
+  }
+  
+  private void run(VerifyEvent event)
+  {
+    if( listeners == null )
+    {
+      repl.logInfo("Input trigger has no listeners");
+      return;
+    }
+    int offset = repl.getTextWidget().getCaretOffset();
+    PartitionData pd = repl.getPartitionAt(offset,partitionResolutionFlag);
+    
+    if( pd == null )
+    {
+      //repl.getTextWidget().
+      //repl.getTextWidget().getCaretOffset()
+      //repl.getTextWidget().print(printer, options)
+      //repl.getTextWidget().scroll(destX, destY, x, y, width, height, all)
+      //repl.getTextWidget().setCaretOffset(offset)
+      //repl.getTextWidget().setFont(font)
+      //repl.getTextWidget().setKeyBinding(key, action)
+      //repl.getTextWidget().setMenu(menu)
+      //repl.getTextWidget().setSelection(start, end)
+      //repl.getTextWidget().showSelection()
+      //repl.addTextInputListener(listener)
+      //repl.addTextListener(listener)
+      //repl.getUndoManager()
+      //repl.prependAutoEditStrategy(strategy, contentType)
+      //repl.prependVerifyKeyListener(listener)
+      //repl.getUndoManager().
+      //repl.revealRange(start, length)
+      //repl.setAutoIndentStrategy(strategy, contentType)
+      //repl.setTabsToSpacesConverter(converter)
+      //repl.setTextColor(color)
+      //repl.setTextColor(color, start, length, controlRedraw)
+      //repl.setUndoManager(undoManager)
+      //repl.CONTENTASSIST_CONTEXT_INFORMATION
+      //repl.CONTENTASSIST_PROPOSALS
+      //repl.DELETE
+      //repl.FORMAT
+      //repl.INFORMATION
+      //repl.PRINT
+      //repl.REDO
+      //repl.SELECT_ALL
+      //repl.SHIFT_LEFT
+      //repl.SHIFT_RIGHT
+      //repl.UNDO
+
+      repl.logError("Partition data for event at "+String.valueOf(offset)
+          +" does not exist, although whole document supposed to be partitioned");
+      return;
+    }
+    String msg = repl.getText(pd);
+    for( IConsoleInputListener rl : listeners )
+    {
+      rl.run(msg,offset,pd,event);
+    }
+  }
+  
+  protected abstract boolean check(VerifyEvent event);
+  
+  /* (non-Javadoc)
+   * @see org.eclipse.swt.custom.VerifyKeyListener#verifyKey(org.eclipse.swt.events.VerifyEvent)
+   */
+  public void verifyKey(VerifyEvent event)
+  {
+    if(check(event)) run(event);
+  }
+
+}

src/org/lispdev/console/DeletedPartitionData.java

+package org.lispdev.console;
+
+import java.util.List;
+
+public class DeletedPartitionData
+{
+  public List<PartitionData> partitions;
+  public String txt;
+  /**
+   * offset from the beginning of edit region
+   */
+  public int offset;
+}

src/org/lispdev/console/FullPartitionData.java

+package org.lispdev.console;
+
+public class FullPartitionData
+{
+  public String txt;
+  public PartitionData partition;
+  
+  public FullPartitionData(String txt, PartitionData pd){
+    this.txt = txt;
+    partition = pd;    
+  }
+  
+  public String toString()
+  {
+    return "txt: "+txt+"; partition = "+partition;
+  }  
+}

src/org/lispdev/console/IConsoleInputListener.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+import org.eclipse.swt.events.VerifyEvent;
+
+/**
+ * @author sk
+ *
+ */
+public interface IConsoleInputListener
+{
+  void run(String msg, int offset, PartitionData pd, VerifyEvent event);
+}

src/org/lispdev/console/ILogExceptionListener.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+/**
+ * Listener for log messages. The messages are sent using log function.
+ */
+public interface ILogExceptionListener
+{
+  /**
+   * Sends exception to listener.
+   */
+  public void log(String msg, Throwable e);
+}

src/org/lispdev/console/ILogListener.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+/**
+ * Listener for log messages. The messages are sent using log function.
+ */
+public interface ILogListener
+{
+  /**
+   * Sends message to log listener.
+   * @param msg
+   */
+  public void log(String msg);
+}

src/org/lispdev/console/IMouseAction.java

+/**
+ * 
+ */
+package org.lispdev.console;
+
+/**
+ * @author sk
+ *
+ */
+public interface IMouseAction
+{
+  void run();
+}

src/org/lispdev/console/LispConsole.java

+/**
+ *
+ */
+package org.lispdev.console;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.DefaultPositionUpdater;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextOperationTarget;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.TextViewerUndoManager;
+import org.eclipse.jface.text.source.AnnotationModel;
+import org.eclipse.jface.text.source.IVerticalRuler;
+import org.eclipse.jface.text.source.projection.ProjectionViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.custom.VerifyKeyListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.VerifyEvent;
+import org.eclipse.swt.events.VerifyListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+
+/**
+ * <p>Console-like viewer with facilities to implement lisp repl similar to
+ * slime-repl.</p>
+ *
+ * <p><b>Usage:</b></p>
+ * <b>TODO</b> Probably some API is open for other unintended ways.<br/>
+ * API will be extended to accommodate functionality necessary for slime style repl.
+ * <p><b>Logging facility:</b> add listeners, if no listener of particular type is
+ * added then prints to console, traces have level, by setting appropriate
+ * variable we can say if we want trace to print, or set clear not to print
+ * (TODO: describe API better, but this is a minor point)</p>
+ *
+ * <p>All text in LispConsole is related to a partition, which has context, id, etc.
+ * It is possible to append text without context, etc., but then it is in null
+ * context</p>
+ *
+ * <ul>
+ * <li/> LispConsole starts empty in read-only mode
+ * <li/> {@link #startEdit()} functions print prompt and switch LispConsole to edit mode.
+ * All editing is done within prompt context. Only text after prompt can
+ * be edited. It is possible to insert readOnly partitions in edit text
+ * (for example copy from LispConsole history). Use {@link #insertPartInEdit()} function.
+ * These partitions are read only and while editing behave like a single character
+ * (i.e. del and backspace removes whole partition).
+ * (TODO: also deal in similar way with copy-paste)
+ * <li/> {@link #stopEdit()} stops editing mode, rendering all just edited mode
+ * read-only and collecting it in single partition. If called when LispConsole is
+ * in read-only mode doesn't do anything.
+ * <li/> {@link #appendText()} functions call stopEdit and then appends text
+ *  with given partition data
+ * <li/> undo works only in last editing region
+ * <li/> {@link #clear()} clears all LispConsole history, and puts it in read-only mode
+ * (like when LispConsole starts)
+ * <li/> {@link #getPartitionAt()} returns partition at particular offset
+ * <li/> {@link #getEditText()} returns text that is currently being edited
+ * <li/> {@link #getText()} returns text given partition
+ * </ul>
+ * <p><b>Key listeners:</b> API provides special key listeners that are attached
+ * using standard appendVerifyKeyListener (prepend.. etc.) functions
+ * (from parent ProjectionViewer).
+ * LispConsole keyListener API facilitates a way of working with LispConsole partitions.
+ * It consists of two components: triggers and listeners.</p>
+ * <ul>
+ * <li/> Triggers: {@link ConsoleInputTrigger} - abstract class that implements
+ * VerifyKeyListener
+ * <li/> Listeners: {@link IConsoleInputListener}
+ * <li/> a trigger waits for key event for which it is registered and then calls
+ * all {@link IConsoleInputListeners} that are registered with it
+ * <li/> {@link ConsoleInputTrigger} is extended by specifying abstract {@code check}
+ * method, which checks for key combination which fires this trigger
+ * <li/> listeners are registered with trigger using function
+ * {@link ConsoleInputTrigger#addInputListener}. When trigger is fired,
+ * it calls all listeners in turn
+ * (TODO: should make listeners return boolean and stop at first true (handled))
+ * <li/> One implementation of trigger is provided by {@link ConsoleEnterTrigger}
+ * <li/> Example implementation of listener: {@link ConsoleEchoListener}
+ * </ul>
+ *
+ * <p><b>Example:</b></p>
+ * <pre>
+
+package org.lispdev.views;
+
+import org.lispdev.console.*;
+
+import org.eclipse.jface.text.source.VerticalRuler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.part.ViewPart;
+
+public class ReplView extends ViewPart
+{
+  public static final String ID = "test.replView";
+
+  public LispConsole repl;
+  private Label info;
+
+
+  @Override
+  public void createPartControl(Composite parent)
+  {
+    GridLayout layout = new GridLayout(1, false);
+    layout.marginLeft = 1;
+    layout.marginTop = 1;
+    layout.marginRight = 1;
+    layout.marginBottom = 1;
+    parent.setLayout(layout);
+
+    GridData gd;
+
+    info = new Label(parent, SWT.BORDER);
+    gd = new GridData();
+    gd.horizontalAlignment = GridData.FILL;
+    gd.grabExcessHorizontalSpace = true;
+    gd.grabExcessVerticalSpace = false;
+    info.setLayoutData(gd);
+    info.setText("Status label for Repl");
+
+    // Put a border around our text viewer
+    Composite comp = new Composite(parent, SWT.BORDER);
+    layout = new GridLayout(1, false);
+    layout.marginLeft = 0;
+    layout.marginTop = 0;
+    layout.marginRight = 0;
+    layout.marginBottom = 0;
+    layout.horizontalSpacing = 0;
+    layout.marginHeight = 0;
+    layout.marginWidth = 0;
+    comp.setLayout(layout);
+    gd = new GridData();
+    gd.horizontalAlignment = GridData.FILL;
+    gd.verticalAlignment = GridData.FILL;
+    gd.grabExcessHorizontalSpace = true;
+    gd.grabExcessVerticalSpace = true;
+    comp.setLayoutData(gd);
+
+    repl = new LispConsole(comp, new VerticalRuler(10),
+        SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.LEFT | SWT.BORDER);
+    repl.getControl().setLayoutData(gd);
+    //repl.getTextWidget().setFont(newFont);
+
+    ConsoleInputTrigger inputTrigger = new ConsoleEnterTrigger(repl,SWT.NONE,LispConsole.BEFORE);
+    ConsoleEchoListener echo = new ConsoleEchoListener(repl);
+    inputTrigger.addInputListener(echo);
+    //repl.appendVerifyKeyListener(inputTrigger);
+    repl.getTextWidget().addVerifyKeyListener(inputTrigger);
+    //"Echo>","echo_prompt","0"
+    repl.setPrompt(new Prompt("Echo>","echo_prompt","0",null,null,SWT.BOLD,true));
+    repl.startEdit();
+  }
+
+  @Override
+  public void setFocus()
+  {
+  }
+
+}
+ * </pre>
+ *
+ *
+ * @author sk
+ *
+ */
+public class LispConsole extends ProjectionViewer
+{
+  private ClearConsoleAction clearAction;
+  private ConsoleConfiguration config;
+  private int MAX_UNDO = 1000;
+  /**
+   * Set maximal number of undo operations.
+   * Becomes active in next edit mode session.
+   */
+  public void setMaxUndoLevels(int m)
+  {
+    MAX_UNDO = m;
+  }
+
+  /**
+   * Edit mode flag. If true - can append text using keyboard. Otherwise can
+   * append text only using appendText functions.
+   */
+  private boolean editFlag = false;
+  private void setEditModeFlag(boolean inEditMode)
+  {
+    this.editFlag = inEditMode;
+  }
+  /**
+   * @return <code>true</code> if in edit mode, or <code>false</code> otherwise
+   */
+  public boolean getEditModeFlag()
+  {
+    return editFlag;
+  }
+
+  private IDocument doc;
+  /**
+   * Start of editable part in edit mode. Undefined in read-only mode.
+   */
+  private int editOffset;
+  private void setEditOffset(int offset)
+  {
+    editOffset = offset;
+  }
+  /**
+   * @return starting offset of editing region
+   */
+  public int getEditOffset()
+  {
+    return editOffset;
+  }
+
+
+  private Prompt prompt;
+
+  public void setPrompt(Prompt prompt)
+  {
+    this.prompt = prompt;
+  }
+
+  /**
+   * Last edit context = Last prompt context + "." + this string
+   */
+  private final static String EDIT_CONTEXT = "_edit_context__";
+  /**
+   * Dummy new line context
+   */
+  private final static String NEW_LINE_CONTEXT = "_new_line_context__";
+  /**
+   * Null context (when data is not given)
+   */
+  private final static String NULL_CONTEXT = "_null_context__";
+
+  /**
+   * Registry that holds top level partitions.
+   */
+  private List<PartitionData> partitionRegistry;
+  /**
+   * Hold style and other information of current (if in edit mode),
+   * or last (if in read-only mode) edit region. In edit mode
+   * each time this partition is accessed all offsets
+   * in this partition should be updated using {@code readOnlyPositions}
+   * hash map.
+   */
+  private PartitionData editPartition;
+
+  /**
+   * Positions hold offsets associated with partition data.
+   */
+  private HashMap<PartitionData,Position> readOnlyPositions;
+
+  private String printReadOnlyPositions()
+  {
+    String res = "{";
+    if(readOnlyPositions != null)
+    {
+      for( PartitionData pd : readOnlyPositions.keySet() )
+      {
+        Position pos = readOnlyPositions.get(pd);
+        res += "\n{"+pd+"= Pos {"+pos.offset+","+pos.length+","+pos.isDeleted()+"}";
+      }
+    }
+    res += "}";
+    return res;
+  }
+
+  private String READ_ONLY_CATEGORY = "repl.read.only.position.category";
+
+  private Stack<DeletedPartitionData> deletedPartitions;
+
+  final TextViewerUndoManager undoManager;
+
+  /**
+   * @return Text in current edit region, or <code>null</code> if in read-only
+   *         mode
+   */
+  public String getEditText()
+  {
+    logTraceEntry("",7);
+    String res = null;
+    if(getEditModeFlag())
+    {
+      try
+      {
+        res = doc.get(getEditOffset(), doc.getLength() - getEditOffset());
+      }
+      catch(BadLocationException e)
+      {
+        logException("getEditText(): Failed", e);
+      }
+    }
+    logTraceReturn(res,7);
+    return res;
+  }
+
+  /**
+   * Finds Position of a partition in <code>partitionRegistry</code>, which
+   * contains <code>offset</code> and assuming that this partition is between
+   * (and including) <code>from</code> and <code>to</code>.
+   *
+   * @note This is a helper function for recursive algorithm in public function
+   *       <code>getPartitionPosition</code>. The following algorithm is
+   *       used (where <code>offset[i]</code> - is offset of
+   *       <code>i</code>'th partition):<br>
+   *       - Ensure that <code>offset[from] < offset < offset[to]</code><br>
+   *       - If <code>to == from + 1</code> return <code>from</code><br>
+   *       - Define <code>midOffset = offset[(from + to)/2]</code><br>
+   *       - If <code>offset == midOffset</code><br>
+   *           - if <code>partitionResolutionFlag == NONE</code> return -1
+   *           - if <code>partitionResolutionFlag == AFTER</code>
+   *             return (from + to)/2
+   *           - else (when <code>partitionResolutionFlag == BEFORE</code>)
+   *             return (from + to)/2 - 1
+   *       - If <code>offset > offset[(from + to)/2]</code>, set
+   *         <code>from = (from + to)/2</code>,<br>
+   *       - otherwise set <code>to = (from + to)/2</code>
+   *
+   * @param offset -
+   *          search for partition containing this offset
+   * @param from -
+   *          lower bound on index
+   * @param to -
+   *          upper bound on index
+   * @return -1 if no <code>partition</code> found, or index in
+   *         <code>partitionRegistry</code> otherwise
+   */
+  private int getPartitionPosition(int offset, int from, int to,
+      int partitionResolutionFlag)
+  {
+    if(doc == null || offset < 0 || offset > doc.getLength())
+    {
+      logTrace("getPartitionPosition:Invalid offset",6);
+      return -1;
+    }
+
+    // process boundary cases
+    if(partitionRegistry == null )
+    {
+      logTrace("getPartitionPosition:partitionRegistry is empty",6);
+      return -1;
+    }
+    if( partitionRegistry.get(from).start > offset )
+    {
+      logTrace("getPartitionPosition: offset = "+offset
+          +" is outside of starting partition: "+ partitionRegistry.get(from).start,6);
+      return -1;
+    }
+    if( partitionRegistry.get(to).start + partitionRegistry.get(to).length < offset)
+    {
+      logTrace("getPartitionPosition: offset = "+offset
+          +" is outside of ending partition: "
+          + partitionRegistry.get(to).start + partitionRegistry.get(to).length,6);
+      return -1;
+    }
+
+    if(partitionRegistry.get(from).start == offset)
+    {
+      return resolveBoundary(from,partitionResolutionFlag);
+    }
+
+    if(partitionRegistry.get(to).start < offset)
+    {
+      return to;
+    }
+
+    if(partitionRegistry.get(to).start == offset)
+    {
+      return resolveBoundary(to,partitionResolutionFlag);
+    }
+
+    // we now know that offset[from] < offset < offset[to]
+    if(from + 1 == to)
+    {
+      return from;
+    }
+
+    int inew = (from + to) / 2;
+    int midOffset = partitionRegistry.get(inew).start;
+    if(offset == midOffset)
+    {
+      return resolveBoundary(inew,partitionResolutionFlag);
+    }
+    if(offset > partitionRegistry.get(inew).start)
+    {
+      return getPartitionPosition(offset, inew, to,
+          partitionResolutionFlag);
+    }
+    else
+    {
+      return getPartitionPosition(offset, from, inew,
+          partitionResolutionFlag);
+    }
+  }
+
+  private int resolveBoundary(int i, int partitionResolutionFlag)
+  {
+    if( partitionResolutionFlag == NONE )
+    {
+      return -1;
+    }
+    if( partitionResolutionFlag == BEFORE )
+    {
+      if( i > 0 )
+      {
+        return i - 1;
+      }
+      else
+      {
+        return -1;
+      }
+    }
+    //if( partitionResolutionFlag == AFTER )
+    {
+      return i;
+    }
+  }
+
+  /**
+   * Partition resolution flag signaling that if offset falls
+   * between partitions, return null
+   */
+  static final public int NONE = 0;
+  /**
+   * Partition resolution flag signaling that if offset falls
+   * between partitions, return the one that is before offset, or null if offset
+   * corresponds to the beginning of the document
+   */
+  static final public int BEFORE = 1;
+  /**
+   * Partition resolution flag signaling that if offset falls
+   * between partitions, return the one that is after offset, or null if offset
+   * corresponds to the end of the document
+   */
+  static final public int AFTER = 2;
+
+  /**
+   * @return partition containing <code>offset</code>. When <code>offset</code>
+   * falls right between two partition <code>partitionResolutionFlag</code>
+   * is used:
+   * BEFORE - returns partition before offset,
+   * or null if in the beginning of document
+   * AFTER - returns partition after offset, or null if in the end of document
+   * NONE - returns null
+   */
+  public PartitionData getPartitionAt(int offset, int partitionResolutionFlag)
+  {
+    logTraceEntry(String.valueOf(offset),5);
+    PartitionData res;
+    // first check boundary conditions
+    if( (partitionResolutionFlag == BEFORE || partitionResolutionFlag == NONE) &&
+        0 == offset )
+    {
+      res = null;
+    }
+    else if( (partitionResolutionFlag == AFTER || partitionResolutionFlag == NONE) &&
+        doc.getLength() == offset )
+    {
+      res = null;
+    }
+    else if(offset > getEditOffset())
+    {
+      logTrace("getPartitionAt: editPartition",7);
+      res = getCurrentEditPartition();
+    }
+    else if( offset == getEditOffset() && partitionResolutionFlag == NONE )
+    {
+      res = null;
+    }
+    else if ( offset == getEditOffset() && partitionResolutionFlag == AFTER )
+    {
+      res = getCurrentEditPartition();
+    }
+    else if(partitionRegistry == null)
+    {
+      logWarning("getPartitionAt: empty registry");
+      res = null;
+    }
+    // now we sure that 0 < offset < editOffset (in case of NONE and AFTER)
+    // and 0 < offset <= editOffset (in case of BEFORE)
+    else
+    {
+      int i = getPartitionPosition(offset, 0, partitionRegistry.size() - 1,
+          partitionResolutionFlag);
+      if(i < 0)
+      {
+        logWarning("getPartitionAt: no partition at "+ String.valueOf(offset));
+        res = null;
+      }
+      else
+      {
+        res = partitionRegistry.get(i);
+        logTrace("getPartitionAt: found partition",7);
+      }
+    }
+    logTraceReturn(String.valueOf(res),5);
+    return res;
+  }
+
+  /**
+   * @return current edit partition data if <code>inEditMode = true</code>, or
+   * <code>null</code> otherwise
+   */
+  private PartitionData getCurrentEditPartition()
+  {
+    if( getEditModeFlag() )
+    {
+      editPartition.length = doc.getLength() - getEditOffset();
+      if( editPartition.children != null )
+      {
+        for( PartitionData pdc : editPartition.children )
+        {
+          Position pos = readOnlyPositions.get(pdc);
+          if( pos == null )
+          {
+            logError("getCurrentEditPartition: pos == null");
+          }
+          else
+          {
+            pdc.start = pos.getOffset() - getEditOffset();
+          }
+        }
+      }
+      return editPartition;
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+  /**
+   * @return text corresponding to partition <code>p</code>,
+   * or <code>null</code> if partition is invalid.
+   */
+  public String getText(PartitionData p)
+  {
+    logTraceEntry(String.valueOf(p),7);
+    String res = null;
+    if(p == null && doc == null)
+    {
+      logWarning("getText: cannot get text when p = "
+          +String.valueOf(p)+", and doc = " + String.valueOf(doc));
+    }
+    else
+    {
+      try
+      {
+        res = doc.get(p.start, p.length);
+      }
+      catch(BadLocationException e)
+      {
+        logException("Could not get text for partition", e);
+      }
+    }
+    logTraceReturn(res,7);
+    return res;
+  }
+
+  public ClearConsoleAction getClearAction()
+  {
+    return clearAction;
+  }
+
+  /**
+   * Find how to extend selection (get new offsets for selection),
+   * so that it doesn't half cover any read-only.
+   * @return new selection
+   */
+  public Point computeExpandedEditSelection()
+  {
+    final int tracelvl = 10;
+    Point sel = getTextWidget().getSelection();
+    logTrace("editOffset = "+getEditOffset(), tracelvl);
+    logTrace("selection = "+sel.toString(),tracelvl);
+    // 1. adjust x
+    if( sel.x < getEditOffset() )
+    {
+      if( sel.y <= getEditOffset() )
+      {
+        return null;
+      }
+      else
+      {
+        sel.x = getEditOffset();
+      }
+    }
+    else
+    {
+      PartitionData pdx = getReadOnlyPartition(sel.x, LispConsole.AFTER);
+      if( pdx != null )
+      {
+        sel.x = getEditOffset() + pdx.start;
+      }
+    }
+
+    // 2. adjust y
+    if( sel.y <= sel.x )
+    {
+      sel.y = sel.x;
+    }
+    PartitionData pdy = getReadOnlyPartition(sel.y, LispConsole.BEFORE);
+    if( pdy != null )
+    {
+      sel.y = getEditOffset() + pdy.start + pdy.length;
+    }
+
+    return sel;
+  }
+
+  /**
+   * Given selection offset, create entry in deletedPartitions for corresponding
+   * partitions in the selection and remove these partitions from
+   * edit area.
+   * @param selection region from which to delete partitions (it is assumed
+   * that this selection is a result of {@link #computeExandedEditSelection()})
+   */
+  public void toDeletePartitions(Point selection)
+  {
+    if( selection == null || editPartition == null
+        || editPartition.children == null )
+    {
+      return;
+    }
+
+    int x = selection.x - getEditOffset();
+    int y = selection.y - getEditOffset();
+    DeletedPartitionData dp = new DeletedPartitionData();
+    dp.txt = getEditText().substring(x,y);
+    dp.offset = x;
+    dp.partitions = new ArrayList<PartitionData>();
+    for( PartitionData pd : getCurrentEditPartition().children )
+    {
+      if( pd.start >= x && pd.start + pd.length <= y )
+      {
+        dp.partitions.add(pd);
+        Position pos = readOnlyPositions.get(pd);
+        try
+        {
+          doc.removePosition(READ_ONLY_CATEGORY, pos);
+        }
+        catch(BadPositionCategoryException e1)
+        {
+          logException("deletePartInEdit: could not delete position",e1);
+        }
+        readOnlyPositions.remove(pd);
+      }
+    }
+    for( PartitionData pd : dp.partitions )
+    {
+      editPartition.children.remove(pd);
+    }
+    deletedPartitions.push(dp);
+  }
+
+  /**
+   * Extends selection to be in edit region and cover read-only
+   * partitions. Then delete text and partitions.
+   * Change this function to do the following:
+   * 1. Find how to extend selection (get new offsets for selection),
+   *    so that it doesn't half cover any read-only.
+   * 2. Given selection offset, delete all read-only partitions in it
+   * 3. Delete text.
+   */
+  public void deleteEditSelectionPartitions()
+  {
+    Point sel = computeExpandedEditSelection();
+    if( sel == null )
+    {
+      return;
+    }
+
+    toDeletePartitions(sel);
+    try
+    {
+      doc.replace(sel.x, sel.y - sel.x, "");
+    }
+    catch(BadLocationException e)
+    {
+      logException("Should not get here", e);
+    }
+    getTextWidget().setSelection(sel.x,sel.x);
+  }
+
+  public LispConsole(Composite parent, IVerticalRuler ruler, int styles)
+  {
+    super(parent, ruler, null, false, styles);
+    setEditable(true);
+    config = new ConsoleConfiguration(this);
+    configure(config);
+
+    clearAction = new ClearConsoleAction(this);
+
+    doc = new Document();
+    partitionRegistry = new ArrayList<PartitionData>();
+    // LispDocumentProvider.connectPartitioner(doc);
+    setDocument(doc, new AnnotationModel());
+    showAnnotations(false);
+    showAnnotationsOverview(false);
+    readOnlyPositions = new HashMap<PartitionData,Position>();
+    deletedPartitions = new Stack<DeletedPartitionData>();
+    undoManager = new TextViewerUndoManager(MAX_UNDO);
+    iniUndoManager();
+    disconnectUndoManager();
+    setEditModeFlag(false);
+    setEditOffset(0);
+    // context menu
+    Menu menu = new Menu(getTextWidget());
+    MenuItem clear = new MenuItem(menu,SWT.PUSH);
+    clear.setText("Clear");
+    clear.addListener(SWT.Selection, new Listener() {
+      public void handleEvent(Event e) {
+        clearAction.run();
+      }
+    });
+    getTextWidget().setMenu(menu);
+    // end context menu
+
+    doc.addPositionUpdater(new DefaultPositionUpdater(READ_ONLY_CATEGORY));
+    appendVerifyKeyListener(new VerifyKeyListener()/*ReadOnlyBackspaceDel(this)*/
+    {
+      public void verifyKey(VerifyEvent event)
+      {
+        final int tracelvl = 10;
+        if( !getEditModeFlag() )
+        {
+          event.doit = false;
+          return;
+        }
+        logTrace(event.toString(),tracelvl);
+        if( !(event.keyCode == SWT.DEL || event.keyCode == SWT.BS
+            || event.character != '\0') )
+        {
+          logTrace("Not text changing",tracelvl);
+          return;
+        }
+        if( isUndoKeyPress(event) || isRedoKeyPress(event) )
+        {
+          logTrace("Undo or redo",tracelvl);
+          return;
+        }
+        Point sel = getTextWidget().getSelection();
+        if( sel.x < getEditOffset() && sel.y < getEditOffset() )
+        {
+          return;
+        }
+        else if ( sel.x == sel.y )
+        {
+          if( event.keyCode == SWT.DEL )
+          {
+            PartitionData pd = getReadOnlyPartition(sel.x, LispConsole.AFTER);
+            if( pd != null )
+            {
+              deletePartInEdit(pd);
+              event.doit = false;
+            }
+            return;
+          }
+          else if( event.keyCode == SWT.BS )
+          {
+            if( sel.x == getEditOffset() )
+            {
+              event.doit = false;
+              return;
+            }
+            PartitionData pd = getReadOnlyPartition(sel.x, LispConsole.BEFORE);
+            if( pd != null )
+            {
+              deletePartInEdit(pd);
+              event.doit = false;
+            }
+            return;
+          }
+        }
+      }
+
+    });
+  }
+
+  /**
+   * Initializes undo manager.
+   */
+  private void iniUndoManager()
+  {
+    // add listeners
+    undoManager.connect(this);
+    setUndoManager(undoManager);
+
+    final StyledText styledText = getTextWidget();
+    styledText.addVerifyListener(new VerifyListener()
+    {
+      public void verifyText(VerifyEvent e)
+      {
+        undoManager.endCompoundChange();
+      }
+    });
+
+    styledText.addKeyListener(new KeyListener()
+    {
+      public void keyPressed(KeyEvent e)
+      {
+        final int tracelvl = 10;
+        logTrace(e.toString(),tracelvl);
+        logTrace("Selection = {"+getTextWidget().getSelection().toString()+"}",tracelvl);
+        String before = null;
+        if(isUndoKeyPress(e) && canDoOperation(ITextOperationTarget.UNDO))
+        {
+          logTrace("before undo - edit: "+editPartition,tracelvl);
+          logTrace("before undo - readonly: "+printReadOnlyPositions(),tracelvl);
+          before = getEditText();
+          doOperation(ITextOperationTarget.UNDO);
+          logTrace("after undo - edit: "+editPartition,tracelvl);
+          logTrace("after undo - readonly: "+printReadOnlyPositions(),tracelvl);
+        }
+        else if(isRedoKeyPress(e) && canDoOperation(ITextOperationTarget.REDO))
+        {
+          logTrace("before redo - edit: "+editPartition,tracelvl);
+          logTrace("before redo - readonly: "+printReadOnlyPositions(),tracelvl);
+          before = getEditText();
+          doOperation(ITextOperationTarget.REDO);
+          logTrace("after redo - edit: "+editPartition,tracelvl);
+          logTrace("after redo - readonly: "+printReadOnlyPositions(),tracelvl);
+        }
+        if(before != null)
+        {
+          String sel = styledText.getSelectionText();
+          Point selection = styledText.getSelectionRange();
+          logTrace("sel = "+sel,tracelvl);
+          logTrace("selection = "+selection,tracelvl);
+          if( sel.length() > 0 ) // restored text
+          {
+            DeletedPartitionData last =
+              deletedPartitions.peek();
+            logTrace("last deleted partition = "+last,tracelvl);
+            if( last.txt.equals(sel)
+                && selection.x == last.offset + getEditOffset() )
+            {
+              for( PartitionData pd : last.partitions )
+              {
+                logTrace("creating read only partition: "+pd,tracelvl);
+                createReadOnlyPartition(pd.start+getEditOffset(), pd.length, pd);
+              }
+              deletedPartitions.pop();
+            }
+          }
+          else // potentially removed text
+          {
+            String after = getEditText();
+            int length = before.length() - after.length();
+            logTrace("length = "+length,tracelvl);
+            if( length > 0 ) //text was removed, check if need to delete partitions
+            {
+              int offset = selection.x - getEditOffset();
+              String diff = before.substring(offset,offset + length);
+              logTrace("offset = "+offset,tracelvl);
+              logTrace("diff = "+diff,tracelvl);
+              logTrace("editPartition = "+editPartition,tracelvl);
+              logTrace("readOnlyPos = "+printReadOnlyPositions(),tracelvl);
+              // find if there is inside the deleted text
+              if( editPartition != null && editPartition.children != null
+                  && editPartition.children.size() > 0 )
+              {
+                List<PartitionData> dp = new ArrayList<PartitionData>();
+                for( PartitionData pd : getCurrentEditPartition().children )
+                {
+                  if( pd.start >= offset && pd.start < offset + diff.length() )
+                  {
+                    logTrace("pd = "+pd,tracelvl);
+                    Position pos = readOnlyPositions.get(pd);
+                    try
+                    {
+                      doc.removePosition(READ_ONLY_CATEGORY, pos);
+                    }
+                    catch(BadPositionCategoryException e1)
+                    {
+                      logException("deletePartInEdit: could not delete position",e1);
+                    }
+                    dp.add(pd);
+                    readOnlyPositions.remove(pd);
+                  }
+                }
+                for( PartitionData pd : dp )
+                {
+                  editPartition.children.remove(pd);
+                }
+                if( dp.size() > 0 )
+                {
+                  DeletedPartitionData dpd = new DeletedPartitionData();
+                  dpd.offset = offset;
+                  dpd.partitions = dp;
+                  dpd.txt = diff;
+                  deletedPartitions.push(dpd);
+                }
+              }
+            }
+          }
+          logTrace("after processing - edit: "+editPartition,tracelvl);
+          logTrace("after processing - readonly: "+printReadOnlyPositions(),tracelvl);
+        }
+      }
+
+      public void keyReleased(KeyEvent e)
+      {
+        // do nothing
+      }
+    });
+  }
+
+  // TODO: should the combination be customizable?
+  private boolean isUndoKeyPress(KeyEvent e)
+  {
+    // CTRL + z
+    return ((e.stateMask & SWT.CONTROL) > 0)
+        && ((e.keyCode == 'z') || (e.keyCode == 'Z'));
+  }
+
+  private boolean isRedoKeyPress(KeyEvent e)
+  {
+    // CTRL + y
+    return ((e.stateMask & SWT.CONTROL) > 0)
+        && ((e.keyCode == 'y') || (e.keyCode == 'Y'));
+  }
+
+  public void setCaret(int offset)
+  {
+    setSelectedRange(Math.min(offset, doc.getLength()), 0);
+  }
+
+  public void setCaretToEnd()
+  {
+    setCaret(doc.getLength());
+  }
+
+  /**
+   * Handles disconnection of undo manager (to prevent collecting undo
+   * operations when editor is not in editing mode)
+   */
+  private void disconnectUndoManager()
+  {
+    undoManager.reset();
+    undoManager.disconnect();
+  }
+
+  /**
+   * Handles connection to undo manager
+   */
+  private void connectUndoManager()
+  {
+    undoManager.connect(this);
+    undoManager.reset();
+    undoManager.setMaximalUndoLevel(MAX_UNDO);
+  }
+
+  /**
+   * Prints prompt and then switches to edit mode. If Repl is in edit mode,
+   * this operation ends current edit session (by calling
+   * <code>stopEdit()</code>) and starts new edit session.
+   * @param prompt - prompt data describing what to print and how to format.
+   */
+  public void startEdit(Prompt prompt)
+  {
+    logTraceEntry(prompt.toString(),7);
+    if( getEditModeFlag() )
+    {
+      logTrace("startEdit: called in edit mode",7);
+      stopEdit();
+    }
+    appendText(prompt.prompt, prompt.partition.clone(), prompt.onNewLine);
+    connectUndoManager();
+    setEditModeFlag(true);
+    editPartition = new PartitionData(getEditOffset(),0,
+        prompt.partition.context+"."+EDIT_CONTEXT,prompt.partition.id);
+    logTrace("startEdit: at offset = " + String.valueOf(getEditOffset())
+        + ", with prompt \"" + prompt + "\", and context \""
+        + prompt.partition.context
+        + "\"",4);
+    getTextWidget().setCaretOffset(getDocument().getLength());
+    logTraceReturn("",7);
+  }
+
+  public void startEdit()
+  {
+    startEdit(prompt);
+  }
+
+  /**
+   * Checks that all text is partitioned
+   * and partitions and styles do not overlap.
+   * @return true if pass sanity check.
+   */
+  public boolean sanityCheck()
+  {
+    if( doc.getLength() == 0 )
+    {
+      //empty document but some partitions
+      if(partitionRegistry != null && partitionRegistry.size() > 0)
+      {
+        logError("Empty Repl, but nonempty partition list");
+        return false;
+      }
+      else
+      {
+        return true;
+      }
+    }
+    // document is non empty, but no partitions
+    if(partitionRegistry == null || partitionRegistry.size() == 0)
+    {
+      //FIXME: assumes that always starts with prompt
+      logError("Empty Repl, but no partitions.");
+      return false;
+    }
+    int offset = 0;
+    for( PartitionData pd : partitionRegistry )
+    {
+      if( pd.start != offset )
+      {
+        logError("Gap between partitions");
+        return false;
+      }
+      if( pd.length + pd.start > doc.getLength() )
+      {
+        logError("Partition extends beyond document");
+        return false;
+      }
+      offset = pd.start + pd.length;
+    }
+    if( getEditModeFlag() )
+    {
+      if( offset != getEditOffset() )
+      {
+        logError("Gap between partition and edit region");
+        return false;
+      }
+    }
+    else
+    {
+      if( offset != doc.getLength() )
+      {
+        logError("Unpartitioned part at the end of the document");
+        return false;
+      }
+    }
+    if( !editSanityCheck() )
+    {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Checks edit partition.
+   * TODO: add check of deleted partitions
+   * @return true if pass sanity check
+   */
+  private boolean editSanityCheck()
+  {
+    if( getEditOffset() > doc.getLength() )
+    {
+      logError("Edit region is beyond Repl length");
+      return false;
+    }
+    if( !getEditModeFlag() ) // in read only mode
+    {
+      if (doc.containsPositionCategory(READ_ONLY_CATEGORY))
+      {
+        logError("In read-only mode Repl should not have any read-only positions");
+        return false;
+      }
+      if( readOnlyPositions.size() > 0 )
+      {
+        logError("In read-only mode there should be no read-only positions");
+        return false;
+      }
+      if ( editPartition != null && editPartition.children != null
+          && editPartition.children.size() > 0 )
+      {
+        logError("In read-only mode editPartition should not " +
+        		"contain read-only partitions");
+        return false;
+      }
+      return true;
+    }
+    else // in edit mode
+    {
+      if( doc.getLength() == getEditOffset() ) // empty edit partition
+      {
+        if (doc.containsPositionCategory(READ_ONLY_CATEGORY))
+        {
+          logError("Empty edit region, but nonempty position category");
+          return false;
+        }
+        if( readOnlyPositions.size() > 0 )
+        {
+          logError("Empty edit region, but have some read-only positions");
+          return false;
+        }
+        if ( editPartition != null && editPartition.children != null
+            && editPartition.children.size() > 0 )
+        {
+          logError("Empty edit region, but have read-only partitions");
+          return false;
+        }
+        return true;
+      }
+      else // nonempty edit partition
+      {
+        int npart = 0;
+        int npos = 0;
+        int ndocpos = 0;
+        if( editPartition != null && editPartition.children != null )
+        {
+          npart = editPartition.children.size();
+        }
+        if( doc.containsPositionCategory(READ_ONLY_CATEGORY) )
+        {
+          try
+          {
+            ndocpos = doc.getPositions(READ_ONLY_CATEGORY).length;
+          }
+          catch(BadPositionCategoryException e)
+          {
+            logException("Bad category: should never get here", e);
+          }
+        }
+        if( readOnlyPositions != null )
+        {
+          npos = readOnlyPositions.size();
+        }
+        if( npart != npos || npart != ndocpos )
+        {
+          logError("Number of read-only partitions should be same " +
+          		"as number of positions");
+        }
+
+        if( npart > 0 )
+        {
+          int totLength = 0;
+          for( PartitionData pd : editPartition.children )
+          {
+            totLength += pd.length;
+            Position pos = readOnlyPositions.get(pd);
+            if( pos == null )
+            {
+              logError("No position for read-only partition");
+              return false;
+            }
+            if( pos.isDeleted() )
+            {
+              logError("Position for read-only partition is marked as deleted");
+              return false;
+            }
+            if( pos.length != pd.length )
+            {
+              logError("Position length != partition length");
+              return false;
+            }
+            // check if any other partition overlaps this one
+            for( Position p : readOnlyPositions.values() )
+            {
+              if( p != pos && p != null
+                  && p.overlapsWith(pos.offset, pos.length))
+              {
+                logError("Read only partitions overlap");
+                return false;
+              }
+            }
+          }
+          if( totLength > doc.getLength() - getEditOffset() )
+          {
+            logError("Total length of read only partitions > " +
+            		"length of edit region in Repl");
+            return false;
+          }
+        }
+      }
+
+    }
+
+    return true;
+  }
+
+  /**
+   * Switch to read-only mode.
+   */
+  public void stopEdit()
+  {
+    logTraceEntry("",7);
+    if( getEditModeFlag() )
+    {
+      try
+      {
+        if( doc.containsPositionCategory(READ_ONLY_CATEGORY) )
+        {
+          doc.removePositionCategory(READ_ONLY_CATEGORY);
+        }
+      }
+      catch(BadPositionCategoryException e)
+      {
+        logException("stopEdit: should never get here...",e);
+      }
+      if( doc.getLength() > 0 )
+      {
+        partitionRegistry.add(getCurrentEditPartition());
+      }
+      readOnlyPositions.clear();
+      deletedPartitions.clear();
+      disconnectUndoManager();
+      setEditModeFlag(false);
+      setEditOffset(doc.getLength());
+      logTrace("stopEdit: stop edit mode at offset = "
+          + String.valueOf(getEditOffset()),5);
+    }
+    else
+    {
+      logTrace("stopEdit: called stopEdit in read-only mode",5);
+    }
+    logTraceReturn("",7);
+  }
+
+  /**
+   * Applies styles from <code>PartitionData</code>, assuming
+   * that PartitionData is set relative to
+   * @param offset
+   * @param pd
+   */
+  private void applyPartitionStyles(int offset, PartitionData pd)
+  {
+    if(doc == null || pd == null || pd.originalStyle == null
+        || pd.start + pd.length > doc.getLength())
+    {
+      return;
+    }
+    int pdoffset = offset + pd.start;
+    for(StyleRange style : pd.originalStyle)
+    {
+      style.start += pdoffset;
+      getTextWidget().setStyleRange(style);
+      style.start -= pdoffset;
+    }
+    if( pd.children != null )
+    {
+      for(PartitionData pdc : pd.children)
+      {
+        applyPartitionStyles(pdoffset,pdc);
+      }
+    }
+  }
+
+  /**
+   * Appends text and partition to repl.
+   */
+  public void appendText(String str, PartitionData data, boolean onNewLine)
+  {
+    logTraceEntry("\""+str+"\","+String.valueOf(data)
+        +","+String.valueOf(onNewLine),7);
+    stopEdit();
+    if(doc != null)
+    {
+      String traceMsg = "";
+      if(!quietTrace)
+      {
+        traceMsg = "appendText: at offset = " + String.valueOf(doc.getLength());
+      }
+      int offset = doc.getLength();
+      try
+      {
+        if(!quietTrace)
+        {
+          if(str.length() > 80)
+          {
+            traceMsg += ", printing:\n" + str + "\n";
+          }
+          else
+          {
+            traceMsg += ", printing: " + str;
+          }
+        }
+        if( onNewLine && doc.getLength() > 0 )
+        {
+          PartitionData pd = new PartitionData(offset,"\n".length(),
+              NEW_LINE_CONTEXT,"0");
+          partitionRegistry.add(pd);
+          doc.replace(offset, 0, "\n");
+          ++offset;
+        }
+        PartitionData pd;
+        if(data == null)
+        {
+          pd = new PartitionData(offset,str.length(), NULL_CONTEXT,"0");
+        }
+        else
+        {
+          pd = data;
+          pd.start = offset;
+        }
+        doc.replace(offset, 0, str);
+        partitionRegistry.add(pd);
+        setEditOffset(doc.getLength());
+        applyPartitionStyles(0,pd);
+      }
+      catch(BadLocationException e)
+      {
+        if(!quietTrace)
+          traceMsg += ", producing exception.";
+        logException("Failed to print to Repl", e);
+      }
+      logTrace(traceMsg,4);
+    }
+    else
+    {
+      logWarning("appendText: Tried to print to uninitialized Repl");
+    }
+    logTraceReturn("",7);
+  }
+
+  /**
+   * Appends text and creates partition data using <code>context</code> and
+   * <code>styles</code>
+   * @param str - text to append
+   * @param context - context for appended text
+   * @param id - id for appended text
+   * @param styles - styles to format the text. Can be <code>null</code>
+   * @param onNewLine - if true starts prompt on new line
+   */
+  public void appendText(String str, String context, String id,
+      StyleRange[] styles, boolean onNewLine)
+  {
+    logTraceEntry("\""+str+"\",\""+context+"\","
+        +String.valueOf(styles),7);
+    appendText(str, new PartitionData(0, str.length(), context, id, styles),
+        onNewLine);
+    logTraceReturn("",7);
+  }
+
+  /**
+   * If <code>inEditMode</code> and <code>offset</code> is inside read-only
+   * partition of current edit region, returns that partition.
+   * Otherwise returns <code>null</code>
+   * @param offset - checks for read-only partition here
+   * @param partitionResolutionFlag - NONE, BEFORE, AFTER
+   * - this is used in functions internal to Repl, so should not be actually
+   * public
+   */
+  public PartitionData getReadOnlyPartition(int offset,
+      int partitionResolutionFlag)
+  {
+    if( offset < getEditOffset() )
+    {
+      return null;
+    }
+    PartitionData pd = getCurrentEditPartition();
+    if( pd == null || pd.children == null )
+    {
+      return null;
+    }
+    for( PartitionData pdc : pd.children)
+    {
+      Position pos = readOnlyPositions.get(pdc);
+      Assert.isTrue(pos != null && !pos.isDeleted());
+      int offset0 = pos.getOffset();
+      int offset1 = offset0 + pos.getLength();
+      if( partitionResolutionFlag == NONE )
+      {
+        if(offset0 == offset || offset1 == offset)
+        {
+          return null;
+        }
+        else if( offset0 < offset && offset < offset1 )
+        {
+          pdc.start = offset0 - getEditOffset();
+          return pdc;
+        }
+      }
+      if( partitionResolutionFlag == BEFORE )
+      {
+        if( offset0 == offset )
+        {