blob: 88cda8ed89ef1e980e4a2b0312a8551cd174d211 [file] [log] [blame]
/*
* 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
* *&#47;
* </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;
}
}
}