| /*=============================================================================# |
| # 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); |
| } |
| } |
| } |
| } |
| |
| } |