Anonymous avatar Anonymous committed 2fc2f51 Merge

Merge editor with lispdev.

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>

.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

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
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.xml
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Add a comment to this file

icons/define-alien-routine.gif

Added
New image
Add a comment to this file

icons/define-alien-type.gif

Added
New image
Add a comment to this file

icons/define-alien-variable.gif

Added
New image
Add a comment to this file

icons/define-condition.gif

Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Add a comment to this file

icons/install-new-package.gif

Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
Add a comment to this file

icons/sort-position.gif

Added
New image
Added
New image
Added
New image
Added
New image
Added
New image
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>

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;
+  }
+}

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;
+    }
+  }
+
+
+}

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);
+}

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();
+  }
+}

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);
+  }
+
+}

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();
+    }
+  }
+*/
+}

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();
+    }
+  }
+}

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;
+    }
+  }
+}

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
+
+  private Object lastSelection;
+
+  private void navigateToSelection(ISelection sel)
+  {
+    if( !sel.isEmpty() )
+    {
+      final Object o = ((IStructuredSelection)sel).getFirstElement();
+      if( o != lastSelection && ( o instanceof Node ))
+      {
+        lastSelection = o;
+        selectAndReveal((Node)o);
+      }
+    }
+  }
+
+  private void selectAndReveal(Node n)
+  {
+    editor.selectAndReveal(n.offset(), 1);
+  }
+
+
+
+  @Override
+  public void mouseEnter(MouseEvent e)
+  {}
+
+  @Override
+  public void mouseExit(MouseEvent e)
+  {}
+
+  @Override
+  public void mouseHover(MouseEvent e)
+  {}
+
+  String search = "";
+
+  private boolean isSearchable(char c)
+  {
+    if("1234567890qwertyuiopasdfghjklzxcvbnm!@#$%^&*()_-=+{}|[]\\:;\"\'<>?,./`~"
+        .indexOf(Character.toLowerCase(c)) >= 0 )
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  @Override
+  public void keyPressed(KeyEvent e)
+  {
+    if( Trace.DEBUG.enabled )
+      Trace.DEBUG.trace(search);
+    if( e.keyCode == SWT.ESC )
+    {
+      search = "";
+    }
+    else if( e.character == SWT.BS )
+    {
+      search = search.substring(0, search.length() - 1);
+    }
+    else if( isSearchable(e.character) )
+    {
+      search += e.character;
+
+      for(TreeItem node : getTreeViewer().getTree().getItems())
+      {
+        if( node.getText().startsWith(search) )
+        {
+          getTreeViewer().getTree().setSelection(node);
+          Object o = node.getData();
+          if( o instanceof Node )
+          {
+            lastSelection = o;
+            selectAndReveal((Node)o);
+          }
+          return;
+        }
+      }
+      this.getSite().getShell().getDisplay().beep();
+    }
+  }
+
+  @Override
+  public void setFocus() {
+          search = "";
+  }
+
+  @Override
+  public void keyReleased(KeyEvent e)
+  {}
+
+}