]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java
Tycho compilation changes for SVN version also.
[simantics/platform.git] / bundles / winterwell.markdown / src / winterwell / markdown / editors / MarkdownContentOutlinePage.java
1 /**\r
2  * Copyright winterwell Mathematics Ltd.\r
3  * @author Daniel Winterstein\r
4  * 11 Jan 2007\r
5  */\r
6 package winterwell.markdown.editors;\r
7 \r
8 import java.util.ArrayList;\r
9 import java.util.Arrays;\r
10 import java.util.HashSet;\r
11 import java.util.List;\r
12 \r
13 import org.eclipse.jface.action.Action;\r
14 import org.eclipse.jface.action.IAction;\r
15 import org.eclipse.jface.action.IToolBarManager;\r
16 import org.eclipse.jface.resource.ImageDescriptor;\r
17 import org.eclipse.jface.text.BadLocationException;\r
18 import org.eclipse.jface.text.DocumentEvent;\r
19 import org.eclipse.jface.text.IDocument;\r
20 import org.eclipse.jface.text.IDocumentListener;\r
21 import org.eclipse.jface.text.IRegion;\r
22 import org.eclipse.jface.text.Region;\r
23 import org.eclipse.jface.viewers.ISelection;\r
24 import org.eclipse.jface.viewers.IStructuredSelection;\r
25 import org.eclipse.jface.viewers.ITreeContentProvider;\r
26 import org.eclipse.jface.viewers.LabelProvider;\r
27 import org.eclipse.jface.viewers.SelectionChangedEvent;\r
28 import org.eclipse.jface.viewers.StructuredSelection;\r
29 import org.eclipse.jface.viewers.TreeViewer;\r
30 import org.eclipse.jface.viewers.Viewer;\r
31 import org.eclipse.swt.SWT;\r
32 import org.eclipse.swt.events.KeyEvent;\r
33 import org.eclipse.swt.events.KeyListener;\r
34 import org.eclipse.swt.widgets.Composite;\r
35 import org.eclipse.swt.widgets.Control;\r
36 import org.eclipse.ui.IActionBars;\r
37 import org.eclipse.ui.part.IPageSite;\r
38 import org.eclipse.ui.texteditor.IDocumentProvider;\r
39 import org.eclipse.ui.views.contentoutline.ContentOutlinePage;\r
40 \r
41 import winterwell.markdown.pagemodel.MarkdownPage;\r
42 import winterwell.markdown.pagemodel.MarkdownPage.Header;\r
43 import winterwell.markdown.pagemodel.MarkdownPage.KLineType;\r
44 import winterwell.utils.StrUtils;\r
45 import winterwell.utils.Utils;\r
46 import winterwell.utils.web.WebUtils;\r
47 \r
48 /**\r
49  *\r
50  *\r
51  * @author Daniel Winterstein\r
52  */\r
53 public final class MarkdownContentOutlinePage extends ContentOutlinePage {\r
54 \r
55         /**\r
56          *\r
57          *\r
58          * @author Daniel Winterstein\r
59          */\r
60         public final class ContentProvider implements ITreeContentProvider,\r
61                         IDocumentListener {\r
62 \r
63                 // protected final static String SEGMENTS= "__md_segments";\r
64                 // //$NON-NLS-1$\r
65                 // protected IPositionUpdater fPositionUpdater= new\r
66                 // DefaultPositionUpdater(SEGMENTS);\r
67                 private MarkdownPage fContent;\r
68                 // protected List fContent= new ArrayList(10);\r
69                 private MarkdownEditor fTextEditor;\r
70 \r
71                 private void parse() {\r
72                         fContent = fTextEditor.getMarkdownPage();\r
73                 }\r
74 \r
75                 /*\r
76                  * @see IContentProvider#inputChanged(Viewer, Object, Object)\r
77                  */\r
78                 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
79                         // Detach from old\r
80                         if (oldInput != null) {\r
81                                 IDocument document = fDocumentProvider.getDocument(oldInput);\r
82                                 if (document != null) {\r
83                                         document.removeDocumentListener(this);\r
84                                 }\r
85                         }\r
86                         fContent = null;\r
87                         // Attach to new\r
88                         if (newInput == null)\r
89                                 return;\r
90                         IDocument document = fDocumentProvider.getDocument(newInput);\r
91                         if (document == null)\r
92                                 return;\r
93                         fTextEditor = MarkdownEditor.getEditor(document);\r
94                         document.addDocumentListener(this);\r
95                         parse();\r
96                 }\r
97 \r
98                 /*\r
99                  * @see IContentProvider#dispose\r
100                  */\r
101                 public void dispose() {\r
102                         fContent = null;\r
103                 }\r
104 \r
105                 /*\r
106                  * @see IContentProvider#isDeleted(Object)\r
107                  */\r
108                 public boolean isDeleted(Object element) {\r
109                         return false;\r
110                 }\r
111 \r
112                 /*\r
113                  * @see IStructuredContentProvider#getElements(Object)\r
114                  */\r
115                 public Object[] getElements(Object element) {\r
116                         return fContent.getHeadings(null).toArray();\r
117                 }\r
118 \r
119                 /*\r
120                  * @see ITreeContentProvider#hasChildren(Object)\r
121                  */\r
122                 public boolean hasChildren(Object element) {\r
123                         if (element == fInput) {\r
124                                 return true;\r
125                         }\r
126                         if (element instanceof MarkdownPage.Header) {\r
127                                 MarkdownPage.Header header = (MarkdownPage.Header) element;\r
128                                 return header.getSubHeaders().size() > 0;\r
129                         }\r
130                         ;\r
131                         return false;\r
132                 }\r
133 \r
134                 /*\r
135                  * @see ITreeContentProvider#getParent(Object)\r
136                  */\r
137                 public Object getParent(Object element) {\r
138                         if (!(element instanceof MarkdownPage.Header))\r
139                                 return null;\r
140                         return ((MarkdownPage.Header) element).getParent();\r
141                 }\r
142 \r
143                 /*\r
144                  * @see ITreeContentProvider#getChildren(Object)\r
145                  */\r
146                 public Object[] getChildren(Object element) {\r
147                         if (element == fInput) {\r
148                                 return fContent.getHeadings(null).toArray();\r
149                         }\r
150                         if (!(element instanceof MarkdownPage.Header))\r
151                                 return null;\r
152                         return ((MarkdownPage.Header) element).getSubHeaders().toArray();\r
153                 }\r
154 \r
155                 public void documentAboutToBeChanged(DocumentEvent event) {\r
156                         // nothing\r
157                 }\r
158 \r
159                 public void documentChanged(DocumentEvent event) {\r
160                         parse();\r
161                         update();\r
162                 }\r
163         }\r
164 \r
165         private Object fInput = null;\r
166         private final IDocumentProvider fDocumentProvider;\r
167         private final MarkdownEditor fTextEditor;\r
168         protected boolean showWordCounts;\r
169         private List<Header> selectedHeaders;\r
170 \r
171         /**\r
172          * @param documentProvider\r
173          * @param mdEditor\r
174          */\r
175         public MarkdownContentOutlinePage(IDocumentProvider documentProvider,\r
176                         MarkdownEditor mdEditor) {\r
177                 fDocumentProvider = documentProvider;\r
178                 fTextEditor = mdEditor;\r
179         }\r
180 \r
181         /*\r
182          * (non-Javadoc) Method declared on ContentOutlinePage\r
183          */\r
184         @Override\r
185         public void createControl(Composite parent) {\r
186                 super.createControl(parent);\r
187                 TreeViewer viewer = getTreeViewer();\r
188                 viewer.setContentProvider(new ContentProvider());\r
189                 // Add word count annotations\r
190                 viewer.setLabelProvider(new LabelProvider() {\r
191                         @Override\r
192                         public String getText(Object element) {\r
193                                 if (!(element instanceof MarkdownPage.Header))\r
194                                         return super.getText(element);\r
195                                 Header header = ((MarkdownPage.Header) element);\r
196                                 String hText = header.toString();\r
197                                 if (!showWordCounts)\r
198                                         return hText;\r
199                                 IRegion region = getRegion(header);\r
200                                 String text;\r
201                                 try {\r
202                                         text = fTextEditor.getDocument().get(region.getOffset(),\r
203                                                         region.getLength());\r
204                                         text = WebUtils.stripTags(text);\r
205                                         text = text.replaceAll("#", "").trim();\r
206                                         assert text.startsWith(hText);\r
207                                         text = text.substring(hText.length());\r
208                                         int wc = StrUtils.wordCount(text);\r
209                                         return hText + " (" + wc + ":" + text.length() + ")";\r
210                                 } catch (BadLocationException e) {\r
211                                         return hText;\r
212                                 }\r
213                         }\r
214                 });\r
215                 viewer.addSelectionChangedListener(this);\r
216 \r
217                 if (fInput != null)\r
218                         viewer.setInput(fInput);\r
219 \r
220                 // Buttons\r
221                 IPageSite site = getSite();\r
222                 IActionBars bars = site.getActionBars();\r
223                 IToolBarManager toolbar = bars.getToolBarManager();\r
224                 // Word count action\r
225                 Action action = new Action("123", IAction.AS_CHECK_BOX) {\r
226                         @Override\r
227                         public void run() {\r
228                                 showWordCounts = isChecked();\r
229                                 update();\r
230                         }\r
231                 };\r
232                 action.setToolTipText("Show/hide section word:character counts");\r
233                 toolbar.add(action);\r
234                 // +/- actions\r
235                 action = new Action("<") {\r
236                         @Override\r
237                         public void run() {\r
238                                 doPromoteDemote(-1);\r
239                         }\r
240                 };\r
241                 action.setToolTipText("Promote the selected section\n -- move it up a level.");\r
242                 toolbar.add(action);\r
243                 //\r
244                 action = new Action(">") {\r
245                         @Override\r
246                         public void run() {\r
247                                 doPromoteDemote(1);\r
248                         }\r
249                 };\r
250                 action.setToolTipText("Demote the selected section\n -- move it down a level.");\r
251                 toolbar.add(action);\r
252                 // up/down actions\r
253                 action = new Action("/\\") {\r
254                         @Override\r
255                         public void run() {\r
256                                 try {\r
257                                         doMove(-1);\r
258                                 } catch (BadLocationException e) {\r
259                                         throw Utils.runtime(e);\r
260                                 }\r
261                         }\r
262                 };\r
263                 action.setToolTipText("Move the selected section earlier");\r
264                 toolbar.add(action);\r
265                 //\r
266                 action = new Action("\\/") {\r
267                         @Override\r
268                         public void run() {\r
269                                 try {\r
270                                         doMove(1);\r
271                                 } catch (BadLocationException e) {\r
272                                         throw Utils.runtime(e);\r
273                                 }\r
274                         }\r
275                 };\r
276                 action.setToolTipText("Move the selected section later");\r
277                 toolbar.add(action);\r
278                 // Collapse\r
279                 ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif");\r
280                 action = new Action("collapse", id) {\r
281                         @Override\r
282                         public void run() {\r
283                                 doCollapseAll();\r
284                         }\r
285                 };\r
286                 action.setImageDescriptor(id);\r
287                 action.setToolTipText("Collapse outline tree");\r
288                 toolbar.add(action);\r
289                 // Sync\r
290                 id = ImageDescriptor.createFromFile(getClass(), "synced.gif");\r
291                 action = new Action("sync") {\r
292                         @Override\r
293                         public void run() {\r
294                                 try {\r
295                                         doSyncToEditor();\r
296                                 } catch (BadLocationException e) {\r
297                                         throw Utils.runtime(e);\r
298                                 }\r
299                         }\r
300                 };\r
301                 action.setImageDescriptor(id);\r
302                 action.setToolTipText("Link with editor");\r
303                 toolbar.add(action);\r
304                 // Add edit ability\r
305                 viewer.getControl().addKeyListener(new KeyListener() {\r
306                         public void keyPressed(KeyEvent e) {\r
307                                 if (e.keyCode==SWT.F2) {\r
308                                         doEditHeader();\r
309                                 }\r
310                         }\r
311                         public void keyReleased(KeyEvent e) {\r
312                                 //\r
313                         }\r
314                 });\r
315         }\r
316 \r
317         /**\r
318          * @throws BadLocationException\r
319          *\r
320          */\r
321         protected void doSyncToEditor() throws BadLocationException {\r
322                 TreeViewer viewer = getTreeViewer();\r
323                 if (viewer == null) return;\r
324                 // Get header\r
325                 MarkdownPage page = fTextEditor.getMarkdownPage();\r
326                 int caretOffset = fTextEditor.getViewer().getTextWidget().getCaretOffset();\r
327                 IDocument doc = fTextEditor.getDocument();\r
328                 int line = doc.getLineOfOffset(caretOffset);\r
329                 List<KLineType> lineTypes = page.getLineTypes();\r
330                 for(; line>-1; line--) {\r
331                         KLineType lt = lineTypes.get(line);\r
332                         if (lt.toString().startsWith("H")) break;\r
333                 }\r
334                 if (line<0) return;\r
335                 Header header = (Header) page.getPageObject(line);\r
336                 // Set\r
337                 IStructuredSelection selection = new StructuredSelection(header);\r
338                 viewer.setSelection(selection , true);\r
339         }\r
340 \r
341         void doEditHeader() {\r
342                 TreeViewer viewer = getTreeViewer();\r
343                 viewer.editElement(selectedHeaders.get(0), 0);\r
344         }\r
345 \r
346         protected void doCollapseAll() {\r
347                 TreeViewer viewer = getTreeViewer();\r
348                 if (viewer == null) return;\r
349 //              Control control = viewer.getControl();\r
350 //              if (control != null && !control.isDisposed()) {\r
351 //                      control.setRedraw(false);\r
352                 viewer.collapseAll();\r
353 //                      control.setRedraw(true);\r
354 //              }\r
355         }\r
356 \r
357         /**\r
358          * Move the selected sections up/down\r
359          * @param i 1 or -1. 1==move later, -1=earlier\r
360          * @throws BadLocationException\r
361          */\r
362         protected void doMove(int i) throws BadLocationException {\r
363                 assert i==1 || i==-1;\r
364                 if (selectedHeaders == null || selectedHeaders.size() == 0)\r
365                         return;\r
366                 // Get text region to move\r
367                 MarkdownPage.Header first = selectedHeaders.get(0);\r
368                 MarkdownPage.Header last = selectedHeaders.get(selectedHeaders.size()-1);\r
369                 int start = fTextEditor.getDocument().getLineOffset(\r
370                                 first.getLineNumber());\r
371                 IRegion r = getRegion(last);\r
372                 int end = r.getOffset() + r.getLength();\r
373                 int length = end - start;\r
374                 // Get new insertion point\r
375                 int insert;\r
376                 if (i==1) {\r
377                         Header nextSection = last.getNext();\r
378                         if (nextSection==null) return;\r
379                         IRegion nr = getRegion(nextSection);\r
380                         insert = nr.getOffset()+nr.getLength();\r
381                 } else {\r
382                         Header prevSection = first.getPrevious();\r
383                         if (prevSection==null) return;\r
384                         IRegion nr = getRegion(prevSection);\r
385                         insert = nr.getOffset();\r
386                 }\r
387                 // Get text\r
388                 String text = fTextEditor.getDocument().get();\r
389                 // Move text\r
390                 String section = text.substring(start, end);\r
391                 String pre, post;\r
392                 if (i==1) {\r
393                         pre = text.substring(0, start) + text.substring(end, insert);\r
394                         post = text.substring(insert);\r
395                 } else {\r
396                         pre = text.substring(0, insert);\r
397                         post = text.substring(insert,start)+text.substring(end);\r
398                 }\r
399                 text =  pre + section + post;\r
400                 assert text.length() == fTextEditor.getDocument().get().length() :\r
401                         text.length()-fTextEditor.getDocument().get().length()+" chars gained/lost";\r
402                 // Update doc\r
403                 fTextEditor.getDocument().set(text);\r
404         }\r
405 \r
406         /**\r
407          * Does not support -------- / ========= underlining, only # headers\r
408          * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1)\r
409          */\r
410         protected void doPromoteDemote(int upDown) {\r
411                 assert upDown==1 || upDown==-1;\r
412                 if (selectedHeaders == null || selectedHeaders.size() == 0)\r
413                         return;\r
414                 HashSet<Header> toAdjust = new HashSet<Header>(selectedHeaders);\r
415                 HashSet<Header> adjusted = new HashSet<Header>();\r
416                 // Adjust\r
417                 MarkdownPage mdPage = fTextEditor.getMarkdownPage();\r
418                 List<String> lines = new ArrayList<String>(mdPage.getText());\r
419                 while(toAdjust.size() != 0) {\r
420                         Header h = toAdjust.iterator().next();\r
421                         toAdjust.remove(h);\r
422                         adjusted.add(h);\r
423                         String line = lines.get(h.getLineNumber());\r
424                         if (upDown==-1) {\r
425                                 if (h.getLevel() == 1) return; // Level 1; can't promote\r
426                                 if (line.startsWith("##")) line = line.substring(1);\r
427                                 else {\r
428                                         return; // TODO support for ------ / ========\r
429                                 }\r
430                         } else line = "#" + line;\r
431                         int ln = h.getLineNumber();\r
432                         lines.set(ln, line);\r
433                         // kids\r
434                         ArrayList<Header> kids = new ArrayList<Header>(h.getSubHeaders());\r
435                         for (Header header : kids) {\r
436                                 if ( ! adjusted.contains(header)) toAdjust.add(header);\r
437                         }\r
438                 }\r
439                 // Set\r
440                 StringBuilder sb = new StringBuilder();\r
441                 for (String line : lines) {\r
442                         sb.append(line);\r
443                 }\r
444                 fTextEditor.getDocument().set(sb.toString());\r
445         }\r
446 \r
447         /**\r
448          * The region of text for this header. This includes the header itself.\r
449          * @param header\r
450          * @return\r
451          * @throws BadLocationException\r
452          */\r
453         protected IRegion getRegion(Header header) {\r
454                 try {\r
455                         IDocument doc = fTextEditor.getDocument();\r
456                         // Line numbers\r
457                         int start = header.getLineNumber();\r
458                         Header next = header.getNext();\r
459                         int end;\r
460                         if (next != null) {\r
461                                 end = next.getLineNumber() - 1;\r
462                         } else {\r
463                                 end = doc.getNumberOfLines() - 1;\r
464                         }\r
465                         int offset = doc.getLineOffset(start);\r
466                         IRegion ei = doc.getLineInformation(end);\r
467                         int length = ei.getOffset() + ei.getLength() - offset;\r
468                         return new Region(offset, length);\r
469                 } catch (BadLocationException ex) {\r
470                         throw Utils.runtime(ex);\r
471                 }\r
472         }\r
473 \r
474         /*\r
475          * (non-Javadoc) Method declared on ContentOutlinePage\r
476          */\r
477         @Override\r
478         public void selectionChanged(SelectionChangedEvent event) {\r
479                 super.selectionChanged(event);\r
480                 selectedHeaders = null;\r
481                 ISelection selection = event.getSelection();\r
482                 if (selection.isEmpty())\r
483                         return;\r
484                 if (!(selection instanceof IStructuredSelection))\r
485                         return;\r
486                 try {\r
487                         IStructuredSelection strucSel = (IStructuredSelection) selection;\r
488                         Object[] sections = strucSel.toArray();\r
489                         selectedHeaders = (List) Arrays.asList(sections);\r
490                         MarkdownPage.Header first = (Header) sections[0];\r
491                         MarkdownPage.Header last = (Header) sections[sections.length - 1];\r
492                         int start = fTextEditor.getDocument().getLineOffset(\r
493                                         first.getLineNumber());\r
494                         int length;\r
495                         if (first == last) {\r
496                                 length = fTextEditor.getDocument().getLineLength(\r
497                                                 first.getLineNumber());\r
498                         } else {\r
499                                 IRegion r = getRegion(last);\r
500                                 int end = r.getOffset() + r.getLength();\r
501                                 length = end - start;\r
502                         }\r
503                         fTextEditor.setHighlightRange(start, length, true);\r
504                 } catch (Exception x) {\r
505                         System.out.println(x.getStackTrace());\r
506                         fTextEditor.resetHighlightRange();\r
507                 }\r
508         }\r
509 \r
510         /**\r
511          * Sets the input of the outline page\r
512          *\r
513          * @param input\r
514          *            the input of this outline page\r
515          */\r
516         public void setInput(Object input) {\r
517                 fInput = input;\r
518                 update();\r
519         }\r
520 \r
521         /**\r
522          * Updates the outline page.\r
523          */\r
524         public void update() {\r
525                 TreeViewer viewer = getTreeViewer();\r
526 \r
527                 if (viewer != null) {\r
528                         Control control = viewer.getControl();\r
529                         if (control != null && !control.isDisposed()) {\r
530                                 control.setRedraw(false);\r
531                                 viewer.setInput(fInput);\r
532                                 viewer.expandAll();\r
533                                 control.setRedraw(true);\r
534                         }\r
535                 }\r
536         }\r
537 \r
538 }\r