/* * Copyright 2013-2017 consulo.io * * 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 consulo.csharp.ide.completion; import org.jetbrains.annotations.NotNull; import consulo.annotations.RequiredDispatchThread; import consulo.annotations.RequiredReadAction; import consulo.csharp.lang.doc.psi.CSharpDocRoot; import consulo.csharp.lang.psi.CSharpFile; import consulo.csharp.lang.psi.CSharpReferenceExpression; import consulo.csharp.lang.psi.CSharpTokens; import consulo.csharp.lang.psi.UsefulPsiTreeUtil; import consulo.dotnet.DotNetTypes; import consulo.dotnet.psi.DotNetExpression; import consulo.dotnet.psi.DotNetGenericParameter; import consulo.dotnet.psi.DotNetGenericParameterListOwner; import consulo.dotnet.psi.DotNetMethodDeclaration; import consulo.dotnet.psi.DotNetParameter; import consulo.dotnet.psi.DotNetParameterListOwner; import consulo.dotnet.psi.DotNetQualifiedElement; import consulo.dotnet.resolve.DotNetPointerTypeRef; import consulo.dotnet.resolve.DotNetTypeRef; import consulo.dotnet.resolve.DotNetTypeRefUtil; import com.intellij.codeInsight.AutoPopupController; import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; import com.intellij.codeStyle.CodeStyleFacade; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; 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; /** * @author VISTALL * @since 06.01.14 */ public class CSharpTypedHandler extends TypedHandlerDelegate { @Override @RequiredDispatchThread public Result beforeCharTyped(char c, Project project, Editor editor, PsiFile file, FileType fileType) { if(!(file instanceof CSharpFile)) { return Result.CONTINUE; } if(c == '.') { if(handleDotAtPointerType(editor, file)) { return Result.STOP; } autoPopupMemberLookup(project, editor); } if(c == '#') { autoPopupMemberLookup(project, editor); } if(c == ';') { if(handleSemicolon(editor)) { return Result.STOP; } } return Result.CONTINUE; } @Override @RequiredDispatchThread public Result charTyped(char c, Project project, Editor editor, @NotNull PsiFile file) { if(c == '/') { if(DumbService.isDumb(file.getProject())) { return Result.CONTINUE; } int offset = editor.getCaretModel().getOffset(); PsiElement elementAt = file.findElementAt(offset - 1); if(elementAt == null) { return Result.CONTINUE; } PsiElement prevSibling = elementAt.getPrevSibling(); if(prevSibling != null && prevSibling.getNode().getElementType() == CSharpTokens.LINE_COMMENT) { PsiElement prevSiblingSkipWhiteSpaces = UsefulPsiTreeUtil.getPrevSiblingSkipWhiteSpaces(prevSibling, true); // we skip if prev token is doc, or whe have already filed comment if(prevSiblingSkipWhiteSpaces instanceof CSharpDocRoot || prevSibling.getTextLength() != 2) { return Result.CONTINUE; } PsiElement nextSibling = elementAt.getNextSibling(); if(nextSibling instanceof DotNetQualifiedElement) { Document document = editor.getDocument(); int lineOfComment = document.getLineNumber(prevSibling.getTextOffset()); int lineOfMember = document.getLineNumber(nextSibling.getTextOffset()); // if we have one than one between elements - skip it if((lineOfMember - lineOfComment) > 1) { return Result.CONTINUE; } Pair<CharSequence, Integer> pair = buildDocComment((DotNetQualifiedElement) nextSibling, editor, offset); document.insertString(offset, pair.getFirst()); editor.getCaretModel().moveToOffset(pair.getSecond()); PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); PsiElement pos = file.findElementAt(pair.getSecond()); assert pos != null; CSharpDocRoot docRoot = PsiTreeUtil.getParentOfType(pos, CSharpDocRoot.class); assert docRoot != null; CodeStyleManager.getInstance(project).reformat(docRoot); } } } return Result.CONTINUE; } @NotNull @RequiredDispatchThread private static Pair<CharSequence, Integer> buildDocComment(DotNetQualifiedElement qualifiedElement, Editor editor, int offset) { String lineIndent = CodeStyleFacade.getInstance(qualifiedElement.getProject()).getLineIndent(editor.getDocument(), offset); int diffForCaret; StringBuilder builder = new StringBuilder(" <summary>\n"); builder.append(lineIndent).append("/// "); diffForCaret = offset + builder.length(); builder.append('\n'); builder.append(lineIndent).append("/// </summary>\n"); if(qualifiedElement instanceof DotNetGenericParameterListOwner) { DotNetGenericParameter[] parameters = ((DotNetGenericParameterListOwner) qualifiedElement).getGenericParameters(); for(DotNetGenericParameter parameter : parameters) { String name = parameter.getName(); builder.append(lineIndent).append("///").append(" <typeparam name=\""); if(name != null) { builder.append(name); } builder.append("\"></typeparam>\n"); } } if(qualifiedElement instanceof DotNetParameterListOwner) { DotNetParameter[] parameters = ((DotNetParameterListOwner) qualifiedElement).getParameters(); for(DotNetParameter parameter : parameters) { String name = parameter.getName(); builder.append(lineIndent).append("///").append(" <param name=\""); if(name != null) { builder.append(name); } builder.append("\"></param>\n"); } } if(qualifiedElement instanceof DotNetMethodDeclaration) { DotNetTypeRef returnTypeRef = ((DotNetMethodDeclaration) qualifiedElement).getReturnTypeRef(); if(!DotNetTypeRefUtil.isVmQNameEqual(returnTypeRef, qualifiedElement, DotNetTypes.System.Void)) { builder.append(lineIndent).append("/// <returns></returns>"); } } // if last char is new line, remove it if(builder.charAt(builder.length() - 1) == '\n') { builder.deleteCharAt(builder.length() - 1); } return Pair.<CharSequence, Integer>create(builder, diffForCaret); } @RequiredDispatchThread private static boolean handleDotAtPointerType(Editor editor, PsiFile file) { if(DumbService.isDumb(file.getProject())) { return false; } int offset = editor.getCaretModel().getOffset(); PsiElement lastElement = file.findElementAt(offset - 1); if(lastElement == null) { return false; } DotNetExpression expression = null; if(lastElement.getParent() instanceof DotNetExpression) { expression = (DotNetExpression) lastElement.getParent(); } if(expression == null) { return false; } DotNetTypeRef typeRef = expression.toTypeRef(true); if(typeRef instanceof DotNetPointerTypeRef) { editor.getDocument().insertString(offset, "->"); editor.getCaretModel().moveToOffset(offset + 2); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); return true; } return false; } private static boolean handleSemicolon(Editor editor) { int offset = editor.getCaretModel().getOffset(); if(offset == editor.getDocument().getTextLength()) { return false; } char charAt = editor.getDocument().getCharsSequence().charAt(offset); if(charAt != ';') { return false; } editor.getCaretModel().moveToOffset(offset + 1); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); return true; } @RequiredDispatchThread private static void autoPopupMemberLookup(Project project, final Editor editor) { AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, new Condition<PsiFile>() { @Override @RequiredReadAction public boolean value(final PsiFile file) { int offset = editor.getCaretModel().getOffset(); PsiElement lastElement = file.findElementAt(offset - 1); if(lastElement == null) { return false; } final PsiElement prevSibling = PsiTreeUtil.prevVisibleLeaf(lastElement); if(prevSibling == null || ".".equals(prevSibling.getText())) { return false; } PsiElement parent = prevSibling; do { parent = parent.getParent(); } while(parent instanceof CSharpReferenceExpression); return true; } }); } }