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