package com.linkedin.intellij.dust; import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.TextRange; import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.util.PsiTreeUtil; import com.linkedin.intellij.dust.psi.*; import org.jetbrains.annotations.NotNull; /** * Created with IntelliJ IDEA. * User: yzhang * Date: 6/4/13 * Time: 1:53 PM * * Based on the intellij mustache plugin */ public class DustTypedHandler extends TypedHandlerDelegate{ // @Override // public Result beforeCharTyped(char c, Project project, Editor editor, PsiFile file, FileType fileType) { // int offset = editor.getCaretModel().getOffset(); // // if (offset == 0 || offset > editor.getDocument().getTextLength()) { // return Result.CONTINUE; // } // // String previousChar = editor.getDocument().getText(new TextRange(offset - 1, offset)); // // if (file.getViewProvider() instanceof DustFileViewProvider) { // PsiDocumentManager.getInstance(project).commitAllDocuments(); // // // we suppress the built-in "}" auto-complete when we see "{{" // if (c == '{' && previousChar.equals("{")) { // // since the "}" autocomplete is built in to IDEA, we need to hack around it a bit by // // intercepting it before it is inserted, doing the work of inserting for the user // // by inserting the '{' the user just typed... // editor.getDocument().insertString(offset, Character.toString(c)); // // ... and position their caret after it as they'd expect... // editor.getCaretModel().moveToOffset(offset + 1); // // // ... then finally telling subsequent responses to this charTyped to do nothing // return Result.STOP; // } // } // // return Result.CONTINUE; // } @Override public Result charTyped(char c, Project project, Editor editor, @NotNull PsiFile file) { int offset = editor.getCaretModel().getOffset(); FileViewProvider provider = file.getViewProvider(); if (offset < 2 || offset > editor.getDocument().getTextLength()) { return Result.CONTINUE; } String previousChar = editor.getDocument().getText(new TextRange(offset - 2, offset - 1)); if (file.getViewProvider() instanceof DustFileViewProvider) { // if we're looking at a closing brace, then auto complete the closing tag if (c == '}' && !previousChar.equals("\\") && !previousChar.equals("/")) { autoInsertCloseTag(project, offset, editor, provider); adjustFormatting(project, offset, editor, file, provider); } } return Result.CONTINUE; } /** * When appropriate, auto-inserts closing tags. i.e. When "{#tagName}" or "{^tagName} is typed, * {/tagName} is automatically inserted */ private void autoInsertCloseTag(Project project, int offset, Editor editor, FileViewProvider provider) { PsiDocumentManager.getInstance(project).commitAllDocuments(); PsiElement elementAtCaret = provider.findElementAt(offset - 1, DustLanguage.class); PsiElement openTag = DustPsiUtil.findParentOpenTagElement(elementAtCaret); String tagName = getTagName(openTag); if (!tagName.trim().equals("")) { boolean addCloseTag = true; PsiElement sibling = openTag.getNextSibling(); DustCloseTag closeTag; while (sibling != null && addCloseTag) { if (sibling instanceof DustCloseTag) { closeTag = (DustCloseTag) sibling; if (getTagName(closeTag).equals(tagName)) { addCloseTag = false; } } sibling = sibling.getNextSibling(); } if (addCloseTag) { // insert the corresponding close tag editor.getDocument().insertString(offset, "{/" + tagName + "}"); } } } private String getTagName(PsiElement tag) { String tagName = ""; if (tag != null && tag.getChildren().length > 1) { PsiElement[] children = tag.getChildren(); if (children.length > 0) { PsiElement tagNameEl = children[0]; if (tagNameEl != null && tagNameEl.getNode().getElementType() == DustTypes.TAG_NAME) { tagName += tagNameEl.getText(); if (children.length > 1) { PsiElement indexDeref = children[1]; if (indexDeref != null && indexDeref.getNode().getElementType() == DustTypes.INDEX_DEREF) { tagName += indexDeref.getText(); } } } } } return tagName; } /** * When appropriate, automatically reduce the indentation for else tags "{:else}" */ private void adjustFormatting(Project project, int offset, Editor editor, PsiFile file, FileViewProvider provider) { PsiElement elementAtCaret = provider.findElementAt(offset - 1, DustLanguage.class); PsiElement elseParent = PsiTreeUtil.findFirstParent(elementAtCaret, true, new Condition<PsiElement>() { @Override public boolean value(PsiElement element) { return element != null && (element instanceof DustElseTag); } }); // run the formatter if the user just completed typing a SIMPLE_INVERSE or a CLOSE_BLOCK_STACHE if (elseParent != null) { // grab the current caret position (AutoIndentLinesHandler is about to mess with it) PsiDocumentManager.getInstance(project).commitAllDocuments(); CaretModel caretModel = editor.getCaretModel(); CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); codeStyleManager.adjustLineIndent(file, editor.getDocument().getLineStartOffset(caretModel.getLogicalPosition().line)); } } }