blob: 072e7af014116f074cc477ea3f96ae0867a6ab5e [file] [log] [blame]
/*=============================================================================#
# 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);
}
}
}