| /* |
| * 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.intellij.javadoc; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.editor.CaretModel; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.LogicalPosition; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleSettings; |
| import com.intellij.psi.codeStyle.CodeStyleSettingsManager; |
| import com.intellij.psi.javadoc.PsiDocTag; |
| import com.intellij.psi.javadoc.PsiDocTagValue; |
| import com.intellij.psi.javadoc.PsiDocToken; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * This class is not singleton but provides {@link #getInstance() single-point-of-usage field}. |
| * |
| * @author Denis Zhdanov |
| * @since 5/27/11 2:35 PM |
| */ |
| public class JavadocHelper { |
| |
| private static final String PARAM_TEXT = "param"; |
| |
| private static final Pair<JavadocParameterInfo, List<JavadocParameterInfo>> EMPTY |
| = new Pair<JavadocParameterInfo, List<JavadocParameterInfo>>(null, Collections.<JavadocParameterInfo>emptyList()); |
| private static final JavadocHelper INSTANCE = new JavadocHelper(); |
| |
| @NotNull |
| public static JavadocHelper getInstance() { |
| return INSTANCE; |
| } |
| |
| /** |
| * Tries to navigate caret at the given editor to the target position inserting missing white spaces if necessary. |
| * |
| * @param position target caret position |
| * @param editor target editor |
| * @param project target project |
| */ |
| @SuppressWarnings("MethodMayBeStatic") |
| public void navigate(@NotNull LogicalPosition position, @NotNull Editor editor, @NotNull final Project project) { |
| final Document document = editor.getDocument(); |
| final CaretModel caretModel = editor.getCaretModel(); |
| final int endLineOffset = document.getLineEndOffset(position.line); |
| final LogicalPosition endLinePosition = editor.offsetToLogicalPosition(endLineOffset); |
| if (endLinePosition.column < position.column && !editor.getSettings().isVirtualSpace() && !editor.isViewer()) { |
| final String toInsert = StringUtil.repeat(" ", position.column - endLinePosition.column); |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| document.insertString(endLineOffset, toInsert); |
| PsiDocumentManager.getInstance(project).commitDocument(document); |
| } |
| }); |
| |
| } |
| caretModel.moveToLogicalPosition(position); |
| } |
| |
| /** |
| * Calculates desired position of target javadoc parameter's description start. |
| * |
| * @param psiFile PSI holder |
| * @param data parsed adjacent javadoc parameters |
| * @param anchor descriptor for the target parameter |
| * @return logical position that points to the desired parameter description start location |
| */ |
| @SuppressWarnings("MethodMayBeStatic") |
| @NotNull |
| public LogicalPosition calculateDescriptionStartPosition(@NotNull PsiFile psiFile, |
| @NotNull Collection<JavadocParameterInfo> data, |
| @NotNull JavadocHelper.JavadocParameterInfo anchor) |
| { |
| int descriptionStartColumn = -1; |
| int parameterNameEndColumn = -1; |
| for (JavadocHelper.JavadocParameterInfo parameterInfo : data) { |
| parameterNameEndColumn = Math.max(parameterNameEndColumn, parameterInfo.parameterNameEndPosition.column); |
| if (parameterInfo.parameterDescriptionStartPosition != null) { |
| descriptionStartColumn = Math.max(descriptionStartColumn, parameterInfo.parameterDescriptionStartPosition.column); |
| } |
| } |
| |
| final CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getInstance(psiFile.getProject()).getCurrentSettings(); |
| final int indentSize = codeStyleSettings.getIndentSize(psiFile.getFileType()); |
| int column; |
| if (codeStyleSettings.JD_ALIGN_PARAM_COMMENTS) { |
| column = Math.max(descriptionStartColumn, parameterNameEndColumn); |
| if (column <= parameterNameEndColumn) { |
| column = parameterNameEndColumn + indentSize; |
| } |
| } |
| else { |
| column = anchor.parameterNameEndPosition.column + indentSize; |
| } |
| return new LogicalPosition(anchor.parameterNameEndPosition.line, column); |
| } |
| |
| /** |
| * Returns information about all lines that contain javadoc parameters and are adjacent to the one that holds given offset. |
| * |
| * @param psiFile PSI holder for the document exposed the given editor |
| * @param editor target editor |
| * @param offset target offset that identifies anchor line to check |
| * @return pair like (javadoc info for the line identified by the given offset; list of javadoc parameter infos for |
| * adjacent lines if any |
| */ |
| @SuppressWarnings("MethodMayBeStatic") |
| @NotNull |
| public Pair<JavadocParameterInfo, List<JavadocParameterInfo>> parse(@NotNull PsiFile psiFile, @NotNull Editor editor, int offset) { |
| List<JavadocParameterInfo> result = new ArrayList<JavadocParameterInfo>(); |
| final PsiElement elementAtCaret = psiFile.findElementAt(offset); |
| if (elementAtCaret == null) { |
| return EMPTY; |
| } |
| |
| PsiDocTag tag = PsiTreeUtil.getParentOfType(elementAtCaret, PsiDocTag.class); |
| if (tag == null) { |
| // Due to javadoc PSI specifics. |
| if (elementAtCaret instanceof PsiWhiteSpace) { |
| for (PsiElement e = elementAtCaret.getPrevSibling(); e != null && tag == null; e = e.getPrevSibling()) { |
| tag = PsiTreeUtil.getParentOfType(e, PsiDocTag.class, false); |
| if (e instanceof PsiWhiteSpace |
| || (e instanceof PsiDocToken && ((PsiDocToken)e).getTokenType() == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS)) |
| { |
| continue; |
| } |
| break; |
| } |
| } |
| } |
| if (tag == null) { |
| return EMPTY; |
| } |
| |
| JavadocParameterInfo anchorInfo = parse(tag, editor); |
| if (anchorInfo == null) { |
| return EMPTY; |
| } |
| |
| // Parse previous parameters. |
| for (PsiElement e = tag.getPrevSibling(); e != null; e = e.getPrevSibling()) { |
| JavadocParameterInfo info = parse(e, editor); |
| if (info == null) { |
| break; |
| } |
| result.add(0, info); |
| } |
| |
| result.add(anchorInfo); |
| |
| // Parse subsequent parameters. |
| for (PsiElement e = tag.getNextSibling(); e != null; e = e.getNextSibling()) { |
| JavadocParameterInfo info = parse(e, editor); |
| if (info == null) { |
| break; |
| } |
| result.add(info); |
| } |
| |
| return Pair.create(anchorInfo, result); |
| } |
| |
| @Nullable |
| private static JavadocParameterInfo parse(@NotNull PsiElement element, @NotNull Editor editor) { |
| final PsiDocTag tag = PsiTreeUtil.getParentOfType(element, PsiDocTag.class, false); |
| if (tag == null || !PARAM_TEXT.equals(tag.getName())) { |
| return null; |
| } |
| |
| final PsiDocTagValue paramRef = PsiTreeUtil.getChildOfType(tag, PsiDocTagValue.class); |
| if (paramRef == null) { |
| return null; |
| } |
| |
| for (PsiElement e = paramRef.getNextSibling(); e != null; e = e.getNextSibling()) { |
| final ASTNode node = e.getNode(); |
| if (node == null) { |
| break; |
| } |
| final IElementType elementType = node.getElementType(); |
| if (elementType == JavaDocTokenType.DOC_COMMENT_DATA) { |
| return new JavadocParameterInfo( |
| editor.offsetToLogicalPosition(paramRef.getTextRange().getEndOffset()), |
| editor.offsetToLogicalPosition(e.getTextRange().getStartOffset()), |
| editor.getDocument().getLineNumber(e.getTextRange().getEndOffset()) |
| ); |
| } |
| else if (elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) { |
| break; |
| } |
| } |
| return new JavadocParameterInfo( |
| editor.offsetToLogicalPosition(paramRef.getTextRange().getEndOffset()), |
| null, |
| editor.getDocument().getLineNumber(paramRef.getTextRange().getEndOffset()) |
| ); |
| } |
| |
| /** |
| * Encapsulates information about source code line that holds javadoc parameter. |
| */ |
| public static class JavadocParameterInfo { |
| |
| /** |
| * Logical position that points to location just after javadoc parameter name. |
| * <p/> |
| * Example: |
| * <pre> |
| * /** |
| * * @param i[X] description |
| * */ |
| * </pre> |
| */ |
| @NotNull public final LogicalPosition parameterNameEndPosition; |
| @Nullable public final LogicalPosition parameterDescriptionStartPosition; |
| /** Last logical line occupied by the current javadoc parameter. */ |
| public final int lastLine; |
| |
| public JavadocParameterInfo(@NotNull LogicalPosition parameterNameEndPosition, |
| LogicalPosition parameterDescriptionStartPosition, |
| int lastLine) |
| { |
| this.parameterNameEndPosition = parameterNameEndPosition; |
| this.parameterDescriptionStartPosition = parameterDescriptionStartPosition; |
| this.lastLine = lastLine; |
| } |
| |
| @Override |
| public String toString() { |
| return "name end: " + parameterNameEndPosition + ", description start: " + parameterDescriptionStartPosition; |
| } |
| } |
| } |