| /*=============================================================================# |
| # 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.IDocContentSections; |
| 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 IDocContentSections 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 IDocContentSections 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; |
| } |
| |
| } |