/*******************************************************************************
* Copyright (c) 2007, 2011 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.wikitext.ui.editor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.DefaultTextHover;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.information.IInformationPresenter;
import org.eclipse.jface.text.information.IInformationProvider;
import org.eclipse.jface.text.information.IInformationProviderExtension;
import org.eclipse.jface.text.information.IInformationProviderExtension2;
import org.eclipse.jface.text.information.InformationPresenter;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.reconciler.IReconciler;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.MonoReconciler;
import org.eclipse.jface.text.rules.ITokenScanner;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.mylyn.internal.wikitext.ui.WikiTextUiPlugin;
import org.eclipse.mylyn.internal.wikitext.ui.editor.assist.AnchorCompletionProcessor;
import org.eclipse.mylyn.internal.wikitext.ui.editor.assist.MarkupTemplateCompletionProcessor;
import org.eclipse.mylyn.internal.wikitext.ui.editor.assist.MultiplexingContentAssistProcessor;
import org.eclipse.mylyn.internal.wikitext.ui.editor.outline.QuickOutlinePopupDialog;
import org.eclipse.mylyn.internal.wikitext.ui.editor.reconciler.MarkupMonoReconciler;
import org.eclipse.mylyn.internal.wikitext.ui.editor.reconciler.MarkupValidationReconcilingStrategy;
import org.eclipse.mylyn.internal.wikitext.ui.editor.reconciler.MultiReconcilingStrategy;
import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.FastMarkupPartitioner;
import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.FileRefHyperlinkDetector;
import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.MarkupDamagerRepairer;
import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.MarkupHyperlinkDetector;
import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.MarkupTokenScanner;
import org.eclipse.mylyn.internal.wikitext.ui.util.WikiTextUiResources;
import org.eclipse.mylyn.wikitext.parser.markup.MarkupLanguage;
import org.eclipse.mylyn.wikitext.parser.outline.OutlineItem;
import org.eclipse.mylyn.wikitext.parser.outline.OutlineParser;
import org.eclipse.mylyn.wikitext.ui.viewer.AbstractTextSourceViewerConfiguration;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.texteditor.HippieProposalProcessor;
import org.eclipse.ui.themes.IThemeManager;
import com.google.common.collect.ImmutableList;
/**
* A source viewer configuration suitable for installing on a markup editor
*
* @author David Green
* @since 1.1
*/
public class MarkupSourceViewerConfiguration extends AbstractTextSourceViewerConfiguration {
private MarkupTokenScanner scanner;
private MarkupTemplateCompletionProcessor completionProcessor;
private AnchorCompletionProcessor anchorCompletionProcessor;
private MarkupLanguage markupLanguage;
private MarkupValidationReconcilingStrategy markupValidationReconcilingStrategy;
private IFile file;
private ITextHover textHover;
private OutlineItem outline;
private Font defaultFont;
private Font defaultMonospaceFont;
private InformationPresenter informationPresenter;
private IShowInTarget showInTarget;
private String fontPreference;
private String monospaceFontPreference;
private MarkupHyperlinkDetector markupHyperlinkDetector;
private FileRefHyperlinkDetector fileRefHyperlinkDetector;
private boolean enableHippieContentAssist = true;
private boolean enableSelfContainedIncrementalFind = false;
/**
* @since 1.3
* @param preferenceStore
* @param fontPreference
* the preference key of the text font
* @param monospaceFontPreference
* the preference key of the monospace text font
* @see #initializeDefaultFonts()
*/
public MarkupSourceViewerConfiguration(IPreferenceStore preferenceStore, String textFontPreference,
String monospaceFontPreference) {
super(preferenceStore);
this.fontPreference = textFontPreference;
this.monospaceFontPreference = monospaceFontPreference;
initializeDefaultFonts();
}
/**
* Initialize with the default font preference keys
*/
public MarkupSourceViewerConfiguration(IPreferenceStore preferenceStore) {
this(preferenceStore, WikiTextUiResources.PREFERENCE_TEXT_FONT, WikiTextUiResources.PREFERENCE_MONOSPACE_FONT);
}
@Override
protected List<IHyperlinkDetector> createCustomHyperlinkDetectors(ISourceViewer sourceViewer) {
List<IHyperlinkDetector> detectors = new ArrayList<>();
if (markupHyperlinkDetector == null) {
markupHyperlinkDetector = new MarkupHyperlinkDetector();
markupHyperlinkDetector.setMarkupLanguage(markupLanguage);
markupHyperlinkDetector.setFile(file);
}
if (fileRefHyperlinkDetector == null && file != null) {
Map<String, List<String>> hyperlinkDectectorFileRefRegexes = WikiTextUiPlugin.getDefault()
.getHyperlinkDectectorFileRefRegexes();
List<String> fileRefHyperlinkRegexes = hyperlinkDectectorFileRefRegexes
.getOrDefault(markupLanguage.getName(), ImmutableList.of());
fileRefHyperlinkDetector = new FileRefHyperlinkDetector(file.getParent(), fileRefHyperlinkRegexes);
}
detectors.add(markupHyperlinkDetector);
detectors.add(fileRefHyperlinkDetector);
detectors.add(markupHyperlinkDetector);
detectors.addAll(super.createCustomHyperlinkDetectors(sourceViewer));
return detectors;
}
/**
* Initialize default fonts. Causes this to re-read font preferences from the preference store. Calling this method
* should only be necessary if font preferences have changed, or if the font preference keys have changed.
*
* @since 1.3
* @see #getFontPreference()
* @see #getMonospaceFontPreference()
*/
public void initializeDefaultFonts() {
Font defaultFont = null;
Font defaultMonospaceFont = null;
if (WikiTextUiPlugin.getDefault() != null) {
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
FontRegistry fontRegistry = themeManager.getCurrentTheme().getFontRegistry();
defaultFont = fontRegistry.get(fontPreference);
defaultMonospaceFont = fontRegistry.get(monospaceFontPreference);
}
if (defaultFont == null) {
// could be the case when running stand-alone
defaultFont = JFaceResources.getDefaultFont();
}
if (this.defaultFont != defaultFont || this.defaultMonospaceFont != defaultMonospaceFont) {
this.defaultFont = defaultFont;
this.defaultMonospaceFont = defaultMonospaceFont;
if (scanner != null) {
scanner.resetFonts(defaultFont, defaultMonospaceFont);
}
}
}
/**
* the font preference key for the text font
*
* @see #initializeDefaultFonts()
* @since 1.3
*/
public String getFontPreference() {
return fontPreference;
}
/**
* the font preference key for the text font
*
* @see #initializeDefaultFonts()
* @since 1.3
*/
public void setFontPreference(String textFontPreference) {
this.fontPreference = textFontPreference;
}
/**
* the monospace font preference key for the text font
*
* @see #initializeDefaultFonts()
* @since 1.3
*/
public String getMonospaceFontPreference() {
return monospaceFontPreference;
}
/**
* the monospace font preference key for the text font
*
* @see #initializeDefaultFonts()
* @since 1.3
*/
public void setMonospaceFontPreference(String monospaceFontPreference) {
this.monospaceFontPreference = monospaceFontPreference;
}
/**
* @since 1.1
*/
public ITokenScanner getMarkupScanner() {
if (scanner == null) {
scanner = new MarkupTokenScanner(defaultFont, defaultMonospaceFont);
}
return scanner;
}
@Override
public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
PresentationReconciler reconciler = new PresentationReconciler();
reconciler.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer));
MarkupDamagerRepairer damagerRepairer = new MarkupDamagerRepairer(getMarkupScanner());
for (String partitionType : FastMarkupPartitioner.ALL_CONTENT_TYPES) {
reconciler.setDamager(damagerRepairer, partitionType);
reconciler.setRepairer(damagerRepairer, partitionType);
}
reconciler.setDamager(damagerRepairer, IDocument.DEFAULT_CONTENT_TYPE);
reconciler.setRepairer(damagerRepairer, IDocument.DEFAULT_CONTENT_TYPE);
return reconciler;
}
@Override
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
if (completionProcessor == null) {
completionProcessor = new MarkupTemplateCompletionProcessor();
completionProcessor.setMarkupLanguage(markupLanguage);
}
if (anchorCompletionProcessor == null && outline != null) {
anchorCompletionProcessor = new AnchorCompletionProcessor();
anchorCompletionProcessor.setOutline(outline);
}
MultiplexingContentAssistProcessor processor = new MultiplexingContentAssistProcessor();
if (anchorCompletionProcessor != null) {
processor.addDelegate(anchorCompletionProcessor);
}
processor.addDelegate(completionProcessor);
if (enableHippieContentAssist) {
HippieProposalProcessor hippieProcessor = new HippieProposalProcessor();
processor.addDelegate(hippieProcessor);
}
IContentAssistProcessor[] processors = createContentAssistProcessors();
if (processors != null) {
for (IContentAssistProcessor cap : processors) {
processor.addDelegate(cap);
}
}
ContentAssistant assistant = new ContentAssistant();
assistant.enableAutoActivation(true);
assistant.enableAutoInsert(true);
assistant.setContentAssistProcessor(processor, IDocument.DEFAULT_CONTENT_TYPE);
for (String partitionType : FastMarkupPartitioner.ALL_CONTENT_TYPES) {
assistant.setContentAssistProcessor(processor, partitionType);
}
return assistant;
}
/**
* subclasses may override this method to create additional content assist processors.
*
* @return processors, or null if there are none.
*/
protected IContentAssistProcessor[] createContentAssistProcessors() {
return null;
}
/**
* Set the markup language of the configuration. Causes the completion processor, validating reconciling strategy
* and other configuration elements to be aware of the markup language in use. This may be called more than once
* during the lifecycle of the editor.
*
* @param markupLanguage
* the markup language
* @since 3.0
*/
public void setMarkupLanguage(MarkupLanguage markupLanguage) {
this.markupLanguage = markupLanguage;
if (completionProcessor != null) {
completionProcessor.setMarkupLanguage(markupLanguage);
}
if (markupValidationReconcilingStrategy != null) {
markupValidationReconcilingStrategy.setMarkupLanguage(markupLanguage);
}
if (markupHyperlinkDetector != null) {
markupHyperlinkDetector.setMarkupLanguage(markupLanguage);
}
}
@Override
public IReconciler getReconciler(ISourceViewer sourceViewer) {
IReconcilingStrategy strategy;
{
if (markupValidationReconcilingStrategy == null) {
markupValidationReconcilingStrategy = new MarkupValidationReconcilingStrategy(sourceViewer);
markupValidationReconcilingStrategy.setMarkupLanguage(markupLanguage);
markupValidationReconcilingStrategy.setResource(file);
}
IReconciler reconciler = super.getReconciler(sourceViewer);
if (reconciler != null) {
MultiReconcilingStrategy multiStrategy = new MultiReconcilingStrategy();
for (String contentType : FastMarkupPartitioner.ALL_CONTENT_TYPES) {
maybeAddReconcilingStrategyForContentType(multiStrategy, reconciler, contentType);
}
maybeAddReconcilingStrategyForContentType(multiStrategy, reconciler, IDocument.DEFAULT_CONTENT_TYPE);
multiStrategy.add(markupValidationReconcilingStrategy);
strategy = multiStrategy;
} else {
strategy = markupValidationReconcilingStrategy;
}
}
MonoReconciler reconciler = new MarkupMonoReconciler(strategy, false);
reconciler.setIsIncrementalReconciler(false);
reconciler.setProgressMonitor(new NullProgressMonitor());
reconciler.setDelay(500);
return reconciler;
}
private void maybeAddReconcilingStrategyForContentType(MultiReconcilingStrategy multiStrategy,
IReconciler reconciler, String contentType) {
final IReconcilingStrategy reconcilingStrategy = reconciler.getReconcilingStrategy(contentType);
if (reconcilingStrategy != null && !multiStrategy.contains(reconcilingStrategy)) {
multiStrategy.add(reconcilingStrategy);
}
}
/**
* Set the file being edited. If a file is being edited this allows for validation to create markers on the file.
* Some editors are not file-based and thus need not invoke this method.
*
* @param file
* the file, which may be null.
*/
public void setFile(IFile file) {
this.file = file;
if (markupValidationReconcilingStrategy != null) {
markupValidationReconcilingStrategy.setResource(file);
}
if (markupHyperlinkDetector != null) {
markupHyperlinkDetector.setFile(file);
}
}
@Override
public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
if (textHover == null) {
textHover = new DefaultTextHover(sourceViewer);
}
return textHover;
}
/**
* provide access to an information presenter that can be used to pop-up a quick outline. Source viewers should
* configure as follows:
*
* <pre>
* public void configure(SourceViewerConfiguration configuration) {
* super.configure(configuration);
* if (configuration instanceof MarkupSourceViewerConfiguration) {
* outlinePresenter = ((MarkupSourceViewerConfiguration) configuration)
* .getOutlineInformationPresenter(this);
* outlinePresenter.install(this);
* }
* }
* </pre>
*
* @param sourceViewer
* the source viewer for which the presenter should be created
* @return the presenter
*/
public IInformationPresenter getOutlineInformationPresenter(ISourceViewer sourceViewer) {
if (informationPresenter == null) {
IInformationControlCreator controlCreator = getOutlineInformationControlCreator();
informationPresenter = new InformationPresenter(controlCreator);
informationPresenter.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer));
// Register information provider
IInformationProvider provider = new InformationProvider(controlCreator);
String[] contentTypes = getConfiguredContentTypes(sourceViewer);
for (String contentType : contentTypes) {
informationPresenter.setInformationProvider(provider, contentType);
}
informationPresenter.setSizeConstraints(60, 20, true, true);
}
return informationPresenter;
}
@Override
public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) {
List<String> contentTypes = new ArrayList<>(3);
contentTypes.addAll(Arrays.asList(FastMarkupPartitioner.ALL_CONTENT_TYPES));
contentTypes.add(IDocument.DEFAULT_CONTENT_TYPE);
return contentTypes.toArray(new String[contentTypes.size()]);
}
protected IInformationControlCreator getOutlineInformationControlCreator() {
return new IInformationControlCreator() {
public IInformationControl createInformationControl(Shell parent) {
QuickOutlinePopupDialog dialog = new QuickOutlinePopupDialog(parent, showInTarget);
return dialog;
}
};
}
/**
* Set the outline on this configuration. Outlines are used for document-internal references as well as for quick
* outline. Editors that call this method must keep the outline up to date as the source document changes. Editors
* that do not maintain an outline need not call this method, since the outline will be computed as needed for the
* quick outline.
*
* @param outlineModel
* @since 3.0
*/
public void setOutline(OutlineItem outlineModel) {
this.outline = outlineModel;
if (anchorCompletionProcessor != null) {
anchorCompletionProcessor.setOutline(outline);
}
}
/**
* the default font, as used by the {@link #getMarkupScanner() scanner}.
*/
public Font getDefaultFont() {
return defaultFont;
}
/**
* The default font, as used by the {@link #getMarkupScanner() scanner}. Note that if a preference store is used
* then {@link #setFontPreference(String)} should be used instead.
*/
public void setDefaultFont(Font defaultFont) {
if (defaultFont == null) {
throw new IllegalArgumentException();
}
if (defaultFont != this.defaultFont) {
this.defaultFont = defaultFont;
if (scanner != null) {
scanner.resetFonts(defaultFont, defaultMonospaceFont);
}
}
}
/**
* the default font for monospace text, as used by the {@link #getMarkupScanner() scanner}.
*/
public Font getDefaultMonospaceFont() {
return defaultMonospaceFont;
}
/**
* the default font for monospace text, as used by the {@link #getMarkupScanner() scanner}. Note that if a
* preference store is used then {@link #setMonospaceFontPreference(String)} should be used instead.
*/
public void setDefaultMonospaceFont(Font defaultMonospaceFont) {
if (this.defaultMonospaceFont != defaultMonospaceFont) {
this.defaultMonospaceFont = defaultMonospaceFont;
if (scanner != null) {
scanner.resetFonts(defaultFont, defaultMonospaceFont);
}
}
}
/**
* provide a {@link IShowInTarget show in target} to connect the quick-outline popup with the editor.
*/
public IShowInTarget getShowInTarget() {
return showInTarget;
}
/**
* provide a {@link IShowInTarget show in target} to connect the quick-outline popup with the editor.
*/
public void setShowInTarget(IShowInTarget showInTarget) {
this.showInTarget = showInTarget;
}
private class InformationProvider
implements IInformationProvider, IInformationProviderExtension, IInformationProviderExtension2 {
private final IInformationControlCreator controlCreator;
public InformationProvider(IInformationControlCreator controlCreator) {
this.controlCreator = controlCreator;
}
@Deprecated
public String getInformation(ITextViewer textViewer, IRegion subject) {
return getInformation2(textViewer, subject).toString();
}
public Object getInformation2(ITextViewer textViewer, IRegion subject) {
if (outline == null) {
// If the outline was not set then parse it. This can happen in a task editor
if (markupLanguage != null) {
IDocument document = textViewer.getDocument();
if (document != null && document.getLength() > 0) {
MarkupLanguage language = markupLanguage.clone();
OutlineParser outlineParser = new OutlineParser();
outlineParser.setMarkupLanguage(language.clone());
String markup = document.get();
final OutlineItem outline = outlineParser.parse(markup);
if (MarkupSourceViewerConfiguration.this.file != null) {
outline.setResourcePath(MarkupSourceViewerConfiguration.this.file.getFullPath().toString());
}
return outline;
}
}
}
return outline;
}
public IRegion getSubject(ITextViewer textViewer, int offset) {
return new Region(offset, 0);
}
public IInformationControlCreator getInformationPresenterControlCreator() {
return controlCreator;
}
}
/**
* Indicate if Hippie content assist should be enabled. The default is true.
*
* @since 1.4
* @see HippieProposalProcessor
*/
public boolean isEnableHippieContentAssist() {
return enableHippieContentAssist;
}
/**
* Indicate if Hippie content assist should be enabled.
*
* @since 1.4
*/
public void setEnableHippieContentAssist(boolean enableHippieContentAssist) {
this.enableHippieContentAssist = enableHippieContentAssist;
}
/**
* Indicate if incremental find should be supported in a self-contained manner. For use when SourceViewer is not
* used in a {@link TextEditor}. Defaults to false.
*
* @since 1.6
*/
public boolean isEnableSelfContainedIncrementalFind() {
return enableSelfContainedIncrementalFind;
}
/**
* Indicate if incremental find should be supported in a self-contained manner. For use when SourceViewer is not
* used in a {@link TextEditor}.
*
* @since 1.6
*/
public void setEnableSelfContainedIncrementalFind(boolean enableSelfContainedIncrementalFind) {
this.enableSelfContainedIncrementalFind = enableSelfContainedIncrementalFind;
}
}