blob: 7930fd3ca5a613689f11eb0becb71d8e29d78591 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 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.text.r;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextDoubleClickStrategy;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.text.core.BasicTextRegion;
import org.eclipse.statet.jcommons.text.core.TextRegion;
import org.eclipse.statet.jcommons.text.core.input.StringParserInput;
import org.eclipse.statet.ecommons.text.ICharPairMatcher;
import org.eclipse.statet.ecommons.text.core.JFaceTextRegion;
import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartition;
import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNode;
import org.eclipse.statet.r.core.rsource.RLexer;
import org.eclipse.statet.r.core.source.RDocumentConstants;
import org.eclipse.statet.r.core.source.RHeuristicTokenScanner;
/**
* Double click strategy aware of R identifier syntax rules, Comments and String (all in one).
* <p>
* Select content inside matching brackets or, if matching pairs found,
* a single identifier.
*/
@NonNullByDefault
public class RDoubleClickStrategy implements ITextDoubleClickStrategy {
private final String partitioning;
private final ICharPairMatcher pairMatcher;
private final RHeuristicTokenScanner scanner;
public RDoubleClickStrategy(final RHeuristicTokenScanner scanner) {
this.scanner= scanner;
this.partitioning= scanner.getDocumentPartitioning();
this.pairMatcher= new RBracketPairMatcher(this.scanner);
}
public RDoubleClickStrategy(final RHeuristicTokenScanner scanner, final ICharPairMatcher pairMatcher) {
this.scanner= scanner;
this.partitioning= scanner.getDocumentPartitioning();
this.pairMatcher= pairMatcher;
}
private @Nullable TreePartition getPartition(final IDocument document, final int offset) throws BadLocationException {
final ITypedRegion partition= TextUtilities.getPartition(document, this.partitioning, offset, false);
if (partition instanceof TreePartition) {
return (TreePartition)partition;
}
return null;
}
@Override
public void doubleClicked(final ITextViewer textViewer) {
final IDocument document= textViewer.getDocument();
final int offset= textViewer.getSelectedRange().x;
if (document == null || offset < 0) {
return;
}
try {
IRegion region= null;
{ TreePartition partition= getPartition(document, offset);
if (partition != null) {
if (partition.getType() == RDocumentConstants.R_DEFAULT_CONTENT_TYPE) {
final IRegion match= this.pairMatcher.match(document, offset);
if (match != null && match.getLength() >= 2) {
region= new Region(match.getOffset() + 1, match.getLength() - 2);
}
}
if (region == null) {
region= getOpenClose(document, partition, offset);
}
if (region == null && partition.getOffset() == offset && offset > 0) {
partition= getPartition(document, offset - 1);
if (partition != null) {
region= getOpenClose(document, partition, offset);
}
}
}
}
if (region == null) {
this.scanner.configure(document);
region= this.scanner.findRWord(offset, true, false);
}
if (region == null) {
region= this.scanner.findBlankRegion(offset, false);
}
if (region != null) {
textViewer.setSelectedRange(region.getOffset(), region.getLength());
return;
}
} catch (final BadLocationException e) {
} catch (final NullPointerException e) {
}
// else
textViewer.setSelectedRange(offset, 0);
}
private final @Nullable IRegion getOpenClose(final IDocument document,
final TreePartition partition, final int offset) throws BadLocationException {
// Start or End in String-Partitions
if (partition.getType() == RDocumentConstants.R_STRING_CONTENT_TYPE
|| partition.getType() == RDocumentConstants.R_QUOTED_SYMBOL_CONTENT_TYPE) {
final TextRegion textRegion= getTextRegion(document, partition);
if (textRegion == null) {
return null;
}
if ((offset >= partition.getStartOffset() && offset <= textRegion.getStartOffset())
|| offset <= partition.getEndOffset() && offset >= textRegion.getEndOffset()) {
return JFaceTextRegion.toJFaceRegion(textRegion);
}
this.scanner.configure(document);
final IRegion region= this.scanner.findCommonWord(offset);
if (region != null) {
return region;
}
else {
return new Region(offset, 0);
}
}
else if (partition.getType() == RDocumentConstants.R_INFIX_OPERATOR_CONTENT_TYPE) {
return partition;
}
else if (partition.getType() == RDocumentConstants.R_COMMENT_CONTENT_TYPE
|| partition.getType() == RDocumentConstants.R_ROXYGEN_CONTENT_TYPE ) {
if (offset == partition.getStartOffset()
|| offset == partition.getStartOffset() + 1) {
return partition;
}
}
return null;
}
private final @Nullable TextRegion getTextRegion(final IDocument document, final TreePartition partition)
throws BadLocationException {
final RLexer rLexer= new RLexer(RLexer.ENABLE_QUICK_CHECK);
final int partitionStart= partition.getOffset();
final IRegion lineInformation= document.getLineInformationOfOffset(partitionStart);
final int parseEnd= Math.min(partitionStart + partition.getLength(),
lineInformation.getOffset() + lineInformation.getLength() );
rLexer.reset(new StringParserInput(document.get(partitionStart, parseEnd - partitionStart))
.init() );
final TextRegion textRegion;
switch (rLexer.next()) {
case STRING_D:
case STRING_S:
case STRING_R:
case SYMBOL_G:
textRegion= rLexer.getTextRegion();
if (textRegion == null) {
return null;
}
if ((partition.getTreeNode().getFlags() & TreePartitionNode.END_UNCLOSED) != 0) {
return new BasicTextRegion(partitionStart + textRegion.getStartOffset(),
partition.getOffset() + partition.getLength() );
}
switch (rLexer.getType()) {
case STRING_D:
case STRING_S:
case SYMBOL_G:
return new BasicTextRegion(partitionStart + textRegion.getStartOffset(),
partitionStart + partition.getLength() - 1 );
case STRING_R:
return new BasicTextRegion(partitionStart + textRegion.getStartOffset(),
partitionStart + partition.getLength() - textRegion.getStartOffset() + 1 );
default:
throw new AssertionError();
}
default:
return null;
}
}
}