Commits

Anonymous committed 2fc2f51 Merge

Merge editor with lispdev.

  • Participants
  • Parent commits c0f7629, 6c12083

Comments (0)

Files changed (78)

+<?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>
+# Turn on debug tracing for com.citi.et.core plugin
+org.lispdev.editor/trace/debug=false
+
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.lispdev.editor</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>
+		<buildCommand>
+			<name>net.sourceforge.metrics.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>net.sourceforge.metrics.nature</nature>
+	</natures>
+</projectDescription>

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

+#Mon Jun 29 11:56:25 BST 2009
+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

File META-INF/MANIFEST.MF

+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Lisp Editor
+Bundle-SymbolicName: org.lispdev.editor;singleton:=true
+Bundle-Version: 0.0.1.none
+Bundle-Activator: org.lispdev.editor.EditorPlugin
+Bundle-Vendor: lispdev.org
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.jface.text;bundle-version="3.5.0",
+ org.eclipse.ui.editors;bundle-version="3.5.0",
+ org.eclipse.ui.views;bundle-version="3.4.0",
+ org.eclipse.core.resources;bundle-version="3.5.0",
+ org.eclipse.ui.console;bundle-version="3.4.0",
+ org.lispdev.parser;bundle-version="0.0.1",
+ org.lispdev.preferences;bundle-version="0.0.0",
+ org.lispdev.log;bundle-version="1.0.0"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-ActivationPolicy: lazy
+Import-Package: org.eclipse.ui.part
+Export-Package: org.lispdev.editor

File build.properties

+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.xml

File icons/apropos.gif

Added
New image

File icons/asd.gif

Added
New image

File icons/backward_nav.gif

Added
New image

File icons/clear.gif

Added
New image

File icons/defaction.gif

Added
New image

File icons/defclass.gif

Added
New image

File icons/defconstant.gif

Added
New image

File icons/defgeneric.gif

Added
New image

File icons/define-alien-routine.gif

Added
New image

File icons/define-alien-type.gif

Added
New image

File icons/define-alien-variable.gif

Added
New image

File icons/define-condition.gif

Added
New image

File icons/defmacro.gif

Added
New image

File icons/defmethod.gif

Added
New image

File icons/defother.gif

Added
New image

File icons/defpackage.gif

Added
New image

File icons/defparameter.gif

Added
New image

File icons/defstruct.gif

Added
New image

File icons/defsystem.gif

Added
New image

File icons/deftype.gif

Added
New image

File icons/defun.gif

Added
New image

File icons/defvar.gif

Added
New image

File icons/disconnected.gif

Added
New image

File icons/error.gif

Added
New image

File icons/forward-nav.gif

Added
New image

File icons/in-package.gif

Added
New image

File icons/inspector.gif

Added
New image

File icons/install-new-package.gif

Added
New image

File icons/lisp-file.gif

Added
New image

File icons/lisp-nature.gif

Added
New image

File icons/lisp-nav.gif

Added
New image

File icons/load-asd.gif

Added
New image

File icons/load-package.gif

Added
New image

File icons/other.gif

Added
New image

File icons/outline-mode.gif

Added
New image

File icons/reconnect.gif

Added
New image

File icons/refresh.gif

Added
New image

File icons/sort-alpha.gif

Added
New image

File icons/sort-position.gif

Added
New image

File icons/sort-type.gif

Added
New image

File icons/thread-debug.gif

Added
New image

File icons/thread-kill.gif

Added
New image

File icons/threads.gif

Added
New image

File icons/warning.gif

Added
New image
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+   <extension
+         point="org.eclipse.ui.editors">
+      <editor
+            class="org.lispdev.editor.LispEditor"
+            contributorClass="org.eclipse.ui.texteditor.BasicTextEditorActionContributor"
+            default="false"
+            extensions="lisp,cl"
+            icon="icons/lisp-file.gif"
+            id="org.lispdev.editor.lispEditor"
+            name="LispEditor">
+      </editor>
+      <editor
+            class="org.lispdev.editor.LispEditor"
+            contributorClass="org.eclipse.ui.texteditor.BasicTextEditorActionContributor"
+            default="false"
+            extensions="asd"
+            icon="icons/asd.gif"
+            id="org.lispdev.editor.asdEditor"
+            name="AsdEditor">
+      </editor>
+   </extension>
+   <extension
+         id="org.lispdev.marker"
+         point="org.eclipse.core.resources.markers">
+   </extension>
+   <extension
+         id="org.lispdev.marker.parsingError"
+         point="org.eclipse.core.resources.markers">
+      <persistent
+            value="true">
+      </persistent>
+      <super
+            type="org.eclipse.core.resources.problemmarker">
+      </super>
+      <super
+            type="org.lispdev.marker">
+      </super>
+   </extension>
+   <extension
+         point="org.eclipse.ui.preferencePages">
+      <page
+            class="org.lispdev.editor.preferences.LispdevPreferencePage"
+            id="org.lispdev"
+            name="Lispdev">
+      </page>
+      <page
+            category="org.lispdev"
+            class="org.lispdev.editor.preferences.EditorPreferencePage"
+            id="org.lispdev.editor"
+            name="Editor">
+      </page>
+      <page
+            category="org.lispdev/org.lispdev.editor"
+            class="org.lispdev.editor.preferences.ColorPreferencePage"
+            id="org.lispdev.editor.colors"
+            name="Colors">
+      </page>
+   </extension>
+
+</plugin>

File src/org/lispdev/editor/ColorManager.java

+package org.lispdev.editor;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * All colors should be obtained through this manager getColor function.
+ */
+public class ColorManager
+{
+  public static RGB DEFAULT_STRING = new RGB(210, 180, 10);
+  public static RGB DEFAULT_NUMBER = new RGB(200, 0, 0);
+  public static RGB DEFAULT_COMMENT = new RGB(0, 128, 0);
+  public static RGB DEFAULT_KEYWORD = new RGB(0, 0 , 128);
+  public static RGB DEFAULT_CONST = new RGB(128, 0, 128);
+  public static RGB DEFAULT_GLOBAL = new RGB(128, 0, 255);
+  public static RGB DEFAULT_KEY = new RGB(0, 0, 255);
+  public static RGB DEFAULT_PARAM = new RGB(128, 128, 0);
+  public static RGB DEFAULT_DEFAULT = new RGB(0, 0, 0);
+
+  public static RGB DEFAULT_CURRENT = new RGB(254,254,235);
+  public static RGB DEFAULT_OUTER = new RGB(235,252,233);
+
+
+  protected Map<RGB, Color> fColorTable = new HashMap<RGB, Color>(10);
+
+  public void dispose()
+  {
+    Iterator<Color> e = fColorTable.values().iterator();
+    while(e.hasNext())
+      e.next().dispose();
+  }
+
+  public Color getColor(RGB rgb)
+  {
+    Color color = fColorTable.get(rgb);
+    if(color == null)
+    {
+      color = new Color(Display.getCurrent(), rgb);
+      fColorTable.put(rgb, color);
+    }
+    return color;
+  }
+}

