/* * 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.actions; import static org.codehaus.groovy.eclipse.refactoring.formatter.GroovyIndentationService.getLineLeadingWhiteSpace; import static org.codehaus.groovy.eclipse.refactoring.formatter.GroovyIndentationService.getLineTextUpto; import java.util.ResourceBundle; import org.codehaus.groovy.eclipse.GroovyPlugin; import org.codehaus.groovy.eclipse.refactoring.formatter.GroovyIndentationService; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.Assert; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.ITextEditorExtension3; import org.eclipse.ui.texteditor.TextEditorAction; public class GroovyTabAction extends TextEditorAction { public GroovyTabAction(ITextEditor editor) { super(fgBundleForConstructedKeys, "Indent.", editor); } @Override public void run() { // update has been called by the framework if (!isEnabled() || !validateEditorInputState()) return; ITextSelection selection = getSelection(); final IDocument d = getDocument(); if (d != null) { try { int offset = selection.getOffset(); // We are making a few assumptions about the circumstances under // which we get to this point in the code: Assert.isTrue(selection.getLength() == 0); Assert.isTrue(isInSmartTabRegion(d, offset)); @SuppressWarnings("cast") GroovyIndentationService indentation = GroovyIndentationService.get(JavaCore.create( ((IFile) getTextEditor().getEditorInput().getAdapter(IFile.class)).getProject())); int tabLine = d.getLineOfOffset(offset); int newIndentLevel = indentation.computeIndentForLine(d, tabLine); String lineStartText = getLineTextUpto(d, offset); int cursorIndent = indentation.indentLevel(lineStartText); if (cursorIndent < newIndentLevel) { // Smart tab should apply String leadingWhiteSpace = getLineLeadingWhiteSpace(d, tabLine); int editOffset = d.getLineOffset(tabLine); int editLength = leadingWhiteSpace.length(); String editText = indentation.createIndentation(newIndentLevel); d.replace(editOffset, editLength, editText); selectAndReveal(editOffset + editText.length(), 0); } else { // Just indent one tab size using whatever style // dictated by preferences (i.e. maybe use tabs or spaces) String editText = indentation.getTabString(); d.replace(offset, 0, editText); selectAndReveal(offset + editText.length(), 0); } } catch (Exception e) { GroovyPlugin.getDefault().logError("something went wrong in smart tab", e); } } } /** * Determine whether a given offset is in a region of text containing * whitespace only at the beginning of line. (Only position for which this * method returns true * should be subjected to "smart tab" processing). */ private boolean isInSmartTabRegion(IDocument d, int offset) throws BadLocationException { String lineStartText = GroovyIndentationService.getLineTextUpto(d, offset); return lineStartText.trim().equals(""); } // ////////////////////////////////////////////////////////////////////// // Code below this line copied from // org.eclipse.jdt.internal.ui.actions.IndentAction /******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Tom Eicher (Avaloq Evolution AG) - block selection mode *******************************************************************************/ @Override public void update() { super.update(); if (isEnabled()) setEnabled(canModifyEditor() && isSmartMode() && isValidSelection()); } /** * Returns if the current selection is valid, i.e. whether it is empty and * the caret in the * whitespace at the start of a line, or covers multiple lines. * * @return <code>true</code> if the selection is valid for an indent operation */ private boolean isValidSelection() { ITextSelection selection = getSelection(); if (selection.isEmpty()) return false; int offset = selection.getOffset(); int length = selection.getLength(); IDocument document = getDocument(); if (document == null) return false; try { IRegion firstLine = document.getLineInformationOfOffset(offset); int lineOffset = firstLine.getOffset(); // either the selection has to be empty and the caret in the WS at // the line start // or the selection has to extend over multiple lines if (length == 0) return document.get(lineOffset, offset - lineOffset).trim().length() == 0; else // return lineOffset + firstLine.getLength() < offset + length; return false; // only enable for empty selections for now } catch (BadLocationException e) {} return false; } /** * Returns the smart preference state. * * @return <code>true</code> if smart mode is on, <code>false</code> otherwise */ private boolean isSmartMode() { ITextEditor editor = getTextEditor(); if (editor instanceof ITextEditorExtension3) return ((ITextEditorExtension3) editor).getInsertMode() == ITextEditorExtension3.SMART_INSERT; return false; } /** * Returns the document currently displayed in the editor, or * <code>null</code> if none can be obtained. * * @return the current document or <code>null</code> */ private IDocument getDocument() { ITextEditor editor = getTextEditor(); if (editor != null) { IDocumentProvider provider = editor.getDocumentProvider(); IEditorInput input = editor.getEditorInput(); if (provider != null && input != null) return provider.getDocument(input); } return null; } /** * Returns the selection on the editor or an invalid selection if none can * be obtained. Returns * never <code>null</code>. * * @return the current selection, never <code>null</code> */ private ITextSelection getSelection() { ISelectionProvider provider = getSelectionProvider(); if (provider != null) { ISelection selection = provider.getSelection(); if (selection instanceof ITextSelection) return (ITextSelection) selection; } // null object return TextSelection.emptySelection(); } /** * Returns the editor's selection provider. * * @return the editor's selection provider or <code>null</code> */ private ISelectionProvider getSelectionProvider() { ITextEditor editor = getTextEditor(); if (editor != null) { return editor.getSelectionProvider(); } return null; } /** * Selects the given range on the editor. * * @param newOffset the selection offset * @param newLength the selection range */ private void selectAndReveal(int newOffset, int newLength) { Assert.isTrue(newOffset >= 0); Assert.isTrue(newLength >= 0); ITextEditor editor = getTextEditor(); if (editor instanceof JavaEditor) { ISourceViewer viewer = ((JavaEditor) editor).getViewer(); if (viewer != null) { viewer.setSelectedRange(newOffset, newLength); } } else { // this is too intrusive, but will never get called anyway getTextEditor().selectAndReveal(newOffset, newLength); } } // ////////////////////////////////////////////////////////////////////////// // Code below copied from // org.eclipse.jdt.internal.ui.javaeditor.JavaEditorMessages /******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Andre Soereng <andreis@fast.no> - [syntax highlighting] highlight numbers * - https://bugs.eclipse.org/bugs/show_bug.cgi?id=63573 *******************************************************************************/ private static final String BUNDLE_FOR_CONSTRUCTED_KEYS = "org.eclipse.jdt.internal.ui.javaeditor.ConstructedJavaEditorMessages";//$NON-NLS-1$ private static ResourceBundle fgBundleForConstructedKeys = ResourceBundle.getBundle(BUNDLE_FOR_CONSTRUCTED_KEYS); }