| /*=============================================================================# |
| # Copyright (c) 2007, 2018 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.docmlet.tex.ui.editors; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.jface.text.AbstractDocument; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IRegion; |
| |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| |
| import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess; |
| import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils; |
| |
| import org.eclipse.statet.docmlet.tex.core.ast.Embedded; |
| import org.eclipse.statet.docmlet.tex.core.ast.TexAstVisitor; |
| import org.eclipse.statet.docmlet.tex.core.model.ITexSourceElement; |
| import org.eclipse.statet.docmlet.tex.core.model.TexModel; |
| import org.eclipse.statet.docmlet.tex.ui.sourceediting.TexEditingSettings; |
| import org.eclipse.statet.ltk.ast.core.AstNode; |
| import org.eclipse.statet.ltk.ast.core.AstVisitor; |
| import org.eclipse.statet.ltk.model.core.elements.IModelElement; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceStructElement; |
| import org.eclipse.statet.ltk.ui.sourceediting.folding.FoldingAnnotation; |
| import org.eclipse.statet.ltk.ui.sourceediting.folding.FoldingEditorAddon.FoldingStructureComputationContext; |
| import org.eclipse.statet.ltk.ui.sourceediting.folding.FoldingProvider; |
| import org.eclipse.statet.ltk.ui.sourceediting.folding.NodeFoldingProvider; |
| import org.eclipse.statet.ltk.ui.sourceediting.folding.SimpleFoldingPosition; |
| |
| |
| /** |
| * Code folding provider for Tex documents. |
| */ |
| public class LtxDefaultFoldingProvider implements FoldingProvider { |
| |
| |
| private static final String TYPE_SECTION= "org.eclipse.statet.docmlet.tex.Section"; //$NON-NLS-1$ |
| private static final String TYPE_EMBEDDED= "org.eclipse.statet.docmlet.tex.Embedded"; //$NON-NLS-1$ |
| |
| |
| private static class ElementFinder extends TexAstVisitor { |
| |
| private final FoldingStructureComputationContext context; |
| private final FoldingConfiguration config; |
| |
| private final NodeFoldingProvider.VisitorMap embeddedVisitors; |
| |
| |
| public ElementFinder(final FoldingStructureComputationContext ctx, |
| final FoldingConfiguration config, |
| final Map<String, ? extends NodeFoldingProvider> embeddedProviders) { |
| this.context= ctx; |
| this.config= config; |
| this.embeddedVisitors= (!embeddedProviders.isEmpty()) ? |
| new NodeFoldingProvider.VisitorMap(embeddedProviders) : |
| null; |
| } |
| |
| |
| public void visit(final ISourceStructElement element) throws InvocationTargetException { |
| if (element.getModelTypeId() == TexModel.LTX_TYPE_ID) { |
| if ((element.getElementType() & IModelElement.MASK_C1) == IModelElement.C1_EMBEDDED) { |
| final TextRegion region= element.getSourceRange(); |
| createEmbeddedRegion(region.getStartOffset(), region.getEndOffset()); |
| |
| if (this.embeddedVisitors != null) { |
| final AstNode node= element.getAdapter(AstNode.class); |
| if (node instanceof Embedded) { |
| final Embedded embedded= (Embedded) node; |
| final AstVisitor visitor= this.embeddedVisitors.get(embedded.getForeignTypeId()); |
| if (visitor != null) { |
| visitor.visit(embedded.getForeignNode()); |
| } |
| } |
| } |
| return; |
| } |
| else if ((element.getElementType() & IModelElement.MASK_C2) == ITexSourceElement.C2_SECTIONING) { |
| final TextRegion region= element.getSourceRange(); |
| createSectionRegion(region.getStartOffset(), region.getEndOffset()); |
| } |
| } |
| |
| final List<? extends ISourceStructElement> children= element.getSourceChildren(null); |
| for (final ISourceStructElement child : children) { |
| visit(child); |
| } |
| } |
| |
| private void createSectionRegion(final int startOffset, final int endOffset) |
| throws InvocationTargetException { |
| try { |
| final AbstractDocument doc= this.context.document; |
| final int startLine= doc.getLineOfOffset(startOffset); |
| int stopLine= doc.getLineOfOffset(endOffset); |
| final IRegion stopLineInfo= doc.getLineInformation(stopLine); |
| if (stopLineInfo.getOffset() + stopLineInfo.getLength() > endOffset) { |
| stopLine--; |
| } |
| if (stopLine - startLine + 1 >= this.config.minLines) { |
| final int offset= doc.getLineOffset(startLine); |
| this.context.addFoldingRegion(new FoldingAnnotation(TYPE_SECTION, false, |
| new SimpleFoldingPosition(offset, doc.getLineOffset(stopLine) + doc.getLineLength(stopLine) - offset) )); |
| } |
| } |
| catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| private void createEmbeddedRegion(final int startOffset, final int endOffset) |
| throws InvocationTargetException { |
| try { |
| final AbstractDocument doc= this.context.document; |
| final int startLine= doc.getLineOfOffset(startOffset); |
| int stopLine= doc.getLineOfOffset(endOffset); |
| final IRegion stopLineInfo= doc.getLineInformation(stopLine); |
| if (stopLineInfo.getOffset() >= endOffset) { |
| stopLine--; |
| } |
| if (stopLine - startLine + 1 >= this.config.minLines) { |
| final int offset= doc.getLineOffset(startLine); |
| this.context.addFoldingRegion(new FoldingAnnotation(TYPE_EMBEDDED, false, |
| new SimpleFoldingPosition(offset, doc.getLineOffset(stopLine) + doc.getLineLength(stopLine) - offset) )); |
| } |
| } |
| catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| } |
| |
| protected static final class FoldingConfiguration { |
| |
| public int minLines; |
| |
| public boolean isRestoreStateEnabled; |
| |
| } |
| |
| |
| private FoldingConfiguration config; |
| |
| private final Map<String, ? extends NodeFoldingProvider> embeddedProviders; |
| |
| |
| /** |
| * Creates new provider. |
| */ |
| public LtxDefaultFoldingProvider() { |
| this.embeddedProviders= Collections.emptyMap(); |
| } |
| |
| /** |
| * Creates new provider with support for additional code folding in embedded elements. |
| * |
| * @param embeddedProvider provider for embedded elements |
| */ |
| public LtxDefaultFoldingProvider(final Map<String, ? extends NodeFoldingProvider> embeddedProviders) { |
| this.embeddedProviders= embeddedProviders; |
| } |
| |
| |
| @Override |
| public boolean checkConfig(final Set<String> groupIds) { |
| boolean changed= false; |
| if (groupIds == null |
| || groupIds.contains(TexEditingSettings.FOLDING_SHARED_GROUP_ID) ) { |
| final FoldingConfiguration config= new FoldingConfiguration(); |
| final PreferenceAccess prefs= PreferenceUtils.getInstancePrefs(); |
| |
| config.isRestoreStateEnabled= prefs.getPreferenceValue( |
| TexEditingSettings.FOLDING_RESTORE_STATE_ENABLED_PREF ); |
| config.minLines= 2; |
| |
| this.config= config; |
| changed |= true; |
| } |
| for (final NodeFoldingProvider provider : this.embeddedProviders.values()) { |
| changed |= provider.checkConfig(groupIds); |
| } |
| return changed; |
| } |
| |
| @Override |
| public boolean isRestoreStateEnabled() { |
| return this.config.isRestoreStateEnabled; |
| } |
| |
| @Override |
| public boolean requiresModel() { |
| return true; |
| } |
| |
| @Override |
| public void collectRegions(final FoldingStructureComputationContext ctx) throws InvocationTargetException { |
| try { |
| final ElementFinder elementFinder= new ElementFinder(ctx, this.config, |
| this.embeddedProviders ); |
| elementFinder.visit(ctx.model.getSourceElement()); |
| } |
| catch (final RuntimeException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| } |