File src/org/lispdev/editor/EditorPlugin.java

+package org.lispdev.editor;
+
+import java.io.PrintStream;
+import java.util.LinkedHashMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.jface.resource.FontRegistry;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.TextAttribute;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.lispdev.editor.preferences.Preference;
+import org.lispdev.editor.utils.ConsoleUtil;
+import org.lispdev.log.Trace;
+import org.lispdev.parser.nodes.TreeRoot;
+import org.lispdev.parser.tokens.TokenKey;
+import org.lispdev.parser.tokens.TokenKeyword;
+import org.lispdev.parser.tokens.TokenMLC;
+import org.lispdev.parser.tokens.TokenNumber;
+import org.lispdev.parser.tokens.TokenOutline;
+import org.lispdev.parser.tokens.TokenParam;
+import org.lispdev.parser.tokens.TokenSLC;
+import org.lispdev.parser.tokens.TokenString;
+import org.lispdev.parser.tokens.TokenSymbol;
+import org.lispdev.preferences.ItemListener;
+import org.lispdev.preferences.PreferenceItem;
+import org.lispdev.preferences.PreferencesService;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class EditorPlugin extends AbstractUIPlugin
+{
+  // The plug-in ID
+  public static final String ID = "org.lispdev.editor";
+
+  public static final String LangID = "CL";
+
+  private static final String CONSOLE_NAME= "Lispdev Editor";
+
+  private FontRegistry fFontRegistry;
+
+  private PrintStream sConsoleStream;
+
+  private static boolean EMIT_TIMING_INFO = false;
+
+  private static long PRE_STARTUP_TIME;
+
+  //private static long EDITOR_START_TIME;
+
+
+  // The shared instance
+  private static EditorPlugin plugin;
+
+  /**
+   * The constructor
+   */
+  public EditorPlugin()
+  {}
+
+  public FontRegistry getFontRegistry()
+  {
+    // Hopefully this gets called late enough, i.e., after a Display has been
+    // created on the current thread (see FontRegistry constructor).
+    if( fFontRegistry == null )
+    {
+      fFontRegistry = new FontRegistry();
+    }
+    return fFontRegistry;
+  }
+
+  public PrintStream getConsoleStream()
+  {
+    if (sConsoleStream == null) {
+        sConsoleStream= ConsoleUtil.findConsoleStream(CONSOLE_NAME);
+    }
+    return sConsoleStream;
+  }
+
+  @Override
+  public void start(BundleContext context) throws Exception
+  {
+    PRE_STARTUP_TIME= System.currentTimeMillis();
+    super.start(context);
+    plugin = this;
+
+    if (EMIT_TIMING_INFO)
+    {
+      getConsoleStream().println(
+          "Entered EditorPlugin.start(); time is " + PRE_STARTUP_TIME);
+    }
+  }
+
+
+  @Override
+  public void stop(BundleContext context) throws Exception
+  {
+    plugin = null;
+    if( colorManager != null )
+    {
+      colorManager.dispose();
+    }
+    for( Preference p : Preference.values() )
+    {
+      p.item().dispose();
+    }
+    super.stop(context);
+  }
+
+  /**
+   * Returns the shared instance
+   *
+   * @return the shared instance
+   */
+  public static EditorPlugin get()
+  {
+    return plugin;
+  }
+
+  //// ast
+  /**
+   * Cache class to hold AST trees for documents.
+   */
+  class AstCache extends LinkedHashMap<IDocument, TreeRoot>
+  {
+    private static final long serialVersionUID = 7606500805493229529L;
+    /**
+     * maximum number of ASTs to keep
+     */
+    final int MAX_ENTRIES = 20;
+
+    @Override
+    protected boolean removeEldestEntry(
+        java.util.Map.Entry<IDocument, TreeRoot> eldest)
+    {
+      return size() > MAX_ENTRIES;
+    }
+  }
+
+  private final AstCache astMap = new AstCache();
+
+  public TreeRoot getAst(IDocument doc)
+  {
+    if(null == doc)
+    {
+      return null;
+    }
+    TreeRoot res = astMap.get(doc);
+    if(res == null)
+    {
+      res = TreeRoot.parse(doc.get(), getKeywords());
+      putAst(doc, res);
+      if( Trace.DEBUG.enabled )
+        Trace.DEBUG.trace("End parse");
+    }
+    return res;
+  }
+
+  public TreeRoot resetAst(IDocument doc)
+  {
+    if(null == doc)
+    {
+      return null;
+    }
+    TreeRoot res = TreeRoot.parse(doc.get(), getKeywords());
+    putAst(doc, res);
+    return res;
+  }
+
+  private void putAst(IDocument doc, TreeRoot tree)
+  {
+    if(doc != null)
+    {
+      astMap.put(doc, tree);
+    }
+  }
+
+  //// keywords
+  private TreeSet<String> keywords;
+  public void addKeyword(String word)
+  {
+    if(keywords == null)
+    {
+      keywords = new TreeSet<String>();
+    }
+    keywords.add(word);
+    astMap.clear();
+  }
+  public void resetKeywords()
+  {
+    keywords = null;
+    astMap.clear();
+  }
+  private SortedSet<String> getKeywords()
+  {
+    if( keywords == null )
+      initKeywords();
+    return keywords;
+  }
+
+  /**
+   * TODO: set keywords from preferences.
+   */
+  private void initKeywords()
+  {
+    final String[] words = ("* block handler-case unwind-protect " +
+    		"multiple-value-bind values make-instance eval-when incf decf " +
+    		"call-next-method defstruct defmethod defconstant defgeneric load " +
+    		"when unless deftype defvar defparameter defclass defpackage " +
+    		"in-package not defun princ eval apply funcall quote identity " +
+    		"function complement backquote lambda set setq setf defmacro " +
+    		"gensym make symbol intern string loop declare plist get getf " +
+    		"putprop remprop hash make array aref car cdr caar cadr cdar cddr " +
+    		"caaar caadr cadar caddr cdaar cdadr cddar cdddr caaaar caaadr " +
+    		"caadar caaddr cadaar cadadr caddar cadddr cdaaar cdaadr cdadar " +
+    		"cdaddr cddaar cddadr cdddar cddddr cons list append reverse " +
+    		"first second third fourth fifth sixth seventh eighth ninth tenth " +
+    		"last nth nthcdr member assoc subst sublis nsubst nsublis remove " +
+    		"length list length mapc mapcar mapl maplist mapcan mapcon rplaca " +
+    		"rplacd nconc delete atom symbolp numberp boundp null listp consp " +
+    		"minusp zerop plusp evenp oddp eq eql equal cond case and or let " +
+    		"let* macrolet if prog prog1 prog2 progn go return do dolist " +
+    		"dotimes catch throw error cerror break continue errset baktrace " +
+    		"evalhook truncate float rem min max abs sin cos tan expt exp sqrt " +
+    		"random logand logior logxor lognot bignums logeqv lognand lognor " +
+    		"logorc2 logtest logbitp logcount integer length t nil").split(" ");
+    keywords = new TreeSet<String>();
+    for(String s : words)
+    {
+      keywords.add(s);
+    }
+  }
+
+  private ColorManager colorManager;
+
+  public ColorManager getColorManager()
+  {
+    if( colorManager == null )
+    {
+      colorManager = new ColorManager();
+    }
+    return colorManager;
+  }
+
+  ///// lisp scanner - token coloring
+
+  private LispScanner lispScanner;
+  private final EditorPreferenceListener editorPreferenceListener =
+    new EditorPreferenceListener();
+
+  private class EditorPreferenceListener implements ItemListener
+  {
+    @Override
+    public void handleChange(PreferenceItem item, Object oldValue, Object newValue)
+    {
+      resetLispScanner();
+      for(IDocument doc : astMap.keySet())
+      {
+        LispDocumentProvider.resetPartitioner(doc);
+      }
+    }
+  }
+
+  private void resetLispScanner()
+  {
+    if( lispScanner == null ) return;
+    PreferencesService service = getPreferences();
+    // in general set service to currently selected project
+    // in this case - this is a workspace based
+    final ColorManager cm = getColorManager();
+    lispScanner.addTokenType(TokenString.class,
+        new TextAttribute(cm.getColor(
+            (RGB)Preference.COLOR_STRING.item().get(service))));
+    lispScanner.addTokenType(TokenNumber.class,
+        new TextAttribute(cm.getColor(
+            (RGB)Preference.COLOR_NUMBER.item().get(service))));
+    lispScanner.addTokenType(TokenKeyword.class,
+        new TextAttribute(cm.getColor(
+            (RGB)Preference.COLOR_KEYWORD.item().get(service))));
+    lispScanner.addTokenType(TokenKey.class,
+        new TextAttribute(cm.getColor(
+            (RGB)Preference.COLOR_KEY.item().get(service))));
+    lispScanner.addTokenType(TokenParam.class,
+        new TextAttribute(cm.getColor(
+            (RGB)Preference.COLOR_PARAM.item().get(service))));
+    TextAttribute commentAttribute =
+      new TextAttribute(cm.getColor(
+          (RGB)Preference.COLOR_COMMENT.item().get(service)));
+    lispScanner.addTokenType(TokenMLC.class, commentAttribute);
+    lispScanner.addTokenType(TokenSLC.class, commentAttribute);
+    lispScanner.addTokenType(TokenOutline.class, commentAttribute);
+    // global
+    lispScanner.addSymbolTokenType(new ISymbolClassifier(){
+      @Override
+      public boolean inClass(TreeRoot ast, TokenSymbol token)
+      {
+        return token.symbol.length() > 1
+          && token.symbol.charAt(0) == '*'
+          && token.symbol.charAt(token.symbol.length()-1) == '*';
+      }
+    }, new TextAttribute(cm.getColor(
+        (RGB)Preference.COLOR_GLOBAL.item().get(service))));
+    // const
+    lispScanner.addSymbolTokenType(new ISymbolClassifier(){
+      @Override
+      public boolean inClass(TreeRoot ast, TokenSymbol token)
+      {
+        return token.symbol.length() > 1
+          && token.symbol.charAt(0) == '+'
+          && token.symbol.charAt(token.symbol.length()-1) == '+';
+      }
+    }, new TextAttribute(cm.getColor(
+        (RGB)Preference.COLOR_CONST.item().get(service))));
+
+  }
+
+  public LispScanner getLispScanner()
+  {
+    if( lispScanner == null )
+    {
+      lispScanner = new LispScanner();
+      resetLispScanner();
+    }
+    return lispScanner;
+  }
+
+  /// preferences
+  /**
+   * The unique project-independent preferences service for this plugin.
+   * Uses whatever this plugin activator's getLanguageID() method returns
+   * as the language.
+   */
+  protected PreferencesService prefServices = null;
+
+  public PreferencesService getPreferences()
+  {
+    if( prefServices == null )
+    {
+      prefServices = new PreferencesService(LangID);
+      // To trigger the automatic invocation of the preferences initializer:
+      try
+      {
+        new DefaultScope().getNode(ID);
+      }
+      catch(Exception e)
+      {
+        // If this ever happens, it will probably be because the preferences
+        // and their initializer haven't been defined yet. In that situation
+        // there's not really anything to do--you can't initialize preferences
+        // that don't exist. So swallow the exception and continue ...
+      }
+      Preference.init(prefServices);
+      for( Preference p : Preference.editorColors() )
+      {
+        p.item().addListener(getPreferences(), editorPreferenceListener);
+      }
+    }
+    return prefServices;
+  }
+
+
+  // === some code for tracing
+  private PrintStream traceStream;
+
+  /**
+   * Sets stream for tracing done by EtTrace enums.
+   */
+  public void setTraceStream(PrintStream pr)
+  {
+    traceStream = pr;
+  }
+
+  /**
+   * Returns stream which receives tracing from EtTrace enums.
+   */
+  public PrintStream getTraceStream()
+  {
+    if( traceStream == null )
+    {
+      return System.out;
+    }
+    else
+    {
+      return traceStream;
+    }
+  }
+
+
+}

