/* * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.groovy.eclipse.editor; import org.codehaus.groovy.eclipse.GroovyPlugin; import org.codehaus.groovy.eclipse.refactoring.formatter.GroovyIndentationService; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.groovy.core.util.ReflectionUtils; import org.eclipse.jdt.internal.ui.text.java.JavaAutoIndentStrategy; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.DocumentCommand; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; /** * This a wrapper around a JavaAutoIndentStrategy, to which it delegates most * requests. However, whenever the Java strategy doesn't quite do what we want * it do for Groovy code, we can intercept the request and handle it ourselves. */ public class GroovyAutoIndentStrategy extends AbstractAutoEditStrategy { private final GroovyIndentationService indentService; private final JavaAutoIndentStrategy javaStrategy; private final boolean closeBraces; public GroovyAutoIndentStrategy(JavaAutoIndentStrategy javaStrategy) { this.javaStrategy = javaStrategy; ReflectionUtils.executeNoArgPrivateMethod(JavaAutoIndentStrategy.class, "clearCachedValues", javaStrategy); this.closeBraces = (Boolean) ReflectionUtils.getPrivateField(JavaAutoIndentStrategy.class, "fCloseBrace", javaStrategy); this.indentService = GroovyIndentationService.get((IJavaProject) ReflectionUtils.getPrivateField(JavaAutoIndentStrategy.class, "fProject", javaStrategy)); } public void customizeDocumentCommand(IDocument d, DocumentCommand c) { if (c.doit) try { if (c.length == 0 && isNewline(d, c.text)) { autoEditAfterNewline(d, c); } else if (c.text.length() > 2) { smartPaste(d, c); } else if ("{".equals(c.text) || "}".equals(c.text)) { // delegate for some simple cases like braces javaStrategy.customizeDocumentCommand(d, c); } } finally { // ensures that prefs will refresh each time they are needed; also saves a little bit of memory indentService.disposePrefs(); } } /** * This method is called when pasting text into the editor. It can decide to * modify the command, for example to adjust indentation of the pasted text. */ private void smartPaste(IDocument d, DocumentCommand c) { try { if (indentService.getPrefs().isSmartPaste() && indentService.isInEmptyLine(d, c.offset)) { int pasteLine = d.getLineOfOffset(c.offset); IRegion pasteLineRegion = d.getLineInformation(pasteLine); Document workCopy = new Document(d.get(0, pasteLineRegion.getOffset())); workCopy.replace(pasteLineRegion.getOffset(), 0, c.text); int startLine = workCopy.getLineOfOffset(pasteLineRegion.getOffset()); int endLine = workCopy.getLineOfOffset(pasteLineRegion.getOffset() + c.text.length()); int indentDiff = 0; boolean isMultiLineComment = false; boolean isMultiLineString= false; for (int line = startLine; line <= endLine; line++) { IRegion lineRegion = workCopy.getLineInformation(line); String text = workCopy.get(lineRegion.getOffset(), lineRegion.getLength()); if (line - startLine < 2) { // For first two lines use indentation logic to move the // lines int oldIndentLevel = indentService.getLineIndentLevel(workCopy, line); int newIndentLevel = indentService.computeIndentForLine(workCopy, line); if (isMultiLineComment) { newIndentLevel++; indentService.fixIndentation(workCopy, line, newIndentLevel); } else if (!isMultiLineString) { indentService.fixIndentation(workCopy, line, newIndentLevel); } indentDiff = newIndentLevel - oldIndentLevel; } else { int oldIndentLevel = indentService.getLineIndentLevel(workCopy, line); int newIndentLevel = oldIndentLevel + indentDiff; if (isMultiLineComment) { indentService.fixIndentation(workCopy, line, newIndentLevel); } else if (!isMultiLineString) { indentService.fixIndentation(workCopy, line, newIndentLevel); } } if (text.indexOf("/*") != -1) { isMultiLineComment = true; } if ((text.indexOf("*/") != -1) && isMultiLineComment) { isMultiLineComment = false; } else if (((text.indexOf("\"\"\"") != -1) || (text.indexOf("'''") != -1)) && !isMultiLineComment) { isMultiLineString = !isMultiLineString; } } // Put the "smart" adjusted paste into the command int workStart = workCopy.getLineOffset(startLine); int workEnd = workCopy.getLineOffset(endLine) + workCopy.getLineLength(endLine); c.text = workCopy.get(workStart, workEnd - workStart); c.offset = pasteLineRegion.getOffset(); c.length = pasteLineRegion.getLength(); c.caretOffset = c.offset + c.text.length(); c.shiftsCaret = false; } } catch (Exception e) { GroovyPlugin.getDefault().logError("Something went wrong in GroovyAutoIndentStrategy.smartPaste", e); } } /** * Applies edits upon newline presses. */ private void autoEditAfterNewline(IDocument d, DocumentCommand c) { try { int orgIndentLevel = indentService.getIndentLevel(d, c.offset); // Add indentation int indentLevel = indentService.computeIndentAfterNewline(d, c.offset); String indentation = indentService.createIndentation(indentLevel); c.text += indentation; // Add closing brace if (closeBraces) { int lengthToCurly = indentService.lengthToNextCurly(d, c.offset); if (shouldInsertBrace(d, c.offset, lengthToCurly > 0)) { // munch all chars from the insertion point to the curly brace (if one already exists) c.length = lengthToCurly; int newCaret = c.offset + c.text.length(); c.text += indentService.newline(d) + indentService.createIndentation(orgIndentLevel) + "}"; c.caretOffset = newCaret; c.shiftsCaret = false; } } } catch (Exception e) { // This is a fail safe, in case anything goes wrong. We should // return normally. This way the edit should still be able to // proceed, but without any "smart" auto edits being applied. GroovyPlugin.getDefault().logError("Something went wrong in GroovyAutoIndentStrategy.autoEditAfterNewline", e); } } /** * Determines whether an offset in a document would be a good one * to insert an automatic closing brace for on the next line. */ private boolean shouldInsertBrace(IDocument d, int enterPos, boolean nextTokenIsCloseBrace) throws BadLocationException { if (indentService.moreOpenThanCloseBefore(d, enterPos) && (nextTokenIsCloseBrace || indentService.isEndOfLine(d, enterPos))) { int lineNum = d.getLineOfOffset(enterPos); int indentLevel = indentService.getLineIndentLevel(d, lineNum); String line; do { line = GroovyIndentationService.getLine(d, ++lineNum); line = line.trim(); } while (line.equals("") && lineNum < d.getNumberOfLines()); int nextIndentLevel = indentService.getLineIndentLevel(d, lineNum); if (nextIndentLevel > indentLevel) return false; if (nextIndentLevel < indentLevel) return true; return !line.startsWith("}"); } return false; } }