JoinLines: better comments processing

1. Improve the support of other languages in CommentJoinLinesHandler
2. Respect right margin in CommentJoinLinesHandler
3. JoinLineProcessor#convertEndComments: do not convert if only whitespaces are going to be added
4. JoinLineProcessor#adjustWhiteSpace: do not add white-spaces before line-break
Also fixes IDEA-125325 Join lines in custom file type should handle end-of-line comments

GitOrigin-RevId: 392fe2455dcf19d21303a0b42ee85db7f824fa85
This commit is contained in:
Tagir Valeev
2019-08-27 14:21:26 +07:00
committed by intellij-monorepo-bot
parent 83aadefe01
commit 0d75036403
16 changed files with 184 additions and 55 deletions

View File

@@ -0,0 +1,5 @@
class A {
// foo<caret>
}

View File

@@ -0,0 +1,8 @@
class A {
<selection>// foo
</selection>
}

View File

@@ -0,0 +1,4 @@
class A {
// foo
}

View File

@@ -0,0 +1,8 @@
class A {
<selection>// foo
int x;</selection>
}

View File

@@ -0,0 +1,4 @@
class A {
/* foo*/ int x;
}

View File

@@ -0,0 +1,4 @@
class A {
// foo<caret>
}

View File

@@ -0,0 +1,4 @@
class A {
// very very <caret>long comment line! very very long comment line!
// very very long comment line! very very long comment line!
}

View File

@@ -0,0 +1,4 @@
class A {
// very very long comment line! very very long comment line! very very long
// <caret>comment line! very very long comment line!
}

View File

@@ -0,0 +1,38 @@
class A {
<selection>// a
// bb
// ccc dddd
// ccc
// bb a bb a bb a
// a
// bb
// ccc dddd
// ccc
// bb a bb a bb a
// a
// bb
// ccc dddd
// ccc
// bb a bb a bb a
// a
// bb
// ccc dddd
// ccc
// bb a bb a bb a
// a
// bb
// ccc dddd
// ccc
// bb a bb a bb a
// a
// bb
// ccc dddd
// ccc
// bb a bb a bb a
// a
// bb
// ccc dddd
// ccc
// bb a bb a bb a</selection>
}

View File

@@ -0,0 +1,9 @@
class A {
// a bb ccc dddd ccc bb a bb a bb a a bb ccc
// dddd ccc bb a bb a bb a a bb ccc dddd ccc bb
// a bb a bb a a bb ccc dddd ccc bb a bb a bb a
// a bb ccc dddd ccc bb a bb a bb a a bb ccc
// dddd ccc bb a bb a bb a a bb ccc dddd ccc bb
// a bb a bb a
}

View File