File src/org/lispdev/editor/ISymbolClassifier.java

+package org.lispdev.editor;
+
+import org.lispdev.parser.nodes.TreeRoot;
+import org.lispdev.parser.tokens.TokenSymbol;
+
+public interface ISymbolClassifier
+{
+  /**
+   * Returns true if token is in the class described by classifier
+   */
+  boolean inClass(TreeRoot ast, TokenSymbol token);
+}

File src/org/lispdev/editor/LispConfiguration.java

+package org.lispdev.editor;
+
+import org.eclipse.jface.text.IAutoEditStrategy;
+import org.eclipse.jface.text.ITextViewerExtension2;
+import org.eclipse.jface.text.presentation.IPresentationReconciler;
+import org.eclipse.jface.text.reconciler.IReconciler;
+import org.eclipse.jface.text.reconciler.MonoReconciler;
+import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
+import org.eclipse.jface.text.source.IAnnotationHover;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.MatchingCharacterPainter;
+import org.eclipse.jface.text.source.SourceViewerConfiguration;
+import org.lispdev.editor.autoedits.AllAutoEdits;
+
+/**
+ * Eclipse uses this class to do the majority of the customization that
+ * makes our Lisp editor a Lisp editor.
+ */
+public class LispConfiguration extends SourceViewerConfiguration
+{
+  private LispEditor editor;
+
+  public LispConfiguration(LispEditor editor)
+  {
+    this.editor = editor;
+  }
+
+  @Override
+  public String[] getConfiguredContentTypes(ISourceViewer sourceViewer)
+  {
+    return LispPartitioner.CONTENT_TYPES;
+  }
+
+  @Override
+  public IPresentationReconciler getPresentationReconciler(
+      ISourceViewer sourceViewer)
+  {
+    // TODO: make argument - preference
+    LispPairMatcher pm = new LispPairMatcher(false);
+    MatchingCharacterPainter painter =
+      new MatchingCharacterPainter(sourceViewer,pm);
+    pm.setPainter(painter);
+    ITextViewerExtension2 ext = (ITextViewerExtension2)sourceViewer;
+    ext.addPainter(painter);
+
+    LispPresentationReconciler reconciler = new LispPresentationReconciler();
+
+    DefaultDamagerRepairer dr =
+      new DefaultDamagerRepairer(EditorPlugin.get().getLispScanner());
+    for(String s : LispPartitioner.CONTENT_TYPES)
+    {
+      reconciler.setDamager(dr, s);
+      reconciler.setRepairer(dr, s);
+    }
+
+    return reconciler;
+  }
+
+  @Override
+  public IReconciler getReconciler(ISourceViewer sourceViewer)
+  {
+    LispReconcilingStrategy strategy = new LispReconcilingStrategy(editor);
+    MonoReconciler reconciler = new MonoReconciler(strategy,false);
+    return reconciler;
+  }
+
+  @Override
+  public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer)
+  {
+    return new MarkerAnnotationHover();
+  }
+
+  @Override
+  public IAutoEditStrategy[] getAutoEditStrategies(ISourceViewer sourceViewer,
+      String contentType)
+  {
+    return AllAutoEdits.get();
+  }
+}

