blob: b6f389bee6d58e15b440db8b1118d5b428a4461a [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.r.ui.editors;
import java.lang.reflect.InvocationTargetException;
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.ecommons.preferences.core.PreferenceAccess;
import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils;
import org.eclipse.statet.internal.r.ui.editors.DefaultRFoldingPreferences;
import org.eclipse.statet.ltk.ast.core.AstNode;
import org.eclipse.statet.ltk.ast.core.AstVisitor;
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;
import org.eclipse.statet.r.core.rsource.ast.Block;
import org.eclipse.statet.r.core.rsource.ast.CForLoop;
import org.eclipse.statet.r.core.rsource.ast.CIfElse;
import org.eclipse.statet.r.core.rsource.ast.CRepeatLoop;
import org.eclipse.statet.r.core.rsource.ast.CWhileLoop;
import org.eclipse.statet.r.core.rsource.ast.DocuComment;
import org.eclipse.statet.r.core.rsource.ast.FDef;
import org.eclipse.statet.r.core.rsource.ast.GenericVisitor;
import org.eclipse.statet.r.core.rsource.ast.NodeType;
import org.eclipse.statet.r.core.rsource.ast.RAstNode;
import org.eclipse.statet.r.core.rsource.ast.SourceComponent;
/**
* Provides code folding for R Scripts
*/
public class RDefaultFoldingProvider implements FoldingProvider, NodeFoldingProvider {
public static final String TYPE_RCODE= "org.eclipse.statet.r.folding.RCode"; //$NON-NLS-1$
public static final String TYPE_ROXYGEN= "org.eclipse.statet.r.folding.Roxygen"; //$NON-NLS-1$
private static class ElementFinder extends GenericVisitor implements AstVisitor {
private final FoldingStructureComputationContext context;
private final FoldingConfiguration config;
public ElementFinder(final FoldingStructureComputationContext ctx, final FoldingConfiguration config) {
this.context= ctx;
this.config= config;
}
private void createRCodeRegion(final int startOffset, final int endOffset) throws InvocationTargetException {
if (startOffset == Integer.MIN_VALUE || endOffset == Integer.MIN_VALUE) {
return;
}
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_RCODE, false,
new SimpleFoldingPosition(offset, doc.getLineOffset(stopLine) + doc.getLineLength(stopLine) - offset) ));
}
}
catch (final BadLocationException e) {
throw new InvocationTargetException(e);
}
}
private void createRCodeContentRegion(final FDef node,
final int startOffset, final int endOffset) throws InvocationTargetException {
if (startOffset == Integer.MIN_VALUE || endOffset == Integer.MIN_VALUE) {
return;
}
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) >= 0) {
final int offset= doc.getLineOffset(startLine);
this.context.addFoldingRegion(new FoldingAnnotation(TYPE_RCODE, false,
new SimpleFoldingPosition(offset, doc.getLineOffset(stopLine) + doc.getLineLength(stopLine) - offset) ));
}
}
catch (final BadLocationException e) {
throw new InvocationTargetException(e);
}
}
private void createRoxygenRegion(final int startOffset, final int endOffset) throws InvocationTargetException {
if (startOffset == Integer.MIN_VALUE || endOffset == Integer.MIN_VALUE) {
return;
}
try {
final AbstractDocument doc= this.context.document;
final int startLine= doc.getLineOfOffset(startOffset);
final int stopLine= doc.getLineOfOffset(endOffset);
if (stopLine - startLine + 1 >= this.config.minRoxygenLines) {
final int offset= doc.getLineOffset(startLine);
this.context.addFoldingRegion(new FoldingAnnotation(TYPE_ROXYGEN,
this.context.isInitial && this.config.collapseInitiallyRoxygen,
new SimpleFoldingPosition(offset, doc.getLineOffset(stopLine) + doc.getLineLength(stopLine) - offset) ));
}
}
catch (final BadLocationException e) {
throw new InvocationTargetException(e);
}
}
@Override
public void visit(final AstNode node) throws InvocationTargetException {
if (node instanceof RAstNode) {
((RAstNode) node).acceptInR(this);
}
else {
final int count= node.getChildCount();
for (int i= 0; i < count; i++) {
final AstNode child= node.getChild(i);
if (child instanceof SourceComponent) {
visit((SourceComponent) child);
}
}
}
}
@Override
public void visit(final SourceComponent node) throws InvocationTargetException {
node.acceptInRChildren(this);
node.acceptInRComments(this);
}
@Override
public void visit(final Block node) throws InvocationTargetException {
if (this.config.enableOtherBlocks) {
createRCodeRegion(node.getStartOffset(), node.getEndOffset());
}
node.acceptInRChildren(this);
}
@Override
public void visit(final FDef node) throws InvocationTargetException {
node.getArgsChild().acceptInR(this);
{
final RAstNode body= node.getContChild();
if (body.getNodeType() == NodeType.BLOCK
&& node.getArgsCloseOffset() != Integer.MIN_VALUE) {
createRCodeContentRegion(node, node.getArgsCloseOffset(), node.getEndOffset());
body.acceptInRChildren(this);
}
else {
body.acceptInR(this);
}
}
}
@Override
public void visit(final CIfElse node) throws InvocationTargetException {
if (this.config.enableOtherBlocks) {
node.getCondChild().acceptInR(this);
{
final RAstNode body= node.getThenChild();
if (body.getNodeType() == NodeType.BLOCK) {
createRCodeRegion(node.getCondCloseOffset(), body.getEndOffset());
body.acceptInRChildren(this);
}
else {
body.acceptInR(this);
}
}
if (node.hasElse()) {
final RAstNode body= node.getElseChild();
if (body.getNodeType() == NodeType.BLOCK) {
createRCodeRegion(node.getElseOffset(), body.getEndOffset());
body.acceptInRChildren(this);
}
else {
body.acceptInR(this);
}
}
}
else {
node.acceptInRChildren(this);
}
}
@Override
public void visit(final CForLoop node) throws InvocationTargetException {
if (this.config.enableOtherBlocks) {
node.getCondChild().acceptInR(this);
{
final RAstNode body= node.getContChild();
if (body.getNodeType() == NodeType.BLOCK) {
createRCodeRegion(node.getCondCloseOffset(), body.getEndOffset());
body.acceptInRChildren(this);
}
else {
body.acceptInR(this);
}
}
}
else {
node.acceptInRChildren(this);
}
}
@Override
public void visit(final CWhileLoop node) throws InvocationTargetException {
if (this.config.enableOtherBlocks) {
node.getCondChild().acceptInR(this);
{
final RAstNode body= node.getContChild();
if (body.getNodeType() == NodeType.BLOCK) {
createRCodeRegion(node.getCondCloseOffset(), body.getEndOffset());
body.acceptInRChildren(this);
}
else {
body.acceptInR(this);
}
}
}
else {
node.acceptInRChildren(this);
}
}
@Override
public void visit(final CRepeatLoop node) throws InvocationTargetException {
if (this.config.enableOtherBlocks) {
{
final RAstNode body= node.getContChild();
if (body.getNodeType() == NodeType.BLOCK) {
createRCodeRegion(node.getStartOffset(), body.getEndOffset());
body.acceptInRChildren(this);
}
else {
body.acceptInR(this);
}
}
}
else {
node.acceptInRChildren(this);
}
}
@Override
public void visit(final DocuComment node) throws InvocationTargetException {
if (this.config.enableRoxygen) {
createRoxygenRegion(node.getStartOffset(), node.getEndOffset());
}
}
}
protected static final class FoldingConfiguration {
public boolean enableOtherBlocks;
public int minLines;
public boolean enableRoxygen;
public int minRoxygenLines;
public boolean collapseInitiallyRoxygen;
public boolean isRestoreStateEnabled;
}
private FoldingConfiguration fConfig;
public RDefaultFoldingProvider() {
}
@Override
public boolean checkConfig(final Set<String> groupIds) {
if (groupIds == null || groupIds.contains(DefaultRFoldingPreferences.GROUP_ID)
|| groupIds.contains(REditorOptions.FOLDING_SHARED_GROUP_ID) ) {
final FoldingConfiguration config= new FoldingConfiguration();
final PreferenceAccess prefs= PreferenceUtils.getInstancePrefs();
config.enableOtherBlocks= prefs.getPreferenceValue(
DefaultRFoldingPreferences.PREF_OTHERBLOCKS_ENABLED );
config.minLines= prefs.getPreferenceValue(
DefaultRFoldingPreferences.PREF_MINLINES_NUM );
if (config.minLines < 2) {
config.minLines= 2;
}
config.enableRoxygen= prefs.getPreferenceValue(
DefaultRFoldingPreferences.PREF_ROXYGEN_ENABLED );
config.minRoxygenLines= prefs.getPreferenceValue(
DefaultRFoldingPreferences.PREF_ROXYGEN_MINLINES_NUM );
if (config.minRoxygenLines < 2) {
config.minLines= 2;
}
config.collapseInitiallyRoxygen= prefs.getPreferenceValue(
DefaultRFoldingPreferences.PREF_ROXYGEN_COLLAPSE_INITIALLY_ENABLED );
config.isRestoreStateEnabled= prefs.getPreferenceValue(
REditorOptions.FOLDING_RESTORE_STATE_ENABLED_PREF );
this.fConfig= config;
return true;
}
return false;
}
@Override
public boolean isRestoreStateEnabled() {
return this.fConfig.isRestoreStateEnabled;
}
@Override
public boolean requiresModel() {
return false;
}
@Override
public void collectRegions(final FoldingStructureComputationContext ctx) throws InvocationTargetException {
if (ctx.ast.getRoot() instanceof RAstNode) {
((RAstNode) ctx.ast.getRoot()).acceptInR(new ElementFinder(ctx, this.fConfig));
}
}
@Override
public AstVisitor createVisitor(final FoldingStructureComputationContext ctx) {
return new ElementFinder(ctx, this.fConfig);
}
}