]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / winterwell.markdown / src / winterwell / markdown / editors / MarkdownContentOutlinePage.java
index 445a322d3e6accc02555c925311519404449f657..4fc336ee6337d45761eeb236867779dda433692e 100644 (file)
-/**\r
- * Copyright winterwell Mathematics Ltd.\r
- * @author Daniel Winterstein\r
- * 11 Jan 2007\r
- */\r
-package winterwell.markdown.editors;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-\r
-import org.eclipse.jface.action.Action;\r
-import org.eclipse.jface.action.IAction;\r
-import org.eclipse.jface.action.IToolBarManager;\r
-import org.eclipse.jface.resource.ImageDescriptor;\r
-import org.eclipse.jface.text.BadLocationException;\r
-import org.eclipse.jface.text.DocumentEvent;\r
-import org.eclipse.jface.text.IDocument;\r
-import org.eclipse.jface.text.IDocumentListener;\r
-import org.eclipse.jface.text.IRegion;\r
-import org.eclipse.jface.text.Region;\r
-import org.eclipse.jface.viewers.ISelection;\r
-import org.eclipse.jface.viewers.IStructuredSelection;\r
-import org.eclipse.jface.viewers.ITreeContentProvider;\r
-import org.eclipse.jface.viewers.LabelProvider;\r
-import org.eclipse.jface.viewers.SelectionChangedEvent;\r
-import org.eclipse.jface.viewers.StructuredSelection;\r
-import org.eclipse.jface.viewers.TreeViewer;\r
-import org.eclipse.jface.viewers.Viewer;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.events.KeyEvent;\r
-import org.eclipse.swt.events.KeyListener;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Control;\r
-import org.eclipse.ui.IActionBars;\r
-import org.eclipse.ui.part.IPageSite;\r
-import org.eclipse.ui.texteditor.IDocumentProvider;\r
-import org.eclipse.ui.views.contentoutline.ContentOutlinePage;\r
-\r
-import winterwell.markdown.pagemodel.MarkdownPage;\r
-import winterwell.markdown.pagemodel.MarkdownPage.Header;\r
-import winterwell.markdown.pagemodel.MarkdownPage.KLineType;\r
-import winterwell.utils.StrUtils;\r
-import winterwell.utils.Utils;\r
-import winterwell.utils.web.WebUtils;\r
-\r
-/**\r
- *\r
- *\r
- * @author Daniel Winterstein\r
- */\r
-public final class MarkdownContentOutlinePage extends ContentOutlinePage {\r
-\r
-       /**\r
-        *\r
-        *\r
-        * @author Daniel Winterstein\r
-        */\r
-       public final class ContentProvider implements ITreeContentProvider,\r
-                       IDocumentListener {\r
-\r
-               // protected final static String SEGMENTS= "__md_segments";\r
-               // //$NON-NLS-1$\r
-               // protected IPositionUpdater fPositionUpdater= new\r
-               // DefaultPositionUpdater(SEGMENTS);\r
-               private MarkdownPage fContent;\r
-               // protected List fContent= new ArrayList(10);\r
-               private MarkdownEditor fTextEditor;\r
-\r
-               private void parse() {\r
-                       fContent = fTextEditor.getMarkdownPage();\r
-               }\r
-\r
-               /*\r
-                * @see IContentProvider#inputChanged(Viewer, Object, Object)\r
-                */\r
-               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
-                       // Detach from old\r
-                       if (oldInput != null) {\r
-                               IDocument document = fDocumentProvider.getDocument(oldInput);\r
-                               if (document != null) {\r
-                                       document.removeDocumentListener(this);\r
-                               }\r
-                       }\r
-                       fContent = null;\r
-                       // Attach to new\r
-                       if (newInput == null)\r
-                               return;\r
-                       IDocument document = fDocumentProvider.getDocument(newInput);\r
-                       if (document == null)\r
-                               return;\r
-                       fTextEditor = MarkdownEditor.getEditor(document);\r
-                       document.addDocumentListener(this);\r
-                       parse();\r
-               }\r
-\r
-               /*\r
-                * @see IContentProvider#dispose\r
-                */\r
-               public void dispose() {\r
-                       fContent = null;\r
-               }\r
-\r
-               /*\r
-                * @see IContentProvider#isDeleted(Object)\r
-                */\r
-               public boolean isDeleted(Object element) {\r
-                       return false;\r
-               }\r
-\r
-               /*\r
-                * @see IStructuredContentProvider#getElements(Object)\r
-                */\r
-               public Object[] getElements(Object element) {\r
-                       return fContent.getHeadings(null).toArray();\r
-               }\r
-\r
-               /*\r
-                * @see ITreeContentProvider#hasChildren(Object)\r
-                */\r
-               public boolean hasChildren(Object element) {\r
-                       if (element == fInput) {\r
-                               return true;\r
-                       }\r
-                       if (element instanceof MarkdownPage.Header) {\r
-                               MarkdownPage.Header header = (MarkdownPage.Header) element;\r
-                               return header.getSubHeaders().size() > 0;\r
-                       }\r
-                       ;\r
-                       return false;\r
-               }\r
-\r
-               /*\r
-                * @see ITreeContentProvider#getParent(Object)\r
-                */\r
-               public Object getParent(Object element) {\r
-                       if (!(element instanceof MarkdownPage.Header))\r
-                               return null;\r
-                       return ((MarkdownPage.Header) element).getParent();\r
-               }\r
-\r
-               /*\r
-                * @see ITreeContentProvider#getChildren(Object)\r
-                */\r
-               public Object[] getChildren(Object element) {\r
-                       if (element == fInput) {\r
-                               return fContent.getHeadings(null).toArray();\r
-                       }\r
-                       if (!(element instanceof MarkdownPage.Header))\r
-                               return null;\r
-                       return ((MarkdownPage.Header) element).getSubHeaders().toArray();\r
-               }\r
-\r
-               public void documentAboutToBeChanged(DocumentEvent event) {\r
-                       // nothing\r
-               }\r
-\r
-               public void documentChanged(DocumentEvent event) {\r
-                       parse();\r
-                       update();\r
-               }\r
-       }\r
-\r
-       private Object fInput = null;\r
-       private final IDocumentProvider fDocumentProvider;\r
-       private final MarkdownEditor fTextEditor;\r
-       protected boolean showWordCounts;\r
-       private List<Header> selectedHeaders;\r
-\r
-       /**\r
-        * @param documentProvider\r
-        * @param mdEditor\r
-        */\r
-       public MarkdownContentOutlinePage(IDocumentProvider documentProvider,\r
-                       MarkdownEditor mdEditor) {\r
-               fDocumentProvider = documentProvider;\r
-               fTextEditor = mdEditor;\r
-       }\r
-\r
-       /*\r
-        * (non-Javadoc) Method declared on ContentOutlinePage\r
-        */\r
-       @Override\r
-       public void createControl(Composite parent) {\r
-               super.createControl(parent);\r
-               TreeViewer viewer = getTreeViewer();\r
-               viewer.setContentProvider(new ContentProvider());\r
-               // Add word count annotations\r
-               viewer.setLabelProvider(new LabelProvider() {\r
-                       @Override\r
-                       public String getText(Object element) {\r
-                               if (!(element instanceof MarkdownPage.Header))\r
-                                       return super.getText(element);\r
-                               Header header = ((MarkdownPage.Header) element);\r
-                               String hText = header.toString();\r
-                               if (!showWordCounts)\r
-                                       return hText;\r
-                               IRegion region = getRegion(header);\r
-                               String text;\r
-                               try {\r
-                                       text = fTextEditor.getDocument().get(region.getOffset(),\r
-                                                       region.getLength());\r
-                                       text = WebUtils.stripTags(text);\r
-                                       text = text.replaceAll("#", "").trim();\r
-                                       assert text.startsWith(hText);\r
-                                       text = text.substring(hText.length());\r
-                                       int wc = StrUtils.wordCount(text);\r
-                                       return hText + " (" + wc + ":" + text.length() + ")";\r
-                               } catch (BadLocationException e) {\r
-                                       return hText;\r
-                               }\r
-                       }\r
-               });\r
-               viewer.addSelectionChangedListener(this);\r
-\r
-               if (fInput != null)\r
-                       viewer.setInput(fInput);\r
-\r
-               // Buttons\r
-               IPageSite site = getSite();\r
-               IActionBars bars = site.getActionBars();\r
-               IToolBarManager toolbar = bars.getToolBarManager();\r
-               // Word count action\r
-               Action action = new Action("123", IAction.AS_CHECK_BOX) {\r
-                       @Override\r
-                       public void run() {\r
-                               showWordCounts = isChecked();\r
-                               update();\r
-                       }\r
-               };\r
-               action.setToolTipText("Show/hide section word:character counts");\r
-               toolbar.add(action);\r
-               // +/- actions\r
-               action = new Action("<") {\r
-                       @Override\r
-                       public void run() {\r
-                               doPromoteDemote(-1);\r
-                       }\r
-               };\r
-               action.setToolTipText("Promote the selected section\n -- move it up a level.");\r
-               toolbar.add(action);\r
-               //\r
-               action = new Action(">") {\r
-                       @Override\r
-                       public void run() {\r
-                               doPromoteDemote(1);\r
-                       }\r
-               };\r
-               action.setToolTipText("Demote the selected section\n -- move it down a level.");\r
-               toolbar.add(action);\r
-               // up/down actions\r
-               action = new Action("/\\") {\r
-                       @Override\r
-                       public void run() {\r
-                               try {\r
-                                       doMove(-1);\r
-                               } catch (BadLocationException e) {\r
-                                       throw Utils.runtime(e);\r
-                               }\r
-                       }\r
-               };\r
-               action.setToolTipText("Move the selected section earlier");\r
-               toolbar.add(action);\r
-               //\r
-               action = new Action("\\/") {\r
-                       @Override\r
-                       public void run() {\r
-                               try {\r
-                                       doMove(1);\r
-                               } catch (BadLocationException e) {\r
-                                       throw Utils.runtime(e);\r
-                               }\r
-                       }\r
-               };\r
-               action.setToolTipText("Move the selected section later");\r
-               toolbar.add(action);\r
-               // Collapse\r
-               ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif");\r
-               action = new Action("collapse", id) {\r
-                       @Override\r
-                       public void run() {\r
-                               doCollapseAll();\r
-                       }\r
-               };\r
-               action.setImageDescriptor(id);\r
-               action.setToolTipText("Collapse outline tree");\r
-               toolbar.add(action);\r
-               // Sync\r
-               id = ImageDescriptor.createFromFile(getClass(), "synced.gif");\r
-               action = new Action("sync") {\r
-                       @Override\r
-                       public void run() {\r
-                               try {\r
-                                       doSyncToEditor();\r
-                               } catch (BadLocationException e) {\r
-                                       throw Utils.runtime(e);\r
-                               }\r
-                       }\r
-               };\r
-               action.setImageDescriptor(id);\r
-               action.setToolTipText("Link with editor");\r
-               toolbar.add(action);\r
-               // Add edit ability\r
-               viewer.getControl().addKeyListener(new KeyListener() {\r
-                       public void keyPressed(KeyEvent e) {\r
-                               if (e.keyCode==SWT.F2) {\r
-                                       doEditHeader();\r
-                               }\r
-                       }\r
-                       public void keyReleased(KeyEvent e) {\r
-                               //\r
-                       }\r
-               });\r
-       }\r
-\r
-       /**\r
-        * @throws BadLocationException\r
-        *\r
-        */\r
-       protected void doSyncToEditor() throws BadLocationException {\r
-               TreeViewer viewer = getTreeViewer();\r
-               if (viewer == null) return;\r
-               // Get header\r
-               MarkdownPage page = fTextEditor.getMarkdownPage();\r
-               int caretOffset = fTextEditor.getViewer().getTextWidget().getCaretOffset();\r
-               IDocument doc = fTextEditor.getDocument();\r
-               int line = doc.getLineOfOffset(caretOffset);\r
-               List<KLineType> lineTypes = page.getLineTypes();\r
-               for(; line>-1; line--) {\r
-                       KLineType lt = lineTypes.get(line);\r
-                       if (lt.toString().startsWith("H")) break;\r
-               }\r
-               if (line<0) return;\r
-               Header header = (Header) page.getPageObject(line);\r
-               // Set\r
-               IStructuredSelection selection = new StructuredSelection(header);\r
-               viewer.setSelection(selection , true);\r
-       }\r
-\r
-       void doEditHeader() {\r
-               TreeViewer viewer = getTreeViewer();\r
-               viewer.editElement(selectedHeaders.get(0), 0);\r
-       }\r
-\r
-       protected void doCollapseAll() {\r
-               TreeViewer viewer = getTreeViewer();\r
-               if (viewer == null) return;\r
-//             Control control = viewer.getControl();\r
-//             if (control != null && !control.isDisposed()) {\r
-//                     control.setRedraw(false);\r
-               viewer.collapseAll();\r
-//                     control.setRedraw(true);\r
-//             }\r
-       }\r
-\r
-       /**\r
-        * Move the selected sections up/down\r
-        * @param i 1 or -1. 1==move later, -1=earlier\r
-        * @throws BadLocationException\r
-        */\r
-       protected void doMove(int i) throws BadLocationException {\r
-               assert i==1 || i==-1;\r
-               if (selectedHeaders == null || selectedHeaders.size() == 0)\r
-                       return;\r
-               // Get text region to move\r
-               MarkdownPage.Header first = selectedHeaders.get(0);\r
-               MarkdownPage.Header last = selectedHeaders.get(selectedHeaders.size()-1);\r
-               int start = fTextEditor.getDocument().getLineOffset(\r
-                               first.getLineNumber());\r
-               IRegion r = getRegion(last);\r
-               int end = r.getOffset() + r.getLength();\r
-               int length = end - start;\r
-               // Get new insertion point\r
-               int insert;\r
-               if (i==1) {\r
-                       Header nextSection = last.getNext();\r
-                       if (nextSection==null) return;\r
-                       IRegion nr = getRegion(nextSection);\r
-                       insert = nr.getOffset()+nr.getLength();\r
-               } else {\r
-                       Header prevSection = first.getPrevious();\r
-                       if (prevSection==null) return;\r
-                       IRegion nr = getRegion(prevSection);\r
-                       insert = nr.getOffset();\r
-               }\r
-               // Get text\r
-               String text = fTextEditor.getDocument().get();\r
-               // Move text\r
-               String section = text.substring(start, end);\r
-               String pre, post;\r
-               if (i==1) {\r
-                       pre = text.substring(0, start) + text.substring(end, insert);\r
-                       post = text.substring(insert);\r
-               } else {\r
-                       pre = text.substring(0, insert);\r
-                       post = text.substring(insert,start)+text.substring(end);\r
-               }\r
-               text =  pre + section + post;\r
-               assert text.length() == fTextEditor.getDocument().get().length() :\r
-                       text.length()-fTextEditor.getDocument().get().length()+" chars gained/lost";\r
-               // Update doc\r
-               fTextEditor.getDocument().set(text);\r
-       }\r
-\r
-       /**\r
-        * Does not support -------- / ========= underlining, only # headers\r
-        * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1)\r
-        */\r
-       protected void doPromoteDemote(int upDown) {\r
-               assert upDown==1 || upDown==-1;\r
-               if (selectedHeaders == null || selectedHeaders.size() == 0)\r
-                       return;\r
-               HashSet<Header> toAdjust = new HashSet<Header>(selectedHeaders);\r
-               HashSet<Header> adjusted = new HashSet<Header>();\r
-               // Adjust\r
-               MarkdownPage mdPage = fTextEditor.getMarkdownPage();\r
-               List<String> lines = new ArrayList<String>(mdPage.getText());\r
-               while(toAdjust.size() != 0) {\r
-                       Header h = toAdjust.iterator().next();\r
-                       toAdjust.remove(h);\r
-                       adjusted.add(h);\r
-                       String line = lines.get(h.getLineNumber());\r
-                       if (upDown==-1) {\r
-                               if (h.getLevel() == 1) return; // Level 1; can't promote\r
-                               if (line.startsWith("##")) line = line.substring(1);\r
-                               else {\r
-                                       return; // TODO support for ------ / ========\r
-                               }\r
-                       } else line = "#" + line;\r
-                       int ln = h.getLineNumber();\r
-                       lines.set(ln, line);\r
-                       // kids\r
-                       ArrayList<Header> kids = new ArrayList<Header>(h.getSubHeaders());\r
-                       for (Header header : kids) {\r
-                               if ( ! adjusted.contains(header)) toAdjust.add(header);\r
-                       }\r
-               }\r
-               // Set\r
-               StringBuilder sb = new StringBuilder();\r
-               for (String line : lines) {\r
-                       sb.append(line);\r
-               }\r
-               fTextEditor.getDocument().set(sb.toString());\r
-       }\r
-\r
-       /**\r
-        * The region of text for this header. This includes the header itself.\r
-        * @param header\r
-        * @return\r
-        * @throws BadLocationException\r
-        */\r
-       protected IRegion getRegion(Header header) {\r
-               try {\r
-                       IDocument doc = fTextEditor.getDocument();\r
-                       // Line numbers\r
-                       int start = header.getLineNumber();\r
-                       Header next = header.getNext();\r
-                       int end;\r
-                       if (next != null) {\r
-                               end = next.getLineNumber() - 1;\r
-                       } else {\r
-                               end = doc.getNumberOfLines() - 1;\r
-                       }\r
-                       int offset = doc.getLineOffset(start);\r
-                       IRegion ei = doc.getLineInformation(end);\r
-                       int length = ei.getOffset() + ei.getLength() - offset;\r
-                       return new Region(offset, length);\r
-               } catch (BadLocationException ex) {\r
-                       throw Utils.runtime(ex);\r
-               }\r
-       }\r
-\r
-       /*\r
-        * (non-Javadoc) Method declared on ContentOutlinePage\r
-        */\r
-       @Override\r
-       public void selectionChanged(SelectionChangedEvent event) {\r
-               super.selectionChanged(event);\r
-               selectedHeaders = null;\r
-               ISelection selection = event.getSelection();\r
-               if (selection.isEmpty())\r
-                       return;\r
-               if (!(selection instanceof IStructuredSelection))\r
-                       return;\r
-               try {\r
-                       IStructuredSelection strucSel = (IStructuredSelection) selection;\r
-                       Object[] sections = strucSel.toArray();\r
-                       selectedHeaders = (List) Arrays.asList(sections);\r
-                       MarkdownPage.Header first = (Header) sections[0];\r
-                       MarkdownPage.Header last = (Header) sections[sections.length - 1];\r
-                       int start = fTextEditor.getDocument().getLineOffset(\r
-                                       first.getLineNumber());\r
-                       int length;\r
-                       if (first == last) {\r
-                               length = fTextEditor.getDocument().getLineLength(\r
-                                               first.getLineNumber());\r
-                       } else {\r
-                               IRegion r = getRegion(last);\r
-                               int end = r.getOffset() + r.getLength();\r
-                               length = end - start;\r
-                       }\r
-                       fTextEditor.setHighlightRange(start, length, true);\r
-               } catch (Exception x) {\r
-                       System.out.println(x.getStackTrace());\r
-                       fTextEditor.resetHighlightRange();\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Sets the input of the outline page\r
-        *\r
-        * @param input\r
-        *            the input of this outline page\r
-        */\r
-       public void setInput(Object input) {\r
-               fInput = input;\r
-               update();\r
-       }\r
-\r
-       /**\r
-        * Updates the outline page.\r
-        */\r
-       public void update() {\r
-               TreeViewer viewer = getTreeViewer();\r
-\r
-               if (viewer != null) {\r
-                       Control control = viewer.getControl();\r
-                       if (control != null && !control.isDisposed()) {\r
-                               control.setRedraw(false);\r
-                               viewer.setInput(fInput);\r
-                               viewer.expandAll();\r
-                               control.setRedraw(true);\r
-                       }\r
-               }\r
-       }\r
-\r
-}\r
+/**
+ * Copyright winterwell Mathematics Ltd.
+ * @author Daniel Winterstein
+ * 11 Jan 2007
+ */
+package winterwell.markdown.editors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
+
+import winterwell.markdown.pagemodel.MarkdownPage;
+import winterwell.markdown.pagemodel.MarkdownPage.Header;
+import winterwell.markdown.pagemodel.MarkdownPage.KLineType;
+import winterwell.utils.StrUtils;
+import winterwell.utils.Utils;
+import winterwell.utils.web.WebUtils;
+
+/**
+ *
+ *
+ * @author Daniel Winterstein
+ */
+public final class MarkdownContentOutlinePage extends ContentOutlinePage {
+
+       /**
+        *
+        *
+        * @author Daniel Winterstein
+        */
+       public final class ContentProvider implements ITreeContentProvider,
+                       IDocumentListener {
+
+               // protected final static String SEGMENTS= "__md_segments";
+               // //$NON-NLS-1$
+               // protected IPositionUpdater fPositionUpdater= new
+               // DefaultPositionUpdater(SEGMENTS);
+               private MarkdownPage fContent;
+               // protected List fContent= new ArrayList(10);
+               private MarkdownEditor fTextEditor;
+
+               private void parse() {
+                       fContent = fTextEditor.getMarkdownPage();
+               }
+
+               /*
+                * @see IContentProvider#inputChanged(Viewer, Object, Object)
+                */
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+                       // Detach from old
+                       if (oldInput != null) {
+                               IDocument document = fDocumentProvider.getDocument(oldInput);
+                               if (document != null) {
+                                       document.removeDocumentListener(this);
+                               }
+                       }
+                       fContent = null;
+                       // Attach to new
+                       if (newInput == null)
+                               return;
+                       IDocument document = fDocumentProvider.getDocument(newInput);
+                       if (document == null)
+                               return;
+                       fTextEditor = MarkdownEditor.getEditor(document);
+                       document.addDocumentListener(this);
+                       parse();
+               }
+
+               /*
+                * @see IContentProvider#dispose
+                */
+               public void dispose() {
+                       fContent = null;
+               }
+
+               /*
+                * @see IContentProvider#isDeleted(Object)
+                */
+               public boolean isDeleted(Object element) {
+                       return false;
+               }
+
+               /*
+                * @see IStructuredContentProvider#getElements(Object)
+                */
+               public Object[] getElements(Object element) {
+                       return fContent.getHeadings(null).toArray();
+               }
+
+               /*
+                * @see ITreeContentProvider#hasChildren(Object)
+                */
+               public boolean hasChildren(Object element) {
+                       if (element == fInput) {
+                               return true;
+                       }
+                       if (element instanceof MarkdownPage.Header) {
+                               MarkdownPage.Header header = (MarkdownPage.Header) element;
+                               return header.getSubHeaders().size() > 0;
+                       }
+                       ;
+                       return false;
+               }
+
+               /*
+                * @see ITreeContentProvider#getParent(Object)
+                */
+               public Object getParent(Object element) {
+                       if (!(element instanceof MarkdownPage.Header))
+                               return null;
+                       return ((MarkdownPage.Header) element).getParent();
+               }
+
+               /*
+                * @see ITreeContentProvider#getChildren(Object)
+                */
+               public Object[] getChildren(Object element) {
+                       if (element == fInput) {
+                               return fContent.getHeadings(null).toArray();
+                       }
+                       if (!(element instanceof MarkdownPage.Header))
+                               return null;
+                       return ((MarkdownPage.Header) element).getSubHeaders().toArray();
+               }
+
+               public void documentAboutToBeChanged(DocumentEvent event) {
+                       // nothing
+               }
+
+               public void documentChanged(DocumentEvent event) {
+                       parse();
+                       update();
+               }
+       }
+
+       private Object fInput = null;
+       private final IDocumentProvider fDocumentProvider;
+       private final MarkdownEditor fTextEditor;
+       protected boolean showWordCounts;
+       private List<Header> selectedHeaders;
+
+       /**
+        * @param documentProvider
+        * @param mdEditor
+        */
+       public MarkdownContentOutlinePage(IDocumentProvider documentProvider,
+                       MarkdownEditor mdEditor) {
+               fDocumentProvider = documentProvider;
+               fTextEditor = mdEditor;
+       }
+
+       /*
+        * (non-Javadoc) Method declared on ContentOutlinePage
+        */
+       @Override
+       public void createControl(Composite parent) {
+               super.createControl(parent);
+               TreeViewer viewer = getTreeViewer();
+               viewer.setContentProvider(new ContentProvider());
+               // Add word count annotations
+               viewer.setLabelProvider(new LabelProvider() {
+                       @Override
+                       public String getText(Object element) {
+                               if (!(element instanceof MarkdownPage.Header))
+                                       return super.getText(element);
+                               Header header = ((MarkdownPage.Header) element);
+                               String hText = header.toString();
+                               if (!showWordCounts)
+                                       return hText;
+                               IRegion region = getRegion(header);
+                               String text;
+                               try {
+                                       text = fTextEditor.getDocument().get(region.getOffset(),
+                                                       region.getLength());
+                                       text = WebUtils.stripTags(text);
+                                       text = text.replaceAll("#", "").trim();
+                                       assert text.startsWith(hText);
+                                       text = text.substring(hText.length());
+                                       int wc = StrUtils.wordCount(text);
+                                       return hText + " (" + wc + ":" + text.length() + ")";
+                               } catch (BadLocationException e) {
+                                       return hText;
+                               }
+                       }
+               });
+               viewer.addSelectionChangedListener(this);
+
+               if (fInput != null)
+                       viewer.setInput(fInput);
+
+               // Buttons
+               IPageSite site = getSite();
+               IActionBars bars = site.getActionBars();
+               IToolBarManager toolbar = bars.getToolBarManager();
+               // Word count action
+               Action action = new Action("123", IAction.AS_CHECK_BOX) {
+                       @Override
+                       public void run() {
+                               showWordCounts = isChecked();
+                               update();
+                       }
+               };
+               action.setToolTipText("Show/hide section word:character counts");
+               toolbar.add(action);
+               // +/- actions
+               action = new Action("<") {
+                       @Override
+                       public void run() {
+                               doPromoteDemote(-1);
+                       }
+               };
+               action.setToolTipText("Promote the selected section\n -- move it up a level.");
+               toolbar.add(action);
+               //
+               action = new Action(">") {
+                       @Override
+                       public void run() {
+                               doPromoteDemote(1);
+                       }
+               };
+               action.setToolTipText("Demote the selected section\n -- move it down a level.");
+               toolbar.add(action);
+               // up/down actions
+               action = new Action("/\\") {
+                       @Override
+                       public void run() {
+                               try {
+                                       doMove(-1);
+                               } catch (BadLocationException e) {
+                                       throw Utils.runtime(e);
+                               }
+                       }
+               };
+               action.setToolTipText("Move the selected section earlier");
+               toolbar.add(action);
+               //
+               action = new Action("\\/") {
+                       @Override
+                       public void run() {
+                               try {
+                                       doMove(1);
+                               } catch (BadLocationException e) {
+                                       throw Utils.runtime(e);
+                               }
+                       }
+               };
+               action.setToolTipText("Move the selected section later");
+               toolbar.add(action);
+               // Collapse
+               ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif");
+               action = new Action("collapse", id) {
+                       @Override
+                       public void run() {
+                               doCollapseAll();
+                       }
+               };
+               action.setImageDescriptor(id);
+               action.setToolTipText("Collapse outline tree");
+               toolbar.add(action);
+               // Sync
+               id = ImageDescriptor.createFromFile(getClass(), "synced.gif");
+               action = new Action("sync") {
+                       @Override
+                       public void run() {
+                               try {
+                                       doSyncToEditor();
+                               } catch (BadLocationException e) {
+                                       throw Utils.runtime(e);
+                               }
+                       }
+               };
+               action.setImageDescriptor(id);
+               action.setToolTipText("Link with editor");
+               toolbar.add(action);
+               // Add edit ability
+               viewer.getControl().addKeyListener(new KeyListener() {
+                       public void keyPressed(KeyEvent e) {
+                               if (e.keyCode==SWT.F2) {
+                                       doEditHeader();
+                               }
+                       }
+                       public void keyReleased(KeyEvent e) {
+                               //
+                       }
+               });
+       }
+
+       /**
+        * @throws BadLocationException
+        *
+        */
+       protected void doSyncToEditor() throws BadLocationException {
+               TreeViewer viewer = getTreeViewer();
+               if (viewer == null) return;
+               // Get header
+               MarkdownPage page = fTextEditor.getMarkdownPage();
+               int caretOffset = fTextEditor.getViewer().getTextWidget().getCaretOffset();
+               IDocument doc = fTextEditor.getDocument();
+               int line = doc.getLineOfOffset(caretOffset);
+               List<KLineType> lineTypes = page.getLineTypes();
+               for(; line>-1; line--) {
+                       KLineType lt = lineTypes.get(line);
+                       if (lt.toString().startsWith("H")) break;
+               }
+               if (line<0) return;
+               Header header = (Header) page.getPageObject(line);
+               // Set
+               IStructuredSelection selection = new StructuredSelection(header);
+               viewer.setSelection(selection , true);
+       }
+
+       void doEditHeader() {
+               TreeViewer viewer = getTreeViewer();
+               viewer.editElement(selectedHeaders.get(0), 0);
+       }
+
+       protected void doCollapseAll() {
+               TreeViewer viewer = getTreeViewer();
+               if (viewer == null) return;
+//             Control control = viewer.getControl();
+//             if (control != null && !control.isDisposed()) {
+//                     control.setRedraw(false);
+               viewer.collapseAll();
+//                     control.setRedraw(true);
+//             }
+       }
+
+       /**
+        * Move the selected sections up/down
+        * @param i 1 or -1. 1==move later, -1=earlier
+        * @throws BadLocationException
+        */
+       protected void doMove(int i) throws BadLocationException {
+               assert i==1 || i==-1;
+               if (selectedHeaders == null || selectedHeaders.size() == 0)
+                       return;
+               // Get text region to move
+               MarkdownPage.Header first = selectedHeaders.get(0);
+               MarkdownPage.Header last = selectedHeaders.get(selectedHeaders.size()-1);
+               int start = fTextEditor.getDocument().getLineOffset(
+                               first.getLineNumber());
+               IRegion r = getRegion(last);
+               int end = r.getOffset() + r.getLength();
+               int length = end - start;
+               // Get new insertion point
+               int insert;
+               if (i==1) {
+                       Header nextSection = last.getNext();
+                       if (nextSection==null) return;
+                       IRegion nr = getRegion(nextSection);
+                       insert = nr.getOffset()+nr.getLength();
+               } else {
+                       Header prevSection = first.getPrevious();
+                       if (prevSection==null) return;
+                       IRegion nr = getRegion(prevSection);
+                       insert = nr.getOffset();
+               }
+               // Get text
+               String text = fTextEditor.getDocument().get();
+               // Move text
+               String section = text.substring(start, end);
+               String pre, post;
+               if (i==1) {
+                       pre = text.substring(0, start) + text.substring(end, insert);
+                       post = text.substring(insert);
+               } else {
+                       pre = text.substring(0, insert);
+                       post = text.substring(insert,start)+text.substring(end);
+               }
+               text =  pre + section + post;
+               assert text.length() == fTextEditor.getDocument().get().length() :
+                       text.length()-fTextEditor.getDocument().get().length()+" chars gained/lost";
+               // Update doc
+               fTextEditor.getDocument().set(text);
+       }
+
+       /**
+        * Does not support -------- / ========= underlining, only # headers
+        * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1)
+        */
+       protected void doPromoteDemote(int upDown) {
+               assert upDown==1 || upDown==-1;
+               if (selectedHeaders == null || selectedHeaders.size() == 0)
+                       return;
+               HashSet<Header> toAdjust = new HashSet<Header>(selectedHeaders);
+               HashSet<Header> adjusted = new HashSet<Header>();
+               // Adjust
+               MarkdownPage mdPage = fTextEditor.getMarkdownPage();
+               List<String> lines = new ArrayList<String>(mdPage.getText());
+               while(toAdjust.size() != 0) {
+                       Header h = toAdjust.iterator().next();
+                       toAdjust.remove(h);
+                       adjusted.add(h);
+                       String line = lines.get(h.getLineNumber());
+                       if (upDown==-1) {
+                               if (h.getLevel() == 1) return; // Level 1; can't promote
+                               if (line.startsWith("##")) line = line.substring(1);
+                               else {
+                                       return; // TODO support for ------ / ========
+                               }
+                       } else line = "#" + line;
+                       int ln = h.getLineNumber();
+                       lines.set(ln, line);
+                       // kids
+                       ArrayList<Header> kids = new ArrayList<Header>(h.getSubHeaders());
+                       for (Header header : kids) {
+                               if ( ! adjusted.contains(header)) toAdjust.add(header);
+                       }
+               }
+               // Set
+               StringBuilder sb = new StringBuilder();
+               for (String line : lines) {
+                       sb.append(line);
+               }
+               fTextEditor.getDocument().set(sb.toString());
+       }
+
+       /**
+        * The region of text for this header. This includes the header itself.
+        * @param header
+        * @return
+        * @throws BadLocationException
+        */
+       protected IRegion getRegion(Header header) {
+               try {
+                       IDocument doc = fTextEditor.getDocument();
+                       // Line numbers
+                       int start = header.getLineNumber();
+                       Header next = header.getNext();
+                       int end;
+                       if (next != null) {
+                               end = next.getLineNumber() - 1;
+                       } else {
+                               end = doc.getNumberOfLines() - 1;
+                       }
+                       int offset = doc.getLineOffset(start);
+                       IRegion ei = doc.getLineInformation(end);
+                       int length = ei.getOffset() + ei.getLength() - offset;
+                       return new Region(offset, length);
+               } catch (BadLocationException ex) {
+                       throw Utils.runtime(ex);
+               }
+       }
+
+       /*
+        * (non-Javadoc) Method declared on ContentOutlinePage
+        */
+       @Override
+       public void selectionChanged(SelectionChangedEvent event) {
+               super.selectionChanged(event);
+               selectedHeaders = null;
+               ISelection selection = event.getSelection();
+               if (selection.isEmpty())
+                       return;
+               if (!(selection instanceof IStructuredSelection))
+                       return;
+               try {
+                       IStructuredSelection strucSel = (IStructuredSelection) selection;
+                       Object[] sections = strucSel.toArray();
+                       selectedHeaders = (List) Arrays.asList(sections);
+                       MarkdownPage.Header first = (Header) sections[0];
+                       MarkdownPage.Header last = (Header) sections[sections.length - 1];
+                       int start = fTextEditor.getDocument().getLineOffset(
+                                       first.getLineNumber());
+                       int length;
+                       if (first == last) {
+                               length = fTextEditor.getDocument().getLineLength(
+                                               first.getLineNumber());
+                       } else {
+                               IRegion r = getRegion(last);
+                               int end = r.getOffset() + r.getLength();
+                               length = end - start;
+                       }
+                       fTextEditor.setHighlightRange(start, length, true);
+               } catch (Exception x) {
+                       System.out.println(x.getStackTrace());
+                       fTextEditor.resetHighlightRange();
+               }
+       }
+
+       /**
+        * Sets the input of the outline page
+        *
+        * @param input
+        *            the input of this outline page
+        */
+       public void setInput(Object input) {
+               fInput = input;
+               update();
+       }
+
+       /**
+        * Updates the outline page.
+        */
+       public void update() {
+               TreeViewer viewer = getTreeViewer();
+
+               if (viewer != null) {
+                       Control control = viewer.getControl();
+                       if (control != null && !control.isDisposed()) {
+                               control.setRedraw(false);
+                               viewer.setInput(fInput);
+                               viewer.expandAll();
+                               control.setRedraw(true);
+                       }
+               }
+       }
+
+}