File src/org/lispdev/editor/LispDocumentProvider.java

+package org.lispdev.editor;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentPartitioner;
+import org.eclipse.ui.editors.text.FileDocumentProvider;
+import org.eclipse.ui.part.FileEditorInput;
+
+public class LispDocumentProvider extends FileDocumentProvider
+{
+  IFile file;
+  public IFile getIFile()
+  {
+    return file;
+  }
+
+  @Override
+  protected IDocument createDocument(Object element) throws CoreException
+  {
+    IDocument document = super.createDocument(element);
+    if(document != null)
+    {
+      if( element instanceof FileEditorInput )
+      {
+        file = ((FileEditorInput)element).getFile();
+      }
+      resetPartitioner(document);
+    }
+    return document;
+  }
+
+  public static void resetPartitioner(IDocument doc)
+  {
+    if( doc == null ) return;
+    IDocumentPartitioner partitioner = new LispPartitioner();
+    partitioner.connect(doc);
+    doc.setDocumentPartitioner(partitioner);
+  }
+
+}

File src/org/lispdev/editor/LispEditor.java

+package org.lispdev.editor;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.IVerticalRuler;
+import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
+import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
+import org.eclipse.jface.text.source.projection.ProjectionSupport;
+import org.eclipse.jface.text.source.projection.ProjectionViewer;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.editors.text.TextEditor;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
+import org.lispdev.log.Log;
+import org.lispdev.parser.nodes.Node;
+import org.lispdev.parser.nodes.TreeRoot;
+
+public class LispEditor extends TextEditor
+{
+
+  public IFile getFile()
+  {
+    return ((LispDocumentProvider)getDocumentProvider()).getIFile();
+  }
+
+  public LispEditor()
+  {
+    super();
+    setSourceViewerConfiguration(new LispConfiguration(this));
+    setDocumentProvider(new LispDocumentProvider());
+  }
+
+  public IDocument getDoc()
+  {
+    return getDocumentProvider().getDocument(getEditorInput());
+  }
+
+  @Override
+  public void dispose()
+  {
+    super.dispose();
+    //partService.removePartListener(partListener);
+  }
+
+  @Override
+  public void createPartControl(Composite parent)
+  {
+    super.createPartControl(parent);
+
+    /// folding support
+    ProjectionViewer viewer = (ProjectionViewer)getSourceViewer();
+    ProjectionSupport projectionSupport =
+      new ProjectionSupport(viewer, getAnnotationAccess(),
+          getSharedColors());
+    projectionSupport.addSummarizableAnnotationType(
+     "org.eclipse.ui.workbench.texteditor.error");
+    projectionSupport.addSummarizableAnnotationType(
+     "org.eclipse.ui.workbench.texteditor.warning");
+    projectionSupport.install();
+    viewer.doOperation(ProjectionViewer.TOGGLE);
+    projectionAnnotationModel = viewer.getProjectionAnnotationModel();
+
+    /// after start listener
+    //partService = getSite().getWorkbenchWindow().getPartService();
+    //partService.addPartListener(partListener);
+  }
+
+  ///=============== outline
+  LispOutlinePage outline;
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public Object getAdapter(Class adapter)
+  {
+    if( IContentOutlinePage.class.equals(adapter) )
+    {
+      if( outline == null )
+      {
+        outline = new LispOutlinePage(this);
+      }
+      return outline;
+    }
+    else
+    {
+      return super.getAdapter(adapter);
+    }
+  }
+
+  public void refreshOutline()
+  {
+    outline.refresh();
+  }
+
+  ///================ folding
+
+  @Override
+  protected ISourceViewer createSourceViewer(Composite parent,
+      IVerticalRuler ruler, int styles)
+  {
+    ISourceViewer viewer = new ProjectionViewer(parent,ruler,
+        getOverviewRuler(),isOverviewRulerVisible(), styles);
+    getSourceViewerDecorationSupport(viewer);
+    return viewer;
+  }
+
+  private ProjectionAnnotationModel projectionAnnotationModel;
+
+  private void addPosition(HashSet<Position> positions, Node el)
+  {
+    TreeRoot ast = EditorPlugin.get().getAst(getDoc());
+    Node[] els = ast.outlineChildren(el,TreeRoot.OUTLINE_GET_CHILDREN,true);
+    final int offset = el.offset();
+    int length = 0;
+    if( els.length > 0 )
+    {
+      length = els[els.length-1].offset() + els[els.length-1].length() - offset;
+    }
+    else
+    {
+      length = el.length();
+    }
+    final int endoffset = offset + length - 1;
+
+    try
+    {
+      final int startLine = getDoc().getLineOfOffset(offset);
+      final int endLine = getDoc().getLineOfOffset(endoffset);
+      if( endLine > startLine )
+      {
+        final int endLineOffset = getDoc().getLineOffset(endLine);
+        final int start = getDoc().getLineOffset(startLine);
+        final int end =
+          endLineOffset + getDoc().getLineLength(endLine) - 1;
+        positions.add(new Position(start, end - start + 1));
+
+        for( Node e : els )
+        {
+          addPosition(positions,e);
+        }
+      }
+    }
+    catch(BadLocationException e)
+    {
+      Log.logException(e);
+    }
+
+  }
+
+  private Position getLastSectionPosition(Node el)
+  {
+    if( el instanceof Node )
+    {
+      TreeRoot ast = EditorPlugin.get().getAst(getDoc());
+      Node[] children =
+        ast.outlineChildren(el,TreeRoot.OUTLINE_GET_CHILDREN,true);
+      if( children.length == 0 )
+      {
+        return new Position(el.offset(),el.length());
+      }
+      else
+      {
+        return getLastSectionPosition(children[children.length-1]);
+      }
+    }
+    return null;
+  }
+
+  public void updateFoldingStructure()
+  {
+    TreeRoot ast = EditorPlugin.get().getAst(getDoc());
+    Node[] outline =
+      ast.outlineChildren(ast,TreeRoot.OUTLINE_GET_CHILDREN,true);
+    HashSet<Position> newPositions = new HashSet<Position>();
+    for( Node o : outline )
+    {
+      addPosition(newPositions,o);
+    }
+
+    // these hold mapping between old annotations and their positions
+    HashSet<Position> oldPositions = new HashSet<Position>();
+    HashMap<Position,ProjectionAnnotation> hashAnnotations =
+      new HashMap<Position, ProjectionAnnotation>();
+
+    // prepare data structures
+    {
+      Iterator<?> it = projectionAnnotationModel.getAnnotationIterator();
+      while(it.hasNext())
+      {
+        ProjectionAnnotation a = (ProjectionAnnotation) it.next();
+        Position p = projectionAnnotationModel.getPosition(a);
+        oldPositions.add(p);
+        hashAnnotations.put(p, a);
+      }
+    }
+
+    //get removed annotations
+    HashSet<Position> remPositions = new HashSet<Position>();
+    remPositions.addAll(oldPositions);
+    remPositions.removeAll(newPositions);
+    Annotation[] remAnnotations = new Annotation[remPositions.size()];
+    {
+      int i = 0;
+      for( Position p : remPositions )
+      {
+        remAnnotations[i] = hashAnnotations.get(p);
+        ++i;
+      }
+    }
+
+    //get new annotations
+    HashMap<ProjectionAnnotation, Position> newAnnotations =
+      new HashMap<ProjectionAnnotation, Position>();
+
+    newPositions.removeAll(oldPositions);
+    for( Position p : newPositions )
+    {
+      newAnnotations.put(new ProjectionAnnotation(), p);
+    }
+
+    //deal with possible garbage in last section when it is collapsed
+    if( outline.length > 0 )
+    {
+      ProjectionAnnotation lastSectionAnnotation =
+        hashAnnotations.get(getLastSectionPosition(outline[outline.length-1]));
+      boolean lastSectionCollapsed = false;
+      if (lastSectionAnnotation != null ) {
+        lastSectionCollapsed = lastSectionAnnotation.isCollapsed();
+        projectionAnnotationModel.expand(lastSectionAnnotation);
+      }
+      projectionAnnotationModel.modifyAnnotations(
+          remAnnotations,newAnnotations,null);
+
+      if ( lastSectionCollapsed ) {
+        projectionAnnotationModel.collapse(lastSectionAnnotation);
+      }
+    }
+
+  }
+
+  /// ================ start up listener
+/*
+  private void runAfterOpen()
+  {
+    highlighter.install(getSourceViewer());
+  }
+
+  private PartListener partListener = new PartListener();
+
+  private IPartService partService;
+
+  class PartListener implements IPartListener2
+  {
+//    @Override
+//    public void partActivated(IWorkbenchPart part)
+//    {
+//    }
+//
+//    @Override
+//    public void partBroughtToTop(IWorkbenchPart part)
+//    {
+//    }
+//
+//    @Override
+//    public void partClosed(IWorkbenchPart part)
+//    {
+//    }
+//
+//    @Override
+//    public void partDeactivated(IWorkbenchPart part)
+//    {
+//    }
+//
+//    @Override
+//    public void partOpened(IWorkbenchPart part)
+//    {
+//      if( part instanceof LispEditor )
+//        runAfterOpen();
+//    }
+
+    @Override
+    public void partActivated(IWorkbenchPartReference partRef)
+    {
+      if( partRef.getPart(false) instanceof LispEditor )
+        runAfterOpen();
+    }
+
+    @Override
+    public void partBroughtToTop(IWorkbenchPartReference partRef)
+    {}
+
+    @Override
+    public void partClosed(IWorkbenchPartReference partRef)
+    {}
+
+    @Override
+    public void partDeactivated(IWorkbenchPartReference partRef)
+    {}
+
+    @Override
+    public void partHidden(IWorkbenchPartReference partRef)
+    {}
+
+    @Override
+    public void partInputChanged(IWorkbenchPartReference partRef)
+    {}
+
+    @Override
+    public void partOpened(IWorkbenchPartReference partRef)
+    {
+      if( partRef.getPart(false) instanceof LispEditor )
+        runAfterOpen();
+    }
+
+    @Override
+    public void partVisible(IWorkbenchPartReference partRef)
+    {
+      if( partRef.getPart(false) instanceof LispEditor )
+        runAfterOpen();
+    }
+  }
+*/
+}

