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