blob: 9a435d5d37c7586bd1fc3597a357b4903a3cf406 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2020 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 java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.filebuffers.IDocumentSetupParticipant;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.spelling.SpellingProblem;
import org.eclipse.statet.ecommons.text.IIndentSettings;
import org.eclipse.statet.ecommons.text.core.sections.DocContentSections;
import org.eclipse.statet.ecommons.text.ui.presentation.ITextPresentationConstants;
import org.eclipse.statet.ecommons.text.ui.settings.DecorationPreferences;
import org.eclipse.statet.ecommons.ui.ISettingsChangedHandler;
/**
* Controls the configuration of an {@link ISourceEditor}.
*/
public abstract class SourceEditorViewerConfigurator implements ISettingsChangedHandler,
PropertyChangeListener {
private final SourceEditorViewerConfiguration configuration;
private ISourceEditor sourceEditor;
private final List<ISourceEditorAddon> addons= new ArrayList<>();
private List<ISourceEditorAddon> configurationAddons;
protected boolean isConfigured;
protected boolean updateCompleteConfig;
protected boolean updateTextPresentation;
protected boolean updateTabSize;
protected boolean updateIndent;
protected boolean updateInfoHovers;
protected SourceEditorViewerConfigurator(final SourceEditorViewerConfiguration config) {
if (config == null) {
throw new NullPointerException("config");
}
this.configuration= config;
}
/**
* A setup participant for the document of the editor.
*
* @return a document setup participant or <code>null</code>.
*/
public abstract IDocumentSetupParticipant getDocumentSetupParticipant();
public final DocContentSections getDocumentContentInfo() {
return this.configuration.getDocumentContentInfo();
}
protected abstract Set<String> getResetGroupIds();
public SourceEditorViewerConfiguration getSourceViewerConfiguration() {
return this.configuration;
}
public void configureSourceViewerDecorationSupport(final SourceViewerDecorationSupport support) {
final ICharacterPairMatcher pairMatcher= this.configuration.getPairMatcher();
final DecorationPreferences preferences= this.configuration.getDecorationPreferences();
if (pairMatcher != null && preferences != null) {
support.setCharacterPairMatcher(pairMatcher);
support.setMatchingCharacterPainterPreferenceKeys(
preferences.getMatchingBracketsEnabled().getKey(),
preferences.getMatchingBracketsColor().getKey() );
}
}
public void setTarget(final ISourceEditor sourceEditor) {
assert (sourceEditor != null);
this.sourceEditor= sourceEditor;
if (!(this.sourceEditor instanceof AbstractDecoratedTextEditor)) {
this.sourceEditor.getViewer().getControl().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(final DisposeEvent e) {
if (SourceEditorViewerConfigurator.this.isConfigured) {
uninstallCurrentAddons();
}
}
});
configureTarget();
}
else {
this.isConfigured= true;
installCurrentAddons();
}
handleSettingsChanged(null, null);
}
protected ISourceViewer getSourceViewer() {
if (this.sourceEditor != null) {
return this.sourceEditor.getViewer();
}
return null;
}
public final void unconfigureTarget() {
if (this.sourceEditor != null) {
this.isConfigured= false;
uninstallCurrentAddons();
this.sourceEditor.getViewer().unconfigure();
}
}
public final void configureTarget() {
if (this.sourceEditor != null) {
this.isConfigured= true;
this.sourceEditor.getViewer().configure(this.configuration);
installCurrentAddons();
}
}
private void installCurrentAddons() {
this.configurationAddons= getSourceViewerConfiguration().getAddOns();
for (final ISourceEditorAddon addon : this.configurationAddons) {
addon.install(this.sourceEditor);
}
for (final ISourceEditorAddon addon : this.addons) {
addon.install(this.sourceEditor);
}
}
private void uninstallCurrentAddons() {
for (final ISourceEditorAddon addon : this.addons) {
addon.uninstall();
}
if (this.configurationAddons != null) {
for (final ISourceEditorAddon addon : this.configurationAddons) {
addon.uninstall();
}
this.configurationAddons= null;
}
}
public final void installAddon(final ISourceEditorAddon installable) {
this.addons.add(installable);
if (this.isConfigured) {
installable.install(this.sourceEditor);
}
}
@Override
public void propertyChange(final PropertyChangeEvent event) {
final String name= event.getPropertyName();
if (name.equals(IIndentSettings.TAB_SIZE_PROP)) {
this.updateTabSize= true;
this.updateIndent= true;
return;
}
if (name.equals(IIndentSettings.INDENT_SPACES_COUNT_PROP)
|| name.equals(IIndentSettings.INDENT_DEFAULT_TYPE_PROP)) {
this.updateIndent= true;
return;
}
}
@Override
public void handleSettingsChanged(Set<String> groupIds, Map<String, Object> options) {
final SourceViewer viewer;
if (this.sourceEditor == null || (viewer= this.sourceEditor.getViewer()) == null) {
return;
}
final Point selectedRange= viewer.getSelectedRange();
if (groupIds == null) {
groupIds= getResetGroupIds();
}
if (options == null) {
options= new HashMap<>();
}
options.put(ISettingsChangedHandler.VIEWER_KEY, viewer);
checkSettingsChanges(groupIds, options);
if (options.containsKey(ITextPresentationConstants.SETTINGSCHANGE_AFFECTSPRESENTATION_KEY)) {
this.updateTextPresentation= true;
}
updateSourceViewer(viewer);
viewer.setSelectedRange(selectedRange.x, selectedRange.y);
}
protected void checkSettingsChanges(final Set<String> groupIds, final Map<String, Object> options) {
this.configuration.handleSettingsChanged(groupIds, options);
}
protected void updateSourceViewer(final ISourceViewer viewer) {
if (!this.isConfigured) {
return;
}
if (this.updateCompleteConfig) {
if (this.sourceEditor instanceof ITextEditor) {
SpellingProblem.removeAllInActiveEditor((ITextEditor) this.sourceEditor, null);
}
reconfigureSourceViewer();
}
else {
if (this.updateTabSize) {
viewer.getTextWidget().setTabs(getSourceViewerConfiguration().getTabWidth(viewer));
}
if (this.updateTextPresentation) {
viewer.invalidateTextPresentation();
}
if (this.updateIndent && this.sourceEditor instanceof SourceEditor1) {
((SourceEditor1) this.sourceEditor).updateIndentSettings();
}
if (this.updateInfoHovers) {
updateConfiguredInfoHovers();
}
}
this.updateCompleteConfig= false;
this.updateTextPresentation= false;
this.updateTabSize= false;
this.updateIndent= false;
}
private final void reconfigureSourceViewer() {
if (this.isConfigured) {
this.isConfigured= false;
this.sourceEditor.getViewer().unconfigure();
this.isConfigured= true;
this.sourceEditor.getViewer().configure(this.configuration);
}
}
private void updateConfiguredInfoHovers() {
final SourceViewer viewer= this.sourceEditor.getViewer();
final String[] contentTypes= this.configuration.getConfiguredContentTypes(viewer);
for (final String contentType : contentTypes) {
((ITextViewerExtension2)viewer).removeTextHovers(contentType);
final int[] stateMasks= this.configuration.getConfiguredTextHoverStateMasks(viewer, contentType);
if (stateMasks != null) {
for (int j= 0; j < stateMasks.length; j++) {
final int stateMask= stateMasks[j];
final ITextHover textHover= this.configuration.getTextHover(viewer, contentType, stateMask);
if (textHover != null) {
viewer.setTextHover(textHover, contentType, stateMask);
}
}
}
else {
final ITextHover textHover= this.configuration.getTextHover(viewer, contentType);
if (textHover != null) {
viewer.setTextHover(textHover, contentType, ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK);
}
}
}
}
}