| /******************************************************************************* |
| * Copyright (c) 2007, 2016 David Green and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * David Green - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.mylyn.internal.wikitext.ui.editor; |
| |
| import static java.text.MessageFormat.format; |
| |
| import java.io.PrintWriter; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.QualifiedName; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.IJobChangeEvent; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.runtime.jobs.JobChangeAdapter; |
| import org.eclipse.jface.action.AbstractAction; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.action.IMenuManager; |
| import org.eclipse.jface.action.MenuManager; |
| import org.eclipse.jface.commands.ActionHandler; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentListener; |
| import org.eclipse.jface.text.IDocumentPartitioner; |
| import org.eclipse.jface.text.IDocumentPartitioningListener; |
| import org.eclipse.jface.text.ITextSelection; |
| import org.eclipse.jface.text.ITextViewerExtension6; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.hyperlink.URLHyperlink; |
| import org.eclipse.jface.text.reconciler.IReconciler; |
| import org.eclipse.jface.text.source.Annotation; |
| import org.eclipse.jface.text.source.ISourceViewer; |
| import org.eclipse.jface.text.source.IVerticalRuler; |
| import org.eclipse.jface.text.source.projection.IProjectionListener; |
| import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; |
| import org.eclipse.jface.text.source.projection.ProjectionSupport; |
| import org.eclipse.jface.text.source.projection.ProjectionViewer; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.mylyn.internal.wikitext.ui.WikiTextUiPlugin; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.actions.PreviewOutlineItemAction; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.actions.SetMarkupLanguageAction; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.operations.AbstractDocumentCommand; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.operations.CommandManager; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.preferences.Preferences; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.reconciler.MarkupMonoReconciler; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.FastMarkupPartitioner; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.MarkupDocumentProvider; |
| import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.MarkupTokenScanner; |
| import org.eclipse.mylyn.internal.wikitext.ui.util.NlsResourceBundle; |
| import org.eclipse.mylyn.wikitext.core.parser.Attributes; |
| import org.eclipse.mylyn.wikitext.core.parser.DocumentBuilder.BlockType; |
| import org.eclipse.mylyn.wikitext.core.parser.MarkupParser; |
| import org.eclipse.mylyn.wikitext.core.parser.builder.HtmlDocumentBuilder; |
| import org.eclipse.mylyn.wikitext.core.parser.markup.AbstractMarkupLanguage; |
| import org.eclipse.mylyn.wikitext.core.parser.markup.MarkupLanguage; |
| import org.eclipse.mylyn.wikitext.core.parser.outline.OutlineItem; |
| import org.eclipse.mylyn.wikitext.core.parser.outline.OutlineParser; |
| import org.eclipse.mylyn.wikitext.ui.WikiText; |
| import org.eclipse.mylyn.wikitext.ui.editor.MarkupSourceViewerConfiguration; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTError; |
| import org.eclipse.swt.browser.Browser; |
| import org.eclipse.swt.browser.LocationEvent; |
| import org.eclipse.swt.browser.LocationListener; |
| import org.eclipse.swt.browser.ProgressAdapter; |
| import org.eclipse.swt.browser.ProgressEvent; |
| import org.eclipse.swt.custom.CTabFolder; |
| import org.eclipse.swt.custom.CTabItem; |
| import org.eclipse.swt.events.KeyAdapter; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorSite; |
| import org.eclipse.ui.IFileEditorInput; |
| import org.eclipse.ui.IPageLayout; |
| import org.eclipse.ui.IPathEditorInput; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.contexts.IContextService; |
| import org.eclipse.ui.editors.text.TextEditor; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.part.IShowInSource; |
| import org.eclipse.ui.part.IShowInTarget; |
| import org.eclipse.ui.part.IShowInTargetList; |
| import org.eclipse.ui.part.ShowInContext; |
| import org.eclipse.ui.progress.UIJob; |
| import org.eclipse.ui.swt.IFocusService; |
| import org.eclipse.ui.texteditor.ContentAssistAction; |
| import org.eclipse.ui.texteditor.ITextEditorActionConstants; |
| import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; |
| import org.eclipse.ui.views.contentoutline.IContentOutlinePage; |
| |
| /** |
| * A text editor for editing lightweight markup. Can be configured to accept any {@link MarkupLanguage}, with pluggable |
| * content assist, validation, and cheat-sheet help content. |
| * |
| * @author David Green |
| * @author Nicolas Bros |
| */ |
| public class MarkupEditor extends TextEditor implements IShowInTarget, IShowInSource, CommandManager { |
| private static final String CSS_CLASS_EDITOR_PREVIEW = "editorPreview"; //$NON-NLS-1$ |
| |
| private static final String RULER_CONTEXT_MENU_ID = "org.eclipse.mylyn.internal.wikitext.ui.editor.MarkupEditor.ruler"; //$NON-NLS-1$ |
| |
| /** |
| * the name of the property that stores the markup language name for per-file preference |
| * |
| * @see IFile#setPersistentProperty(QualifiedName, String) property |
| */ |
| private static final String MARKUP_LANGUAGE = "markupLanguage"; //$NON-NLS-1$ |
| |
| /** |
| * the source editing context |
| */ |
| public static final String CONTEXT = "org.eclipse.mylyn.wikitext.ui.editor.markupSourceContext"; //$NON-NLS-1$ |
| |
| /** |
| * the ID of the editor |
| */ |
| public static final String ID = "org.eclipse.mylyn.wikitext.ui.editor.markupEditor"; //$NON-NLS-1$ |
| |
| private static final String[] SHOW_IN_TARGETS = { // |
| "org.eclipse.ui.views.ResourceNavigator", //$NON-NLS-1$ |
| "org.eclipse.jdt.ui.PackageExplorer", //$NON-NLS-1$ |
| "org.eclipse.ui.navigator.ProjectExplorer", // 3.5 //$NON-NLS-1$ |
| IPageLayout.ID_OUTLINE }; |
| |
| private static IShowInTargetList SHOW_IN_TARGET_LIST = new IShowInTargetList() { |
| public String[] getShowInTargetIds() { |
| return SHOW_IN_TARGETS; |
| } |
| }; |
| |
| private IDocument document; |
| |
| private IDocumentListener documentListener; |
| |
| private boolean previewDirty = true; |
| |
| private boolean outlineDirty = true; |
| |
| private Browser browser; |
| |
| private MarkupEditorOutline outlinePage; |
| |
| private OutlineItem outlineModel; |
| |
| private final OutlineParser outlineParser = new OutlineParser(); |
| |
| { |
| outlineParser.setLabelMaxLength(48); |
| outlineModel = outlineParser.createRootItem(); |
| } |
| |
| private boolean disableReveal = false; |
| |
| private ISourceViewer viewer; |
| |
| private IPropertyChangeListener preferencesListener; |
| |
| private IDocumentPartitioningListener documentPartitioningListener; |
| |
| private final MarkupSourceViewerConfiguration sourceViewerConfiguration; |
| |
| private CTabItem sourceTab; |
| |
| private ProjectionSupport projectionSupport; |
| |
| private Map<String, HeadingProjectionAnnotation> projectionAnnotationById; |
| |
| private boolean updateJobScheduled = false; |
| |
| protected int documentGeneration = 0; |
| |
| public static final String EDITOR_SOURCE_VIEWER = "org.eclipse.mylyn.wikitext.ui.editor.sourceViewer"; //$NON-NLS-1$ |
| |
| private UIJob updateOutlineJob; |
| |
| private IFoldingStructure foldingStructure; |
| |
| private CTabFolder tabFolder; |
| |
| private CTabItem previewTab; |
| |
| public MarkupEditor() { |
| setDocumentProvider(new MarkupDocumentProvider()); |
| sourceViewerConfiguration = new MarkupSourceViewerConfiguration(getPreferenceStore()); |
| sourceViewerConfiguration.setOutline(outlineModel); |
| sourceViewerConfiguration.setShowInTarget(this); |
| setSourceViewerConfiguration(sourceViewerConfiguration); |
| } |
| |
| @Override |
| protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { |
| sourceViewerConfiguration.initializeDefaultFonts(); |
| tabFolder = new CTabFolder(parent, SWT.BOTTOM); |
| |
| { |
| sourceTab = new CTabItem(tabFolder, SWT.NONE); |
| updateSourceTabLabel(); |
| |
| viewer = new MarkupProjectionViewer(tabFolder, ruler, getOverviewRuler(), isOverviewRulerVisible(), |
| styles | SWT.WRAP); |
| |
| sourceTab.setControl(((Viewer) viewer).getControl()); |
| tabFolder.setSelection(sourceTab); |
| } |
| |
| try { |
| previewTab = new CTabItem(tabFolder, SWT.NONE); |
| previewTab.setText(Messages.MarkupEditor_preview); |
| previewTab.setToolTipText(Messages.MarkupEditor_preview_tooltip); |
| |
| browser = new Browser(tabFolder, SWT.NONE); |
| // bug 260479: open hyperlinks in a browser |
| browser.addLocationListener(new LocationListener() { |
| public void changed(LocationEvent event) { |
| event.doit = false; |
| } |
| |
| public void changing(LocationEvent event) { |
| // if it looks like an absolute URL |
| if (event.location.matches("([a-zA-Z]{3,8})://?.*")) { //$NON-NLS-1$ |
| |
| // workaround for browser problem (bug 262043) |
| int idxOfSlashHash = event.location.indexOf("/#"); //$NON-NLS-1$ |
| if (idxOfSlashHash != -1) { |
| // allow javascript-based scrolling to work |
| if (!event.location.startsWith("file:///#")) { //$NON-NLS-1$ |
| event.doit = false; |
| } |
| return; |
| } |
| // workaround end |
| |
| event.doit = false; |
| try { |
| PlatformUI.getWorkbench() |
| .getBrowserSupport() |
| .createBrowser("org.eclipse.ui.browser") //$NON-NLS-1$ |
| .openURL(new URL(event.location)); |
| } catch (Exception e) { |
| new URLHyperlink(new Region(0, 1), event.location).open(); |
| } |
| } |
| } |
| }); |
| previewTab.setControl(browser); |
| } catch (SWTError e) { |
| // disable preview, the exception is probably due to the internal browser not being available |
| if (previewTab != null) { |
| previewTab.dispose(); |
| previewTab = null; |
| } |
| logPreviewTabUnavailable(e); |
| } |
| |
| tabFolder.addSelectionListener(new SelectionListener() { |
| |
| public void widgetDefaultSelected(SelectionEvent selectionevent) { |
| widgetSelected(selectionevent); |
| } |
| |
| public void widgetSelected(SelectionEvent selectionevent) { |
| if (isShowingPreview()) { |
| updatePreview(); |
| } |
| } |
| }); |
| viewer.getTextWidget().addSelectionListener(new SelectionListener() { |
| public void widgetDefaultSelected(SelectionEvent e) { |
| } |
| |
| public void widgetSelected(SelectionEvent e) { |
| updateOutlineSelection(); |
| } |
| |
| }); |
| viewer.getTextWidget().addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyReleased(KeyEvent e) { |
| if (isRelevantKeyCode(e.keyCode)) { |
| updateOutlineSelection(); |
| } |
| } |
| |
| private boolean isRelevantKeyCode(int keyCode) { |
| // for some reason not all key presses result in a selection change |
| switch (keyCode) { |
| case SWT.ARROW_DOWN: |
| case SWT.ARROW_LEFT: |
| case SWT.ARROW_RIGHT: |
| case SWT.ARROW_UP: |
| case SWT.PAGE_DOWN: |
| case SWT.PAGE_UP: |
| return true; |
| } |
| return false; |
| } |
| }); |
| viewer.getTextWidget().addMouseListener(new MouseAdapter() { |
| @Override |
| public void mouseUp(MouseEvent e) { |
| updateOutlineSelection(); |
| } |
| }); |
| |
| IFocusService focusService = (IFocusService) PlatformUI.getWorkbench().getService(IFocusService.class); |
| if (focusService != null) { |
| focusService.addFocusTracker(viewer.getTextWidget(), MarkupEditor.EDITOR_SOURCE_VIEWER); |
| } |
| |
| viewer.getTextWidget().setData(MarkupLanguage.class.getName(), getMarkupLanguage()); |
| viewer.getTextWidget().setData(ISourceViewer.class.getName(), viewer); |
| |
| getSourceViewerDecorationSupport(viewer); |
| |
| updateDocument(); |
| |
| if (preferencesListener == null) { |
| preferencesListener = new IPropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent event) { |
| if (viewer.getTextWidget() == null || viewer.getTextWidget().isDisposed()) { |
| return; |
| } |
| if (isFontPreferenceChange(event)) { |
| viewer.getTextWidget().getDisplay().asyncExec(new Runnable() { |
| public void run() { |
| reloadPreferences(); |
| } |
| }); |
| } |
| } |
| }; |
| WikiTextUiPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(preferencesListener); |
| } |
| |
| return viewer; |
| } |
| |
| private void logPreviewTabUnavailable(SWTError e) { |
| WikiTextUiPlugin.getDefault().getLog().log(WikiTextUiPlugin.getDefault() |
| .createStatus(format(Messages.MarkupEditor_previewUnavailable, e.getMessage()), IStatus.ERROR, e)); |
| } |
| |
| @Override |
| public void createPartControl(Composite parent) { |
| super.createPartControl(parent); |
| ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); |
| // fix bug 267553: font problems can occur if the default font of the text widget doesn't match the |
| // default font returned by the token scanner |
| if (sourceViewerConfiguration.getDefaultFont() != null) { |
| viewer.getTextWidget().setFont(sourceViewerConfiguration.getDefaultFont()); |
| } |
| |
| projectionSupport = new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors()); |
| projectionSupport.install(); |
| |
| syncProjectionModeWithPreferences(); |
| |
| viewer.addProjectionListener(new IProjectionListener() { |
| public void projectionDisabled() { |
| projectionAnnotationById = null; |
| saveProjectionPreferences(); |
| } |
| |
| public void projectionEnabled() { |
| saveProjectionPreferences(); |
| updateProjectionAnnotations(); |
| } |
| }); |
| |
| if (!outlineDirty && isFoldingEnabled()) { |
| updateProjectionAnnotations(); |
| } |
| JFaceResources.getFontRegistry().addListener(preferencesListener); |
| } |
| |
| private void reloadPreferences() { |
| previewDirty = true; |
| syncProjectionModeWithPreferences(); |
| ((MarkupTokenScanner) sourceViewerConfiguration.getMarkupScanner()).reloadPreferences(); |
| sourceViewerConfiguration.initializeDefaultFonts(); |
| viewer.invalidateTextPresentation(); |
| } |
| |
| private boolean isFontPreferenceChange(PropertyChangeEvent event) { |
| if (event.getProperty().equals(sourceViewerConfiguration.getFontPreference()) |
| || event.getProperty().equals(sourceViewerConfiguration.getMonospaceFontPreference())) { |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| protected void handlePreferenceStoreChanged(PropertyChangeEvent event) { |
| super.handlePreferenceStoreChanged(event); |
| reloadPreferences(); |
| } |
| |
| private void syncProjectionModeWithPreferences() { |
| ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); |
| if (viewer.isProjectionMode() != WikiTextUiPlugin.getDefault().getPreferences().isEditorFolding()) { |
| viewer.doOperation(ProjectionViewer.TOGGLE); |
| } |
| } |
| |
| @Override |
| public void updatePartControl(IEditorInput input) { |
| super.updatePartControl(input); |
| updateDocument(); |
| } |
| |
| public void saveProjectionPreferences() { |
| if (isFoldingEnabled() != WikiTextUiPlugin.getDefault().getPreferences().isEditorFolding()) { |
| Preferences preferences = WikiTextUiPlugin.getDefault().getPreferences().clone(); |
| preferences.setEditorFolding(isFoldingEnabled()); |
| preferences.save(WikiTextUiPlugin.getDefault().getPreferenceStore(), false); |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| if (document != null) { |
| if (documentListener != null) { |
| document.removeDocumentListener(documentListener); |
| } |
| if (documentPartitioningListener != null) { |
| document.removeDocumentPartitioningListener(documentPartitioningListener); |
| } |
| document = null; |
| } |
| if (preferencesListener != null) { |
| WikiTextUiPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(preferencesListener); |
| JFaceResources.getFontRegistry().addListener(preferencesListener); |
| preferencesListener = null; |
| } |
| super.dispose(); |
| } |
| |
| @Override |
| protected void initializeEditor() { |
| super.initializeEditor(); // ORDER DEPENDENCY |
| setHelpContextId(CONTEXT); // ORDER DEPENDENCY |
| setRulerContextMenuId(RULER_CONTEXT_MENU_ID); |
| |
| } |
| |
| @Override |
| protected void doSetInput(IEditorInput input) throws CoreException { |
| super.doSetInput(input); |
| updateDocument(); |
| IFile file = getFile(); |
| if (sourceViewerConfiguration != null) { |
| sourceViewerConfiguration.setFile(file); |
| } |
| initializeMarkupLanguage(input); |
| outlineModel.setResourcePath(file == null ? null : file.getFullPath().toString()); |
| } |
| |
| private void updateDocument() { |
| if (getSourceViewer() != null) { |
| IDocument previousDocument = document; |
| document = getSourceViewer().getDocument(); |
| if (previousDocument == document) { |
| return; |
| } |
| if (previousDocument != null && documentListener != null) { |
| previousDocument.removeDocumentListener(documentListener); |
| } |
| if (previousDocument != null && documentPartitioningListener != null) { |
| previousDocument.removeDocumentPartitioningListener(documentPartitioningListener); |
| } |
| if (document != null) { |
| if (documentListener == null) { |
| documentListener = new IDocumentListener() { |
| public void documentAboutToBeChanged(DocumentEvent event) { |
| } |
| |
| public void documentChanged(DocumentEvent event) { |
| previewDirty = true; |
| outlineDirty = true; |
| synchronized (MarkupEditor.this) { |
| ++documentGeneration; |
| } |
| scheduleOutlineUpdate(); |
| if (isShowingPreview()) { |
| updatePreview(); |
| } |
| } |
| |
| }; |
| } |
| document.addDocumentListener(documentListener); |
| if (documentPartitioningListener == null) { |
| documentPartitioningListener = new IDocumentPartitioningListener() { |
| |
| public void documentPartitioningChanged(IDocument document) { |
| // async update |
| scheduleOutlineUpdate(); |
| } |
| }; |
| } |
| document.addDocumentPartitioningListener(documentPartitioningListener); |
| } |
| |
| previewDirty = true; |
| outlineDirty = true; |
| updateOutline(); |
| } |
| } |
| |
| /** |
| * JavaScript that returns the current top scroll position of the browser widget |
| */ |
| private static final String JAVASCRIPT_GETSCROLLTOP = "function getScrollTop() { " //$NON-NLS-1$ |
| + " if(typeof pageYOffset!='undefined') return pageYOffset;" //$NON-NLS-1$ |
| + " else{" + //$NON-NLS-1$ |
| "var B=document.body;" + //$NON-NLS-1$ |
| "var D=document.documentElement;" + //$NON-NLS-1$ |
| "D=(D.clientHeight)?D:B;return D.scrollTop;}" //$NON-NLS-1$ |
| + "}; return getScrollTop();"; //$NON-NLS-1$ |
| |
| /** |
| * updates the preview |
| */ |
| private void updatePreview() { |
| updatePreview(null); |
| } |
| |
| /** |
| * updates the preview and optionally reveal the section that corresponds to the given outline item. |
| * |
| * @param outlineItem |
| * the outline item, or null |
| */ |
| private void updatePreview(final OutlineItem outlineItem) { |
| if (previewDirty && browser != null) { |
| Object result = browser.evaluate(JAVASCRIPT_GETSCROLLTOP); |
| final int verticalScrollbarPos = result != null ? ((Number) result).intValue() : 0; |
| String xhtml = null; |
| if (document == null) { |
| xhtml = "<?xml version=\"1.0\" ?><html xmlns=\"http://www.w3.org/1999/xhtml\"><body></body></html>"; //$NON-NLS-1$ |
| } else { |
| try { |
| IFile file = getFile(); |
| String title = file == null ? "" : file.getName(); //$NON-NLS-1$ |
| if (title.lastIndexOf('.') != -1) { |
| title = title.substring(0, title.lastIndexOf('.')); |
| } |
| StringWriter writer = new StringWriter(); |
| HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer) { |
| @Override |
| protected void emitAnchorHref(String href) { |
| if (href.startsWith("#")) { //$NON-NLS-1$ |
| writer.writeAttribute("onclick", //$NON-NLS-1$ |
| String.format("javascript: window.location.hash = '%s'; return false;", href)); //$NON-NLS-1$ |
| writer.writeAttribute("href", "#"); //$NON-NLS-1$//$NON-NLS-2$ |
| } else { |
| super.emitAnchorHref(href); |
| } |
| } |
| |
| @Override |
| public void beginHeading(int level, Attributes attributes) { |
| attributes.appendCssClass(CSS_CLASS_EDITOR_PREVIEW); |
| super.beginHeading(level, attributes); |
| } |
| |
| @Override |
| public void beginBlock(BlockType type, Attributes attributes) { |
| attributes.appendCssClass(CSS_CLASS_EDITOR_PREVIEW); |
| super.beginBlock(type, attributes); |
| } |
| }; |
| builder.setTitle(title); |
| |
| IPath location = file == null ? null : file.getLocation(); |
| if (location != null) { |
| builder.setBaseInHead(true); |
| builder.setBase(location.removeLastSegments(1).toFile().toURI()); |
| } |
| |
| String css = WikiTextUiPlugin.getDefault().getPreferences().getMarkupViewerCss(); |
| if (css != null && css.length() > 0) { |
| builder.addCssStylesheet(new HtmlDocumentBuilder.Stylesheet(new StringReader(css))); |
| } |
| |
| MarkupLanguage markupLanguage = getMarkupLanguage(); |
| if (markupLanguage != null) { |
| markupLanguage = markupLanguage.clone(); |
| if (markupLanguage instanceof AbstractMarkupLanguage) { |
| ((AbstractMarkupLanguage) markupLanguage).setEnableMacros(true); |
| } |
| |
| if (markupLanguage instanceof AbstractMarkupLanguage) { |
| AbstractMarkupLanguage language = (AbstractMarkupLanguage) markupLanguage; |
| language.setFilterGenerativeContents(false); |
| language.setBlocksOnly(false); |
| } |
| |
| MarkupParser markupParser = new MarkupParser(); |
| markupParser.setBuilder(builder); |
| markupParser.setMarkupLanguage(markupLanguage); |
| |
| markupParser.parse(document.get()); |
| } else { |
| builder.beginDocument(); |
| builder.beginBlock(BlockType.PREFORMATTED, new Attributes()); |
| builder.characters(document.get()); |
| builder.endBlock(); |
| builder.endDocument(); |
| } |
| xhtml = writer.toString(); |
| } catch (Exception e) { |
| StringWriter stackTrace = new StringWriter(); |
| PrintWriter writer = new PrintWriter(stackTrace); |
| e.printStackTrace(writer); |
| writer.close(); |
| |
| StringWriter documentWriter = new StringWriter(); |
| HtmlDocumentBuilder builder = new HtmlDocumentBuilder(documentWriter); |
| builder.beginDocument(); |
| builder.beginBlock(BlockType.PREFORMATTED, new Attributes()); |
| builder.characters(stackTrace.toString()); |
| builder.endBlock(); |
| builder.endDocument(); |
| |
| xhtml = documentWriter.toString(); |
| } |
| } |
| browser.addProgressListener(new ProgressAdapter() { |
| |
| @Override |
| public void completed(ProgressEvent event) { |
| browser.removeProgressListener(this); |
| if (outlineItem != null) { |
| revealInBrowser(outlineItem); |
| } else { |
| browser.execute(String.format("window.scrollTo(0,%d);", verticalScrollbarPos)); //$NON-NLS-1$ |
| } |
| } |
| |
| }); |
| browser.setText(xhtml); |
| previewDirty = false; |
| } else if (outlineItem != null && browser != null) { |
| revealInBrowser(outlineItem); |
| } |
| } |
| |
| public IFile getFile() { |
| IEditorInput editorInput = getEditorInput(); |
| if (editorInput instanceof IFileEditorInput) { |
| IFileEditorInput fileEditorInput = (IFileEditorInput) editorInput; |
| return fileEditorInput.getFile(); |
| } |
| return null; |
| } |
| |
| @SuppressWarnings("rawtypes") |
| @Override |
| public Object getAdapter(Class adapter) { |
| if (IContentOutlinePage.class == adapter) { |
| if (!isOutlinePageValid()) { |
| outlinePage = new MarkupEditorOutline(this); |
| } |
| return outlinePage; |
| } |
| if (adapter == OutlineItem.class) { |
| return getOutlineModel(); |
| } |
| if (adapter == IFoldingStructure.class) { |
| if (!isFoldingEnabled()) { |
| return null; |
| } |
| if (foldingStructure == null) { |
| foldingStructure = new FoldingStructure(this); |
| } |
| return foldingStructure; |
| } |
| if (adapter == IShowInTargetList.class) { |
| return SHOW_IN_TARGET_LIST; |
| } |
| return super.getAdapter(adapter); |
| } |
| |
| public ISourceViewer getViewer() { |
| return viewer; |
| } |
| |
| public OutlineItem getOutlineModel() { |
| // ensure that outline model is caught up with current version of document |
| if (outlineDirty) { |
| updateOutlineNow(); |
| } |
| return outlineModel; |
| } |
| |
| private void scheduleOutlineUpdate() { |
| synchronized (MarkupEditor.this) { |
| if (updateJobScheduled) { |
| return; |
| } |
| } |
| updateOutlineJob = new UIJob(Messages.MarkupEditor_updateOutline) { |
| @Override |
| public IStatus runInUIThread(IProgressMonitor monitor) { |
| synchronized (MarkupEditor.this) { |
| updateJobScheduled = false; |
| } |
| if (!outlineDirty) { |
| return Status.CANCEL_STATUS; |
| } |
| updateOutline(); |
| return Status.OK_STATUS; |
| } |
| }; |
| updateOutlineJob.addJobChangeListener(new JobChangeAdapter() { |
| @Override |
| public void scheduled(IJobChangeEvent event) { |
| synchronized (MarkupEditor.this) { |
| updateJobScheduled = true; |
| } |
| } |
| |
| @Override |
| public void done(IJobChangeEvent event) { |
| synchronized (MarkupEditor.this) { |
| updateJobScheduled = false; |
| updateOutlineJob = null; |
| } |
| } |
| }); |
| updateOutlineJob.setUser(false); |
| updateOutlineJob.setSystem(true); |
| updateOutlineJob.setPriority(Job.INTERACTIVE); |
| updateOutlineJob.schedule(600); |
| } |
| |
| private void updateOutlineNow() { |
| if (!outlineDirty) { |
| return; |
| } |
| if (!isSourceViewerValid()) { |
| return; |
| } |
| // we maintain the outline even if the outline page is not in use, which allows us to use the outline for |
| // content assist and other things |
| |
| MarkupLanguage markupLanguage = getMarkupLanguage(); |
| if (markupLanguage == null) { |
| return; |
| } |
| final MarkupLanguage language = markupLanguage.clone(); |
| final String content = document.get(); |
| final int contentGeneration; |
| synchronized (MarkupEditor.this) { |
| contentGeneration = documentGeneration; |
| } |
| outlineParser.setMarkupLanguage(language); |
| OutlineItem rootItem = outlineParser.parse(content); |
| updateOutline(contentGeneration, rootItem); |
| } |
| |
| private void updateOutline() { |
| if (!outlineDirty) { |
| return; |
| } |
| if (!isSourceViewerValid()) { |
| return; |
| } |
| // we maintain the outline even if the outline page is not in use, which allows us to use the outline for |
| // content assist and other things |
| |
| MarkupLanguage markupLanguage = getMarkupLanguage(); |
| if (markupLanguage == null) { |
| return; |
| } |
| final MarkupLanguage language = markupLanguage.clone(); |
| |
| final Display display = getSourceViewer().getTextWidget().getDisplay(); |
| final String content = document.get(); |
| final int contentGeneration; |
| synchronized (MarkupEditor.this) { |
| contentGeneration = documentGeneration; |
| } |
| // we parse the outline in another thread so that the UI remains responsive |
| Job parseOutlineJob = new Job(MarkupEditor.class.getSimpleName() + "#updateOutline") { //$NON-NLS-1$ |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| outlineParser.setMarkupLanguage(language); |
| if (shouldCancel()) { |
| return Status.CANCEL_STATUS; |
| } |
| final OutlineItem rootItem = outlineParser.parse(content); |
| if (shouldCancel()) { |
| return Status.CANCEL_STATUS; |
| } |
| |
| display.asyncExec(new Runnable() { |
| public void run() { |
| updateOutline(contentGeneration, rootItem); |
| } |
| }); |
| return Status.OK_STATUS; |
| } |
| |
| private boolean shouldCancel() { |
| synchronized (MarkupEditor.this) { |
| if (contentGeneration != documentGeneration) { |
| return true; |
| } |
| } |
| return false; |
| } |
| }; |
| parseOutlineJob.setPriority(Job.INTERACTIVE); |
| parseOutlineJob.setSystem(true); |
| parseOutlineJob.schedule(); |
| } |
| |
| private void updateOutline(int contentGeneration, OutlineItem rootItem) { |
| if (!isSourceViewerValid()) { |
| return; |
| } |
| synchronized (this) { |
| if (contentGeneration != documentGeneration) { |
| return; |
| } |
| } |
| outlineDirty = false; |
| |
| outlineModel.clear(); |
| outlineModel.moveChildren(rootItem); |
| |
| IFile file = getFile(); |
| outlineModel.setResourcePath(file == null ? null : file.getFullPath().toString()); |
| |
| if (isOutlinePageValid()) { |
| outlinePage.refresh(); |
| |
| outlinePage.getControl().getDisplay().asyncExec(new Runnable() { |
| public void run() { |
| if (isOutlinePageValid()) { |
| updateOutlineSelection(); |
| } |
| } |
| }); |
| } |
| updateProjectionAnnotations(); |
| } |
| |
| private boolean isOutlinePageValid() { |
| return outlinePage != null && outlinePage.getControl() != null && !outlinePage.getControl().isDisposed(); |
| } |
| |
| private boolean isSourceViewerValid() { |
| return getSourceViewer() != null && getSourceViewer().getTextWidget() != null |
| && !getSourceViewer().getTextWidget().isDisposed(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void updateProjectionAnnotations() { |
| ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); |
| ProjectionAnnotationModel projectionAnnotationModel = viewer.getProjectionAnnotationModel(); |
| if (projectionAnnotationModel != null) { |
| List<Annotation> newProjectionAnnotations = new ArrayList<>( |
| projectionAnnotationById == null ? 10 : projectionAnnotationById.size() + 2); |
| Map<HeadingProjectionAnnotation, Position> annotationToPosition = new HashMap<>(); |
| |
| List<OutlineItem> children = outlineModel.getChildren(); |
| if (!children.isEmpty()) { |
| createProjectionAnnotations(newProjectionAnnotations, annotationToPosition, children, |
| document.getLength()); |
| } |
| if (newProjectionAnnotations.isEmpty() |
| && (projectionAnnotationById == null || projectionAnnotationById.isEmpty())) { |
| return; |
| } |
| |
| Map<String, HeadingProjectionAnnotation> newProjectionAnnotationById = new HashMap<>(); |
| |
| if (projectionAnnotationById != null) { |
| Set<HeadingProjectionAnnotation> toDelete = new HashSet<>(projectionAnnotationById.size()); |
| Iterator<Entry<HeadingProjectionAnnotation, Position>> newPositionIt = annotationToPosition.entrySet() |
| .iterator(); |
| while (newPositionIt.hasNext()) { |
| Entry<HeadingProjectionAnnotation, Position> newAnnotationEnt = newPositionIt.next(); |
| |
| HeadingProjectionAnnotation newAnnotation = newAnnotationEnt.getKey(); |
| Position newPosition = newAnnotationEnt.getValue(); |
| HeadingProjectionAnnotation annotation = projectionAnnotationById.get(newAnnotation.getHeadingId()); |
| if (annotation != null) { |
| Position position = projectionAnnotationModel.getPosition(annotation); |
| if (newPosition.equals(position)) { |
| newPositionIt.remove(); |
| newProjectionAnnotationById.put(annotation.getHeadingId(), annotation); |
| } else { |
| toDelete.add(annotation); |
| if (annotation.isCollapsed()) { |
| newAnnotation.markCollapsed(); |
| } else { |
| newAnnotation.markExpanded(); |
| } |
| newProjectionAnnotationById.put(annotation.getHeadingId(), newAnnotation); |
| } |
| } else { |
| newProjectionAnnotationById.put(newAnnotation.getHeadingId(), newAnnotation); |
| } |
| } |
| Iterator<Annotation> annotationIt = projectionAnnotationModel.getAnnotationIterator(); |
| while (annotationIt.hasNext()) { |
| Annotation annotation = annotationIt.next(); |
| if (annotation instanceof HeadingProjectionAnnotation) { |
| HeadingProjectionAnnotation projectionAnnotation = (HeadingProjectionAnnotation) annotation; |
| if (!projectionAnnotationById.containsKey(projectionAnnotation.getHeadingId()) |
| && !toDelete.contains(projectionAnnotation)) { |
| toDelete.add(projectionAnnotation); |
| } |
| } |
| } |
| projectionAnnotationModel.modifyAnnotations( |
| toDelete.isEmpty() ? null : toDelete.toArray(new Annotation[toDelete.size()]), |
| annotationToPosition, null); |
| } else { |
| projectionAnnotationModel.modifyAnnotations(null, annotationToPosition, null); |
| for (HeadingProjectionAnnotation annotation : annotationToPosition.keySet()) { |
| newProjectionAnnotationById.put(annotation.getHeadingId(), annotation); |
| } |
| } |
| projectionAnnotationById = newProjectionAnnotationById; |
| } else { |
| projectionAnnotationById = null; |
| } |
| } |
| |
| private void createProjectionAnnotations(List<Annotation> newProjectionAnnotations, |
| Map<HeadingProjectionAnnotation, Position> annotationToPosition, List<OutlineItem> children, |
| int endOffset) { |
| final int size = children.size(); |
| final int lastIndex = size - 1; |
| for (int x = 0; x < size; ++x) { |
| OutlineItem child = children.get(x); |
| if (child.getId() == null || child.getId().length() == 0) { |
| continue; |
| } |
| int offset = child.getOffset(); |
| int end; |
| if (x == lastIndex) { |
| end = endOffset; |
| } else { |
| end = children.get(x + 1).getOffset(); |
| } |
| int length = end - offset; |
| |
| if (length > 0) { |
| HeadingProjectionAnnotation annotation = new HeadingProjectionAnnotation(child.getId()); |
| Position position = new Position(offset, length); |
| |
| newProjectionAnnotations.add(annotation); |
| annotationToPosition.put(annotation, position); |
| } |
| |
| if (!child.getChildren().isEmpty()) { |
| createProjectionAnnotations(newProjectionAnnotations, annotationToPosition, child.getChildren(), end); |
| } |
| } |
| } |
| |
| private void updateOutlineSelection() { |
| if (disableReveal) { |
| return; |
| } |
| if (outlineModel != null && outlinePage != null) { |
| |
| disableReveal = true; |
| try { |
| OutlineItem item = getNearestMatchingOutlineItem(); |
| if (item != null) { |
| outlinePage.setSelection(new StructuredSelection(item)); |
| } |
| } finally { |
| disableReveal = false; |
| } |
| } |
| } |
| |
| /** |
| * get the outline item nearest matching the selection in the source viewer |
| */ |
| private OutlineItem getNearestMatchingOutlineItem() { |
| Point selectedRange = getSourceViewer().getSelectedRange(); |
| if (selectedRange != null) { |
| return outlineModel.findNearestMatchingOffset(selectedRange.x); |
| } |
| return null; |
| } |
| |
| @Override |
| protected void initializeKeyBindingScopes() { |
| setKeyBindingScopes(new String[] { CONTEXT }); |
| } |
| |
| @Override |
| public void init(IEditorSite site, IEditorInput input) throws PartInitException { |
| super.init(site, input); |
| |
| IContextService contextService = (IContextService) site.getService(IContextService.class); |
| contextService.activateContext(CONTEXT); |
| |
| } |
| |
| private void initializeMarkupLanguage(IEditorInput input) { |
| MarkupLanguage markupLanguage = loadMarkupLanguagePreference(); |
| if (markupLanguage == null) { |
| String name = input.getName(); |
| if (input instanceof IFileEditorInput) { |
| name = ((IFileEditorInput) input).getFile().getName(); |
| } else if (input instanceof IPathEditorInput) { |
| name = ((IPathEditorInput) input).getPath().lastSegment(); |
| } |
| markupLanguage = WikiText.getMarkupLanguageForFilename(name); |
| if (markupLanguage == null) { |
| markupLanguage = WikiText.getMarkupLanguage("Textile"); //$NON-NLS-1$ |
| } |
| } |
| setMarkupLanguage(markupLanguage, false); |
| } |
| |
| public void setMarkupLanguage(MarkupLanguage markupLanguage, boolean persistSetting) { |
| if (markupLanguage instanceof AbstractMarkupLanguage) { |
| ((AbstractMarkupLanguage) markupLanguage).setEnableMacros(false); |
| } |
| ((MarkupDocumentProvider) getDocumentProvider()).setMarkupLanguage(markupLanguage); |
| |
| IDocument document = getDocumentProvider().getDocument(getEditorInput()); |
| IDocumentPartitioner partitioner = document.getDocumentPartitioner(); |
| if (partitioner instanceof FastMarkupPartitioner) { |
| final FastMarkupPartitioner fastMarkupPartitioner = (FastMarkupPartitioner) partitioner; |
| fastMarkupPartitioner.setMarkupLanguage(markupLanguage); |
| } |
| sourceViewerConfiguration.setMarkupLanguage(markupLanguage); |
| if (getSourceViewer() != null) { |
| getSourceViewer().invalidateTextPresentation(); |
| } |
| outlineDirty = true; |
| scheduleOutlineUpdate(); |
| updateSourceTabLabel(); |
| |
| if (viewer != null) { |
| viewer.getTextWidget().setData(MarkupLanguage.class.getName(), getMarkupLanguage()); |
| } |
| |
| if (persistSetting && markupLanguage != null) { |
| storeMarkupLanguagePreference(markupLanguage); |
| } |
| if (persistSetting) { |
| ISourceViewer sourceViewer = getSourceViewer(); |
| if (sourceViewer instanceof MarkupProjectionViewer) { |
| IReconciler reconciler = ((MarkupProjectionViewer) sourceViewer).getReconciler(); |
| if (reconciler instanceof MarkupMonoReconciler) { |
| ((MarkupMonoReconciler) reconciler).forceReconciling(); |
| } |
| } |
| } |
| } |
| |
| private void updateSourceTabLabel() { |
| if (sourceTab != null) { |
| // bug 270215 carbon shows tooltip in source editing area. |
| boolean isCarbon = Platform.WS_CARBON.equals(Platform.getWS()); |
| |
| MarkupLanguage markupLanguage = getMarkupLanguage(); |
| if (markupLanguage == null) { |
| sourceTab.setText(Messages.MarkupEditor_markupSource); |
| if (!isCarbon) { |
| sourceTab.setToolTipText(Messages.MarkupEditor_markupSource_tooltip); |
| } |
| } else { |
| sourceTab.setText( |
| NLS.bind(Messages.MarkupEditor_markupSource_named, new Object[] { markupLanguage.getName() })); |
| if (!isCarbon) { |
| sourceTab.setToolTipText(NLS.bind(Messages.MarkupEditor_markupSource_tooltip_named, |
| new Object[] { markupLanguage.getName() })); |
| } |
| } |
| } |
| } |
| |
| private MarkupLanguage loadMarkupLanguagePreference() { |
| IFile file = getFile(); |
| if (file != null) { |
| return loadMarkupLanguagePreference(file); |
| } |
| return null; |
| } |
| |
| /** |
| * lookup the markup language preference of a file based on the persisted preference. |
| * |
| * @param file |
| * the file for which the preference should be looked up |
| * @return the markup language preference, or null if it was not set or could not be loaded. |
| */ |
| public static MarkupLanguage loadMarkupLanguagePreference(IFile file) { |
| String languageName = getMarkupLanguagePreference(file); |
| if (languageName != null) { |
| return WikiText.getMarkupLanguage(languageName); |
| } |
| return null; |
| } |
| |
| /** |
| * lookup the markup language preference of a file based on the persisted preference. |
| * |
| * @param file |
| * the file for which the preference should be looked up |
| * @return the markup language name, or null if no preference exists |
| */ |
| public static String getMarkupLanguagePreference(IFile file) { |
| if (file.exists()) { |
| try { |
| return file.getPersistentProperty( |
| new QualifiedName(WikiTextUiPlugin.getDefault().getPluginId(), MarkupEditor.MARKUP_LANGUAGE)); |
| } catch (CoreException e) { |
| WikiTextUiPlugin.getDefault().log(IStatus.ERROR, Messages.MarkupEditor_markupPreferenceError, e); |
| } |
| } |
| return null; |
| } |
| |
| private void storeMarkupLanguagePreference(MarkupLanguage markupLanguage) { |
| if (markupLanguage == null) { |
| throw new IllegalArgumentException(); |
| } |
| IFile file = getFile(); |
| if (file != null) { |
| MarkupLanguage defaultMarkupLanguage = WikiText.getMarkupLanguageForFilename(file.getName()); |
| String preference = markupLanguage.getName(); |
| if (defaultMarkupLanguage != null && defaultMarkupLanguage.getName().equals(preference)) { |
| preference = null; |
| } |
| try { |
| file.setPersistentProperty( |
| new QualifiedName(WikiTextUiPlugin.getDefault().getPluginId(), MARKUP_LANGUAGE), preference); |
| } catch (CoreException e) { |
| WikiTextUiPlugin.getDefault().log(IStatus.ERROR, |
| NLS.bind(Messages.MarkupEditor_markupPreferenceError2, new Object[] { preference }), e); |
| } |
| } |
| } |
| |
| public MarkupLanguage getMarkupLanguage() { |
| IDocument document = getDocumentProvider().getDocument(getEditorInput()); |
| IDocumentPartitioner partitioner = document.getDocumentPartitioner(); |
| MarkupLanguage markupLanguage = null; |
| if (partitioner instanceof FastMarkupPartitioner) { |
| markupLanguage = ((FastMarkupPartitioner) partitioner).getMarkupLanguage(); |
| } |
| return markupLanguage; |
| } |
| |
| @Override |
| protected void createActions() { |
| super.createActions(); |
| |
| IAction action; |
| |
| // action = new ShowCheatSheetAction(this); |
| // setAction(action.getId(),action); |
| |
| action = new ContentAssistAction(new NlsResourceBundle(Messages.class), "ContentAssistProposal_", this); //$NON-NLS-1$ |
| action.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); |
| setAction("ContentAssistProposal", action); //$NON-NLS-1$ |
| markAsStateDependentAction("ContentAssistProposal", true); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public void setAction(String actionID, IAction action) { |
| if (action != null && action.getActionDefinitionId() != null && !isCommandAction(action)) { |
| // bug 336679: don't activate handlers for CommandAction. |
| // We do this by class name so that we don't rely on internals |
| IHandlerService handlerService = (IHandlerService) getSite().getService(IHandlerService.class); |
| handlerService.activateHandler(action.getActionDefinitionId(), new ActionHandler(action)); |
| } |
| super.setAction(actionID, action); |
| } |
| |
| private boolean isCommandAction(IAction action) { |
| for (Class<?> clazz = action.getClass(); clazz != Object.class |
| && clazz != AbstractAction.class; clazz = clazz.getSuperclass()) { |
| if (clazz.getName().equals("org.eclipse.ui.internal.actions.CommandAction")) { //$NON-NLS-1$ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| protected void editorContextMenuAboutToShow(IMenuManager menu) { |
| super.editorContextMenuAboutToShow(menu); |
| |
| final MarkupLanguage markupLanguage = getMarkupLanguage(); |
| MenuManager markupLanguageMenu = new MenuManager(Messages.MarkupEditor_markupLanguage); |
| for (String markupLanguageName : new TreeSet<>(WikiText.getMarkupLanguageNames())) { |
| markupLanguageMenu.add(new SetMarkupLanguageAction(this, markupLanguageName, |
| markupLanguage != null && markupLanguageName.equals(markupLanguage.getName()))); |
| } |
| |
| menu.prependToGroup(ITextEditorActionConstants.GROUP_SETTINGS, markupLanguageMenu); |
| |
| OutlineItem nearestOutlineItem = getNearestMatchingOutlineItem(); |
| if (nearestOutlineItem != null && !nearestOutlineItem.isRootItem()) { |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_OPEN, |
| new PreviewOutlineItemAction(this, nearestOutlineItem)); |
| } |
| } |
| |
| public boolean isFoldingEnabled() { |
| ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); |
| return viewer.getProjectionAnnotationModel() != null; |
| } |
| |
| public boolean show(ShowInContext context) { |
| ISelection selection = context.getSelection(); |
| if (selection instanceof IStructuredSelection) { |
| for (Object element : ((IStructuredSelection) selection).toArray()) { |
| if (element instanceof OutlineItem) { |
| OutlineItem item = (OutlineItem) element; |
| selectAndReveal(item); |
| if (isOutlinePageValid()) { |
| outlinePage.setSelection(selection); |
| } |
| return true; |
| } |
| } |
| } else if (selection instanceof ITextSelection) { |
| ITextSelection textSel = (ITextSelection) selection; |
| selectAndReveal(textSel.getOffset(), textSel.getLength()); |
| return true; |
| } |
| return false; |
| } |
| |
| public void selectAndReveal(OutlineItem item) { |
| selectAndReveal(item.getOffset(), item.getLength()); |
| if (isShowingPreview()) { |
| // scroll preview to the selected item. |
| revealInBrowser(item); |
| } |
| } |
| |
| private void revealInBrowser(OutlineItem item) { |
| browser.execute(String.format("document.getElementById('%s').scrollIntoView(true);window.location.hash = '%s';", //$NON-NLS-1$ |
| item.getId(), item.getId())); |
| } |
| |
| public ShowInContext getShowInContext() { |
| OutlineItem item = getNearestMatchingOutlineItem(); |
| return new ShowInContext(getEditorInput(), |
| item == null ? new StructuredSelection() : new StructuredSelection(item)); |
| } |
| |
| /** |
| * Causes the editor to display the preview at the specified outline item. |
| */ |
| public void showPreview(OutlineItem outlineItem) { |
| if (!isShowingPreview()) { |
| tabFolder.setSelection(previewTab); |
| } |
| updatePreview(outlineItem); |
| } |
| |
| public void perform(AbstractDocumentCommand command) throws CoreException { |
| disableReveal = true; |
| try { |
| command.execute(((ITextViewerExtension6) getViewer()).getUndoManager(), getViewer().getDocument()); |
| } finally { |
| disableReveal = false; |
| } |
| updateOutlineSelection(); |
| } |
| |
| private boolean isShowingPreview() { |
| return previewTab != null && tabFolder.getSelection() == previewTab; |
| } |
| |
| protected boolean getInitialWordWrapStatus() { |
| return true; |
| } |
| } |