blob: 21233ce91dd5140ccd57059282d970edbb5dbc9c [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2019 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.sourceediting;
import static org.eclipse.statet.ecommons.text.ui.BracketLevel.AUTODELETE;
import static org.eclipse.statet.r.core.source.IRDocumentConstants.R_DEFAULT_CONTENT_CONSTRAINT;
import static org.eclipse.statet.r.core.source.RHeuristicTokenScanner.CURLY_BRACKET_TYPE;
import static org.eclipse.statet.r.core.source.RHeuristicTokenScanner.ROUND_BRACKET_TYPE;
import static org.eclipse.statet.r.core.source.RHeuristicTokenScanner.SQUARE_BRACKET_TYPE;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedModeUI;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.link.LinkedPositionGroup;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import org.eclipse.statet.jcommons.collections.ImCollections;
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.ITokenScanner;
import org.eclipse.statet.ecommons.text.IndentUtil;
import org.eclipse.statet.ecommons.text.TextUtil;
import org.eclipse.statet.ecommons.text.core.sections.DocContentSections;
import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartition;
import org.eclipse.statet.ecommons.text.ui.BracketLevel.InBracketPosition;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.internal.r.ui.RUIPlugin;
import org.eclipse.statet.ltk.ast.core.AstInfo;
import org.eclipse.statet.ltk.ui.sourceediting.ISmartInsertSettings;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditorAddon;
import org.eclipse.statet.ltk.ui.sourceediting.SourceEditor1;
import org.eclipse.statet.nico.ui.console.InputSourceViewer;
import org.eclipse.statet.r.core.IRCoreAccess;
import org.eclipse.statet.r.core.RCodeStyleSettings;
import org.eclipse.statet.r.core.rsource.RSourceIndenter;
import org.eclipse.statet.r.core.rsource.ast.RAstNode;
import org.eclipse.statet.r.core.rsource.ast.RScanner;
import org.eclipse.statet.r.core.source.IRDocumentConstants;
import org.eclipse.statet.r.core.source.RHeuristicTokenScanner;
import org.eclipse.statet.r.ui.RUI;
import org.eclipse.statet.r.ui.editors.REditorOptions;
/**
* Auto edit strategy for R code:
* - auto indent on keys
* - special indent with tab key
* - auto indent on paste
* - auto close of pairs
*/
public class RAutoEditStrategy extends DefaultIndentLineAutoEditStrategy
implements ISourceEditorAddon {
private static final char[] CURLY_BRACKETS= new char[] { '{', '}' };
private static final StringParserInput DEFAULT_PARSER_INPUT= new StringParserInput();
private class RealTypeListener implements VerifyKeyListener {
@Override
public void verifyKey(final VerifyEvent event) {
if (!event.doit) {
return;
}
switch (event.character) {
case '{':
case '}':
case '(':
case ')':
case '[':
case '%':
case '"':
case '\'':
case '`':
case '#':
event.doit= !customizeKeyPressed(event.character);
return;
case '\t':
if (event.stateMask == 0) {
event.doit= !customizeKeyPressed(event.character);
}
return;
case 0x0A:
case 0x0D:
if (RAutoEditStrategy.this.editor3 != null) {
event.doit= !customizeKeyPressed('\n');
}
return;
default:
return;
}
}
}
private final ISourceEditor editor;
private final ITextEditorExtension3 editor3;
private final DocContentSections documentContentInfo;
private final SourceViewer viewer;
private final RealTypeListener typeListener;
private final IRCoreAccess rCoreAccess;
private final REditorOptions editorOptions;
private AbstractDocument document;
private TextRegion validRange;
private RHeuristicTokenScanner scanner;
private RCodeStyleSettings rCodeStyle;
private RSourceIndenter indenter;
private boolean ignoreCommands= false;
public RAutoEditStrategy(final IRCoreAccess rCoreAccess, final ISourceEditor editor) {
assert (rCoreAccess != null);
assert (editor != null);
this.editor= editor;
this.documentContentInfo= editor.getDocumentContentInfo();
this.rCoreAccess= rCoreAccess;
this.editorOptions= RUIPlugin.getInstance().getREditorSettings(rCoreAccess.getPrefs());
assert (this.editorOptions != null);
this.viewer= this.editor.getViewer();
this.editor3= (editor instanceof SourceEditor1) ? (SourceEditor1) editor : null;
this.typeListener= new RealTypeListener();
}
@Override
public void install(final ISourceEditor editor) {
assert (editor.getViewer() == this.viewer);
this.viewer.prependVerifyKeyListener(this.typeListener);
}
@Override
public void uninstall() {
this.viewer.removeVerifyKeyListener(this.typeListener);
}
private final ITypedRegion initCustomization(final int offset, final int c)
throws BadLocationException, BadPartitioningException {
assert(this.document != null);
if (this.scanner == null) {
this.scanner= createScanner();
}
this.rCodeStyle= this.rCoreAccess.getRCodeStyle();
final ITypedRegion partition= this.document.getPartition(
this.scanner.getDocumentPartitioning(), offset, true );
// InputDocument of console does not (yet) return a TreePartition
this.validRange= (partition instanceof TreePartition) ?
getValidRange(offset, (TreePartition) partition, c) :
new BasicTextRegion(0, this.document.getLength());
return (this.validRange != null) ? partition : null;
}
protected RHeuristicTokenScanner createScanner() {
return RHeuristicTokenScanner.create(this.documentContentInfo);
}
protected TextRegion getValidRange(final int offset, final TreePartition partition, final int c) {
return new BasicTextRegion(0, this.document.getLength());
}
protected final IDocument getDocument() {
return this.document;
}
protected final DocContentSections getDocumentContentInfo() {
return this.documentContentInfo;
}
private final void quitCustomization() {
this.document= null;
this.rCodeStyle= null;
}
private final boolean isSmartInsertEnabled() {
return ((this.editor3 != null) ?
(this.editor3.getInsertMode() == ITextEditorExtension3.SMART_INSERT) :
this.editorOptions.isSmartInsertEnabledByDefault() );
}
private final boolean isBlockSelection() {
final StyledText textWidget= this.viewer.getTextWidget();
return (textWidget.getBlockSelection() && textWidget.getSelectionRanges().length > 2);
}
private final boolean isClosedBracket(final int backwardOffset, final int forwardOffset, final int searchType) {
int[] balance= new int[3];
balance[searchType]++;
this.scanner.configureDefaultPartitions(this.document);
balance= this.scanner.computeBracketBalance(backwardOffset, forwardOffset, balance, searchType);
return (balance[searchType] <= 0);
}
private final boolean isClosedString(int offset, final int end, final boolean endVirtual, final char sep) {
this.scanner.configure(this.document);
boolean in= true; // we start always inside after a sep
final char[] chars= new char[] { sep, '\\' };
while (offset < end) {
offset= this.scanner.scanForward(offset, end, chars);
if (offset == RHeuristicTokenScanner.NOT_FOUND) {
offset= end;
break;
}
offset++;
if (this.scanner.getChar() == '\\') {
offset++;
}
else {
in= !in;
}
}
return (offset == end) && (!in ^ endVirtual);
}
private boolean isCharAt(final int offset, final char c) throws BadLocationException {
return (offset >= this.validRange.getStartOffset() && offset < this.validRange.getEndOffset()
&& this.document.getChar(offset) == c);
}
private boolean isValueChar(final int offset) throws BadLocationException {
if (offset >= this.validRange.getStartOffset() && offset < this.validRange.getEndOffset()) {
final int c= this.document.getChar(offset);
return (c == '"' || c == '\'' || c == '`' || Character.isLetterOrDigit(c));
}
return false;
}
private boolean isAfterRoxygen(final int offset) throws BadLocationException {
this.scanner.configure(this.document);
final int line= this.document.getLineOfOffset(offset);
if (line > 0 && this.scanner.findAnyNonBlankBackward(offset, this.document.getLineOffset(line)-1, false) == ITokenScanner.NOT_FOUND) {
final IRegion prevLineInfo= this.document.getLineInformation(line-1);
if (prevLineInfo.getLength() > 0 && TextUtilities.getPartition(this.document,
this.scanner.getDocumentPartitioning(),
prevLineInfo.getOffset()+prevLineInfo.getLength()-1, false).getType() == IRDocumentConstants.R_ROXYGEN_CONTENT_TYPE) {
return true;
}
}
return false;
}
@Override
public void customizeDocumentCommand(final IDocument d, final DocumentCommand c) {
if (this.ignoreCommands || c.doit == false || c.text == null) {
return;
}
if (!isSmartInsertEnabled() || isBlockSelection()) {
super.customizeDocumentCommand(d, c);
return;
}
try {
this.document= (AbstractDocument) d;
final ITypedRegion partition= initCustomization(c.offset, -1);
if (partition == null) {
return;
}
final String contentType= partition.getType();
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)) {
if (c.length == 0 && TextUtilities.equals(d.getLegalLineDelimiters(), c.text) != -1) {
smartIndentOnNewLine(c, contentType);
}
else if (c.text.length() > 1 && this.editorOptions.isSmartPasteEnabled()) {
smartPaste(c);
}
}
}
catch (final Exception e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, RUI.BUNDLE_ID, -1,
"An error occurred when customizing action for document command in R auto edit strategy.", e )); //$NON-NLS-1$
}
finally {
quitCustomization();
}
}
/**
* Second main entry method for real single key presses.
*
* @return <code>true</code>, if key was processed by method
*/
private boolean customizeKeyPressed(final char c) {
if (!isSmartInsertEnabled() || !UIAccess.isOkToUse(this.viewer) || isBlockSelection()) {
return false;
}
try {
this.document= (AbstractDocument) this.viewer.getDocument();
ITextSelection selection= (ITextSelection) this.viewer.getSelection();
final ITypedRegion partition= initCustomization(selection.getOffset(), c);
if (partition == null) {
return false;
}
final String contentType= partition.getType();
this.ignoreCommands= true;
final DocumentCommand command= new DocumentCommand() {};
command.offset= selection.getOffset();
command.length= selection.getLength();
command.doit= true;
command.shiftsCaret= true;
command.caretOffset= -1;
int linkedMode= -1;
int linkedModeOffset= -1;
boolean contextInfo= false;
final int cEnd= command.offset + command.length;
KEY: switch (c) {
case '\t':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)
|| contentType == IRDocumentConstants.R_COMMENT_CONTENT_TYPE
|| contentType == IRDocumentConstants.R_ROXYGEN_CONTENT_TYPE ) {
if (command.length == 0 || this.document.getLineOfOffset(command.offset) == this.document.getLineOfOffset(cEnd)) {
command.text= "\t"; //$NON-NLS-1$
switch (smartIndentOnTab(command)) {
case -1:
return false;
case 0:
break;
case 1:
break KEY;
}
if (this.rCodeStyle.getReplaceOtherTabsWithSpaces()) {
final IndentUtil indentation= new IndentUtil(this.document, this.rCodeStyle);
command.text= indentation.createTabSpacesCompletionString(indentation.getColumn(command.offset));
break KEY;
}
}
}
return false;
case '}':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)) {
command.text= "}"; //$NON-NLS-1$
smartIndentOnClosingBracket(command);
break KEY;
}
return false;
case '{':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)) {
command.text= "{"; //$NON-NLS-1$
if (this.editorOptions.isSmartCurlyBracketsEnabled() && !isValueChar(cEnd)) {
if (!isClosedBracket(command.offset, cEnd, CURLY_BRACKET_TYPE)) {
command.text= "{}"; //$NON-NLS-1$
linkedMode= 1 | AUTODELETE;
}
else if (isCharAt(cEnd, '}')) {
linkedMode= 1;
}
}
linkedModeOffset= smartIndentOnFirstLineCharDefault2(command);
break KEY;
}
return false;
case '(':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)) {
command.text= "("; //$NON-NLS-1$
if (this.editorOptions.isSmartRoundBracketsEnabled() && !isValueChar(cEnd)) {
if (!isClosedBracket(command.offset, cEnd, ROUND_BRACKET_TYPE)) {
command.text= "()"; //$NON-NLS-1$
linkedMode= 2 | AUTODELETE;
}
else if (isCharAt(cEnd, ')')) {
linkedMode= 2;
}
}
if (isValueChar(command.offset - 1)) {
contextInfo= true;
}
break KEY;
}
return false;
case ')':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)) {
command.text= ")"; //$NON-NLS-1$
smartIndentOnFirstLineCharDefault2(command); // required?
break KEY;
}
return false;
case '[':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)) {
command.text= "["; //$NON-NLS-1$
if (this.editorOptions.isSmartSquareBracketsEnabled() && !isValueChar(cEnd)) {
if (!isClosedBracket(command.offset, cEnd, SQUARE_BRACKET_TYPE)) {
command.text= "[]"; //$NON-NLS-1$
if (TextUtil.countBackward(this.document, command.offset, '[') % 2 == 1
&& isCharAt(cEnd, ']') ) {
linkedMode= 3 | AUTODELETE;
}
else {
linkedMode= 2 | AUTODELETE;
}
}
else if (isCharAt(cEnd, ']')) {
linkedMode= 2;
}
}
break KEY;
}
return false;
case '%':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)
&& this.editorOptions.isSmartSpecialPercentEnabled()) {
final IRegion line= this.document.getLineInformationOfOffset(cEnd);
this.scanner.configure(this.document, IRDocumentConstants.R_INFIX_OPERATOR_CONTENT_TYPE);
if (this.scanner.count(cEnd, line.getOffset()+line.getLength(), '%') % 2 == 0) {
command.text= "%%"; //$NON-NLS-1$
linkedMode= 2 | AUTODELETE;
break KEY;
}
}
return false;
case '"':
case '\'':
case '`':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)
&& this.editorOptions.isSmartStringsEnabled()
&& !isValueChar(cEnd) && !isValueChar(command.offset - 1) ) {
final IRegion line= this.document.getLineInformationOfOffset(cEnd);
if (!isClosedString(cEnd, line.getOffset() + line.getLength(), false, c)) {
command.text= new String(new char[] { c, c });
linkedMode= 2 | AUTODELETE;
break KEY;
}
}
return false;
case '\n':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)
|| contentType == IRDocumentConstants.R_COMMENT_CONTENT_TYPE) {
command.text= TextUtilities.getDefaultLineDelimiter(this.document);
smartIndentOnNewLine(command, contentType);
break KEY;
}
else if (contentType == IRDocumentConstants.R_ROXYGEN_CONTENT_TYPE) {
command.text= TextUtilities.getDefaultLineDelimiter(this.document);
smartIndentAfterNewLine1(command, command.text);
break KEY;
}
return false;
case '#':
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(contentType)
&& isAfterRoxygen(command.offset)) {
command.text= "#' "; //$NON-NLS-1$
break KEY;
}
return false;
default:
assert (false);
return false;
}
if (command.text.length() > 0 && this.editor.isEditable(true)) {
this.viewer.getTextWidget().setRedraw(false);
try {
this.document.replace(command.offset, command.length, command.text);
final int cursor= (command.caretOffset >= 0) ? command.caretOffset :
command.offset+command.text.length();
selection= new TextSelection(this.document, cursor, 0);
this.viewer.setSelection(selection, true);
if (linkedMode >= 0) {
if (linkedModeOffset < 0) {
linkedModeOffset= command.offset;
}
createLinkedMode(linkedModeOffset, c, linkedMode).enter();
}
}
finally {
this.viewer.getTextWidget().setRedraw(true);
}
if (contextInfo
&& this.viewer.canDoOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION)) {
this.viewer.doOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION);
}
}
return true;
}
catch (final Exception e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, RUI.BUNDLE_ID, -1,
"An error occurred when customizing action for pressed key in R auto edit strategy.", e )); //$NON-NLS-1$
}
finally {
this.ignoreCommands= false;
quitCustomization();
}
return false;
}
/**
* Generic method to indent lines using the RSourceIndenter, called algorithm 2.
* @param c handle to read and save the document informations
* @param indentCurrentLine
* @param setCaret positive values indicates the line to set the caret
* @param traceCursor offset to update and return (offset at state after insertion of c.text)
*/
private Position[] smartIndentLine2(final DocumentCommand c, final boolean indentCurrentLine, final int setCaret, final Position[] tracePos) throws BadLocationException, BadPartitioningException, CoreException {
if (this.editor3 == null) {
return tracePos;
}
final TextRegion validRegion= this.validRange;
// new algorithm using RSourceIndenter
final int cEnd= c.offset+c.length;
if (cEnd > validRegion.getEndOffset()) {
return tracePos;
}
this.scanner.configure(this.document);
final int smartEnd;
final String smartAppend;
if (endsWithNewLine(c.text)) {
final IRegion cEndLine= this.document.getLineInformationOfOffset(cEnd);
final int validEnd= (cEndLine.getOffset() + cEndLine.getLength() <= validRegion.getEndOffset()) ?
cEndLine.getOffset() + cEndLine.getLength() :
validRegion.getEndOffset();
final int next= this.scanner.findAnyNonBlankForward(cEnd, validEnd, false);
smartEnd= (next >= 0) ? next : validEnd;
switch(this.scanner.getChar()) {
case '}':
case '{':
case '|':
case '&':
smartAppend= ""; //$NON-NLS-1$
break;
default:
smartAppend= "DUMMY+"; //$NON-NLS-1$
break;
}
}
else {
smartEnd= cEnd;
smartAppend= ""; //$NON-NLS-1$
}
int shift= 0;
if (c.offset < validRegion.getStartOffset()
|| c.offset > validRegion.getEndOffset()) {
return tracePos;
}
if (c.offset > 2500) {
final int line= this.document.getLineOfOffset(c.offset) - 40;
if (line >= 10) {
shift= this.document.getLineOffset(line);
final ITypedRegion partition= this.document.getPartition(
this.scanner.getDocumentPartitioning(), shift, true );
if (!R_DEFAULT_CONTENT_CONSTRAINT.matches(partition.getType())) {
shift= partition.getOffset();
}
}
}
if (shift < validRegion.getStartOffset()) {
shift= validRegion.getStartOffset();
}
int dummyDocEnd= cEnd+1500;
if (dummyDocEnd > validRegion.getEndOffset()) {
dummyDocEnd= validRegion.getEndOffset();
}
final String text;
{ final StringBuilder s= new StringBuilder(
(c.offset-shift) +
c.text.length() +
(smartEnd-cEnd) +
smartAppend.length() +
(dummyDocEnd-smartEnd) );
s.append(this.document.get(shift, c.offset-shift));
s.append(c.text);
if (smartEnd-cEnd > 0) {
s.append(this.document.get(cEnd, smartEnd-cEnd));
}
s.append(smartAppend);
s.append(this.document.get(smartEnd, dummyDocEnd-smartEnd));
text= s.toString();
}
// Create temp doc to compute indent
int dummyCoffset= c.offset-shift;
int dummyCend= dummyCoffset+c.text.length();
final AbstractDocument dummyDoc= new Document(text);
final TextParserInput parserInput= (Display.getCurrent() == Display.getDefault()) ?
DEFAULT_PARSER_INPUT.reset(text) : new StringParserInput(text);
// Lines to indent
int dummyFirstLine= dummyDoc.getLineOfOffset(dummyCoffset);
final int dummyLastLine= dummyDoc.getLineOfOffset(dummyCend);
if (!indentCurrentLine) {
dummyFirstLine++;
}
if (dummyFirstLine > dummyLastLine) {
return tracePos;
}
// Compute indent
final RScanner scanner= new RScanner(AstInfo.LEVEL_MINIMAL);
final RAstNode rootNode= scanner.scanSourceUnit(parserInput.init());
if (this.indenter == null) {
this.indenter= new RSourceIndenter(this.scanner);
}
this.indenter.setup(this.rCoreAccess);
final TextEdit edit= this.indenter.getIndentEdits(dummyDoc, rootNode, 0, dummyFirstLine, dummyLastLine);
// Apply indent to temp doc
final Position cPos= new Position(dummyCoffset, c.text.length());
dummyDoc.addPosition(cPos);
if (tracePos != null) {
for (int i= 0; i < tracePos.length; i++) {
tracePos[i].offset -= shift;
dummyDoc.addPosition(tracePos[i]);
}
}
c.length= c.length+edit.getLength()
// add space between two replacement regions
// minus overlaps with c.text
-TextUtil.overlaps(edit.getOffset(), edit.getExclusiveEnd(), dummyCoffset, dummyCend);
if (edit.getOffset() < dummyCoffset) { // move offset, if edit begins before c
dummyCoffset= edit.getOffset();
c.offset= shift+dummyCoffset;
}
edit.apply(dummyDoc, TextEdit.NONE);
// Read indent for real doc
int dummyChangeEnd= edit.getExclusiveEnd();
dummyCend= cPos.getOffset()+cPos.getLength();
if (!cPos.isDeleted && dummyCend > dummyChangeEnd) {
dummyChangeEnd= dummyCend;
}
c.text= dummyDoc.get(dummyCoffset, dummyChangeEnd-dummyCoffset);
if (setCaret != 0) {
c.caretOffset= shift+this.indenter.getNewIndentOffset(dummyFirstLine+setCaret-1);
c.shiftsCaret= false;
}
this.indenter.clear();
if (tracePos != null) {
for (int i= 0; i < tracePos.length; i++) {
tracePos[i].offset += shift;
}
}
return tracePos;
}
private final boolean endsWithNewLine(final String text) {
for (int i= text.length()-1; i >= 0; i--) {
final char c= text.charAt(i);
if (c == '\r' || c == '\n') {
return true;
}
if (c != ' ' && c != '\t') {
return false;
}
}
return false;
}
private void smartIndentOnNewLine(final DocumentCommand c, final String partitionType)
throws BadLocationException, BadPartitioningException, CoreException {
final int before= c.offset - 1;
final int behind= c.offset + c.length;
final String lineDelimiter= c.text;
if (R_DEFAULT_CONTENT_CONSTRAINT.matches(partitionType)
&& before >= 0 && behind < this.validRange.getEndOffset()
&& this.document.getChar(before) == '{' && this.document.getChar(behind) == '}') {
c.text= c.text + c.text;
}
try {
smartIndentLine2(c, false, 1, null);
}
catch (final Exception e) {
RUIPlugin.logError(RUIPlugin.INTERNAL_ERROR, "An error occurred while customize a command in R auto edit strategy (algorithm 2).", e); //$NON-NLS-1$
smartIndentAfterNewLine1(c, lineDelimiter);
}
}
private void smartIndentAfterNewLine1(final DocumentCommand c, final String lineDelimiter)
throws BadLocationException, BadPartitioningException, CoreException {
final StringBuilder sb= new StringBuilder(c.text);
int nlIndex= lineDelimiter.length();
final int line= this.document.getLineOfOffset(c.offset);
int checkOffset= Math.max(0, c.offset);
final ITypedRegion partition= this.document.getPartition(
this.scanner.getDocumentPartitioning(), checkOffset, true );
if (partition.getType() == IRDocumentConstants.R_COMMENT_CONTENT_TYPE) {
checkOffset= partition.getOffset();
}
else if (partition.getType() == IRDocumentConstants.R_ROXYGEN_CONTENT_TYPE) {
checkOffset= -1;
if (c.length == 0 && line + 1 < this.document.getNumberOfLines()) {
final int offset= this.document.getLineOffset(line + 1);
this.scanner.configure(this.document);
final int next= this.scanner.findAnyNonBlankForward(offset, ITokenScanner.UNBOUND, true);
if (next >= 0 && this.scanner.getPartition(next).getType() == IRDocumentConstants.R_ROXYGEN_CONTENT_TYPE) {
sb.append("#' "); //$NON-NLS-1$
}
}
this.scanner.configure(this.document);
}
final IndentUtil util= new IndentUtil(this.document, this.rCodeStyle);
final int column= util.getLineIndent(line, false)[IndentUtil.COLUMN_IDX];
if (checkOffset > 0) {
// new block?:
this.scanner.configure(this.document);
final int match= this.scanner.findAnyNonBlankBackward(checkOffset, this.document.getLineOffset(line) - 1, false);
if (match >= 0 && this.document.getChar(match) == '{') {
final String indent= util.createIndentString(util.getNextLevelColumn(column, 1));
sb.insert(nlIndex, indent);
nlIndex+= indent.length() + lineDelimiter.length();
}
}
if (nlIndex <= sb.length()) {
sb.insert(nlIndex, util.createIndentString(column));
}
c.text= sb.toString();
}
private int smartIndentOnTab(final DocumentCommand c) throws BadLocationException {
final IRegion line= this.document.getLineInformation(this.document.getLineOfOffset(c.offset));
int first;
this.scanner.configure(this.document);
first= this.scanner.findAnyNonBlankBackward(c.offset, line.getOffset()-1, false);
if (first != ITokenScanner.NOT_FOUND) { // not first char
return 0;
}
// first= scanner.findAnyNonBlankForward(c.offset, line.getOffset()+line.getLength(), false);
// if (c.offset == line.getOffset() || c.offset != first) {
// try {
// final Position cursorPos= new Position(first, 0);
// smartIndentLine2(c, true, 0, new Position[] { cursorPos });
// c.caretOffset= cursorPos.getOffset();
// return 1;
// }
// catch (final Exception e) {
// RUIPlugin.logError(RUIPlugin.INTERNAL_ERROR, "An error occurred while customize a command in R auto edit strategy (algorithm 2).", e); //$NON-NLS-1$
// return -1;
// }
// }
final IndentUtil indentation= new IndentUtil(this.document, this.rCodeStyle);
final int column= indentation.getColumn(c.offset);
if (this.editorOptions.getSmartInsertTabAction() != ISmartInsertSettings.TabAction.INSERT_TAB_CHAR) {
c.text= indentation.createIndentCompletionString(column);
}
return 1;
}
private void smartIndentOnClosingBracket(final DocumentCommand c) throws BadLocationException {
final int lineOffset= this.document.getLineOffset(this.document.getLineOfOffset(c.offset));
this.scanner.configure(this.document);
if (this.scanner.findAnyNonBlankBackward(c.offset, lineOffset-1, false) != ITokenScanner.NOT_FOUND) {
// not first char
return;
}
try {
final Position cursorPos= new Position(c.offset+1, 0);
smartIndentLine2(c, true, 0, new Position[] { cursorPos });
c.caretOffset= cursorPos.getOffset();
return;
}
catch (final Exception e) {
RUIPlugin.logError(RUIPlugin.INTERNAL_ERROR, "An error occurred while customize a command in R auto edit strategy (algorithm 2).", e); //$NON-NLS-1$
smartIndentOnClosingBracket1(c);
}
}
private void smartIndentOnClosingBracket1(final DocumentCommand c) throws BadLocationException {
final int lineOffset= this.document.getLineOffset(this.document.getLineOfOffset(c.offset));
final int blockStart= this.scanner.findOpeningPeer(lineOffset, CURLY_BRACKETS);
if (blockStart == ITokenScanner.NOT_FOUND) {
return;
}
final IndentUtil util= new IndentUtil(this.document, this.rCodeStyle);
final int column= util.getLineIndent(this.document.getLineOfOffset(blockStart), false)[IndentUtil.COLUMN_IDX];
c.text= util.createIndentString(column) + c.text;
c.length += c.offset - lineOffset;
c.offset= lineOffset;
}
private int smartIndentOnFirstLineCharDefault2(final DocumentCommand c) throws BadLocationException {
final int lineOffset= this.document.getLineOffset(this.document.getLineOfOffset(c.offset));
this.scanner.configure(this.document);
if (this.scanner.findAnyNonBlankBackward(c.offset, lineOffset-1, false) != ITokenScanner.NOT_FOUND) {
// not first char
return c.offset;
}
try {
final Position cursorPos= new Position(c.offset+1, 0);
smartIndentLine2(c, true, 0, new Position[] { cursorPos });
return (c.caretOffset= cursorPos.getOffset()) -1;
}
catch (final Exception e) {
RUIPlugin.logError(RUIPlugin.INTERNAL_ERROR, "An error occurred while customize a command in R auto edit strategy (algorithm 2).", e); //$NON-NLS-1$
return -1;
}
}
private void smartPaste(final DocumentCommand c) throws BadLocationException {
final int lineOffset= this.document.getLineOffset(this.document.getLineOfOffset(c.offset));
this.scanner.configure(this.document);
final boolean firstLine= (this.scanner.findAnyNonBlankBackward(c.offset, lineOffset-1, false) == ITokenScanner.NOT_FOUND);
try {
smartIndentLine2(c, firstLine, 0, null);
}
catch (final Exception e) {
RUIPlugin.logError(RUIPlugin.INTERNAL_ERROR, "An error occurred while customize a command in R auto edit strategy (algorithm 2).", e); //$NON-NLS-1$
}
}
private LinkedModeUI createLinkedMode(final int offset, final char type, final int mode)
throws BadLocationException {
final LinkedModeModel model= new LinkedModeModel();
int pos= 0;
final LinkedPositionGroup group= new LinkedPositionGroup();
final InBracketPosition position= RBracketLevel.createPosition(type, this.document,
offset + 1, 0, pos++);
group.addPosition(position);
model.addGroup(group);
model.forceInstall();
final RBracketLevel level= new RBracketLevel(model,
getDocument(), getDocumentContentInfo(),
ImCollections.<LinkedPosition>newList(position), (mode & 0xffff0000) |
((this.viewer instanceof InputSourceViewer) ? RBracketLevel.CONSOLE_MODE : 0) );
/* create UI */
final LinkedModeUI ui= new LinkedModeUI(model, this.viewer);
ui.setCyclingMode(LinkedModeUI.CYCLE_NEVER);
ui.setExitPosition(this.viewer, offset + (mode & 0xff), 0, pos);
ui.setSimpleMode(true);
ui.setExitPolicy(level);
return ui;
}
}