blob: 1e405d0a7f933da147d6ecda0037be2f2ab82b95 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ltk.ui.sourceediting;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import static org.eclipse.statet.ltk.ui.LtkUI.BUNDLE_ID;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.internal.text.html.BrowserInformationControl;
import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.AbstractInformationControlManager;
import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultInformationControl;
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.ITextViewerExtension2;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.information.IInformationPresenter;
import org.eclipse.jface.text.information.IInformationProvider;
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.quickassist.IQuickAssistAssistant;
import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
import org.eclipse.jface.text.quickassist.QuickAssistAssistant;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.ITokenScanner;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateVariableResolver;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
import org.eclipse.statet.ecommons.templates.TemplateVariableProcessor;
import org.eclipse.statet.ecommons.templates.WordFinder;
import org.eclipse.statet.ecommons.text.ICharPairMatcher;
import org.eclipse.statet.ecommons.text.IIndentSettings;
import org.eclipse.statet.ecommons.text.IndentUtil;
import org.eclipse.statet.ecommons.text.core.sections.DocContentSections;
import org.eclipse.statet.ecommons.text.ui.DefaultBrowserInformationInput;
import org.eclipse.statet.ecommons.text.ui.presentation.ITextPresentationConstants;
import org.eclipse.statet.ecommons.text.ui.settings.DecorationPreferences;
import org.eclipse.statet.ecommons.text.ui.settings.TextStyleManager;
import org.eclipse.statet.ecommons.ui.ISettingsChangedHandler;
import org.eclipse.statet.ecommons.ui.dialogs.DialogUtils;
import org.eclipse.statet.ecommons.ui.util.InformationDispatchHandler;
import org.eclipse.statet.internal.ltk.ui.LtkUIPlugin;
import org.eclipse.statet.ltk.ui.LtkUIPreferences;
import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistPreferences;
import org.eclipse.statet.ltk.ui.sourceediting.assist.ContentAssist;
import org.eclipse.statet.ltk.ui.sourceediting.assist.InfoHoverDescriptor;
import org.eclipse.statet.ltk.ui.sourceediting.assist.InfoHoverRegistry;
import org.eclipse.statet.ltk.ui.sourceediting.assist.InfoHoverRegistry.EffectiveHovers;
/**
* Abstract configuration for {@link SourceEditor}s.
*/
public abstract class SourceEditorViewerConfiguration extends TextSourceViewerConfiguration
implements ISettingsChangedHandler {
static final boolean DEBUG= true;
/**
* Flag to enable configuration for editing / showing templates.
*
* Overwrite: {@link #getTemplateVariableProcessor()} */
public static final int TEMPLATE_MODE= 1 << 3;
/**
* Flag to enable configuration for comparing sources.
*/
public static final int COMPARE_MODE= 1 << 4;
private static class AssistInformationControlCreator extends AbstractReusableInformationControlCreator {
@Override
protected IInformationControl doCreateInformationControl(final Shell parent) {
if (BrowserInformationControl.isAvailable(parent)) {
return new BrowserInformationControl(parent, JFaceResources.DIALOG_FONT, false) {
@Override
public void setInformation(final String content) {
if (content.startsWith("...<br")) { // spell correction change proposal //$NON-NLS-1$
setInput(new DefaultBrowserInformationInput("", //$NON-NLS-1$
content, DefaultBrowserInformationInput.FORMAT_HTMLSOURCE_INPUT ));
}
else {
setInput(new DefaultBrowserInformationInput("", //$NON-NLS-1$
content, DefaultBrowserInformationInput.FORMAT_TEXT_INPUT ));
}
}
};
}
else {
return new DefaultInformationControl(parent, new HTMLTextPresenter(false));
}
}
}
private static IInformationControlCreator assistTemplateInformationControlCreator;
private static IInformationControlCreator assistDefaultInformationControlCreator;
private static final IInformationControlCreator DEFAULT_INFORMATION_CONTROL_CREATOR=
new IInformationControlCreator() {
@Override
public IInformationControl createInformationControl(final Shell parent) {
return new DefaultInformationControl(parent, true);
}
};
private static final ITextHover NO_HOVER= new ITextHover() {
@Override
public IRegion getHoverRegion(final ITextViewer textViewer, final int offset) {
return null;
}
@Override
public String getHoverInfo(final ITextViewer textViewer, final IRegion hoverRegion) {
return null;
}
};
private final DocContentSections documentContentInfo;
private final int flags;
private final SourceEditor editor;
private TextStyleManager textStyles;
private final CopyOnWriteIdentityListSet<ISettingsChangedHandler> settingsHandler= new CopyOnWriteIdentityListSet<>();
private Map<String, ITokenScanner> scanners;
private IPresentationReconciler presentationReconciler;
private ICharPairMatcher pairMatcher;
private ContentAssist contentAssistant;
private IQuickAssistAssistant quickAssistant;
private DecorationPreferences decorationPreferences;
private AssistPreferences assistPreferences;
private EffectiveHovers infoHovers;
private Map<Integer, ITextHover> infoHoverCache;
public SourceEditorViewerConfiguration(final DocContentSections documentContentInfo, final int flags,
final SourceEditor sourceEditor) {
if (documentContentInfo == null) {
throw new NullPointerException("documentContentInfo"); //$NON-NLS-1$
}
this.documentContentInfo= documentContentInfo;
this.flags= flags;
this.editor= sourceEditor;
}
protected void setup(final IPreferenceStore preferenceStore,
final DecorationPreferences decoPrefs, final AssistPreferences assistPrefs) {
assert (preferenceStore != null);
this.fPreferenceStore= preferenceStore;
this.decorationPreferences= decoPrefs;
this.assistPreferences= assistPrefs;
}
public int getFlags() {
return this.flags;
}
protected void initTextStyles() {
}
protected void setTextStyles(final TextStyleManager textStyles) {
this.textStyles= textStyles;
}
protected TextStyleManager getTextStyles() {
if (this.textStyles == null) {
initTextStyles();
}
return this.textStyles;
}
protected void initScanners() {
}
protected void addScanner(final String contentType, final ITokenScanner scanner) {
this.scanners.put(contentType, scanner);
if (scanner instanceof ISettingsChangedHandler) {
this.settingsHandler.add((ISettingsChangedHandler) scanner);
}
}
protected ITokenScanner getScanner(final String contentType) {
if (this.scanners == null) {
this.scanners= new HashMap<>();
initScanners();
}
final ITokenScanner scanner= this.scanners.get(contentType);
if (DEBUG && scanner == null) {
StatusManager.getManager().handle(new Status(IStatus.WARNING, BUNDLE_ID,
"No scanner for contentType= " + contentType + "." ));
}
return scanner;
}
protected SourceEditor getSourceEditor() {
return this.editor;
}
public final DocContentSections getDocumentContentInfo() {
return this.documentContentInfo;
}
@Override
public final String getConfiguredDocumentPartitioning(final ISourceViewer sourceViewer) {
return this.documentContentInfo.getPartitioning();
}
public IPreferenceStore getPreferences() {
return this.fPreferenceStore;
}
public DecorationPreferences getDecorationPreferences() {
return this.decorationPreferences;
}
public AssistPreferences getAssistPreferences() {
return this.assistPreferences;
}
@Override
public void handleSettingsChanged(final Set<String> groupIds, final Map<String, Object> options) {
if (this.assistPreferences != null
&& (groupIds.contains(LtkUIPreferences.ASSIST_GROUP_ID) || groupIds.contains(this.assistPreferences.getGroupId())) ) {
if (this.contentAssistant != null) {
this.assistPreferences.configure(this.contentAssistant);
}
if (this.quickAssistant != null) {
this.assistPreferences.configure(this.quickAssistant);
}
}
if (this.textStyles != null && this.textStyles.affectsTextPresentation(groupIds)) {
options.put(ITextPresentationConstants.SETTINGSCHANGE_AFFECTSPRESENTATION_KEY, Boolean.TRUE);
}
for (final ISettingsChangedHandler handler : this.settingsHandler.toList()) {
handler.handleSettingsChanged(groupIds, options);
}
}
@Override
public IPresentationReconciler getPresentationReconciler(final ISourceViewer sourceViewer) {
if (this.presentationReconciler == null) {
this.presentationReconciler= createPresentationReconciler();
}
return this.presentationReconciler;
}
protected IPresentationReconciler createPresentationReconciler() {
final PresentationReconciler reconciler= new PresentationReconciler();
reconciler.setDocumentPartitioning(getConfiguredDocumentPartitioning(null));
initPresentationReconciler(reconciler);
return reconciler;
}
PresentationReconciler getPresentationReconciler() {
return (PresentationReconciler) this.presentationReconciler;
}
protected void initPresentationReconciler(final PresentationReconciler reconciler) {
final String[] contentTypes= getConfiguredContentTypes(null);
for (final String contentType : contentTypes) {
final ITokenScanner scanner= getScanner(contentType);
if (scanner != null) {
final DefaultDamagerRepairer dr= new DefaultDamagerRepairer(scanner);
reconciler.setDamager(dr, contentType);
reconciler.setRepairer(dr, contentType);
}
}
}
public List<SourceEditorAddon> getAddOns() {
return new ArrayList<>();
}
public ICharPairMatcher getPairMatcher() {
if (this.pairMatcher == null) {
this.pairMatcher= createPairMatcher();
}
return this.pairMatcher;
}
protected ICharPairMatcher createPairMatcher() {
return null;
}
protected IIndentSettings getIndentSettings() {
return null;
}
@Override
public int getTabWidth(final ISourceViewer sourceViewer) {
final IIndentSettings settings= getIndentSettings();
if (settings != null) {
return settings.getTabSize();
}
return super.getTabWidth(sourceViewer);
}
@Override
public String[] getIndentPrefixes(final ISourceViewer sourceViewer, final String contentType) {
final IIndentSettings settings= getIndentSettings();
if (settings != null) {
final String[] prefixes= getIndentPrefixesForTab(getTabWidth(sourceViewer));
if (settings.getIndentDefaultType() == IIndentSettings.IndentationType.SPACES) {
for (int i= prefixes.length-2; i > 0; i--) {
prefixes[i]= prefixes[i-1];
}
prefixes[0]= new String(IndentUtil.repeat(' ', settings.getIndentSpacesCount()));
}
return prefixes;
}
else {
return super.getIndentPrefixes(sourceViewer, contentType);
}
}
public boolean isSmartInsertSupported() {
return false;
}
public boolean isSmartInsertByDefault() {
return true;
}
@Override
public IContentAssistant getContentAssistant(final ISourceViewer sourceViewer) {
if (this.contentAssistant == null) {
this.contentAssistant= ((getFlags() & TEMPLATE_MODE) != 0) ?
createTemplateVariableContentAssistant(sourceViewer) :
createContentAssistant(sourceViewer);
if (this.contentAssistant != null) {
if (this.assistPreferences != null) {
this.assistPreferences.configure(this.contentAssistant);
}
this.contentAssistant.setProposalPopupOrientation(IContentAssistant.PROPOSAL_OVERLAY);
this.contentAssistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
this.contentAssistant.setInformationControlCreator(
getAssistInformationControlCreator(sourceViewer) );
}
}
return this.contentAssistant;
}
public ContentAssist getContentAssist() {
return this.contentAssistant;
}
protected ContentAssist createContentAssistant(final ISourceViewer sourceViewer) {
if (getSourceEditor() != null) {
final ContentAssist assistant= new ContentAssist();
assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(null));
assistant.setRestoreCompletionProposalSize(DialogUtils.getDialogSettings(
LtkUIPlugin.getInstance(), "ContentAssist.Proposal.size")); //$NON-NLS-1$
initContentAssist(assistant);
return assistant;
}
return null;
}
protected void initContentAssist(final ContentAssist assistant) {
}
@Override
public IQuickAssistAssistant getQuickAssistAssistant(final ISourceViewer sourceViewer) {
if (this.quickAssistant == null) {
this.quickAssistant= createQuickAssistant(sourceViewer);
if (this.quickAssistant != null) {
if (this.assistPreferences != null) {
this.assistPreferences.configure(this.quickAssistant);
}
this.quickAssistant.setInformationControlCreator(getAssistInformationControlCreator(sourceViewer));
}
}
return this.quickAssistant;
}
protected IQuickAssistAssistant createQuickAssistant(final ISourceViewer sourceViewer) {
final IQuickAssistProcessor processor= createQuickAssistProcessor();
if (processor != null) {
final QuickAssistAssistant assistant= new QuickAssistAssistant();
assistant.setQuickAssistProcessor(processor);
assistant.enableColoredLabels(true);
assistant.setRestoreCompletionProposalSize(DialogUtils.getDialogSettings(
nonNullAssert(Platform.getBundle(EditorsUI.PLUGIN_ID)),
"quick_assist_proposal_size" )); //$NON-NLS-1$
return assistant;
}
return super.getQuickAssistAssistant(sourceViewer);
}
protected IQuickAssistProcessor createQuickAssistProcessor() {
return null;
}
protected IInformationControlCreator getAssistInformationControlCreator(final ISourceViewer sourceViewer) {
if ((getFlags() & TEMPLATE_MODE) != 0) {
if (assistTemplateInformationControlCreator == null) {
assistTemplateInformationControlCreator= new IInformationControlCreator() {
@Override
public IInformationControl createInformationControl(final Shell parent) {
return new DefaultInformationControl(parent,
InformationDispatchHandler.getAdditionalInfoAffordanceString() );
}
};
}
return assistTemplateInformationControlCreator;
}
if (assistDefaultInformationControlCreator == null) {
assistDefaultInformationControlCreator= new AssistInformationControlCreator();
}
return assistDefaultInformationControlCreator;
}
@Override
public int[] getConfiguredTextHoverStateMasks(final ISourceViewer sourceViewer, final String contentType) {
if ((getFlags() & TEMPLATE_MODE) != 0) {
return new int[] { ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK };
}
if (getSourceEditor() != null) {
return getInfoHoverStateMasks(contentType);
}
return super.getConfiguredTextHoverStateMasks(sourceViewer, contentType);
}
@Override
public ITextHover getTextHover(final ISourceViewer sourceViewer, final String contentType) {
return getTextHover(sourceViewer, contentType, ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK);
}
@Override
public ITextHover getTextHover(final ISourceViewer sourceViewer, final String contentType, final int stateMask) {
if ((getFlags() & TEMPLATE_MODE) != 0) {
return getTemplateVariableTextHover(sourceViewer);
}
return getInfoHover(sourceViewer, contentType, stateMask);
}
protected int[] getInfoHoverStateMasks(final String contentType) {
final String[] contentTypes= getConfiguredContentTypes(null);
if (contentTypes != null && contentTypes.length > 0 && contentTypes[0].equals(contentType)) {
initInfoHovers();
}
if (this.infoHovers != null && isInfoHoverDefaultContentType(contentType)) {
return this.infoHovers.getStateMasks();
}
return new int[] { ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK };
}
protected void initInfoHovers() {
final InfoHoverRegistry registry= getInfoHoverRegistry();
this.infoHovers= (registry != null) ? registry.getEffectiveHoverDescriptors() : null;
if (this.infoHoverCache == null) {
this.infoHoverCache= new HashMap<>();
}
else {
this.infoHoverCache.clear();
}
}
public EffectiveHovers getConfiguredInfoHovers() {
return this.infoHovers;
}
protected ITextHover getInfoHover(final ISourceViewer sourceViewer, final String contentType, final int stateMask) {
if (this.infoHovers != null && isInfoHoverDefaultContentType(contentType)) {
final Integer key= Integer.valueOf(stateMask);
ITextHover hover= this.infoHoverCache.get(key);
if (hover == null) {
final InfoHoverDescriptor descriptor= this.infoHovers.getDescriptor(stateMask);
if (descriptor != null) {
hover= createInfoHover(descriptor);
if (hover == null) {
hover= NO_HOVER;
}
this.infoHoverCache.put(key, hover);
}
}
return (hover != NO_HOVER) ? hover : null;
}
return null;
}
protected boolean isInfoHoverDefaultContentType(final String contentType) {
final String[] partitioning= getConfiguredContentTypes(null);
return (partitioning != null && partitioning.length > 0 && partitioning[0].equals(contentType));
}
protected InfoHoverRegistry getInfoHoverRegistry() {
return null;
}
protected ITextHover createInfoHover(final InfoHoverDescriptor descriptor) {
return null;
}
@Override
public IInformationPresenter getInformationPresenter(final ISourceViewer sourceViewer) {
final InformationPresenter presenter= new InformationPresenter(
DEFAULT_INFORMATION_CONTROL_CREATOR);
presenter.setDocumentPartitioning(getConfiguredDocumentPartitioning(null));
// Register information provider
final IInformationProvider provider= getInformationProvider();
final String[] contentTypes= getConfiguredContentTypes(null);
for (int i= 0; i < contentTypes.length; i++) {
presenter.setInformationProvider(provider, contentTypes[i]);
}
// sizes: see org.eclipse.jface.text.TextViewer.TEXT_HOVER_*_CHARS
presenter.setSizeConstraints(100, 12, true, true);
return presenter;
}
protected IInformationProvider getInformationProvider() {
return null;
}
@Override
protected Map getHyperlinkDetectorTargets(final ISourceViewer sourceViewer) {
final Map<String, IAdaptable> targets= super.getHyperlinkDetectorTargets(sourceViewer);
collectHyperlinkDetectorTargets(targets, sourceViewer);
return targets;
}
protected void collectHyperlinkDetectorTargets(final Map<String, IAdaptable> targets, final ISourceViewer sourceViewer) {
}
public IInformationPresenter getQuickPresenter(final ISourceViewer sourceViewer,
final int operation) {
final IInformationProvider provider= getQuickInformationProvider(sourceViewer, operation);
if (provider == null) {
return null;
}
final InformationPresenter presenter= new InformationPresenter(((IInformationProviderExtension2) provider).getInformationPresenterControlCreator());
presenter.setDocumentPartitioning(getConfiguredDocumentPartitioning(null));
presenter.setAnchor(AbstractInformationControlManager.ANCHOR_GLOBAL);
presenter.setSizeConstraints(50, 20, true, false);
final String[] contentTypes= getConfiguredContentTypes(null);
for (int i= 0; i < contentTypes.length; i++) {
presenter.setInformationProvider(provider, contentTypes[i]);
}
return presenter;
}
protected IInformationProvider getQuickInformationProvider(final ISourceViewer sourceViewer,
final int operation) {
return null;
}
/* For TemplateEditors ********************************************************/
protected TemplateVariableProcessor getTemplateVariableProcessor() {
throw new UnsupportedOperationException();
}
protected ContentAssist createTemplateVariableContentAssistant(final ISourceViewer sourceViewer) {
final ContentAssist assistant= new ContentAssist();
assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(null));
for (final String contentType : getConfiguredContentTypes(null)) {
assistant.setContentAssistProcessor(getTemplateVariableProcessor(), contentType);
}
return assistant;
}
protected static class TemplateVariableTextHover implements ITextHover {
private final TemplateVariableProcessor processor;
/**
* @param processor the template variable processor
*/
public TemplateVariableTextHover(final TemplateVariableProcessor processor) {
this.processor= processor;
}
@Override
public String getHoverInfo(final ITextViewer textViewer, final IRegion subject) {
try {
final IDocument doc= textViewer.getDocument();
final int offset= subject.getOffset();
if (offset >= 2 && "${".equals(doc.get(offset-2, 2))) { //$NON-NLS-1$
final String varName= doc.get(offset, subject.getLength());
final TemplateContextType contextType= this.processor.getContextType();
if (contextType != null) {
final Iterator iter= contextType.resolvers();
while (iter.hasNext()) {
final TemplateVariableResolver var= (TemplateVariableResolver) iter.next();
if (varName.equals(var.getType())) {
return var.getDescription();
}
}
}
}
} catch (final BadLocationException e) {
}
return null;
}
@Override
public IRegion getHoverRegion(final ITextViewer textViewer, final int offset) {
if (textViewer != null) {
return WordFinder.findWord(textViewer.getDocument(), offset);
}
return null;
}
}
public ITextHover getTemplateVariableTextHover(final ISourceViewer sourceViewer) {
final Integer key= Integer.valueOf(0);
ITextHover hover;
if (this.infoHoverCache != null) {
hover= this.infoHoverCache.get(key);
}
else {
hover= new TemplateVariableTextHover(getTemplateVariableProcessor());
this.infoHoverCache= Collections.singletonMap(key, hover);
}
return hover;
}
}