blob: 00b027f6d71db39a7aa322b43eb1d64f37f31be1 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 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.core.refactoring;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.osgi.util.NLS;
import org.eclipse.text.edits.TextEdit;
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.jcommons.text.core.input.TextParserInput;
import org.eclipse.statet.ecommons.text.IndentUtil;
import org.eclipse.statet.ecommons.text.core.PartitionConstraint;
import org.eclipse.statet.internal.r.core.refactoring.Messages;
import org.eclipse.statet.ltk.ast.core.AstInfo;
import org.eclipse.statet.ltk.ast.core.util.AstSelection;
import org.eclipse.statet.ltk.model.core.ModelManager;
import org.eclipse.statet.ltk.model.core.element.SourceUnit;
import org.eclipse.statet.ltk.model.core.element.SourceUnitModelInfo;
import org.eclipse.statet.ltk.refactoring.core.RefactoringAdapter;
import org.eclipse.statet.r.core.RCodeStyleSettings;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.RCoreAccess;
import org.eclipse.statet.r.core.model.RModel;
import org.eclipse.statet.r.core.model.RSourceUnit;
import org.eclipse.statet.r.core.rlang.RTerminal;
import org.eclipse.statet.r.core.rsource.RLexer;
import org.eclipse.statet.r.core.rsource.RSourceConstants;
import org.eclipse.statet.r.core.rsource.RSourceIndenter;
import org.eclipse.statet.r.core.rsource.ast.Assignment;
import org.eclipse.statet.r.core.rsource.ast.RAstNode;
import org.eclipse.statet.r.core.rsource.ast.RParser;
import org.eclipse.statet.r.core.rsource.ast.SourceComponent;
import org.eclipse.statet.r.core.source.RDocumentConstants;
import org.eclipse.statet.r.core.source.RHeuristicTokenScanner;
/**
* RefactoringAdapter for R
*/
public class RRefactoringAdapter extends RefactoringAdapter {
private RLexer lexer;
public RRefactoringAdapter() {
super(RModel.R_TYPE_ID);
}
@Override
public String getPluginIdentifier() {
return RCore.BUNDLE_ID;
}
@Override
public RHeuristicTokenScanner getScanner(final SourceUnit su) {
return RHeuristicTokenScanner.create(su.getDocumentContentInfo());
}
@Override
public boolean isCommentContent(final ITypedRegion partition) {
return (partition != null
&& partition.getType() == RDocumentConstants.R_COMMENT_CONTENT_TYPE );
}
public TextRegion trimToAstRegion(final AbstractDocument document, final TextRegion region,
final RHeuristicTokenScanner scanner) {
scanner.configure(document, RDocumentConstants.R_CODE_CONTENT_CONSTRAINT);
int start= region.getStartOffset();
int stop= region.getEndOffset();
int result;
while (stop > start) {
result= scanner.findNonBlankBackward(stop, start, true);
if (result >= 0) {
if (scanner.getChar() == ';') {
stop= result;
continue;
}
else {
stop= result + 1;
break;
}
}
else {
stop= start;
break;
}
}
while (start < stop) {
result= scanner.findNonBlankForward(start, stop, true);
if (result >= 0) {
if (scanner.getChar() == ';') {
start= result + 1;
continue;
}
else {
start= result;
break;
}
}
else {
start= stop;
break;
}
}
return new BasicTextRegion(start, stop);
}
public RAstNode searchPotentialNameNode(final SourceUnit su, TextRegion region,
final boolean allowAssignRegion, final IProgressMonitor monitor) {
final SubMonitor m= SubMonitor.convert(monitor, 5);
su.connect(m.newChild(1));
try {
final AbstractDocument document= su.getDocument(m.newChild(1));
final RHeuristicTokenScanner scanner= getScanner(su);
region= trimToAstRegion(document, region, scanner);
final SourceUnitModelInfo modelInfo= su.getModelInfo(RModel.R_TYPE_ID,
ModelManager.MODEL_FILE, m.newChild(1) );
if (modelInfo != null) {
final AstSelection astSelection= AstSelection.search(modelInfo.getAst().getRoot(),
region.getStartOffset(), region.getEndOffset(),
AstSelection.MODE_COVERING_SAME_LAST );
if (astSelection.getCovering() instanceof RAstNode) {
final RAstNode node= (RAstNode) astSelection.getCovering();
return getPotentialNameNode(node, allowAssignRegion);
}
}
return null;
}
finally {
m.setWorkRemaining(1);
su.disconnect(m.newChild(1));
}
}
public TextRegion expandSelectionRegion(final AbstractDocument document, final TextRegion region,
final TextRegion limit, final RHeuristicTokenScanner scanner) {
scanner.configure(document, new PartitionConstraint() {
@Override
public boolean matches(final String partitionType) {
return (partitionType != RDocumentConstants.R_COMMENT_CONTENT_TYPE);
}
});
final int min= limit.getStartOffset();
final int max= limit.getEndOffset();
int start= region.getStartOffset();
int end= region.getEndOffset();
int result;
while (start > min) {
result= scanner.findNonBlankBackward(start, min, true);
if (result >= 0) {
if (RDocumentConstants.R_DEFAULT_CONTENT_CONSTRAINT.matches(scanner.getPartition(result).getType())
&& scanner.getChar() == ';') {
start= result;
continue;
}
else {
start= result + 1;
break;
}
}
else {
start= min;
break;
}
}
while (end < max) {
result= scanner.findNonBlankForward(end, max, true);
if (result >= 0) {
if (RDocumentConstants.R_DEFAULT_CONTENT_CONSTRAINT.matches(scanner.getPartition(result).getType())
&& scanner.getChar() == ';') {
end= result + 1;
continue;
}
else {
end= result;
break;
}
}
else {
end= max;
break;
}
}
return new BasicTextRegion(start, end);
}
public String validateIdentifier(final String value, final String identifierMessageName) {
if (value == null || value.isEmpty()) {
return (identifierMessageName != null) ?
NLS.bind(Messages.RIdentifiers_error_EmptyFor_message, identifierMessageName, Messages.RIdentifiers_error_Empty_message) :
Messages.RIdentifiers_error_Empty_message;
}
if (this.lexer == null) {
this.lexer= new RLexer();
}
this.lexer.reset(new StringParserInput(value).init());
final RTerminal nextToken= this.lexer.next();
if (nextToken == RTerminal.EOF) {
return (identifierMessageName != null) ?
NLS.bind(Messages.RIdentifiers_error_EmptyFor_message, identifierMessageName, Messages.RIdentifiers_error_Empty_message) :
Messages.RIdentifiers_error_Empty_message;
}
if ((nextToken != RTerminal.SYMBOL && nextToken != RTerminal.SYMBOL_G)
|| ((this.lexer.getFlags() & RSourceConstants.STATUSFLAG_REAL_ERROR) != 0)
|| (this.lexer.next() != RTerminal.EOF)) {
return (identifierMessageName != null) ?
NLS.bind(Messages.RIdentifiers_error_InvalidFor_message, identifierMessageName, Messages.RIdentifiers_error_Empty_message) :
Messages.RIdentifiers_error_Invalid_message;
}
return null;
}
static RAstNode getPotentialNameNode(final RAstNode node, final boolean allowAssignRegion) {
switch (node.getNodeType()) {
case A_LEFT:
case A_RIGHT:
case A_EQUALS:
case A_COLON:
if (allowAssignRegion) {
final Assignment assignment= (Assignment) node;
if (assignment.isSearchOperator()) {
switch (assignment.getTargetChild().getNodeType()) {
case SYMBOL:
case STRING_CONST:
return assignment.getTargetChild();
default:
break;
}
}
}
return null;
case SYMBOL:
case STRING_CONST:
return node;
default:
return null;
}
}
public static String getQuotedIdentifier(final String identifier) {
int length;
if (identifier == null || (length= identifier.length()) == 0) {
return "";
}
if (identifier.charAt(0) == '`') {
if (length > 1 && identifier.charAt(length - 1) == '`') {
return identifier;
}
else {
return identifier + '`';
}
}
else {
return '`' + identifier + '`';
}
}
public static String getUnquotedIdentifier(final String identifier) {
int length;
if (identifier == null || (length= identifier.length()) == 0) {
return "";
}
if (identifier.charAt(0) == '`') {
if (length > 1 && identifier.charAt(length - 1) == '`') {
return identifier.substring(1, length - 2);
}
else {
return identifier.substring(1, length - 1);
}
}
else {
return identifier;
}
}
static String indent(final StringBuilder sb, final AbstractDocument orgDoc, final int offset,
final SourceUnit su, final RHeuristicTokenScanner scanner) throws BadLocationException, CoreException {
final RCoreAccess coreConfig= (su instanceof RSourceUnit) ? ((RSourceUnit)su).getRCoreAccess() : RCore.getWorkbenchAccess();
final IndentUtil indentUtil= new IndentUtil(orgDoc, coreConfig.getRCodeStyle());
final int column= indentUtil.getColumn(offset);
final String initial= indentUtil.createIndentString(column);
final String prefix= initial + "1\n"; //$NON-NLS-1$
sb.insert(0, prefix);
String text= sb.toString();
final Document doc= new Document(text);
final TextParserInput parseInput= new StringParserInput(text);
text= null;
final RParser rParser= new RParser(AstInfo.LEVEL_MINIMAL);
final SourceComponent rootNode= rParser.scanSourceUnit(parseInput.init());
final RSourceIndenter indenter= new RSourceIndenter(scanner, coreConfig);
final TextEdit edits= indenter.getIndentEdits(doc, rootNode, 0, 1, doc.getNumberOfLines() - 1);
edits.apply(doc, 0);
return doc.get(prefix.length(), doc.getLength() - prefix.length());
}
/**
* Prepare the insertion of a command (text) before another command (offset)
*
* The method prepares the insertion by modifying the text and returning the offset
* where to insert the modified text
*
* @param text the command to insert, will be modified
* @param orgDoc the document
* @param offset the offset where to insert the command
* @param su the source unit, if available
* @return the offset to insert the modified text
* @throws BadLocationException
* @throws CoreException
*/
static int prepareInsertBefore(final StringBuilder text, final AbstractDocument orgDoc, final int offset,
final SourceUnit su) throws BadLocationException, CoreException {
final RCoreAccess coreConfig= (su instanceof RSourceUnit) ? ((RSourceUnit)su).getRCoreAccess() : RCore.getWorkbenchAccess();
final IndentUtil indentUtil= new IndentUtil(orgDoc, coreConfig.getRCodeStyle());
final int line= orgDoc.getLineOfOffset(offset);
final int[] lineIndent= indentUtil.getLineIndent(line, false);
if (lineIndent[IndentUtil.OFFSET_IDX] == offset) { // first char/command in line
text.insert(0, indentUtil.createIndentString(lineIndent[IndentUtil.COLUMN_IDX]));
text.append(orgDoc.getDefaultLineDelimiter());
return orgDoc.getLineOffset(line);
}
else {
text.append("; "); //$NON-NLS-1$
return offset;
}
}
static RCodeStyleSettings getCodeStyle(final SourceUnit su) {
if (su instanceof RSourceUnit) {
return ((RSourceUnit)su).getRCoreAccess().getRCodeStyle();
}
return RCore.getWorkbenchAccess().getRCodeStyle();
}
}