]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java
migrated to svn revision 33108
[simantics/platform.git] / bundles / winterwell.markdown / src / winterwell / markdown / editors / MarkdownContentOutlinePage.java
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java
new file mode 100644 (file)
index 0000000..445a322
--- /dev/null
@@ -0,0 +1,538 @@
+/**\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