@@ -16,11 +16,13 @@
package com.intellij.java.codeInsight;
import com.intellij.JavaTestUtil;
import com.intellij.application.options.CodeStyle;
import com.intellij.ide.DataManager;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.testFramework.LightJavaCodeInsightTestCase;
@@ -245,6 +247,21 @@ public class JoinLinesTest extends LightJavaCodeInsightTestCase {
public void testConvertComment() { doTest();}
public void testConvertComment2() { doTest();}
public void testConvertFinalLineComment() { doTest();}
public void testConvertFinalLineComment2() { doTest();}
public void testConvertFinalLineComment3() { doTest();}
public void testConvertLongLine() {
CommonCodeStyleSettings settings = getJavaSettings();
settings.RIGHT_MARGIN = 79;
doTest();
}
public void testConvertMultiLongLine() {
CommonCodeStyleSettings settings = getJavaSettings();
settings.RIGHT_MARGIN = 50;
doTest();
}
public void testConvertManyEndOfLineComments() { doTest();}
public void testConvertMixed() { doTest();}

View File

@@ -30,6 +30,9 @@ public interface JoinRawLinesHandlerDelegate extends JoinLinesHandlerDelegate {
* In contrast to {@link JoinLinesHandlerDelegate#tryJoinLines(Document, PsiFile, int, int) tryJoinLines()}, this method
* is called on an unmodified document.
*
* This joiner is allowed to keep number of lines the same,
* but it should not increase number of lines in the document.
*
* @param document where the lines are
* @param file where the lines are
* @param start offset right after the last non-space char of first line;

View File

@@ -1,13 +1,16 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.editorActions;
import com.intellij.application.options.CodeStyle;
import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.Commenter;
import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
public class CommentJoinLinesHandler implements JoinRawLinesHandlerDelegate {
@@ -19,27 +22,59 @@ public class CommentJoinLinesHandler implements JoinRawLinesHandlerDelegate {
@Override
public int tryJoinRawLines(@NotNull Document document, @NotNull PsiFile file, int start, int end) {
PsiElement prevElement = file.findElementAt(start);
if (prevElement instanceof PsiWhiteSpace) {
prevElement = file.findElementAt(start - 1);
}
PsiComment prevComment = PsiTreeUtil.getNonStrictParentOfType(prevElement, PsiComment.class);
CharSequence text = document.getText();
if (start > 0 && text.charAt(start) == '\n') start--;
PsiComment prevComment = PsiTreeUtil.getNonStrictParentOfType(file.findElementAt(start), PsiComment.class);
PsiComment nextComment = PsiTreeUtil.getNonStrictParentOfType(file.findElementAt(end), PsiComment.class);
if (prevComment == null || nextComment == null) return CANNOT_JOIN;
boolean sameComment = prevComment == nextComment;
boolean adjacentLineComments = false;
CharSequence text = document.getText();
if (text.charAt(end) == '*' && end < text.length() && text.charAt(end + 1) != '/') {
Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(file.getLanguage());
if (!(commenter instanceof CodeDocumentationAwareCommenter)) return CANNOT_JOIN;
CodeDocumentationAwareCommenter docCommenter = (CodeDocumentationAwareCommenter)commenter;
String lineCommentPrefix = docCommenter.getLineCommentPrefix();
String blockCommentSuffix = docCommenter.getBlockCommentSuffix();
if ("*/".equals(blockCommentSuffix) && text.charAt(end) == '*' && end < text.length() && text.charAt(end + 1) != '/') {
/* remove leading asterisk
* <-- like this
*/
end = StringUtil.skipWhitespaceForward(text, end + 1);
}
else if (!sameComment &&
!(start >= 2 && text.charAt(start - 2) == '*' && text.charAt(start - 1) == '/') &&
text.charAt(end) == '/' && end + 1 < text.length() && text.charAt(end + 1) == '/') {
else if (!sameComment && lineCommentPrefix != null &&
!(blockCommentSuffix != null && CharArrayUtil.regionMatches(text, start - 2, blockCommentSuffix)) &&
CharArrayUtil.regionMatches(text, end, lineCommentPrefix)) {
// merge two line comments
// like this
adjacentLineComments = true;
end = StringUtil.skipWhitespaceForward(text, end + 2);
end = StringUtil.skipWhitespaceForward(text, end + lineCommentPrefix.length());
int lineNumber = document.getLineNumber(start);
int lineStart = document.getLineStartOffset(lineNumber);
int nextEnd = document.getLineEndOffset(lineNumber + 1);
int margin = CodeStyle.getSettings(file).getRightMargin(file.getLanguage());
int lineLength = start + 1 - lineStart;
if (lineLength <= margin && lineLength + (nextEnd - end) + 1 > margin) {
// Respect right margin
int allowedEnd = end + margin - lineLength - 1;
assert allowedEnd < nextEnd;
while (allowedEnd > end && !Character.isWhitespace(text.charAt(allowedEnd))) {
allowedEnd--;
}
if (allowedEnd <= end) {
// do nothing, just move the caret
return end;
}
int endOfMovedPart = StringUtil.skipWhitespaceBackward(text, allowedEnd);
CharSequence toMove = text.subSequence(end, endOfMovedPart);
int lineBreakPos = CharArrayUtil.indexOf(text, "\n", start);
document.deleteString(end, allowedEnd + 1);
document.replaceString(start + 1, lineBreakPos, " " + toMove);
return start + 2 + toMove.length() + (end - lineBreakPos);
}
}
document.replaceString(start, end, adjacentLineComments || sameComment ? " " : "");
return start;
document.replaceString(start + 1, end, adjacentLineComments || sameComment ? " " : "");
return start + 1;
}
}

View File

@@ -24,6 +24,7 @@ import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.DocumentUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -86,7 +87,8 @@ public class JoinLinesHandler extends EditorActionHandler {
if (doc.getLineStartOffset(endLine) == caret.getSelectionEnd()) endLine--;
}
// joining lines, several times if selection is multiline
if (endLine >= doc.getLineCount()) return;
int lineCount = endLine - startLine;
int line = startLine;
@@ -101,7 +103,7 @@ public class JoinLinesHandler extends EditorActionHandler {
private static class JoinLineProcessor {
private final @NotNull DocumentEx myDoc;
private final @NotNull PsiFile myFile;
private final int myLine;
private int myLine;
private final @NotNull PsiDocumentManager myManager;
private final @NotNull CodeStyleManager myStyleManager;
private final @NotNull ProgressIndicator myIndicator;
@@ -154,8 +156,10 @@ public class JoinLinesHandler extends EditorActionHandler {
if (lastNonSpaceOffset > myDoc.getLineStartOffset(line)) {
PsiComment comment = getCommentElement(myFile.findElementAt(lastNonSpaceOffset - 1));
if (comment != null) {
int nextStart = StringUtil.skipWhitespaceForward(text, myDoc.getLineStartOffset(line + 1));
if (getCommentElement(myFile.findElementAt(nextStart)) == null) {
int nextStart = CharArrayUtil.shiftForward(text, myDoc.getLineStartOffset(line + 1), " \t\n");
if (nextStart < text.length() &&
myDoc.getLineNumber(nextStart) <= myLine + lineCount &&
getCommentElement(myFile.findElementAt(nextStart)) == null) {
endComments.add(comment);
}
}
@@ -192,9 +196,11 @@ public class JoinLinesHandler extends EditorActionHandler {
int start = limits.getStartOffset();
int end = limits.getEndOffset();
int rc = CANNOT_JOIN;
JoinRawLinesHandlerDelegate rawJoiner = null;
for (JoinLinesHandlerDelegate delegate : list) {
if (delegate instanceof JoinRawLinesHandlerDelegate) {
rc = ((JoinRawLinesHandlerDelegate)delegate).tryJoinRawLines(myDoc, myFile, start, end);
rawJoiner = (JoinRawLinesHandlerDelegate)delegate;
rc = rawJoiner.tryJoinRawLines(myDoc, myFile, start, end);
if (rc != CANNOT_JOIN) {
myCaretRestoreOffset = checkOffset(rc, delegate, myDoc);
break;
@@ -209,8 +215,18 @@ public class JoinLinesHandler extends EditorActionHandler {
myManager.doPostponedOperationsAndUnblockDocument(myDoc);
myManager.commitDocument(myDoc);
int afterLines = myDoc.getLineCount();
// Single Join two lines procedure could join more than two (e.g. if it removes braces)
count += Math.max(beforeLines - afterLines, 1);
if (afterLines > beforeLines) {
LOG.error("Raw joiner increased number of lines: " + rawJoiner + " (" + rawJoiner.getClass() + ")");
}
if (afterLines >= beforeLines && myLine == startLine) {
// if number of lines is the same, continue processing from the next line
myLine++;
startLine++;
count++;
} else {
// Single Join two lines procedure could join more than two (e.g. if it removes braces)
count += Math.max(beforeLines - afterLines, 1);
}
beforeLines = afterLines;
text = myDoc.getCharsSequence();
}
@@ -297,7 +313,7 @@ public class JoinLinesHandler extends EditorActionHandler {
RangeMarker marker = markers.get(i);
if (!marker.isValid()) continue;
int end = StringUtil.skipWhitespaceForward(text, marker.getStartOffset());
int spacesToCreate = myStyleManager.getSpacing(myFile, end);
int spacesToCreate = text.charAt(end) == '\n' ? 0 : myStyleManager.getSpacing(myFile, end);
spacesToAdd[i] = spacesToCreate < 0 ? 1 : spacesToCreate;
}
DocumentUtil.executeInBulk(myDoc, size > 100, () -> {

View File

@@ -20,7 +20,6 @@ import com.intellij.codeInsight.editorActions.JoinRawLinesHandlerDelegate;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.TokenSet;
@@ -53,7 +52,6 @@ public class PyJoinLinesHandler implements JoinRawLinesHandlerDelegate {
new StringLiteralJoiner(),
new StmtJoiner(), // strings before stmts to let doc strings join
new BinaryExprJoiner(),
new CommentJoiner(),
new StripBackslashJoiner()
};
@@ -327,34 +325,6 @@ public class PyJoinLinesHandler implements JoinRawLinesHandlerDelegate {
return req.document.getLineStartOffset(lineNumber);
}
private static class CommentJoiner implements Joiner {
@Override
public Result join(@NotNull Request req) {
if (req.leftElem instanceof PsiComment && req.rightElem instanceof PsiComment) {
final CharSequence text = req.document.getCharsSequence();
final TextRange rightRange = req.rightElem.getTextRange();
final int initialPos = rightRange.getStartOffset() + 1;
int pos = initialPos; // cut '#'
final int last = rightRange.getEndOffset();
while (pos < last && " \t".indexOf(text.charAt(pos)) >= 0) pos += 1;
int right = pos - initialPos + 1; // account for the '#'
String substring = req.rightElem.getText().substring(right);
String replacement = " " + findReplacement(substring, getStringToJoinMaxLength(req, 0));
right += replacement.length() - 1; // account for the '#'
if (!replacement.trim().isEmpty()) {
replacement += "\n";
req.document.insertString(req.secondLineStartOffset + right, "# ");
}
return new Result(replacement, 0, 0, right);
}
return null;
}
}
private static class StripBackslashJoiner implements Joiner {
static final TokenSet SINGLE_QUOTED_STRINGS = TokenSet.create(PyTokenTypes.SINGLE_QUOTED_STRING, PyTokenTypes.SINGLE_QUOTED_UNICODE);

View File

@@ -1,4 +1,4 @@
# this comment is very very very very very very long. And this is the second
# line of this very long comment
# this comment is very very very very very very long. And this is the second
# <caret>line of this very long comment
def test():
pass