blob: 23c41bf5febec7973398c1e423c30aa353876809 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 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 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.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
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 SourceEditor}.
*/
@NonNullByDefault
public abstract class SourceEditorViewerConfigurator implements ISettingsChangedHandler,
PropertyChangeListener {
private final SourceEditorViewerConfiguration configuration;
private @Nullable SourceEditor sourceEditor;
private final List<SourceEditorAddon> addons= new ArrayList<>();
private @Nullable List<SourceEditorAddon> configurationAddons;
protected boolean isConfigured;
protected boolean updateCompleteConfig;
protected boolean updateTextPresentation;
protected boolean updateTabSize;
protected boolean updateIndent;
protected boolean updateInfoHovers;
protected SourceEditorViewerConfigurator(final SourceEditorViewerConfiguration config) {
this.configuration= nonNullAssert(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 SourceEditor sourceEditor) {
this.sourceEditor= nonNullAssert(sourceEditor);
if (!(sourceEditor instanceof AbstractDecoratedTextEditor)) {
sourceEditor.getViewer().getControl().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(final DisposeEvent e) {
if (SourceEditorViewerConfigurator.this.isConfigured) {
uninstallCurrentAddons();
}
}
});
configureTarget();
}
else {
this.isConfigured= true;
installCurrentAddons(sourceEditor);
}
handleSettingsChanged(null, null);
}
protected @Nullable ISourceViewer getSourceViewer() {
final var sourceEditor= this.sourceEditor;
if (sourceEditor != null) {
return sourceEditor.getViewer();
}
return null;
}
public final void unconfigureTarget() {
final var sourceEditor= this.sourceEditor;
if (sourceEditor != null) {
this.isConfigured= false;
uninstallCurrentAddons();
sourceEditor.getViewer().unconfigure();
}
}
public final void configureTarget() {
final var sourceEditor= this.sourceEditor;
if (sourceEditor != null) {
this.isConfigured= true;
sourceEditor.getViewer().configure(this.configuration);
installCurrentAddons(sourceEditor);
}
}
private void installCurrentAddons(final SourceEditor sourceEditor) {
final var configurationAddons= getSourceViewerConfiguration().getAddOns();
this.configurationAddons= configurationAddons;
for (final SourceEditorAddon addon : configurationAddons) {
addon.install(sourceEditor);
}
for (final SourceEditorAddon addon : this.addons) {
addon.install(sourceEditor);
}
}
private void uninstallCurrentAddons() {
for (final SourceEditorAddon addon : this.addons) {
addon.uninstall();
}
{ final var configurationAddons= this.configurationAddons;
if (configurationAddons != null) {
this.configurationAddons= null;
for (final SourceEditorAddon addon : configurationAddons) {
addon.uninstall();
}
}
}
}
public final void installAddon(final SourceEditorAddon installable) {
this.addons.add(installable);
if (this.isConfigured) {
final var sourceEditor= nonNullAssert(this.sourceEditor);
installable.install(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(@Nullable Set<String> groupIds,
@Nullable Map<String, Object> options) {
final var sourceEditor= this.sourceEditor;
final SourceViewer viewer;
if (sourceEditor == null || (viewer= 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;
}
final var sourceEditor= nonNullAssert(this.sourceEditor);
if (this.updateCompleteConfig) {
if (sourceEditor instanceof ITextEditor) {
SpellingProblem.removeAllInActiveEditor((ITextEditor)sourceEditor, null);
}
reconfigureSourceViewer(sourceEditor.getViewer());
}
else {
if (this.updateTabSize) {
viewer.getTextWidget().setTabs(getSourceViewerConfiguration().getTabWidth(viewer));
}
if (this.updateTextPresentation) {
viewer.invalidateTextPresentation();
}
if (this.updateIndent && sourceEditor instanceof SourceEditor1) {
((SourceEditor1)sourceEditor).updateIndentSettings();
}
if (this.updateInfoHovers) {
updateConfiguredInfoHovers(sourceEditor.getViewer());
}
}
this.updateCompleteConfig= false;
this.updateTextPresentation= false;
this.updateTabSize= false;
this.updateIndent= false;
}
private final void reconfigureSourceViewer(final SourceViewer viewer) {
this.isConfigured= false;
viewer.unconfigure();
this.isConfigured= true;
viewer.configure(this.configuration);
}
private void updateConfiguredInfoHovers(final SourceViewer viewer) {
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);
}
}
}
}
}