/*=============================================================================#
# Copyright (c) 2007-2016 Stephan Wahlbrink (WalWare.de) and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.docmlet.wikitext.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 de.walware.ecommons.ltk.ast.IAstNode;
import de.walware.ecommons.ltk.ast.ICommonAstVisitor;
import de.walware.ecommons.ltk.core.model.ISourceElement;
import de.walware.ecommons.ltk.core.model.ISourceStructElement;
import de.walware.ecommons.ltk.ui.sourceediting.folding.FoldingAnnotation;
import de.walware.ecommons.ltk.ui.sourceediting.folding.FoldingEditorAddon.FoldingStructureComputationContext;
import de.walware.ecommons.ltk.ui.sourceediting.folding.FoldingProvider;
import de.walware.ecommons.ltk.ui.sourceediting.folding.NodeFoldingProvider;
import de.walware.ecommons.ltk.ui.sourceediting.folding.SimpleFoldingPosition;
import de.walware.ecommons.preferences.PreferencesUtil;
import de.walware.ecommons.preferences.core.IPreferenceAccess;
import de.walware.docmlet.wikitext.core.ast.Embedded;
import de.walware.docmlet.wikitext.core.ast.WikitextAstVisitor;
import de.walware.docmlet.wikitext.core.model.IWikitextSourceElement;
import de.walware.docmlet.wikitext.core.model.WikitextModel;
import de.walware.docmlet.wikitext.ui.sourceediting.WikitextEditingSettings;
/**
* Code folding provider for Tex documents.
*/
public class WikidocDefaultFoldingProvider implements FoldingProvider {
private static final String TYPE_SECTION= "de.walware.docmlet.wikitext.Section"; //$NON-NLS-1$
private static final String TYPE_EMBEDDED= "de.walware.docmlet.wikitext.Embedded"; //$NON-NLS-1$
private static class ElementFinder extends WikitextAstVisitor {
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() == WikitextModel.WIKIDOC_TYPE_ID) {
if ((element.getElementType() & ISourceElement.MASK_C1) == IWikitextSourceElement.C1_EMBEDDED) {
final IRegion region= element.getSourceRange();
createEmbeddedRegion(region.getOffset(), region.getOffset()+region.getLength());
if (this.embeddedVisitors != null) {
final IAstNode node= (IAstNode) element.getAdapter(IAstNode.class);
if (node instanceof Embedded) {
final Embedded embedded= (Embedded) node;
final ICommonAstVisitor visitor= this.embeddedVisitors.get(embedded.getForeignTypeId());
if (visitor != null) {
visitor.visit(embedded.getForeignNode());
}
}
}
return;
}
else if ((element.getElementType() & ISourceElement.MASK_C2) == IWikitextSourceElement.C2_SECTIONING) {
final IRegion region= element.getSourceRange();
createSectionRegion(region.getOffset(), region.getOffset()+region.getLength());
}
}
final List<? extends ISourceStructElement> children= element.getSourceChildren(null);
for (final ISourceStructElement child : children) {
visit(child);
}
}
private void createSectionRegion(final int startOffset, final int stopOffset)
throws InvocationTargetException {
try {
final AbstractDocument doc= this.context.document;
final int startLine= doc.getLineOfOffset(startOffset);
int stopLine= doc.getLineOfOffset(stopOffset);
final IRegion stopLineInfo= doc.getLineInformation(stopLine);
if (stopLineInfo.getOffset() + stopLineInfo.getLength() > stopOffset) {
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 stopOffset)
throws InvocationTargetException {
try {
final AbstractDocument doc= this.context.document;
final int startLine= doc.getLineOfOffset(startOffset);
int stopLine= doc.getLineOfOffset(stopOffset);
final IRegion stopLineInfo= doc.getLineInformation(stopLine);
if (stopLineInfo.getOffset() >= stopOffset) {
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 WikidocDefaultFoldingProvider() {
this.embeddedProviders= Collections.emptyMap();
}
/**
* Creates new provider with support for additional code folding in embedded elements.
*
* @param embeddedProviders provider for embedded elements
*/
public WikidocDefaultFoldingProvider(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(WikitextEditingSettings.FOLDING_SHARED_GROUP_ID) ) {
final FoldingConfiguration config= new FoldingConfiguration();
final IPreferenceAccess prefs= PreferencesUtil.getInstancePrefs();
config.isRestoreStateEnabled= prefs.getPreferenceValue(
WikitextEditingSettings.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);
}
}
}