2 * Copyright winterwell Mathematics Ltd.
3 * @author Daniel Winterstein
6 package winterwell.markdown.editors;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.HashSet;
11 import java.util.List;
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;
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;
51 * @author Daniel Winterstein
53 public final class MarkdownContentOutlinePage extends ContentOutlinePage {
58 * @author Daniel Winterstein
60 public final class ContentProvider implements ITreeContentProvider,
63 // protected final static String SEGMENTS= "__md_segments";
65 // protected IPositionUpdater fPositionUpdater= new
66 // DefaultPositionUpdater(SEGMENTS);
67 private MarkdownPage fContent;
68 // protected List fContent= new ArrayList(10);
69 private MarkdownEditor fTextEditor;
71 private void parse() {
72 fContent = fTextEditor.getMarkdownPage();
76 * @see IContentProvider#inputChanged(Viewer, Object, Object)
78 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
80 if (oldInput != null) {
81 IDocument document = fDocumentProvider.getDocument(oldInput);
82 if (document != null) {
83 document.removeDocumentListener(this);
90 IDocument document = fDocumentProvider.getDocument(newInput);
93 fTextEditor = MarkdownEditor.getEditor(document);
94 document.addDocumentListener(this);
99 * @see IContentProvider#dispose
101 public void dispose() {
106 * @see IContentProvider#isDeleted(Object)
108 public boolean isDeleted(Object element) {
113 * @see IStructuredContentProvider#getElements(Object)
115 public Object[] getElements(Object element) {
116 return fContent.getHeadings(null).toArray();
120 * @see ITreeContentProvider#hasChildren(Object)
122 public boolean hasChildren(Object element) {
123 if (element == fInput) {
126 if (element instanceof MarkdownPage.Header) {
127 MarkdownPage.Header header = (MarkdownPage.Header) element;
128 return header.getSubHeaders().size() > 0;
135 * @see ITreeContentProvider#getParent(Object)
137 public Object getParent(Object element) {
138 if (!(element instanceof MarkdownPage.Header))
140 return ((MarkdownPage.Header) element).getParent();
144 * @see ITreeContentProvider#getChildren(Object)
146 public Object[] getChildren(Object element) {
147 if (element == fInput) {
148 return fContent.getHeadings(null).toArray();
150 if (!(element instanceof MarkdownPage.Header))
152 return ((MarkdownPage.Header) element).getSubHeaders().toArray();
155 public void documentAboutToBeChanged(DocumentEvent event) {
159 public void documentChanged(DocumentEvent event) {
165 private Object fInput = null;
166 private final IDocumentProvider fDocumentProvider;
167 private final MarkdownEditor fTextEditor;
168 protected boolean showWordCounts;
169 private List<Header> selectedHeaders;
172 * @param documentProvider
175 public MarkdownContentOutlinePage(IDocumentProvider documentProvider,
176 MarkdownEditor mdEditor) {
177 fDocumentProvider = documentProvider;
178 fTextEditor = mdEditor;
182 * (non-Javadoc) Method declared on ContentOutlinePage
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() {
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();
199 IRegion region = getRegion(header);
202 text = fTextEditor.getDocument().get(region.getOffset(),
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) {
215 viewer.addSelectionChangedListener(this);
218 viewer.setInput(fInput);
221 IPageSite site = getSite();
222 IActionBars bars = site.getActionBars();
223 IToolBarManager toolbar = bars.getToolBarManager();
225 Action action = new Action("123", IAction.AS_CHECK_BOX) {
228 showWordCounts = isChecked();
232 action.setToolTipText("Show/hide section word:character counts");
235 action = new Action("<") {
241 action.setToolTipText("Promote the selected section\n -- move it up a level.");
244 action = new Action(">") {
250 action.setToolTipText("Demote the selected section\n -- move it down a level.");
253 action = new Action("/\\") {
258 } catch (BadLocationException e) {
259 throw Utils.runtime(e);
263 action.setToolTipText("Move the selected section earlier");
266 action = new Action("\\/") {
271 } catch (BadLocationException e) {
272 throw Utils.runtime(e);
276 action.setToolTipText("Move the selected section later");
279 ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif");
280 action = new Action("collapse", id) {
286 action.setImageDescriptor(id);
287 action.setToolTipText("Collapse outline tree");
290 id = ImageDescriptor.createFromFile(getClass(), "synced.gif");
291 action = new Action("sync") {
296 } catch (BadLocationException e) {
297 throw Utils.runtime(e);
301 action.setImageDescriptor(id);
302 action.setToolTipText("Link with editor");
305 viewer.getControl().addKeyListener(new KeyListener() {
306 public void keyPressed(KeyEvent e) {
307 if (e.keyCode==SWT.F2) {
311 public void keyReleased(KeyEvent e) {
318 * @throws BadLocationException
321 protected void doSyncToEditor() throws BadLocationException {
322 TreeViewer viewer = getTreeViewer();
323 if (viewer == null) return;
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;
335 Header header = (Header) page.getPageObject(line);
337 IStructuredSelection selection = new StructuredSelection(header);
338 viewer.setSelection(selection , true);
341 void doEditHeader() {
342 TreeViewer viewer = getTreeViewer();
343 viewer.editElement(selectedHeaders.get(0), 0);
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);
358 * Move the selected sections up/down
359 * @param i 1 or -1. 1==move later, -1=earlier
360 * @throws BadLocationException
362 protected void doMove(int i) throws BadLocationException {
363 assert i==1 || i==-1;
364 if (selectedHeaders == null || selectedHeaders.size() == 0)
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
377 Header nextSection = last.getNext();
378 if (nextSection==null) return;
379 IRegion nr = getRegion(nextSection);
380 insert = nr.getOffset()+nr.getLength();
382 Header prevSection = first.getPrevious();
383 if (prevSection==null) return;
384 IRegion nr = getRegion(prevSection);
385 insert = nr.getOffset();
388 String text = fTextEditor.getDocument().get();
390 String section = text.substring(start, end);
393 pre = text.substring(0, start) + text.substring(end, insert);
394 post = text.substring(insert);
396 pre = text.substring(0, insert);
397 post = text.substring(insert,start)+text.substring(end);
399 text = pre + section + post;
400 assert text.length() == fTextEditor.getDocument().get().length() :
401 text.length()-fTextEditor.getDocument().get().length()+" chars gained/lost";
403 fTextEditor.getDocument().set(text);
407 * Does not support -------- / ========= underlining, only # headers
408 * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1)
410 protected void doPromoteDemote(int upDown) {
411 assert upDown==1 || upDown==-1;
412 if (selectedHeaders == null || selectedHeaders.size() == 0)
414 HashSet<Header> toAdjust = new HashSet<Header>(selectedHeaders);
415 HashSet<Header> adjusted = new HashSet<Header>();
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();
423 String line = lines.get(h.getLineNumber());
425 if (h.getLevel() == 1) return; // Level 1; can't promote
426 if (line.startsWith("##")) line = line.substring(1);
428 return; // TODO support for ------ / ========
430 } else line = "#" + line;
431 int ln = h.getLineNumber();
434 ArrayList<Header> kids = new ArrayList<Header>(h.getSubHeaders());
435 for (Header header : kids) {
436 if ( ! adjusted.contains(header)) toAdjust.add(header);
440 StringBuilder sb = new StringBuilder();
441 for (String line : lines) {
444 fTextEditor.getDocument().set(sb.toString());
448 * The region of text for this header. This includes the header itself.
451 * @throws BadLocationException
453 protected IRegion getRegion(Header header) {
455 IDocument doc = fTextEditor.getDocument();
457 int start = header.getLineNumber();
458 Header next = header.getNext();
461 end = next.getLineNumber() - 1;
463 end = doc.getNumberOfLines() - 1;
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);
475 * (non-Javadoc) Method declared on ContentOutlinePage
478 public void selectionChanged(SelectionChangedEvent event) {
479 super.selectionChanged(event);
480 selectedHeaders = null;
481 ISelection selection = event.getSelection();
482 if (selection.isEmpty())
484 if (!(selection instanceof IStructuredSelection))
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());
496 length = fTextEditor.getDocument().getLineLength(
497 first.getLineNumber());
499 IRegion r = getRegion(last);
500 int end = r.getOffset() + r.getLength();
501 length = end - start;
503 fTextEditor.setHighlightRange(start, length, true);
504 } catch (Exception x) {
505 System.out.println(x.getStackTrace());
506 fTextEditor.resetHighlightRange();
511 * Sets the input of the outline page
514 * the input of this outline page
516 public void setInput(Object input) {
522 * Updates the outline page.
524 public void update() {
525 TreeViewer viewer = getTreeViewer();
527 if (viewer != null) {
528 Control control = viewer.getControl();
529 if (control != null && !control.isDisposed()) {
530 control.setRedraw(false);
531 viewer.setInput(fInput);
533 control.setRedraw(true);