blob: 813c3eadd718312266898e85b34e52ddd7484fa9 [file] [log] [blame]
/*
* Copyright 2000-2013 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.codeInsight.editorActions;
import com.intellij.codeInsight.documentation.DocCommentFixer;
import com.intellij.lang.*;
import com.intellij.lang.documentation.CodeDocumentationProvider;
import com.intellij.lang.documentation.CompositeDocumentationProvider;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorAction;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
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.CodeStyleManager;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
/**
* Creates documentation comment for the current context if it's not created yet (e.g. the caret is inside a method which
* doesn't have a doc comment).
* <p/>
* Updates existing documentation comment if necessary if the one exists. E.g. we've changed method signature and want to remove all
* outdated parameters and create stubs for the new ones.
*
* @author Denis Zhdanov
* @since 9/20/12 10:15 AM
*/
public class FixDocCommentAction extends EditorAction {
@NotNull @NonNls public static final String ACTION_ID = "FixDocComment";
public FixDocCommentAction() {
super(new MyHandler());
}
private static final class MyHandler extends EditorActionHandler {
@Override
public void execute(Editor editor, DataContext dataContext) {
Project project = CommonDataKeys.PROJECT.getData(dataContext);
if (project == null) {
return;
}
PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(dataContext);
if (psiFile == null) {
return;
}
process(psiFile, editor, project, editor.getCaretModel().getOffset());
}
}
private static void process(@NotNull final PsiFile file, @NotNull final Editor editor, @NotNull final Project project, int offset) {
PsiElement elementAtOffset = file.findElementAt(offset);
if (elementAtOffset == null) {
return;
}
generateOrFixComment(elementAtOffset, project, editor);
}
/**
* Generates comment if it's not exist or try to fix if exists
*
* @param element target element for which a comment should be generated
* @param project current project
* @param editor target editor
*/
public static void generateOrFixComment(@NotNull final PsiElement element, @NotNull final Project project, @NotNull final Editor editor) {
Language language = element.getLanguage();
final CodeDocumentationProvider docProvider;
final DocumentationProvider langDocumentationProvider = LanguageDocumentation.INSTANCE.forLanguage(language);
if (langDocumentationProvider instanceof CompositeDocumentationProvider) {
docProvider = ((CompositeDocumentationProvider)langDocumentationProvider).getFirstCodeDocumentationProvider();
}
else if (langDocumentationProvider instanceof CodeDocumentationProvider) {
docProvider = (CodeDocumentationProvider)langDocumentationProvider;
}
else {
docProvider = null;
}
if (docProvider == null) {
return;
}
final Pair<PsiElement, PsiComment> pair = docProvider.parseContext(element);
if (pair == null) {
return;
}
Commenter c = LanguageCommenters.INSTANCE.forLanguage(language);
if (!(c instanceof CodeDocumentationAwareCommenter)) {
return;
}
final CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter)c;
final Runnable task;
if (pair.second == null || pair.second.getTextRange().isEmpty()) {
task = new Runnable() {
@Override
public void run() {
generateComment(pair.first, editor, docProvider, commenter, project);
}
};
}
else {
final DocCommentFixer fixer = DocCommentFixer.EXTENSION.forLanguage(language);
if (fixer == null) {
return;
}
else {
task = new Runnable() {
@Override
public void run() {
fixer.fixComment(project, editor, pair.second);
}
};
}
}
final Runnable command = new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(task);
}
};
CommandProcessor.getInstance().executeCommand(project, command, "Fix documentation", null);
}
/**
* Generates a comment if possible.
* <p/>
* It's assumed that this method {@link PsiDocumentManager#commitDocument(Document) syncs} all PSI-document
* changes during the processing.
*
* @param anchor target element for which a comment should be generated
* @param editor target editor
* @param commenter commenter to use
* @param project current project
*/
private static void generateComment(@NotNull PsiElement anchor,
@NotNull Editor editor,
@NotNull CodeDocumentationProvider documentationProvider,
@NotNull CodeDocumentationAwareCommenter commenter,
@NotNull Project project)
{
Document document = editor.getDocument();
int commentStartOffset = anchor.getTextRange().getStartOffset();
int lineStartOffset = document.getLineStartOffset(document.getLineNumber(commentStartOffset));
if (lineStartOffset > 0 && lineStartOffset < commentStartOffset) {
// Example:
// void test1() {
// }
// void test2() {
// <offset>
// }
// We want to insert the comment at the start of the line where 'test2()' is declared.
int nonWhiteSpaceOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), commentStartOffset - 1, " \t");
commentStartOffset = Math.max(nonWhiteSpaceOffset, lineStartOffset);
}
int commentBodyRelativeOffset = 0;
int caretOffsetToSet = -1;
StringBuilder buffer = new StringBuilder();
String commentPrefix = commenter.getDocumentationCommentPrefix();
if (commentPrefix != null) {
buffer.append(commentPrefix).append("\n");
commentBodyRelativeOffset += commentPrefix.length() + 1;
}
String linePrefix = commenter.getDocumentationCommentLinePrefix();
if (linePrefix != null) {
buffer.append(linePrefix);
commentBodyRelativeOffset += linePrefix.length();
caretOffsetToSet = commentStartOffset + commentBodyRelativeOffset;
}
buffer.append("\n");
commentBodyRelativeOffset++;
String commentSuffix = commenter.getDocumentationCommentSuffix();
if (commentSuffix != null) {
buffer.append(commentSuffix).append("\n");
}
if (buffer.length() <= 0) {
return;
}
document.insertString(commentStartOffset, buffer);
PsiDocumentManager docManager = PsiDocumentManager.getInstance(project);
docManager.commitDocument(document);
Pair<PsiElement, PsiComment> pair = documentationProvider.parseContext(anchor);
if (pair == null || pair.second == null) {
return;
}
String stub = documentationProvider.generateDocumentationContentStub(pair.second);
CaretModel caretModel = editor.getCaretModel();
if (stub != null) {
int insertionOffset = commentStartOffset + commentBodyRelativeOffset;
//if (CodeStyleSettingsManager.getSettings(project).JD_ADD_BLANK_AFTER_DESCRIPTION) {
// buffer.setLength(0);
// if (linePrefix != null) {
// buffer.append(linePrefix);
// }
// buffer.append("\n");
// buffer.append(stub);
// stub = buffer.toString();
//}
document.insertString(insertionOffset, stub);
docManager.commitDocument(document);
pair = documentationProvider.parseContext(anchor);
}
if (caretOffsetToSet >= 0) {
caretModel.moveToOffset(caretOffsetToSet);
}
if (pair == null || pair.second == null) {
return;
}
int start = Math.min(calcStartReformatOffset(pair.first), calcStartReformatOffset(pair.second));
int end = pair.second.getTextRange().getEndOffset();
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
codeStyleManager.reformatText(anchor.getContainingFile(), start, end);
int caretOffset = caretModel.getOffset();
if (caretOffset > 0 && caretOffset <= document.getTextLength()) {
char c = document.getCharsSequence().charAt(caretOffset - 1);
if (!StringUtil.isWhiteSpace(c)) {
document.insertString(caretOffset, " ");
caretModel.moveToOffset(caretOffset + 1);
}
}
}
private static int calcStartReformatOffset(@NotNull PsiElement element) {
int result = element.getTextRange().getStartOffset();
for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
if (e instanceof PsiWhiteSpace) {
result = e.getTextRange().getStartOffset();
}
else {
break;
}
}
return result;
}
}