| /*=============================================================================# |
| # Copyright (c) 2008, 2020 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.RScanner; |
| 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 RScanner astScanner= new RScanner(AstInfo.LEVEL_MINIMAL); |
| final SourceComponent rootNode= astScanner.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(); |
| } |
| |
| } |