File src/org/lispdev/editor/LispEditorResources.java

+package org.lispdev.editor;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.swt.graphics.Image;
+
+public class LispEditorResources
+{
+  public static final String SORT_ALPHA = "sort-alpha"; //$NON-NLS-1$
+  public static final String OUTLINE_MODE = "flatten-outline"; //$NON-NLS-1$
+  public static final String SORT_TYPE = "sort-type"; //$NON-NLS-1$
+  public static final String SORT_POSITION = "sort-position"; //$NON-NLS-1$
+  public static final String ERROR = "error"; //$NON-NLS-1$
+  public static final String WARNING = "warning"; //$NON-NLS-1$
+  public static final String RECONNECT = "reconnect"; //$NON-NLS-1$
+  public static final String DISCONNECTED = "disconnected"; //$NON-NLS-1$
+  public static final String CLEAR = "clear"; //$NON-NLS-1$
+  public static final String THREAD_DEBUG = "thread-debug"; //$NON-NLS-1$
+  public static final String THREAD_KILL = "thread-kill"; //$NON-NLS-1$
+  public static final String REFRESH = "refresh"; //$NON-NLS-1$
+  public static final String FORWARD_NAV = "forward-nav"; //$NON-NLS-1$
+  public static final String BACKWARD_NAV = "backward-nav"; //$NON-NLS-1$
+  public static final String RUN_TESTS = "run-tests"; //$NON-NLS-1$
+  public static final String STEP = "step"; //$NON-NLS-1$
+  public static final String EXEC = "exec"; //$NON-NLS-1$
+
+  private final static String[][] keywordImageArray = {
+      {"defclass", "defclass.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defcomponent", "defclass.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defconstant", "defconstant.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defgeneric", "defgeneric.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defmacro", "defmacro.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defmethod", "defmethod.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defpackage", "defpackage.gif"}, //$NON-NLS-1$
+      {"load-package", "load-package.gif"}, //$NON-NLS-1$
+      {"defsystem", "defsystem.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defparameter", "defparameter.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defstruct", "defstruct.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"deftype", "deftype.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defun", "defun.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defaction", "defaction.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defvar", "defvar.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"define-condition", "define-condition.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"define-alien-routine", "define-alien-routine.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"define-alien-variable", "define-alien-variable.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"define-alien-type", "define-alien-type.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"lambda", "lisp-nature.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"in-package", "in-package.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"defother", "defother.gif"}, //$NON-NLS-1$ //$NON-NLS-2$
+      {"other", "other.gif"} //$NON-NLS-1$ //$NON-NLS-2$
+  };
+
+  /**
+   * Type typewords associated with icons
+   *
+   * @see getImageForType()
+   */
+  private static final Set<String> typewords =
+    Collections.synchronizedSet(new HashSet<String>());
+
+  static
+  {
+    ImageRegistry imageReg = EditorPlugin.get().getImageRegistry();
+
+    // Put all of the type keywords into the typeword set:
+    // (Note: If you define a new type keyword that is associated with an
+    // image, you need to add it here and also to the image registry below!)
+
+    for(String[] keyw_image : keywordImageArray)
+    {
+      typewords.add(keyw_image[0]);
+      ImageDescriptor idesc = loadImageDescriptor(keyw_image[1]);
+      imageReg.put(keyw_image[0], idesc);
+    }
+
+    // Everything else:
+    imageReg.put(SORT_ALPHA, loadImageDescriptor("sort-alpha.gif")); //$NON-NLS-1$
+    imageReg.put(OUTLINE_MODE, loadImageDescriptor("outline-mode.gif")); //$NON-NLS-1$
+    imageReg.put(SORT_TYPE, loadImageDescriptor("sort-type.gif")); //$NON-NLS-1$
+    imageReg.put(SORT_POSITION, loadImageDescriptor("sort-position.gif")); //$NON-NLS-1$
+
+    imageReg.put(ERROR, loadImageDescriptor("error.gif")); //$NON-NLS-1$
+    imageReg.put(WARNING, loadImageDescriptor("warning.gif")); //$NON-NLS-1$
+
+    imageReg.put(RECONNECT, loadImageDescriptor("reconnect.gif")); //$NON-NLS-1$
+    imageReg.put(DISCONNECTED, loadImageDescriptor("disconnected.gif")); //$NON-NLS-1$
+
+    imageReg.put(CLEAR, loadImageDescriptor("clear.gif")); //$NON-NLS-1$
+
+    imageReg.put(THREAD_DEBUG, loadImageDescriptor("thread-debug.gif")); //$NON-NLS-1$
+    imageReg.put(THREAD_KILL, loadImageDescriptor("thread-kill.gif")); //$NON-NLS-1$
+    imageReg.put(REFRESH, loadImageDescriptor("refresh.gif")); //$NON-NLS-1$
+    imageReg.put(FORWARD_NAV, loadImageDescriptor("forward-nav.gif")); //$NON-NLS-1$
+    imageReg.put(BACKWARD_NAV, loadImageDescriptor("backward-nav.gif")); //$NON-NLS-1$
+
+    imageReg.put(STEP, loadImageDescriptor("step.gif")); //$NON-NLS-1$
+    imageReg.put(EXEC, loadImageDescriptor("exec-lisp.gif")); //$NON-NLS-1$
+    imageReg.put(RUN_TESTS, loadImageDescriptor("run-tests.gif")); //$NON-NLS-1$
+  }
+
+  public static Image getImageForType(String type)
+  {
+    if( type == null || type.length() == 0 ) return null;
+    type = type.replace("(", "").toLowerCase(); //$NON-NLS-1$ //$NON-NLS-2$
+    if( typewords.contains(type) )
+    {
+      return getImage(type);
+    }
+    else if( type.startsWith("def") ){ //$NON-NLS-1$
+      // Defining something, but it doesn't match a type keyword...
+      return getImage("defother");
+    }
+    else
+    {
+      return getImage("other");
+    }
+  }
+
+  public static Image getImage(String id)
+  {
+    ImageRegistry ir = EditorPlugin.get().getImageRegistry();
+    return ir.get(id);
+  }
+
+  public static ImageDescriptor getImageDescriptor(String id)
+  {
+    ImageRegistry ir = EditorPlugin.get().getImageRegistry();
+    return ir.getDescriptor(id);
+  }
+
+  /**
+   * Loads an ImageDescriptor from a file in the icons directory
+   *
+   * @param name
+   *          - name of the file to be loaded
+   * @return An ImageDescriptor representing the image in the file
+   */
+  private static ImageDescriptor loadImageDescriptor(String name)
+  {
+    try
+    {
+      URL installURL =
+        Platform.getBundle("org.lispdev.editor").getEntry("/"); //$NON-NLS-1$ //$NON-NLS-2$
+      URL url = new URL(installURL, "icons/" + name); //$NON-NLS-1$
+      return ImageDescriptor.createFromURL(url);
+    }
+    catch(MalformedURLException e)
+    {
+      // should not happen
+      return ImageDescriptor.getMissingImageDescriptor();
+    }
+  }
+}

