2 * Copyright winterwell Mathematics Ltd.
\r
3 * @author Daniel Winterstein
\r
6 package winterwell.markdown.editors;
\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
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
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
51 * @author Daniel Winterstein
\r
53 public final class MarkdownContentOutlinePage extends ContentOutlinePage {
\r
58 * @author Daniel Winterstein
\r
60 public final class ContentProvider implements ITreeContentProvider,
\r
63 // protected final static String SEGMENTS= "__md_segments";
\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
71 private void parse() {
\r
72 fContent = fTextEditor.getMarkdownPage();
\r
76 * @see IContentProvider#inputChanged(Viewer, Object, Object)
\r
78 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
\r
80 if (oldInput != null) {
\r
81 IDocument document = fDocumentProvider.getDocument(oldInput);
\r
82 if (document != null) {
\r
83 document.removeDocumentListener(this);
\r
88 if (newInput == null)
\r
90 IDocument document = fDocumentProvider.getDocument(newInput);
\r
91 if (document == null)
\r
93 fTextEditor = MarkdownEditor.getEditor(document);
\r
94 document.addDocumentListener(this);
\r
99 * @see IContentProvider#dispose
\r
101 public void dispose() {
\r
106 * @see IContentProvider#isDeleted(Object)
\r
108 public boolean isDeleted(Object element) {
\r
113 * @see IStructuredContentProvider#getElements(Object)
\r
115 public Object[] getElements(Object element) {
\r
116 return fContent.getHeadings(null).toArray();
\r
120 * @see ITreeContentProvider#hasChildren(Object)
\r
122 public boolean hasChildren(Object element) {
\r
123 if (element == fInput) {
\r
126 if (element instanceof MarkdownPage.Header) {
\r
127 MarkdownPage.Header header = (MarkdownPage.Header) element;
\r
128 return header.getSubHeaders().size() > 0;
\r
135 * @see ITreeContentProvider#getParent(Object)
\r
137 public Object getParent(Object element) {
\r
138 if (!(element instanceof MarkdownPage.Header))
\r
140 return ((MarkdownPage.Header) element).getParent();
\r
144 * @see ITreeContentProvider#getChildren(Object)
\r
146 public Object[] getChildren(Object element) {
\r
147 if (element == fInput) {
\r
148 return fContent.getHeadings(null).toArray();
\r
150 if (!(element instanceof MarkdownPage.Header))
\r
152 return ((MarkdownPage.Header) element).getSubHeaders().toArray();
\r
155 public void documentAboutToBeChanged(DocumentEvent event) {
\r
159 public void documentChanged(DocumentEvent event) {
\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
172 * @param documentProvider
\r
175 public MarkdownContentOutlinePage(IDocumentProvider documentProvider,
\r
176 MarkdownEditor mdEditor) {
\r
177 fDocumentProvider = documentProvider;
\r
178 fTextEditor = mdEditor;
\r
182 * (non-Javadoc) Method declared on ContentOutlinePage
\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
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
199 IRegion region = getRegion(header);
\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
215 viewer.addSelectionChangedListener(this);
\r
217 if (fInput != null)
\r
218 viewer.setInput(fInput);
\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
227 public void run() {
\r
228 showWordCounts = isChecked();
\r
232 action.setToolTipText("Show/hide section word:character counts");
\r
233 toolbar.add(action);
\r
235 action = new Action("<") {
\r
237 public void run() {
\r
238 doPromoteDemote(-1);
\r
241 action.setToolTipText("Promote the selected section\n -- move it up a level.");
\r
242 toolbar.add(action);
\r
244 action = new Action(">") {
\r
246 public void run() {
\r
247 doPromoteDemote(1);
\r
250 action.setToolTipText("Demote the selected section\n -- move it down a level.");
\r
251 toolbar.add(action);
\r
253 action = new Action("/\\") {
\r
255 public void run() {
\r
258 } catch (BadLocationException e) {
\r
259 throw Utils.runtime(e);
\r
263 action.setToolTipText("Move the selected section earlier");
\r
264 toolbar.add(action);
\r
266 action = new Action("\\/") {
\r
268 public void run() {
\r
271 } catch (BadLocationException e) {
\r
272 throw Utils.runtime(e);
\r
276 action.setToolTipText("Move the selected section later");
\r
277 toolbar.add(action);
\r
279 ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif");
\r
280 action = new Action("collapse", id) {
\r
282 public void run() {
\r
286 action.setImageDescriptor(id);
\r
287 action.setToolTipText("Collapse outline tree");
\r
288 toolbar.add(action);
\r
290 id = ImageDescriptor.createFromFile(getClass(), "synced.gif");
\r
291 action = new Action("sync") {
\r
293 public void run() {
\r
296 } catch (BadLocationException e) {
\r
297 throw Utils.runtime(e);
\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
311 public void keyReleased(KeyEvent e) {
\r
318 * @throws BadLocationException
\r
321 protected void doSyncToEditor() throws BadLocationException {
\r
322 TreeViewer viewer = getTreeViewer();
\r
323 if (viewer == null) return;
\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
334 if (line<0) return;
\r
335 Header header = (Header) page.getPageObject(line);
\r
337 IStructuredSelection selection = new StructuredSelection(header);
\r
338 viewer.setSelection(selection , true);
\r
341 void doEditHeader() {
\r
342 TreeViewer viewer = getTreeViewer();
\r
343 viewer.editElement(selectedHeaders.get(0), 0);
\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
358 * Move the selected sections up/down
\r
359 * @param i 1 or -1. 1==move later, -1=earlier
\r
360 * @throws BadLocationException
\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
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
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
382 Header prevSection = first.getPrevious();
\r
383 if (prevSection==null) return;
\r
384 IRegion nr = getRegion(prevSection);
\r
385 insert = nr.getOffset();
\r
388 String text = fTextEditor.getDocument().get();
\r
390 String section = text.substring(start, end);
\r
393 pre = text.substring(0, start) + text.substring(end, insert);
\r
394 post = text.substring(insert);
\r
396 pre = text.substring(0, insert);
\r
397 post = text.substring(insert,start)+text.substring(end);
\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
403 fTextEditor.getDocument().set(text);
\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
410 protected void doPromoteDemote(int upDown) {
\r
411 assert upDown==1 || upDown==-1;
\r
412 if (selectedHeaders == null || selectedHeaders.size() == 0)
\r
414 HashSet<Header> toAdjust = new HashSet<Header>(selectedHeaders);
\r
415 HashSet<Header> adjusted = new HashSet<Header>();
\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
423 String line = lines.get(h.getLineNumber());
\r
425 if (h.getLevel() == 1) return; // Level 1; can't promote
\r
426 if (line.startsWith("##")) line = line.substring(1);
\r
428 return; // TODO support for ------ / ========
\r
430 } else line = "#" + line;
\r
431 int ln = h.getLineNumber();
\r
432 lines.set(ln, line);
\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
440 StringBuilder sb = new StringBuilder();
\r
441 for (String line : lines) {
\r
444 fTextEditor.getDocument().set(sb.toString());
\r
448 * The region of text for this header. This includes the header itself.
\r
451 * @throws BadLocationException
\r
453 protected IRegion getRegion(Header header) {
\r
455 IDocument doc = fTextEditor.getDocument();
\r
457 int start = header.getLineNumber();
\r
458 Header next = header.getNext();
\r
460 if (next != null) {
\r
461 end = next.getLineNumber() - 1;
\r
463 end = doc.getNumberOfLines() - 1;
\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
475 * (non-Javadoc) Method declared on ContentOutlinePage
\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
484 if (!(selection instanceof IStructuredSelection))
\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
495 if (first == last) {
\r
496 length = fTextEditor.getDocument().getLineLength(
\r
497 first.getLineNumber());
\r
499 IRegion r = getRegion(last);
\r
500 int end = r.getOffset() + r.getLength();
\r
501 length = end - start;
\r
503 fTextEditor.setHighlightRange(start, length, true);
\r
504 } catch (Exception x) {
\r
505 System.out.println(x.getStackTrace());
\r
506 fTextEditor.resetHighlightRange();
\r
511 * Sets the input of the outline page
\r
514 * the input of this outline page
\r
516 public void setInput(Object input) {
\r
522 * Updates the outline page.
\r
524 public void update() {
\r
525 TreeViewer viewer = getTreeViewer();
\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