diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java index 6ad314ae79c7..a984fb8cf8b8 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java @@ -25,18 +25,24 @@ package com.intellij.codeInsight.editorActions; import com.intellij.ide.DataManager; +import com.intellij.ide.IdeBundle; +import com.intellij.idea.ActionsBundle; 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.actionSystem.IdeActions; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; 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.progress.ProgressManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; @@ -44,13 +50,14 @@ import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.SequentialModalProgressTask; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import static com.intellij.codeInsight.editorActions.JoinLinesHandlerDelegate.CANNOT_JOIN; -public class JoinLinesHandler extends EditorWriteActionHandler { +public class JoinLinesHandler extends EditorActionHandler { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.JoinLinesHandler"); private final EditorActionHandler myOriginalHandler; @@ -67,7 +74,7 @@ public class JoinLinesHandler extends EditorWriteActionHandler { } @Override - public void executeWriteAction(@NotNull final Editor editor, @Nullable Caret caret, final DataContext dataContext) { + public void doExecute(@NotNull final Editor editor, @Nullable Caret caret, final DataContext dataContext) { assert caret != null; if (!(editor.getDocument() instanceof DocumentEx)) { myOriginalHandler.execute(editor, caret, dataContext); @@ -95,162 +102,187 @@ public class JoinLinesHandler extends EditorWriteActionHandler { } final int startReformatOffset = CharArrayUtil.shiftBackward(doc.getCharsSequence(), doc.getLineEndOffset(startLine), " \t"); - CodeEditUtil.setNodeReformatStrategy(node -> node.getTextRange().getStartOffset() >= startReformatOffset); - try { - doJob(editor, doc, caret, project, docManager, psiFile, startLine, endLine); - } - finally { - CodeEditUtil.setNodeReformatStrategy(null); - } + // joining lines, several times if selection is multiline + int lineCount = endLine - startLine; + int line = startLine; + String action = ActionsBundle.actionText(IdeActions.ACTION_EDITOR_JOIN_LINES); + + ProgressManager.getInstance().run(new SequentialModalProgressTask.Adapter(project, action) { + final Ref caretRestoreOffset = new Ref<>(-1); + int count = 0; + + @Override + public boolean isDone() { + return count >= lineCount; + } + + @Override + public boolean iteration() { + if (line >= doc.getLineCount() - 1) { + count = lineCount; // finishing iteration + } + else { + new WriteCommandAction(project, action, "Join lines") { + @Override + protected void run(@NotNull Result result) throws Throwable { + CodeEditUtil.setNodeReformatStrategy(node -> node.getTextRange().getStartOffset() >= startReformatOffset); + try { + doJoinTwoLines(doc, project, docManager, psiFile, line, caretRestoreOffset); + } + finally { + CodeEditUtil.setNodeReformatStrategy(null); + } + } + }.execute(); + } + count++; + getIndicator().setFraction(((double)count)/lineCount); + return isDone(); + } + + @Override + public void onSuccess() { + if (caret.hasSelection()) { + caret.moveToOffset(caret.getSelectionEnd()); + } + else if (caretRestoreOffset.get() != CANNOT_JOIN) { + caret.moveToOffset(caretRestoreOffset.get()); + if (caret == editor.getCaretModel().getPrimaryCaret()) { // performance + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + caret.removeSelection(); + } + } + }.setCancelText(IdeBundle.message("action.stop"))); } - private static void doJob(@NotNull Editor editor, - @NotNull DocumentEx doc, - @NotNull Caret caret, - @NotNull Project project, - @NotNull PsiDocumentManager docManager, - @NotNull PsiFile psiFile, - int startLine, - int endLine) { - int caretRestoreOffset = -1; - // joining lines, several times if selection is multiline - for (int i = startLine; i < endLine; i++) { - if (startLine >= doc.getLineCount() - 1) break; + private static void doJoinTwoLines(@NotNull DocumentEx doc, + @NotNull Project project, + @NotNull PsiDocumentManager docManager, + @NotNull PsiFile psiFile, + int startLine, + Ref caretRestoreOffset) { + docManager.doPostponedOperationsAndUnblockDocument(doc); + docManager.commitDocument(doc); + CharSequence text = doc.getCharsSequence(); + JoinLinesOffsets offsets = calcJoinLinesOffsets(psiFile, doc, startLine); - docManager.doPostponedOperationsAndUnblockDocument(doc); - docManager.commitDocument(doc); - CharSequence text = doc.getCharsSequence(); - JoinLinesOffsets offsets = calcJoinLinesOffsets(psiFile, doc, startLine); + if (offsets.isStartLineEndsWithComment && !offsets.isNextLineStartsWithComment) { + tryConvertEndOfLineComment(doc, offsets.elementAtStartLineEnd); + offsets = calcJoinLinesOffsets(psiFile, doc, startLine); + } - if (offsets.isStartLineEndsWithComment && !offsets.isNextLineStartsWithComment) { - tryConvertEndOfLineComment(doc, offsets.elementAtStartLineEnd); - offsets = calcJoinLinesOffsets(psiFile, doc, startLine); - } - - int rc = -1; - int start; - int end; - 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)) { - if (delegate instanceof JoinRawLinesHandlerDelegate) { - rc = ((JoinRawLinesHandlerDelegate)delegate).tryJoinRawLines(doc, psiFile, start, end); - if (rc != CANNOT_JOIN) { - caretRestoreOffset = rc; - break; - } + int rc = -1; + int start; + int end; + 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)) { + if (delegate instanceof JoinRawLinesHandlerDelegate) { + rc = ((JoinRawLinesHandlerDelegate)delegate).tryJoinRawLines(doc, psiFile, start, end); + if (rc != CANNOT_JOIN) { + caretRestoreOffset.set(rc); + break; } } - if (rc == CANNOT_JOIN) { // remove indents and newline, run non-raw joiners - if (offsets.lastNonSpaceOffsetInStartLine == doc.getLineStartOffset(startLine)) { - doc.deleteString(doc.getLineStartOffset(startLine), offsets.firstNonSpaceOffsetInNextLine); + } + if (rc == CANNOT_JOIN) { // remove indents and newline, run non-raw joiners + if (offsets.lastNonSpaceOffsetInStartLine == doc.getLineStartOffset(startLine)) { + doc.deleteString(doc.getLineStartOffset(startLine), offsets.firstNonSpaceOffsetInNextLine); - int indent = -1; - try { - docManager.commitDocument(doc); - indent = CodeStyleManager.getInstance(project).adjustLineIndent(psiFile, startLine == 0 ? 0 : doc.getLineStartOffset(startLine)); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - - if (caretRestoreOffset == CANNOT_JOIN) { - caretRestoreOffset = indent; - } - - continue; - } - - doc.deleteString(offsets.lineEndOffset, offsets.lineEndOffset + doc.getLineSeparatorLength(startLine)); - - text = doc.getCharsSequence(); - limits = findStartAndEnd(text, offsets.lineEndOffset - 1, offsets.lineEndOffset, doc.getTextLength()); - start = limits.getStartOffset(); end = limits.getEndOffset(); - - // Check if we're joining splitted string literal. - docManager.commitDocument(doc); - - for(JoinLinesHandlerDelegate delegate: Extensions.getExtensions(JoinLinesHandlerDelegate.EP_NAME)) { - rc = delegate.tryJoinLines(doc, psiFile, start, end); - if (rc != CANNOT_JOIN) break; - } - } - docManager.doPostponedOperationsAndUnblockDocument(doc); - - if (rc != CANNOT_JOIN) { - if (caretRestoreOffset == CANNOT_JOIN) caretRestoreOffset = rc; - continue; - } - - - if (caretRestoreOffset == CANNOT_JOIN) caretRestoreOffset = start == offsets.lineEndOffset ? start : start + 1; - - - 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++; - } - else if (text.charAt(end) == '/') { - end += 2; - while (end < doc.getTextLength() && (text.charAt(end) == ' ' || text.charAt(end) == '\t')) 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 == offsets.lineEndOffset ? start : start + 1, end, " "); - - if (start <= doc.getLineStartOffset(startLine)) { + int indent = -1; try { docManager.commitDocument(doc); - CodeStyleManager.getInstance(project).adjustLineIndent(psiFile, doc.getLineStartOffset(startLine)); + indent = CodeStyleManager.getInstance(project).adjustLineIndent(psiFile, startLine == 0 ? 0 : doc.getLineStartOffset(startLine)); } catch (IncorrectOperationException e) { LOG.error(e); } + + if (caretRestoreOffset.get() == CANNOT_JOIN) { + caretRestoreOffset.set(indent); + } + + return; } - int prevLineCount = doc.getLineCount(); + doc.deleteString(offsets.lineEndOffset, offsets.lineEndOffset + doc.getLineSeparatorLength(startLine)); + text = doc.getCharsSequence(); + limits = findStartAndEnd(text, offsets.lineEndOffset - 1, offsets.lineEndOffset, doc.getTextLength()); + start = limits.getStartOffset(); end = limits.getEndOffset(); + + // Check if we're joining splitted string literal. docManager.commitDocument(doc); + + for(JoinLinesHandlerDelegate delegate: Extensions.getExtensions(JoinLinesHandlerDelegate.EP_NAME)) { + rc = delegate.tryJoinLines(doc, psiFile, start, end); + if (rc != CANNOT_JOIN) break; + } + } + docManager.doPostponedOperationsAndUnblockDocument(doc); + + if (rc != CANNOT_JOIN) { + if (caretRestoreOffset.get() == CANNOT_JOIN) caretRestoreOffset.set(rc); + return; + } + + + if (caretRestoreOffset.get() == CANNOT_JOIN) caretRestoreOffset.set(start == offsets.lineEndOffset ? start : start + 1); + + + 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++; + } + else if (text.charAt(end) == '/') { + end += 2; + while (end < doc.getTextLength() && (text.charAt(end) == ' ' || text.charAt(end) == '\t')) end++; + } + + doc.replaceString(start == offsets.lineEndOffset ? start : start + 1, end, " "); + return; + } + + while (end < doc.getTextLength() && (text.charAt(end) == ' ' || text.charAt(end) == '\t')) end++; + doc.replaceString(start == offsets.lineEndOffset ? start : start + 1, end, " "); + + if (start <= doc.getLineStartOffset(startLine)) { try { - CodeStyleManager.getInstance(project).reformatRange(psiFile, start + 1, end, true); + docManager.commitDocument(doc); + CodeStyleManager.getInstance(project).adjustLineIndent(psiFile, doc.getLineStartOffset(startLine)); } catch (IncorrectOperationException e) { LOG.error(e); } + } - if (prevLineCount < doc.getLineCount()) { - docManager.doPostponedOperationsAndUnblockDocument(doc); - end = doc.getLineEndOffset(startLine) + doc.getLineSeparatorLength(startLine); - start = end - doc.getLineSeparatorLength(startLine); - int addedLinesCount = doc.getLineCount() - prevLineCount - 1; - while (end < doc.getTextLength() && - (text.charAt(end) == ' ' || text.charAt(end) == '\t' || text.charAt(end) == '\n' && addedLinesCount > 0)) { - if (text.charAt(end) == '\n') addedLinesCount--; - end++; - } - doc.replaceString(start, end, " "); + int prevLineCount = doc.getLineCount(); + + docManager.commitDocument(doc); + try { + CodeStyleManager.getInstance(project).reformatRange(psiFile, start + 1, end, true); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + + if (prevLineCount < doc.getLineCount()) { + docManager.doPostponedOperationsAndUnblockDocument(doc); + end = doc.getLineEndOffset(startLine) + doc.getLineSeparatorLength(startLine); + start = end - doc.getLineSeparatorLength(startLine); + int addedLinesCount = doc.getLineCount() - prevLineCount - 1; + while (end < doc.getTextLength() && + (text.charAt(end) == ' ' || text.charAt(end) == '\t' || text.charAt(end) == '\n' && addedLinesCount > 0)) { + if (text.charAt(end) == '\n') addedLinesCount--; + end++; } + doc.replaceString(start, end, " "); + } - docManager.commitDocument(doc); - } - docManager.commitDocument(doc); // cheap on an already-committed doc - - if (caret.hasSelection()) { - caret.moveToOffset(caret.getSelectionEnd()); - } - else if (caretRestoreOffset != CANNOT_JOIN) { - caret.moveToOffset(caretRestoreOffset); - if (caret == editor.getCaretModel().getPrimaryCaret()) { // performance - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - } - caret.removeSelection(); - } + docManager.commitDocument(doc); } private static class JoinLinesOffsets { diff --git a/platform/lang-impl/src/com/intellij/util/SequentialModalProgressTask.java b/platform/lang-impl/src/com/intellij/util/SequentialModalProgressTask.java index c26642608387..1a7fa175eb4c 100644 --- a/platform/lang-impl/src/com/intellij/util/SequentialModalProgressTask.java +++ b/platform/lang-impl/src/com/intellij/util/SequentialModalProgressTask.java @@ -121,4 +121,23 @@ public class SequentialModalProgressTask extends Task.Modal { protected void prepare(@NotNull SequentialTask task) { task.prepare(); } + + public abstract static class Adapter extends SequentialModalProgressTask implements SequentialTask { + public Adapter(@Nullable Project project, @NotNull String title) { + super(project, title); + setTask(this); + } + + public Adapter(@Nullable Project project, @NotNull String title, boolean canBeCancelled) { + super(project, title, canBeCancelled); + } + + @Override + public void prepare() { + } + + @Override + public void stop() { + } + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/JoinLinesAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/JoinLinesAction.java index 19b3c6fb5081..e60ea072e020 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/JoinLinesAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/JoinLinesAction.java @@ -27,6 +27,7 @@ package com.intellij.openapi.editor.actions; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; +import com.intellij.util.DocumentUtil; public class JoinLinesAction extends TextComponentEditorAction { public JoinLinesAction() { @@ -51,24 +52,28 @@ public class JoinLinesAction extends TextComponentEditorAction { if (doc.getLineStartOffset(endLine) == caret.getSelectionEnd()) endLine--; } - int caretRestoreOffset = -1; + int[] caretRestoreOffset = new int[] {-1}; + int lineCount = endLine - startLine; + final int line = startLine; - for (int i = startLine; i < endLine; i++) { - if (startLine >= doc.getLineCount() - 1) break; - CharSequence text = doc.getCharsSequence(); - int end = doc.getLineEndOffset(startLine) + doc.getLineSeparatorLength(startLine); - int start = end - doc.getLineSeparatorLength(startLine); - while (start > 0 && (text.charAt(start) == ' ' || text.charAt(start) == '\t')) start--; - if (caretRestoreOffset == -1) caretRestoreOffset = start + 1; - while (end < doc.getTextLength() && (text.charAt(end) == ' ' || text.charAt(end) == '\t')) end++; - doc.replaceString(start, end, " "); - } + DocumentUtil.executeInBulk(doc, lineCount > 1000, () -> { + for (int i = 0; i < lineCount; i++) { + if (line >= doc.getLineCount() - 1) break; + CharSequence text = doc.getCharsSequence(); + int end = doc.getLineEndOffset(line) + doc.getLineSeparatorLength(line); + int start = end - doc.getLineSeparatorLength(line); + while (start > 0 && (text.charAt(start) == ' ' || text.charAt(start) == '\t')) start--; + if (caretRestoreOffset[0] == -1) caretRestoreOffset[0] = start + 1; + while (end < doc.getTextLength() && (text.charAt(end) == ' ' || text.charAt(end) == '\t')) end++; + doc.replaceString(start, end, " "); + } + }); if (caret.hasSelection()) { caret.moveToOffset(caret.getSelectionEnd()); } else { - if (caretRestoreOffset != -1) { - caret.moveToOffset(caretRestoreOffset); + if (caretRestoreOffset[0] != -1) { + caret.moveToOffset(caretRestoreOffset[0]); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); caret.removeSelection(); }