File src/org/lispdev/editor/LispMarkers.java

+package org.lispdev.editor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.ui.texteditor.MarkerUtilities;
+import org.lispdev.log.Log;
+import org.lispdev.parser.ParseError;
+import org.lispdev.parser.nodes.TreeRoot;
+
+public class LispMarkers
+{
+  static final String MARKER = "org.lispdev.marker";
+  static final String PARSING_ERROR = MARKER + ".parsingError";
+  public static final String COMPILE = MARKER + ".compile";
+
+  private static void deleteMarkers(IFile file, String marketType)
+  {
+    if( file != null )
+    {
+      try
+      {
+        file.deleteMarkers(marketType, true, IResource.DEPTH_ZERO);
+      }
+      catch(CoreException e1)
+      {
+        Log.logException(e1);
+      }
+    }
+  }
+
+  private static HashMap<String, Object> getMarketAttributes(
+      String msg, int severity)
+  {
+    HashMap<String,Object> attr = new HashMap<String, Object>(2);
+    attr.put(IMarker.MESSAGE, msg);
+    attr.put(IMarker.SEVERITY, severity);
+
+    return attr;
+  }
+
+  private static HashMap<String, Object> getMarketAttributes(int offset,
+      int length, String msg, int severity)
+  {
+    HashMap<String,Object> attr = new HashMap<String, Object>(5);
+    attr.put(IMarker.CHAR_START, new Integer(offset));
+    attr.put(IMarker.CHAR_END, new Integer(offset + length));
+
+    attr.put(IMarker.MESSAGE, msg);
+    attr.put(IMarker.SEVERITY, severity);
+
+    return attr;
+  }
+
+  private static HashMap<String, Object> getMarketAttributes(int offset,
+      int length, int lineNum, String msg, int severity)
+  {
+    HashMap<String,Object> attr = new HashMap<String, Object>(6);
+    attr.put(IMarker.CHAR_START, new Integer(offset));
+    attr.put(IMarker.CHAR_END, new Integer(offset + length));
+
+    attr.put(IMarker.MESSAGE, msg);
+    attr.put(IMarker.SEVERITY, severity);
+
+    return attr;
+  }
+
+  public static void addMarker(IFile file, int offset, int length,
+      int lineNum, String msg, int severity, String markerType)
+  {
+    try
+    {
+      MarkerUtilities.createMarker(file,
+          getMarketAttributes(offset, length, lineNum, msg, severity),
+          markerType);
+    }
+    catch(CoreException e)
+    {
+      Log.logException(e);
+    }
+  }
+
+  public static void addMarker(IFile file, int offset, int length,
+      String msg, int severity, String markerType)
+  {
+    try
+    {
+      MarkerUtilities.createMarker(file,
+          getMarketAttributes(offset, length, msg, severity), markerType);
+    }
+    catch(CoreException e)
+    {
+      Log.logException(e);
+    }
+  }
+
+  public static void addMarker(IResource project, String msg, int severity,
+      String markerType)
+  {
+    try
+    {
+      MarkerUtilities.createMarker(project, getMarketAttributes(msg, severity),
+          markerType);
+    }
+    catch(CoreException e)
+    {
+      Log.logException(e);
+    }
+  }
+
+  private static void addMarker(IFile file, IDocument doc,
+      int offset, int length, String msg, int severity, String markerType)
+  {
+    try
+    {
+      final int lineNum = doc.getLineOfOffset(offset);
+      addMarker(file,offset,length,lineNum,msg,severity,markerType);
+    }
+    catch(BadLocationException e)
+    {
+      Log.logException(e);
+    }
+  }
+
+  private static String getErrMessage(ParseError err, boolean endOfFile)
+  {
+    StringBuilder sb = new StringBuilder();
+    if( err.expected != null )
+    {
+      sb.append("Expected '");
+      sb.append(err.expected);
+      sb.append("'");
+      if( err.found != null )
+      {
+        sb.append(" but found '");
+        sb.append(err.found.equals("EOF") && !endOfFile ? "" : err.found);
+        sb.append("'");
+      }
+    }
+    else if( err.found != null )
+    {
+      sb.append("Found '");
+      sb.append(err.found);
+      sb.append("'");
+    }
+
+    return sb.toString();
+  }
+
+  public static void updateParsingMarkers(IFile file, IDocument doc)
+  {
+    TreeRoot tree = EditorPlugin.get().getAst(doc);
+    if( file == null || tree == null ) return;
+    deleteMarkers(file, PARSING_ERROR);
+    Map<ParseError, int[]> errors = tree.errors();
+    final int len = doc.getLength();
+    for( Map.Entry<ParseError,int[]> e : errors.entrySet() )
+    {
+      ParseError err = e.getKey();
+      int[] range = e.getValue();
+      addMarker(file,doc,range[0],range[1],
+          getErrMessage(err, range[0]+1 >= len),
+          IMarker.SEVERITY_ERROR, PARSING_ERROR);
+    }
+  }
+
+  private static void deleteTasks(IFile file)
+  {
+    if( file == null )
+    {
+      return;
+    }
+    try
+    {
+      file.deleteMarkers(IMarker.TASK, false, IResource.DEPTH_ZERO);
+    }
+    catch(CoreException ce)
+    {}
+  }
+
+  private static void addTask(IFile file, String message, int lineNumber)
+  {
+    if( file == null )
+    {
+      return;
+    }
+    try
+    {
+      IMarker marker = file.createMarker(IMarker.TASK);
+      marker.setAttribute(IMarker.MESSAGE, message);
+      if( lineNumber == -1 )
+      {
+        lineNumber = 1;
+      }
+      marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
+    }
+    catch(CoreException e)
+    {
+      Log.logException(e);
+    }
+  }
+
+  public static void updateTasks(IFile file, IDocument doc)
+  {
+    if( file == null || doc == null )
+    {
+      return;
+    }
+    deleteTasks(file);
+    String[] lines = doc.get().split("\n");
+    int numLines = 0;
+    for(String line : lines)
+    {
+      if( line.matches(".*;.*TODO:.*\\s*") )
+      {
+        String[] strs = line.trim().split("TODO:");
+        for(int i = 1; i < strs.length; ++i)
+        {
+          addTask(file, "TODO:" + strs[i], numLines + 1);
+        }
+      }
+      ++numLines;
+    }
+  }
+}

