join lines: convert end-of-line comments to block comments when possible (IDEABKL-290)

This commit is contained in:
Dmitry Jemerov
2015-02-11 14:42:27 +01:00
parent 93af2752b9
commit 9dee6e8ff3
6 changed files with 121 additions and 45 deletions

View File

@@ -0,0 +1,7 @@
class Foo {
void f() {
if (ff) <caret>{ //comment
return null;
}
}
}

View File

@@ -0,0 +1,6 @@
class Foo {
void f() {
if (ff) { /*comment*/ return null;
}
}
}

View File

@@ -201,6 +201,10 @@ public class JoinLinesTest extends LightCodeInsightTestCase {
public void testLeaveTrailingComment() throws Exception { doTest(); }
public void testConvertComment() throws Exception {
doTest();
}
private void doTest() throws Exception {
doTest(".java");
}

View File

@@ -39,11 +39,17 @@ public interface PsiParserFacade {
PsiElement createWhiteSpaceFromText(@NotNull @NonNls String s) throws IncorrectOperationException;
/**
* Creates a comment for the specified language.
* Creates a line comment for the specified language.
*/
@NotNull
PsiComment createLineCommentFromText(@NotNull LanguageFileType fileType, @NotNull String text) throws IncorrectOperationException;
/**
* Creates a line comment for the specified language.
*/
@NotNull
PsiComment createBlockCommentFromText(@NotNull Language language, @NotNull String text) throws IncorrectOperationException;
/**
* Creates a line comment for the specified language or block comment if language doesn't support line ones
*/

View File

@@ -63,15 +63,20 @@ public class PsiParserFacadeImpl implements PsiParserFacade {
}
PsiFile aFile = createDummyFile(prefix + text, fileType);
PsiElement[] children = aFile.getChildren();
for (PsiElement aChildren : children) {
if (aChildren instanceof PsiComment) {
PsiComment comment = (PsiComment)aChildren;
DummyHolderFactory.createHolder(myManager, (TreeElement)SourceTreeToPsiMap.psiElementToTree(comment), null);
return comment;
}
}
throw new IncorrectOperationException("Incorrect comment \"" + text + "\".");
return findPsiCommentChild(aFile);
}
@NotNull
@Override
public PsiComment createBlockCommentFromText(@NotNull Language language, @NotNull String text) throws IncorrectOperationException {
Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(language);
assert commenter != null : language;
final String blockCommentPrefix = commenter.getBlockCommentPrefix();
final String blockCommentSuffix = commenter.getBlockCommentSuffix();
PsiFile aFile = PsiFileFactory.getInstance(myManager.getProject()).createFileFromText("_Dummy_", language,
(blockCommentPrefix + text + blockCommentSuffix));
return findPsiCommentChild(aFile);
}
@Override
@@ -86,6 +91,10 @@ public class PsiParserFacadeImpl implements PsiParserFacade {
assert prefix != null || (blockCommentPrefix != null && blockCommentSuffix != null);
PsiFile aFile = PsiFileFactory.getInstance(myManager.getProject()).createFileFromText("_Dummy_", lang, prefix != null ? (prefix + text) : (blockCommentPrefix + text + blockCommentSuffix));
return findPsiCommentChild(aFile);
}
private PsiComment findPsiCommentChild(PsiFile aFile) {
PsiElement[] children = aFile.getChildren();
for (PsiElement aChildren : children) {
if (aChildren instanceof PsiComment) {
@@ -94,7 +103,7 @@ public class PsiParserFacadeImpl implements PsiParserFacade {
return comment;
}
}
throw new IncorrectOperationException("Incorrect comment \"" + text + "\".");
throw new IncorrectOperationException("Incorrect comment \"" + aFile.getText() + "\".");
}
protected PsiFile createDummyFile(String text, final LanguageFileType fileType) {

View File

@@ -26,23 +26,21 @@ package com.intellij.codeInsight.editorActions;
import com.intellij.ide.DataManager;
import com.intellij.lang.ASTNode;
import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.Commenter;
import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.util.PsiTreeUtil;
@@ -126,36 +124,24 @@ public class JoinLinesHandler extends EditorWriteActionHandler {
// joining lines, several times if selection is multiline
for (int i = startLine; i < endLine; i++) {
if (i >= doc.getLineCount() - 1) break;
int lineEndOffset = doc.getLineEndOffset(startLine);
docManager.doPostponedOperationsAndUnblockDocument(doc);
docManager.commitDocument(doc);
CharSequence text = doc.getCharsSequence();
int firstNonSpaceOffsetInNextLine = doc.getLineStartOffset(startLine + 1);
while (firstNonSpaceOffsetInNextLine < text.length() - 1
&& (text.charAt(firstNonSpaceOffsetInNextLine) == ' ' || text.charAt(firstNonSpaceOffsetInNextLine) == '\t'))
{
firstNonSpaceOffsetInNextLine++;
}
PsiElement elementAtNextLineStart = psiFile.findElementAt(firstNonSpaceOffsetInNextLine);
boolean isNextLineStartsWithComment = isCommentElement(elementAtNextLineStart);
JoinLinesOffsets offsets = calcJoinLinesOffsets(psiFile, doc, startLine);
int lastNonSpaceOffsetInStartLine = lineEndOffset;
while (lastNonSpaceOffsetInStartLine > 0 &&
(text.charAt(lastNonSpaceOffsetInStartLine - 1) == ' ' || text.charAt(lastNonSpaceOffsetInStartLine - 1) == '\t')) {
lastNonSpaceOffsetInStartLine--;
if (offsets.isStartLineEndsWithComment && !offsets.isNextLineStartsWithComment) {
tryConvertEndOfLineComment(doc, offsets.elementAtStartLineEnd);
offsets = calcJoinLinesOffsets(psiFile, doc, startLine);
}
int elemOffset = lastNonSpaceOffsetInStartLine > doc.getLineStartOffset(startLine) ? lastNonSpaceOffsetInStartLine - 1 : -1;
PsiElement elementAtStartLineEnd = elemOffset == -1 ? null : psiFile.findElementAt(elemOffset);
boolean isStartLineEndsWithComment = isCommentElement(elementAtStartLineEnd);
int rc = -1;
int start;
int end;
TextRange limits = findStartAndEnd(text, lastNonSpaceOffsetInStartLine, firstNonSpaceOffsetInNextLine, doc.getTextLength());
TextRange limits = findStartAndEnd(text, offsets.lastNonSpaceOffsetInStartLine, offsets.firstNonSpaceOffsetInNextLine, doc.getTextLength());
start = limits.getStartOffset(); end = limits.getEndOffset();
// run raw joiners
for(JoinLinesHandlerDelegate delegate: Extensions.getExtensions(JoinLinesHandlerDelegate.EP_NAME)) {
for (JoinLinesHandlerDelegate delegate: Extensions.getExtensions(JoinLinesHandlerDelegate.EP_NAME)) {
if (delegate instanceof JoinRawLinesHandlerDelegate) {
rc = ((JoinRawLinesHandlerDelegate)delegate).tryJoinRawLines(doc, psiFile, start, end);
if (rc != CANNOT_JOIN) {
@@ -165,8 +151,8 @@ public class JoinLinesHandler extends EditorWriteActionHandler {
}
}
if (rc == CANNOT_JOIN) { // remove indents and newline, run non-raw joiners
if (lastNonSpaceOffsetInStartLine == doc.getLineStartOffset(startLine)) {
doc.deleteString(doc.getLineStartOffset(startLine), firstNonSpaceOffsetInNextLine);
if (offsets.lastNonSpaceOffsetInStartLine == doc.getLineStartOffset(startLine)) {
doc.deleteString(doc.getLineStartOffset(startLine), offsets.firstNonSpaceOffsetInNextLine);
int indent = -1;
try {
@@ -184,10 +170,10 @@ public class JoinLinesHandler extends EditorWriteActionHandler {
continue;
}
doc.deleteString(lineEndOffset, lineEndOffset + doc.getLineSeparatorLength(startLine));
doc.deleteString(offsets.lineEndOffset, offsets.lineEndOffset + doc.getLineSeparatorLength(startLine));
text = doc.getCharsSequence();
limits = findStartAndEnd(text, lineEndOffset - 1, lineEndOffset, doc.getTextLength());
limits = findStartAndEnd(text, offsets.lineEndOffset - 1, offsets.lineEndOffset, doc.getTextLength());
start = limits.getStartOffset(); end = limits.getEndOffset();
// Check if we're joining splitted string literal.
@@ -206,10 +192,10 @@ public class JoinLinesHandler extends EditorWriteActionHandler {
}
if (caretRestoreOffset == CANNOT_JOIN) caretRestoreOffset = start == lineEndOffset ? start : start + 1;
if (caretRestoreOffset == CANNOT_JOIN) caretRestoreOffset = start == offsets.lineEndOffset ? start : start + 1;
if (isStartLineEndsWithComment && isNextLineStartsWithComment) {
if (offsets.isStartLineEndsWithComment && offsets.isNextLineStartsWithComment) {
if (text.charAt(end) == '*' && end < text.length() && text.charAt(end + 1) != '/') {
end++;
while (end < doc.getTextLength() && (text.charAt(end) == ' ' || text.charAt(end) == '\t')) end++;
@@ -219,12 +205,12 @@ public class JoinLinesHandler extends EditorWriteActionHandler {
while (end < doc.getTextLength() && (text.charAt(end) == ' ' || text.charAt(end) == '\t')) end++;
}
doc.replaceString(start == lineEndOffset ? start : start + 1, end, " ");
doc.replaceString(start == offsets.lineEndOffset ? start : start + 1, end, " ");
continue;
}
while (end < doc.getTextLength() && (text.charAt(end) == ' ' || text.charAt(end) == '\t')) end++;
doc.replaceString(start == lineEndOffset ? start : start + 1, end, " ");
doc.replaceString(start == offsets.lineEndOffset ? start : start + 1, end, " ");
if (start <= doc.getLineStartOffset(startLine)) {
try {
@@ -275,6 +261,64 @@ public class JoinLinesHandler extends EditorWriteActionHandler {
}
}
private static class JoinLinesOffsets {
int lineEndOffset;
int lastNonSpaceOffsetInStartLine;
int firstNonSpaceOffsetInNextLine;
boolean isStartLineEndsWithComment;
boolean isNextLineStartsWithComment;
PsiElement elementAtStartLineEnd;
}
private static JoinLinesOffsets calcJoinLinesOffsets(PsiFile psiFile, Document doc, int startLine) {
JoinLinesOffsets offsets = new JoinLinesOffsets();
CharSequence text = doc.getCharsSequence();
offsets.lineEndOffset = doc.getLineEndOffset(startLine);
offsets.firstNonSpaceOffsetInNextLine = doc.getLineStartOffset(startLine + 1);
while (offsets.firstNonSpaceOffsetInNextLine < text.length() - 1
&& (text.charAt(offsets.firstNonSpaceOffsetInNextLine) == ' ' || text.charAt(offsets.firstNonSpaceOffsetInNextLine) == '\t'))
{
offsets.firstNonSpaceOffsetInNextLine++;
}
PsiElement elementAtNextLineStart = psiFile.findElementAt(offsets.firstNonSpaceOffsetInNextLine);
offsets.isNextLineStartsWithComment = isCommentElement(elementAtNextLineStart);
offsets.lastNonSpaceOffsetInStartLine = offsets.lineEndOffset;
while (offsets.lastNonSpaceOffsetInStartLine > 0 &&
(text.charAt(offsets.lastNonSpaceOffsetInStartLine - 1) == ' ' || text.charAt(offsets.lastNonSpaceOffsetInStartLine - 1) == '\t')) {
offsets.lastNonSpaceOffsetInStartLine--;
}
int elemOffset = offsets.lastNonSpaceOffsetInStartLine > doc.getLineStartOffset(startLine) ? offsets.lastNonSpaceOffsetInStartLine - 1 : -1;
offsets.elementAtStartLineEnd = elemOffset == -1 ? null : psiFile.findElementAt(elemOffset);
offsets.isStartLineEndsWithComment = isCommentElement(offsets.elementAtStartLineEnd);
return offsets;
}
private static void tryConvertEndOfLineComment(Document doc, PsiElement commentElement) {
Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(commentElement.getLanguage());
if (commenter instanceof CodeDocumentationAwareCommenter) {
CodeDocumentationAwareCommenter docCommenter = (CodeDocumentationAwareCommenter) commenter;
String lineCommentPrefix = commenter.getLineCommentPrefix();
String blockCommentPrefix = commenter.getBlockCommentPrefix();
String blockCommentSuffix = commenter.getBlockCommentSuffix();
if (commentElement.getNode().getElementType() == docCommenter.getLineCommentTokenType() &&
blockCommentPrefix != null && blockCommentSuffix != null && lineCommentPrefix != null) {
String commentText = StringUtil.trimStart(commentElement.getText(), lineCommentPrefix);
try {
Project project = commentElement.getProject();
PsiParserFacade parserFacade = PsiParserFacade.SERVICE.getInstance(project);
PsiComment newComment = parserFacade.createBlockCommentFromText(commentElement.getLanguage(), commentText);
commentElement.replace(newComment);
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(doc);
}
catch (IncorrectOperationException e) {
LOG.info("Failed to replace line comment with block comment", e);
}
}
}
}
private static boolean isCommentElement(@Nullable final PsiElement element) {
return element != null && PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null;
}