/* * Copyright 2000-2014 JetBrains s.r.o. * * 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 com.jetbrains.python.codeInsight; import com.intellij.codeInsight.completion.InsertHandler; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actions.EditorActionUtil; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.completion.PythonLookupElement; import com.jetbrains.python.psi.PyStatementWithElse; import com.jetbrains.python.psi.PyTryExceptStatement; /** * Adjusts indentation after a final part keyword is inserted, e.g. an "else:". * User: dcheryasov * Date: Mar 2, 2010 6:48:40 PM */ public class PyUnindentingInsertHandler implements InsertHandler<PythonLookupElement> { public final static PyUnindentingInsertHandler INSTANCE = new PyUnindentingInsertHandler(); private PyUnindentingInsertHandler() { } public void handleInsert(InsertionContext context, PythonLookupElement item) { unindentAsNeeded(context.getProject(), context.getEditor(), context.getFile()); } /** * Unindent current line to be flush with a starting part, detecting the part if necessary. * * @param project * @param editor * @param file * @return true if unindenting succeeded */ public static boolean unindentAsNeeded(Project project, Editor editor, PsiFile file) { // TODO: handle things other than "else" final Document document = editor.getDocument(); int offset = editor.getCaretModel().getOffset(); CharSequence text = document.getCharsSequence(); if (offset >= text.length()) { offset = text.length() - 1; } int line_start_offset = document.getLineStartOffset(document.getLineNumber(offset)); int nonspace_offset = findBeginning(line_start_offset, text); Class<? extends PsiElement> parentClass = null; int last_offset = nonspace_offset + PyNames.FINALLY.length(); // the longest of all if (last_offset > offset) last_offset = offset; int local_length = last_offset - nonspace_offset + 1; if (local_length > 0) { String piece = text.subSequence(nonspace_offset, last_offset + 1).toString(); final int else_len = PyNames.ELSE.length(); if (local_length >= else_len) { if ((piece.startsWith(PyNames.ELSE) || piece.startsWith(PyNames.ELIF)) && (else_len == piece.length() || piece.charAt(else_len) < 'a' || piece.charAt(else_len) > 'z')) { parentClass = PyStatementWithElse.class; } } final int except_len = PyNames.EXCEPT.length(); if (local_length >= except_len) { if (piece.startsWith(PyNames.EXCEPT) && (except_len == piece.length() || piece.charAt(except_len) < 'a' || piece.charAt(except_len) > 'z')) { parentClass = PyTryExceptStatement.class; } } final int finally_len = PyNames.FINALLY.length(); if (local_length >= finally_len) { if (piece.startsWith(PyNames.FINALLY) && (finally_len == piece.length() || piece.charAt(finally_len) < 'a' || piece.charAt(finally_len) > 'z')) { parentClass = PyTryExceptStatement.class; } } } if (parentClass == null) return false; // failed PsiDocumentManager.getInstance(project).commitDocument(document); // reparse PsiElement token = file.findElementAt(offset - 2); // -1 is our ':'; -2 is even safer. PsiElement outer = PsiTreeUtil.getParentOfType(token, parentClass); if (outer != null) { int outer_offset = outer.getTextOffset(); int outer_indent = outer_offset - document.getLineStartOffset(document.getLineNumber(outer_offset)); assert outer_indent >= 0; int current_indent = nonspace_offset - line_start_offset; int indent = outer_indent - current_indent; EditorActionUtil.indentLine(project, editor, document.getLineNumber(offset), editor.getSettings().isUseTabCharacter(project) ? indent * editor.getSettings().getTabSize(project) : indent); return true; } return false; } // finds offset of first non-space in the line private static int findBeginning(int start_offset, CharSequence text) { int current_offset = start_offset; int text_length = text.length(); while (current_offset < text_length) { char current_char = text.charAt(current_offset); if (current_char != ' ' && current_char != '\t' && current_char != '\n') break; current_offset += 1; } return current_offset; } }