X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Fwinterwell.markdown%2Fsrc%2Fwinterwell%2Fmarkdown%2Feditors%2FMarkdownContentOutlinePage.java;fp=bundles%2Fwinterwell.markdown%2Fsrc%2Fwinterwell%2Fmarkdown%2Feditors%2FMarkdownContentOutlinePage.java;h=445a322d3e6accc02555c925311519404449f657;hb=2531cdf245f42bce854d43f4d49a23983c79db96;hp=0000000000000000000000000000000000000000;hpb=857dbc869796d772864327ce02f19dc252b159fc;p=simantics%2Fplatform.git diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java new file mode 100644 index 000000000..445a322d3 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java @@ -0,0 +1,538 @@ +/** + * 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
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 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
toAdjust = new HashSet
(selectedHeaders); + HashSet
adjusted = new HashSet
(); + // Adjust + MarkdownPage mdPage = fTextEditor.getMarkdownPage(); + List lines = new ArrayList(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
kids = new ArrayList
(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); + } + } + } + +}