File src/org/lispdev/editor/LispOutlinePage.java

+package org.lispdev.editor;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IStatusLineManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.viewers.DecoratingLabelProvider;
+import org.eclipse.jface.viewers.ILabelDecorator;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.jface.viewers.ViewerSorter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
+import org.lispdev.log.Trace;
+import org.lispdev.parser.nodes.Hidden;
+import org.lispdev.parser.nodes.Node;
+import org.lispdev.parser.nodes.Sexp;
+import org.lispdev.parser.nodes.TreeRoot;
+import org.lispdev.parser.nodes.TreeWalker;
+import org.lispdev.parser.tokens.TokenOutline;
+
+public class LispOutlinePage extends ContentOutlinePage
+implements MouseTrackListener, KeyListener
+{
+  TreeRoot ast;
+  LispEditor editor;
+  ISelectionChangedListener selectionListener;
+
+  public LispOutlinePage(LispEditor editor)
+  {
+    this.editor = editor;
+    ast = EditorPlugin.get().getAst(editor.getDoc());
+  }
+
+  public void refresh()
+  {
+    getTreeViewer().refresh();
+  }
+
+  private boolean outlineMode = false;
+
+  ViewerFilter filterSections = new ViewerFilter(){
+    @Override
+    public boolean select(Viewer viewer, Object parentElement, Object element)
+    {
+      return !(element instanceof Hidden);
+    }
+  };
+
+  @Override
+  public void makeContributions(IMenuManager menuManager,
+      IToolBarManager toolbarManager, IStatusLineManager statusLineManager)
+  {
+    IAction sortAction = new Action("Sort", SWT.TOGGLE){
+      @Override
+      public void run()
+      {
+        getTreeViewer().setSorter( isChecked() ? new ViewerSorter() : null );
+      }
+    };
+    sortAction.setToolTipText("Sort");
+    sortAction.setImageDescriptor(
+        LispEditorResources.getImageDescriptor(LispEditorResources.SORT_ALPHA));
+    toolbarManager.add(sortAction);
+
+    IAction outlineModeAction = new Action("Outline Mode", SWT.TOGGLE){
+      @Override
+      public void run()
+      {
+        outlineMode = isChecked();
+        getTreeViewer().refresh();
+      }
+    };
+    outlineModeAction.setToolTipText("Outline Mode");
+    outlineModeAction.setImageDescriptor(
+        LispEditorResources.getImageDescriptor(
+            LispEditorResources.OUTLINE_MODE));
+    toolbarManager.add(outlineModeAction);
+  }
+
+  @Override
+  public void createControl(Composite parent)
+  {
+    super.createControl(parent);
+
+    if( editor == null || editor.getDoc() == null )
+    {
+      return;
+    }
+
+    TreeViewer viewer = getTreeViewer();
+    viewer.setContentProvider(new LispContentProvider());
+    viewer.setLabelProvider(new LispOutlineLabelProvider());
+    viewer.setInput(EditorPlugin.get().getAst(editor.getDoc()));
+    viewer.getTree().addMouseTrackListener(this);
+    selectionListener = new ISelectionChangedListener(){
+      @Override
+      public void selectionChanged(SelectionChangedEvent event)
+      {
+        navigateToSelection(event.getSelection());
+      }};
+    viewer.addSelectionChangedListener(selectionListener);
+  }
+
+  @Override
+  public void dispose()
+  {
+    TreeViewer viewer = getTreeViewer();
+    //viewer.getControl().removeMouseListener(this);
+    //viewer.getControl().removeKeyListener(this);
+    viewer.removeSelectionChangedListener(selectionListener);
+    super.dispose();
+  }
+
+  private class LispContentProvider implements ITreeContentProvider
+  {
+
+    @Override
+    public Object[] getChildren(Object parentElement)
+    {
+      return
+        ast.outlineChildren((Node)parentElement,
+            TreeRoot.OUTLINE_GET_CHILDREN,!outlineMode);
+    }
+
+    @Override
+    public Object getParent(Object element)
+    {
+      return ast.outlineParent((Node)element,!outlineMode);
+    }
+
+    @Override
+    public boolean hasChildren(Object element)
+    {
+      return ast.outlineChildren((Node)element,
+          TreeRoot.OUTLINE_GET_FIRST,!outlineMode)!= null;
+    }
+
+    @Override
+    public Object[] getElements(Object inputElement)
+    {
+      ast = (TreeRoot)inputElement;
+      return
+        ast.outlineChildren(ast,TreeRoot.OUTLINE_GET_CHILDREN,!outlineMode);
+    }
+
+    @Override
+    public void dispose()
+    {}
+
+    @Override
+    public void inputChanged(Viewer viewer, Object oldInput, Object newInput)
+    {
+    }
+
+  }
+
+  private static class LispOutlineLabelProvider extends DecoratingLabelProvider
+  {
+    public LispOutlineLabelProvider()
+    {
+      this(new LispLabelProvider(),null);
+    }
+
+    private LispOutlineLabelProvider(ILabelProvider provider,
+        ILabelDecorator decorator)
+    {
+      super(provider, decorator);
+    }
+
+    private static class LispLabelProvider implements ILabelProvider
+    {
+
+      @Override
+      public Image getImage(Object element)
+      {
+        if( element instanceof Sexp )
+        {
+          return LispEditorResources.
+            getImageForType(TreeWalker.getInfo((Sexp)element).type);
+        }
+        if( element instanceof Node )
+        {
+          //Node t = (Node)element;
+          String type = "none";//(t.token == null ? "" : t.token.getText());
+          return LispEditorResources.getImageForType(type);
+        }
+        else //section
+        {
+          return LispEditorResources.getImageForType("section");
+        }
+      }
+
+      @Override
+      public String getText(Object element)
+      {
+        if( element instanceof Sexp )
+        {
+          Sexp s = (Sexp)element;
+          String name = TreeWalker.getInfo(s).name;
+          if( name != null )
+          {
+            return name;
+          }
+        }
+        else if( element instanceof Node )
+        {
+          Node n = (Node)element;
+          if( n.firstToken() instanceof TokenOutline )
+          {
+            TokenOutline t = (TokenOutline)n.firstToken();
+            return t.text().substring(t.level());
+          }
+        }
+        return element.toString();
+      }
+
+      @Override
+      public void addListener(ILabelProviderListener listener)
+      {
+      }
+
+      @Override
+      public void dispose()
+      {
+      }
+
+      @Override
+      public boolean isLabelProperty(Object element, String property)
+      {
+        return false;
+      }
+
+      @Override
+      public void removeListener(ILabelProviderListener listener)
+      {
+      }
+
+    }
+  }
+
+  // navigation