| /* |
| * 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.openapi.editor.impl.softwrap.mapping; |
| |
| import com.intellij.codeInsight.folding.CodeFoldingManager; |
| import com.intellij.openapi.editor.*; |
| import com.intellij.openapi.editor.impl.AbstractEditorTest; |
| import com.intellij.openapi.editor.impl.EditorImpl; |
| import com.intellij.openapi.editor.impl.SoftWrapModelImpl; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.psi.codeStyle.CommonCodeStyleSettings; |
| import com.intellij.testFramework.EditorTestUtil; |
| import com.intellij.testFramework.TestFileType; |
| import gnu.trove.TIntHashSet; |
| import gnu.trove.TIntProcedure; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * @author Denis Zhdanov |
| * @since 09/16/2010 |
| */ |
| public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorTest { |
| |
| private boolean mySmartHome; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| if (myEditor == null) { |
| return; |
| } |
| |
| EditorSettings settings = myEditor.getSettings(); |
| mySmartHome = settings.isSmartHome(); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| if (myEditor != null) { |
| EditorSettings settings = myEditor.getSettings(); |
| settings.setUseSoftWraps(false); |
| settings.setSmartHome(mySmartHome); |
| } |
| super.tearDown(); |
| } |
| |
| public void testSoftWrapAdditionOnTyping() throws Exception { |
| String text = |
| "this is a test string that is expected to end just before right margin<caret>"; |
| init(100, text); |
| |
| int offset = myEditor.getDocument().getTextLength() + 1; |
| assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); |
| type(" thisisalongtokenthatisnotexpectedtobebrokenintopartsduringsoftwrapping"); |
| SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset); |
| |
| // This test fails randomly at TeamCity. The logging below is intended to help analyzing the problem. |
| if (softWrap == null) { |
| fail(String.format( |
| "Expected soft wrap to be located on offset %d. Actual: %s. Document text: %s", |
| offset, getSoftWrapModel().getRegisteredSoftWraps(), myEditor.getDocument().getCharsSequence() |
| )); |
| } |
| } |
| |
| public void testLongLineOfIdSymbolsIsNotSoftWrapped() throws Exception { |
| String text = |
| "abcdefghijklmnopqrstuvwxyz<caret>\n" + |
| "123\n" + |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| |
| init(15, text); |
| assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); |
| type('1'); |
| assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); |
| |
| int offset = myEditor.getDocument().getText().indexOf("\n"); |
| type(" test"); |
| assertEquals(1, getSoftWrapModel().getRegisteredSoftWraps().size()); |
| assertNotNull(getSoftWrapModel().getSoftWrap(offset)); |
| } |
| |
| public void testFoldRegionCollapsing() throws Exception { |
| String text = |
| "class Test {\n" + |
| " public void foo() {\n" + |
| " System.out.println(\"test\");\n" + |
| " }\n" + |
| "}"; |
| |
| init(40, text); |
| final FoldingModel foldingModel = myEditor.getFoldingModel(); |
| assertEmpty(foldingModel.getAllFoldRegions()); |
| |
| final int startOffset = text.indexOf('{'); |
| final int endOffset = text.indexOf('}') + 1; |
| |
| VisualPosition foldStartPosition = myEditor.offsetToVisualPosition(startOffset); |
| |
| addFoldRegion(startOffset, endOffset, "..."); |
| |
| final FoldRegion foldRegion = getFoldRegion(startOffset); |
| assertNotNull(foldRegion); |
| assertTrue(foldRegion.isExpanded()); |
| toggleFoldRegionState(foldRegion, false); |
| |
| // Expecting that all offsets that belong to collapsed fold region point to the region's start. |
| assertEquals(foldStartPosition, myEditor.offsetToVisualPosition(startOffset + 5)); |
| } |
| |
| public void testTypingEnterAtDocumentEnd() throws IOException { |
| String text = |
| "class Test {\n" + |
| " public void foo() {\n" + |
| " System.out.println(\"test\");\n" + |
| " }\n" + |
| "}<caret>"; |
| |
| init(40, text); |
| type('\n'); |
| VisualPosition position = myEditor.getCaretModel().getVisualPosition(); |
| assertEquals(new VisualPosition(5, 0), position); |
| } |
| |
| public void testDeleteDocumentTail() throws IOException { |
| String text = |
| "class Test {\n" + |
| " public void foo() {\n" + |
| " System.out.println(\"test\");\n" + |
| " }\n" + |
| "}\n" + |
| "abcde"; |
| |
| init(40, text); |
| int offset = text.indexOf("abcde"); |
| myEditor.getSelectionModel().setSelection(offset, text.length()); |
| delete(); |
| assertEquals(new VisualPosition(5, 0), myEditor.getCaretModel().getVisualPosition()); |
| } |
| |
| public void testTypingTabOnLastEmptyLine() throws IOException { |
| String text = |
| "class Test {\n" + |
| "}\n" + |
| "<caret>"; |
| |
| init(40, text); |
| type('\t'); |
| assertEquals(new VisualPosition(2, 4), myEditor.getCaretModel().getVisualPosition()); |
| } |
| |
| public void testTypingThatExceedsRightMarginOnLastSoftWrappedLine() throws IOException { |
| String text = |
| "line1\n" + |
| "long line<caret>"; |
| |
| init(48, text, 7); |
| |
| int softWrapsBefore = getSoftWrapModel().getRegisteredSoftWraps().size(); |
| assertTrue(softWrapsBefore > 0); |
| |
| for (int i = 0; i < 10; i++) { |
| type(String.valueOf(i)); |
| assertEquals(softWrapsBefore, getSoftWrapModel().getRegisteredSoftWraps().size()); |
| assertEquals(myEditor.getDocument().getTextLength(), myEditor.getCaretModel().getOffset()); |
| } |
| } |
| |
| public void testTrailingFoldRegionRemoval() throws IOException { |
| String text = |
| "public class BrokenAlignment {\n" + |
| " @SuppressWarnings({ \"SomeInspectionIWantToIgnore\" })\n" + |
| " public void doSomething(int x, int y) {\n" + |
| " }\n" + |
| "}"; |
| |
| init(100, text); |
| |
| int startFoldOffset = text.indexOf('@'); |
| int endFoldOffset = text.indexOf(')'); |
| addCollapsedFoldRegion(startFoldOffset, endFoldOffset, "/SomeInspectionIWantToIgnore/"); |
| |
| int endSelectionOffset = text.lastIndexOf("}\n") + 1; |
| myEditor.getSelectionModel().setSelection(startFoldOffset, endSelectionOffset); |
| |
| delete(); |
| // Don't expect any exceptions here. |
| } |
| |
| public void testTypeNewLastLineAndSymbolOnIt() throws IOException { |
| // Inspired by IDEA-59439 |
| String text = |
| "This is a test document\n" + |
| "line1\n" + |
| "line2\n" + |
| "line3\n" + |
| "line4\n" + |
| "line5\n" + |
| "line6<caret>"; |
| |
| init(100, text); |
| type("\nq"); |
| assertEquals(new VisualPosition(7, 1), myEditor.offsetToVisualPosition(myEditor.getDocument().getTextLength())); |
| } |
| |
| public void testTrailingSoftWrapOffsetShiftOnTyping() throws IOException { |
| // The main idea is to type on a logical line before soft wrap in order to ensure that its offset is correctly shifted back. |
| String text = |
| "line1<caret>\n" + |
| "second line that is long enough to be soft wrapped"; |
| init(15, text); |
| |
| TIntHashSet offsetsBefore = collectSoftWrapStartOffsets(1); |
| assertTrue(!offsetsBefore.isEmpty()); |
| |
| type('2'); |
| final TIntHashSet offsetsAfter = collectSoftWrapStartOffsets(1); |
| assertSame(offsetsBefore.size(), offsetsAfter.size()); |
| offsetsBefore.forEach(new TIntProcedure() { |
| @Override |
| public boolean execute(int value) { |
| assertTrue(offsetsAfter.contains(value + 1)); |
| return true; |
| } |
| }); |
| } |
| |
| public void testSoftWrapAwareMappingAfterLeadingFoldRegionCollapsing() throws IOException { |
| String text = |
| "line to fold 1\n" + |
| "line to fold 2\n" + |
| "line to fold 3\n" + |
| "ordinary line 1\n" + |
| "ordinary line 2\n" + |
| "ordinary line 3\n" + |
| "ordinary line 4\n" + |
| "line that is long enough to be soft wrapped\n" + |
| "ordinary line 5\n" + |
| "ordinary line 6\n" + |
| "ordinary line 7\n" + |
| "ordinary line 8\n"; |
| |
| init(30, text); |
| LogicalPosition position = myEditor.visualToLogicalPosition(new VisualPosition(8, 0)); |
| assertSame(7, position.line); // Position from soft-wrapped part of the line |
| |
| addCollapsedFoldRegion(0, text.indexOf("ordinary line 1") - 1, "..."); |
| assertSame(7, myEditor.visualToLogicalPosition(new VisualPosition(6, 0)).line); // Check that soft wraps cache is correctly updated |
| } |
| |
| public void testCaretPositionOnFoldRegionExpand() throws IOException { |
| // We had a problem that caret preserved its visual position instead of offset. This test checks that. |
| |
| String text = |
| "/**\n" + |
| " * This is a test comment\n" + |
| " */\n" + |
| "public class Test {\n" + |
| "}"; |
| init(100, text); |
| |
| addCollapsedFoldRegion(0, text.indexOf("public") - 1, "/**...*/"); |
| |
| int offset = text.indexOf("class"); |
| CaretModel caretModel = myEditor.getCaretModel(); |
| caretModel.moveToOffset(offset); |
| assertEquals(offset, caretModel.getOffset()); |
| assertEquals(1, caretModel.getVisualPosition().line); |
| |
| toggleFoldRegionState(getFoldRegion(0), true); |
| assertEquals(3, caretModel.getVisualPosition().line); |
| assertEquals(offset, caretModel.getOffset()); |
| } |
| |
| public void testBackspaceAtTheEndOfSoftWrappedLine() throws IOException { |
| // There was a problem that removing text from the last document line that was soft-wrapped removed soft wraps as well. |
| String text = |
| "This a long string that is expected to be wrapped in more than one visual line<caret>"; |
| init(20, text); |
| |
| List<SoftWrap> softWrapsBeforeModification = new ArrayList<SoftWrap>(getSoftWrapModel().getRegisteredSoftWraps()); |
| assertTrue(softWrapsBeforeModification.size() > 0); |
| |
| backspace(); |
| assertEquals(softWrapsBeforeModification, getSoftWrapModel().getRegisteredSoftWraps()); |
| } |
| |
| public void testRemoveOfAllSymbolsFromLastLine() throws IOException { |
| // There was a problem that removing all text from the last document line corrupted soft wraps cache. |
| String text = |
| "Line1\n" + |
| "Long line2 that is expected to be soft-wrapped<caret>"; |
| init(20, text); |
| |
| List<SoftWrap> softWrapsBeforeModification = new ArrayList<SoftWrap>(getSoftWrapModel().getRegisteredSoftWraps()); |
| assertTrue(softWrapsBeforeModification.size() > 0); |
| |
| int offset = myEditor.getCaretModel().getOffset(); |
| VisualPosition positionBeforeModification = myEditor.offsetToVisualPosition(offset); |
| type("\n123"); |
| myEditor.getSelectionModel().setSelection(offset + 1, myEditor.getDocument().getTextLength()); |
| delete(); |
| assertEquals(softWrapsBeforeModification, getSoftWrapModel().getRegisteredSoftWraps()); |
| assertEquals(positionBeforeModification, myEditor.offsetToVisualPosition(offset)); |
| } |
| |
| public void testTypingBeforeCollapsedFoldRegion() throws IOException { |
| // We had a problem that soft wraps cache entries that lay after the changed region were considered to be affected by the change. |
| // This test checks that situation. |
| String text = |
| "\n" + |
| "fold line1\n" + |
| "fold line2\n" + |
| "fold line3\n" + |
| "fold line4\n" + |
| "normal line1"; |
| init(20, text); |
| |
| int afterFoldOffset = text.indexOf("normal line1"); |
| addCollapsedFoldRegion(text.indexOf("fold line1") + 2, afterFoldOffset - 1, "..."); |
| VisualPosition beforeModification = myEditor.offsetToVisualPosition(afterFoldOffset); |
| |
| myEditor.getCaretModel().moveToOffset(0); |
| type('a'); |
| |
| assertEquals(beforeModification, myEditor.offsetToVisualPosition(afterFoldOffset + 1)); |
| } |
| |
| public void testCollapsedFoldRegionPlaceholderThatExceedsVisibleWidth() throws IOException { |
| String text = |
| "line1\n" + |
| "line2\n" + |
| "line3\n" + |
| "line4\n" + |
| "line5\n" + |
| "this is long line that is expected to be soft-wrapped\n" + |
| "line6"; |
| init(15, text); |
| |
| assertTrue(!getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); |
| |
| int offsetAfterSingleLineFoldRegion = text.indexOf("line2") - 1; |
| int lineAfterSingleLineFoldRegion = myEditor.offsetToVisualPosition(offsetAfterSingleLineFoldRegion).line; |
| |
| int offsetAfterMultiLineFoldRegion = text.indexOf("this") - 1; |
| int lineAfterMultiLineFoldRegion = myEditor.offsetToVisualPosition(offsetAfterMultiLineFoldRegion).line; |
| |
| int offsetAfterSoftWrap = text.indexOf("line6") - 1; |
| VisualPosition beforePositionAfterSoftWrap = myEditor.offsetToVisualPosition(offsetAfterSoftWrap); |
| |
| // Add single-line fold region which placeholder text is long enough to exceed visual area width. |
| addCollapsedFoldRegion(2, offsetAfterSingleLineFoldRegion, "this is a very long placeholder for the single-line fold region"); |
| assertEquals(lineAfterSingleLineFoldRegion + 1, myEditor.offsetToVisualPosition(offsetAfterSingleLineFoldRegion).line); |
| lineAfterSingleLineFoldRegion++; |
| |
| assertEquals(lineAfterMultiLineFoldRegion + 1, myEditor.offsetToVisualPosition(offsetAfterMultiLineFoldRegion).line); |
| lineAfterMultiLineFoldRegion++; |
| |
| beforePositionAfterSoftWrap = new VisualPosition(beforePositionAfterSoftWrap.line + 1, beforePositionAfterSoftWrap.column); |
| assertEquals(beforePositionAfterSoftWrap, myEditor.offsetToVisualPosition(offsetAfterSoftWrap)); |
| |
| // Add multi-line fold region which placeholder is also long enough to exceed visual area width. |
| addCollapsedFoldRegion(text.indexOf("line2") + 2, offsetAfterMultiLineFoldRegion, "long enough placeholder for multi-line fold region"); |
| assertEquals(lineAfterSingleLineFoldRegion, myEditor.offsetToVisualPosition(offsetAfterSingleLineFoldRegion).line); |
| assertEquals(lineAfterMultiLineFoldRegion - 2, myEditor.offsetToVisualPosition(offsetAfterMultiLineFoldRegion).line); |
| beforePositionAfterSoftWrap = new VisualPosition(beforePositionAfterSoftWrap.line - 2, beforePositionAfterSoftWrap.column); |
| assertEquals(beforePositionAfterSoftWrap, myEditor.offsetToVisualPosition(offsetAfterSoftWrap)); |
| } |
| |
| public void testInsertNewStringAndTypeOnItBeforeFoldRegion() throws IOException { |
| // There was incorrect processing of fold regions when document change was performed right before them. |
| String text = |
| "/**\n" + |
| " * comment\n" + |
| " */\n" + |
| "class Test {\n" + |
| "}"; |
| init(100, text); |
| String placeholder = "/**...*/"; |
| addCollapsedFoldRegion(0, text.indexOf("class") - 1, placeholder); |
| |
| myEditor.getCaretModel().moveToOffset(0); |
| type("\n"); |
| myEditor.getCaretModel().moveToOffset(0); |
| type("import"); |
| |
| // Check that fold region info is still correct. |
| int foldStartOffset = myEditor.getDocument().getText().indexOf("/**"); |
| int foldEndOffset = myEditor.getDocument().getText().indexOf("class") - 1; |
| assertEquals(new VisualPosition(1, 0), myEditor.offsetToVisualPosition(foldStartOffset)); |
| assertEquals(new VisualPosition(1, 0), myEditor.offsetToVisualPosition((foldStartOffset + foldEndOffset) / 2)); |
| assertEquals(new VisualPosition(1, placeholder.length()), myEditor.offsetToVisualPosition(foldEndOffset)); |
| } |
| |
| public void testUpdateFoldRegionDataOnTextRemoveBeforeIt() throws IOException { |
| String text = |
| "1\n" + |
| "/**\n" + |
| " * comment\n" + |
| " */\n" + |
| "class Test {\n" + |
| "}"; |
| init(100, text); |
| String placeholder = "/**...*/"; |
| addCollapsedFoldRegion(2, text.indexOf("class") - 1, placeholder); |
| |
| myEditor.getCaretModel().moveToOffset(0); |
| delete(); |
| assertEquals(new VisualPosition(1, placeholder.length()), myEditor.offsetToVisualPosition(text.indexOf("class") - 2)); |
| } |
| |
| public void testRemoveCollapsedFoldRegionThatStartsLogicalLine() throws IOException { |
| // There was a problem that soft wraps cache updated on document modification didn't contain information about removed |
| // fold region but fold model still provided cached information about it. |
| String text = |
| "package org;\n" + |
| "\n" + |
| "@SuppressWarnings(\"all\")\n" + |
| "class Test {\n" + |
| "}"; |
| |
| init(100, text); |
| int startOffset = text.indexOf("@"); |
| int endOffset = text.indexOf("class") - 1; |
| addCollapsedFoldRegion(startOffset, endOffset, "xxx"); |
| |
| // Delete collapsed fold region that starts logical line. |
| myEditor.getSelectionModel().setSelection(startOffset, endOffset); |
| delete(); |
| |
| assertEquals(startOffset, myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(2, 0)))); |
| } |
| |
| public void testFoldRegionThatStartsAtLineEnd() throws IOException { |
| String text = |
| "line1\n" + |
| "line2\n" + |
| "line3\n" + |
| "line4\n" + |
| "line5"; |
| |
| init(30, text, 7); |
| int start = text.indexOf("line3") - 1; |
| addCollapsedFoldRegion(start, text.length(), "..."); |
| assertEquals(1, getSoftWrapModel().getRegisteredSoftWraps().size()); |
| assertEquals(start, getSoftWrapModel().getRegisteredSoftWraps().get(0).getStart()); |
| } |
| |
| public void testHomeProcessing() throws IOException { |
| String text = |
| "class Test {\n" + |
| " public String s = \"this is a long string literal that is expected to be soft-wrapped into multiple visual lines\";\n" + |
| "}"; |
| |
| init(30, text); |
| myEditor.getCaretModel().moveToOffset(text.indexOf("}") - 1); |
| |
| List<? extends SoftWrap> softWraps = new ArrayList<SoftWrap>(getSoftWrapModel().getRegisteredSoftWraps()); |
| assertTrue(!softWraps.isEmpty()); |
| |
| CaretModel caretModel = myEditor.getCaretModel(); |
| int expectedVisualLine = caretModel.getVisualPosition().line; |
| while (!softWraps.isEmpty()) { |
| SoftWrap softWrap = softWraps.get(softWraps.size() - 1); |
| int caretOffsetBefore = caretModel.getOffset(); |
| |
| home(); |
| |
| // Expecting the caret to be moved at the nearest soft wrap start offset. |
| int caretOffset = caretModel.getOffset(); |
| assertTrue(caretOffset < caretOffsetBefore); |
| assertEquals(softWrap.getStart(), caretOffset); |
| assertEquals(new VisualPosition(expectedVisualLine, softWrap.getIndentInColumns()), caretModel.getVisualPosition()); |
| |
| // Expected that caret is moved to visual line start when it's located on soft wrap start offset at the moment. |
| home(); |
| assertEquals(softWrap.getStart(), caretModel.getOffset()); |
| assertEquals(new VisualPosition(expectedVisualLine, 0), caretModel.getVisualPosition()); |
| |
| softWraps.remove(softWraps.size() - 1); |
| expectedVisualLine--; |
| } |
| |
| // Expecting caret to be located on the first non-white space symbol of non-soft wrapped line. |
| home(); |
| assertEquals(text.indexOf("public"), caretModel.getOffset()); |
| assertEquals(new VisualPosition(expectedVisualLine, text.indexOf("public") - text.indexOf("{\n") - 2), caretModel.getVisualPosition()); |
| } |
| |
| public void testEndProcessing() throws IOException { |
| String text = |
| "class Test {\n" + |
| " public String s = \"this is a long string literal that is expected to be soft-wrapped into multiple visual lines\"; \n" + |
| "}"; |
| |
| init(30, text); |
| myEditor.getCaretModel().moveToOffset(text.indexOf("\n") + 1); |
| |
| List<? extends SoftWrap> softWraps = new ArrayList<SoftWrap>(getSoftWrapModel().getRegisteredSoftWraps()); |
| assertTrue(!softWraps.isEmpty()); |
| |
| CaretModel caretModel = myEditor.getCaretModel(); |
| int expectedVisualLine = caretModel.getVisualPosition().line; |
| while (!softWraps.isEmpty()) { |
| SoftWrap softWrap = softWraps.get(0); |
| int caretOffsetBefore = caretModel.getOffset(); |
| |
| end(); |
| |
| // Expecting the caret to be moved at the last non-white space symbol on the current visual line. |
| int caretOffset = caretModel.getOffset(); |
| assertTrue(caretOffset > caretOffsetBefore); |
| assertFalse(caretOffset > softWrap.getStart()); |
| if (caretOffset < softWrap.getStart()) { |
| // There is a possible case that there are white space symbols between caret position applied on 'end' processing and |
| // soft wrap. Let's check that and emulate one more 'end' typing in order to move caret right before soft wrap. |
| for (int i = caretOffset; i < softWrap.getStart(); i++) { |
| char c = text.charAt(i); |
| assertTrue(c == ' ' || c == '\t'); |
| } |
| caretOffsetBefore = caretOffset; |
| end(); |
| caretOffset = caretModel.getOffset(); |
| assertTrue(caretOffset > caretOffsetBefore); |
| } |
| |
| assertEquals(softWrap.getStart(), caretOffset); |
| assertEquals( |
| new VisualPosition(expectedVisualLine, myEditor.offsetToVisualPosition(softWrap.getStart() - 1).column + 1), |
| caretModel.getVisualPosition() |
| ); |
| |
| softWraps.remove(0); |
| expectedVisualLine++; |
| } |
| |
| // Check that caret is placed on a last non-white space symbol on current logical line. |
| end(); |
| int lastNonWhiteSpaceSymbolOffset = text.indexOf("\";") + 2; |
| assertEquals(lastNonWhiteSpaceSymbolOffset, caretModel.getOffset()); |
| assertEquals(myEditor.offsetToVisualPosition(lastNonWhiteSpaceSymbolOffset), caretModel.getVisualPosition()); |
| assertEquals(expectedVisualLine, caretModel.getVisualPosition().line); |
| |
| // Check that caret is place to the very end of the logical line. |
| end(); |
| int lastSymbolOffset = myEditor.getDocument().getLineEndOffset(caretModel.getLogicalPosition().line); |
| assertEquals(lastSymbolOffset, caretModel.getOffset()); |
| assertEquals(myEditor.offsetToVisualPosition(lastSymbolOffset), caretModel.getVisualPosition()); |
| assertEquals(expectedVisualLine, caretModel.getVisualPosition().line); |
| } |
| |
| public void testSoftWrapToHardWrapConversion() throws IOException { |
| String text = |
| "this is line 1\n" + |
| "this is line 2\n" + |
| "this is line 3\n" + |
| "this is line 4\n" + |
| "this is line 5"; |
| |
| init(50, text, 7); |
| VisualPosition changePosition = new VisualPosition(1, 0); |
| myEditor.getCaretModel().moveToVisualPosition(changePosition); |
| |
| int logicalLinesBefore = myEditor.offsetToLogicalPosition(text.length()).line; |
| int offsetBefore = myEditor.getCaretModel().getOffset(); |
| |
| LogicalPosition logicalPositionBefore = myEditor.visualToLogicalPosition(changePosition); |
| assertEquals(1, logicalPositionBefore.softWrapLinesOnCurrentLogicalLine); |
| assertTrue(logicalPositionBefore.column > 0); |
| |
| SoftWrap softWrap = getSoftWrapModel().getSoftWrap(offsetBefore); |
| assertNotNull(softWrap); |
| |
| type('a'); |
| |
| LogicalPosition logicalPositionAfter = myEditor.visualToLogicalPosition(changePosition); |
| assertEquals(new LogicalPosition(1, 0, 0, 0, 0, 0, 0), logicalPositionAfter); |
| assertEquals(offsetBefore + softWrap.getText().length() + 1, myEditor.getCaretModel().getOffset()); |
| assertEquals(logicalLinesBefore + 1, myEditor.offsetToLogicalPosition(text.length()).line); |
| } |
| |
| //public void testPastingInsideSelection() throws IOException { |
| // String text = |
| // "this is line number 0\n" + |
| // "this is line number 1\n" + |
| // "this is line number 2\n" + |
| // "this is line number 3\n" + |
| // "this is line number 4\n" + |
| // "this is line number 5\n" + |
| // "this is line number 6\n" + |
| // "this is the last line"; |
| // |
| // init(100, text); |
| // int lineToSelect = 4; |
| // myEditor.getCaretModel().moveToOffset(text.indexOf("number " + lineToSelect)); |
| // Document document = myEditor.getDocument(); |
| // |
| // int startOffset = document.getLineStartOffset(lineToSelect); |
| // int endOffset = document.getLineEndOffset(lineToSelect); |
| // myEditor.getSelectionModel().setSelection(startOffset, endOffset); |
| // |
| // VisualPosition positionBefore = myEditor.offsetToVisualPosition(document.getLineStartOffset(lineToSelect + 1)); |
| // List<SoftWrap> softWrapsBefore = new ArrayList<SoftWrap>(getSoftWrapModel().getRegisteredSoftWraps()); |
| // |
| // copy(); |
| // paste(); |
| // |
| // assertEquals(positionBefore, myEditor.offsetToVisualPosition(document.getLineStartOffset(lineToSelect + 1))); |
| // assertEquals(softWrapsBefore, getSoftWrapModel().getRegisteredSoftWraps()); |
| //} |
| |
| public void testRemoveHugeLogicalLineThatLaysBeforeSoftWrappedLines() throws IOException { |
| String text = |
| "short line\n" + |
| "this is a long line that is expected to be soft wrapped into more than one or even two visual lines\n" + |
| "1. just a line that is long enough to be soft wrapped\n" + |
| "2. just a line that is long enough to be soft wrapped\n" + |
| "3. just a line that is long enough to be soft wrapped\n" + |
| "4. just a line that is long enough to be soft wrapped"; |
| |
| init(15, text); |
| Document document = myEditor.getDocument(); |
| int start = document.getLineStartOffset(1); |
| int end = document.getLineEndOffset(1) + 1; |
| int visualLinesToRemove = getSoftWrapModel().getSoftWrapsForLine(1).size() + 1; |
| |
| List<VisualPosition> positionsBefore = new ArrayList<VisualPosition>(); |
| for (int i = end; i < text.length(); i++) { |
| positionsBefore.add(myEditor.offsetToVisualPosition(i)); |
| } |
| Collections.reverse(positionsBefore); |
| |
| myEditor.getSelectionModel().setSelection(start, end); |
| delete(); |
| |
| // Check that all remembered positions are just shifted to expected number of visual lines. |
| for (int i = start; i < document.getTextLength(); i++) { |
| VisualPosition position = positionsBefore.remove(positionsBefore.size() - 1); |
| assertEquals(new VisualPosition(position.line - visualLinesToRemove, position.column), myEditor.offsetToVisualPosition(i)); |
| } |
| } |
| |
| public void testVerticalCaretShiftOnLineComment() throws IOException { |
| String text = |
| "1. just a line that is long enough to be soft wrapped\n" + |
| "2. just a line that is long enough to be soft wrapped\n" + |
| "3. just a line that is long enough to be soft wrapped\n" + |
| "4. just a line that is long enough to be soft wrapped"; |
| init(15, text); |
| |
| CaretModel caretModel = myEditor.getCaretModel(); |
| caretModel.moveToOffset(text.indexOf("2.") + 2); |
| lineComment(); |
| |
| assertEquals(myEditor.offsetToLogicalPosition(text.indexOf("3.") + 2), caretModel.getLogicalPosition()); |
| } |
| |
| private static TIntHashSet collectSoftWrapStartOffsets(int documentLine) { |
| TIntHashSet result = new TIntHashSet(); |
| for (SoftWrap softWrap : myEditor.getSoftWrapModel().getSoftWrapsForLine(documentLine)) { |
| result.add(softWrap.getStart()); |
| } |
| return result; |
| } |
| |
| public void testNonSmartHome() throws IOException { |
| String text = |
| " this is a string that starts with white space and is long enough to be soft-wrapped\n" + |
| " this is a 'prefix' text before collapsed multi-line folding that is long enough to be soft-wrapped first fold line\n" + |
| "second fold line"; |
| init(30, text); |
| |
| addCollapsedFoldRegion(text.indexOf("first fold line"), text.length(), "..."); |
| |
| List<? extends SoftWrap> softWraps = getSoftWrapModel().getRegisteredSoftWraps(); |
| assertTrue(!softWraps.isEmpty()); |
| |
| CaretModel caretModel = myEditor.getCaretModel(); |
| SoftWrap softWrap = softWraps.get(0); |
| |
| // Test non-smart home |
| myEditor.getSettings().setSmartHome(false); |
| caretModel.moveToOffset(softWrap.getStart() + 1); |
| int visLine = caretModel.getVisualPosition().line; |
| home(); |
| assertEquals(new VisualPosition(visLine, 0), caretModel.getVisualPosition()); |
| |
| caretModel.moveToOffset(text.length()); |
| home(); |
| visLine = caretModel.getVisualPosition().line; |
| assertEquals(new VisualPosition(visLine, 0), caretModel.getVisualPosition()); |
| } |
| |
| public void testFoldRegionsUpdate() throws IOException { |
| String text = |
| "import java.util.List;\n" + |
| "import java.util.ArrayList;\n" + |
| "\n" + |
| "class Test {\n" + |
| "}"; |
| init(40, text); |
| |
| final int foldStartOffset = "import".length() + 1; |
| int foldEndOffset = text.indexOf("class") - 2; |
| addCollapsedFoldRegion(foldStartOffset, foldEndOffset, "..."); |
| |
| // Simulate addition of the new import that modifies existing fold region. |
| myEditor.getDocument().insertString(foldEndOffset, "\nimport java.util.Date;\n"); |
| final FoldingModel foldingModel = myEditor.getFoldingModel(); |
| foldingModel.runBatchFoldingOperation(new Runnable() { |
| @Override |
| public void run() { |
| FoldRegion oldFoldRegion = getFoldRegion(foldStartOffset); |
| assertNotNull(oldFoldRegion); |
| foldingModel.removeFoldRegion(oldFoldRegion); |
| |
| int newFoldEndOffset = myEditor.getDocument().getText().indexOf("class") - 2; |
| FoldRegion newFoldRegion = foldingModel.addFoldRegion(foldStartOffset, newFoldEndOffset, "..."); |
| assertNotNull(newFoldRegion); |
| newFoldRegion.setExpanded(false); |
| } |
| }); |
| CodeFoldingManager.getInstance(getProject()).updateFoldRegions(myEditor); |
| assertEquals(new VisualPosition(2, 0), myEditor.logicalToVisualPosition(new LogicalPosition(5, 0))); |
| } |
| |
| public void testModificationOfSoftWrappedFoldRegion() throws IOException { |
| String text = |
| "import java.util.List;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;\n" + |
| "import java.util.LinkedList;\n" + |
| "import java.util.Set;\n" + |
| "\n" + |
| "class Test {\n" + |
| "}"; |
| init(40, text); |
| |
| final int foldStartOffset = text.indexOf("java.util.Collections"); |
| final int foldEndOffset = text.indexOf("class") - 2; |
| addCollapsedFoldRegion(foldStartOffset, foldEndOffset, "..."); |
| |
| int modificationOffset = text.indexOf("java.util.Set"); |
| myEditor.getDocument().insertString(modificationOffset, "import java.util.HashSet;\n"); |
| // Used to get StackOverflowError here, hence, no additional checking is performed. |
| } |
| |
| public void testLongSoftWrappedLineWithNonWrappedEndInTheMiddleOfDocument() throws IOException { |
| // Inspired by IDEA-70114 |
| |
| String text = |
| "111\n" + |
| "222\n" + |
| "33333333 33333333333333333333333333333333\n" + |
| "444"; |
| init(15, text); |
| |
| assertEquals(new LogicalPosition(3, 0), myEditor.visualToLogicalPosition(new VisualPosition(4, 0))); |
| } |
| |
| public void testDeleteThatEndsOnLineWithMultiLineFoldRegion() throws IOException { |
| String text = |
| "111\n" + |
| "222\n" + |
| "333\n" + |
| "444 55\n" + |
| "666 77"; |
| |
| init(15, text); |
| |
| int foldStart = text.indexOf("5"); |
| int foldEnd = text.indexOf("7"); |
| addCollapsedFoldRegion(foldStart, foldEnd, "..."); |
| |
| int selectionStart = text.indexOf("2"); |
| int selectionEnd = text.indexOf("4"); |
| getEditor().getSelectionModel().setSelection(selectionStart, selectionEnd); |
| |
| delete(); |
| assertEquals(new VisualPosition(1, 8), getEditor().offsetToVisualPosition(getEditor().getDocument().getTextLength() - 1)); |
| } |
| |
| public void testNoWrapAtFirstNonWsSymbolWithCustomIndent() throws IOException { |
| String text = |
| " 1111111111111111111111111111111"; |
| init(10, text); |
| getEditor().getSettings().setCustomSoftWrapIndent(0); |
| getEditor().getSettings().setUseCustomSoftWrapIndent(true); |
| int textLength = getEditor().getDocument().getTextLength(); |
| //Trigger soft wraps recalculation. |
| assertEquals(new LogicalPosition(0, textLength), myEditor.offsetToLogicalPosition(textLength)); |
| |
| // Don't expect soft wraps to be registered as there is no point in wrapping at the first non-white space symbol position |
| // in all cases when soft wrap is located at the left screen edge. |
| assertEmpty(getSoftWrapModel().getRegisteredSoftWraps()); |
| } |
| |
| public void testLeadingTabWithShiftedWidth() throws IOException { |
| // Inspired by IDEA-76353. The point is that we need to consider cached information about tab symbols width during logical |
| // position to offset mapping |
| String text = "\t test"; |
| init(15, text); |
| ((EditorImpl)myEditor).setPrefixTextAndAttributes(" ", new TextAttributes()); |
| myEditor.getCaretModel().moveToOffset(text.length()); |
| } |
| |
| public void testSoftWrapCacheReset() throws IOException { |
| // Inspired by IDEA-76537 - the point is to drop cached document info on complete soft wraps recalculation |
| String text = |
| "\t first line\n" + |
| "\t second line\n" + |
| "\t third line"; |
| |
| // Make soft wraps to build a document info cache. |
| init(40, text); |
| |
| // Modify document while soft wraps processing is off. |
| final EditorSettings settings = getEditor().getSettings(); |
| settings.setUseSoftWraps(false); |
| int startOffset = text.indexOf("\t third") - 1; |
| getEditor().getDocument().deleteString(startOffset, text.length()); |
| |
| // Enable soft wraps and ensure that the cache is correctly re-built. |
| settings.setUseSoftWraps(true); |
| |
| getEditor().getCaretModel().moveToOffset(getEditor().getDocument().getTextLength()); |
| type("\n test"); |
| |
| final int offset = getEditor().getDocument().getTextLength() - 1; |
| final LogicalPosition logicalPosition = getEditor().offsetToLogicalPosition(offset); |
| assertEquals(offset, getEditor().logicalPositionToOffset(logicalPosition)); |
| |
| final VisualPosition visualPosition = getEditor().offsetToVisualPosition(offset); |
| assertEquals(visualPosition, getEditor().logicalToVisualPosition(logicalPosition)); |
| assertEquals(logicalPosition, getEditor().visualToLogicalPosition(visualPosition)); |
| } |
| |
| public void testSoftWrapsRecalculationOnTabWidthChange() throws IOException { |
| // Inspired by IDEA-78616 - the point is to recalculate soft wraps when tab width is changed. |
| String text = |
| "\t<caret> my text"; |
| |
| // Build soft wraps cache. |
| init(40, text); |
| |
| VisualPosition caretPositionBefore = getEditor().getCaretModel().getVisualPosition(); |
| |
| // Change tab size. |
| final CommonCodeStyleSettings.IndentOptions indentOptions = getCurrentCodeStyleSettings().getIndentOptions(); |
| assertNotNull(indentOptions); |
| indentOptions.TAB_SIZE++; |
| |
| ((EditorImpl)getEditor()).reinitSettings(); |
| assertEquals( |
| new VisualPosition(caretPositionBefore.line, caretPositionBefore.column + 1), |
| getEditor().getCaretModel().getVisualPosition() |
| ); |
| } |
| |
| public void testNoPreliminarySoftWrapAtLineEnd() throws IOException { |
| // We used to make soft wrap when the string was couple of visual columns before the right screen edge even if it could |
| // be completely shown. |
| init(37, "a b c", 7); |
| assertEmpty(getSoftWrapModel().getRegisteredSoftWraps()); |
| } |
| |
| public void testNoPreliminarySoftWrapBeforeFoldingAtLineEnd() throws IOException { |
| final String text = "a b c test"; |
| init(50, text, 7); |
| addCollapsedFoldRegion(text.indexOf("t"), text.length(), "."); |
| assertEmpty(getSoftWrapModel().getRegisteredSoftWraps()); |
| } |
| |
| public void testMultiLineFoldRegionBeforeWrapPosition() throws IOException { |
| final String text = |
| "package org.denis;\n" + |
| "\n" + |
| "\n" + |
| "public class BrokenAlignment {\n" + |
| "\n" + |
| " void method1(int a) {\n" + |
| " }\n" + |
| "\n" + |
| " Object method2(Object ... data) {\n" + |
| " return new Runnable() {\n" + |
| " public void run() {\n" + |
| " System.out.println();\n" + |
| " }\n" + |
| " };\n" + |
| " }\n" + |
| "\n" + |
| "}"; |
| init(511, text, TestFileType.JAVA, 10); |
| |
| addCollapsedFoldRegion(text.indexOf("new Runnable"), text.indexOf("System"), "Runnable() { "); |
| |
| int start = text.indexOf("System"); |
| start = text.indexOf("\n", start); |
| int end = text.indexOf(';', start); |
| addCollapsedFoldRegion(start, end, " }"); |
| |
| final List<? extends SoftWrap> wraps = getSoftWrapModel().getRegisteredSoftWraps(); |
| assertEquals(1, wraps.size()); |
| |
| assertEquals(end, wraps.get(0).getStart()); |
| assertEquals(myEditor.offsetToVisualPosition(start), myEditor.offsetToVisualPosition(start + 1)); |
| assertEquals(myEditor.offsetToVisualPosition(start).line, myEditor.offsetToVisualPosition(end - 1).line); |
| } |
| |
| public void testEnsureBeforeSoftWrapSignIsVisible() throws IOException { |
| final String text = "a.b.c.d"; |
| init(43, text, 7); |
| |
| checkSoftWraps(text.indexOf('c') + 1); |
| } |
| |
| public void testWrapAfterCollapsedFoldRegion() throws IOException { |
| final String text = |
| "this is a long text to fold more"; |
| init(12, text); |
| |
| int start = text.indexOf("long"); |
| int end = text.indexOf("more"); |
| addCollapsedFoldRegion(start, end, "placeholder which is long enough to be wrapped"); |
| |
| checkSoftWraps(start, end); |
| } |
| |
| public void testFoldRegionWithLongPlaceholderText() throws IOException { |
| final String text = |
| "this is a string with(fold region), end)\n" + |
| "second string"; |
| init(40, text); |
| |
| int start = text.indexOf("fold"); |
| int end = text.indexOf(','); |
| addCollapsedFoldRegion(start, end, "this is a really long fold region placeholder"); |
| |
| checkSoftWraps(start, end); |
| assertEquals(new VisualPosition(3, 0), myEditor.offsetToVisualPosition(text.indexOf("second"))); |
| } |
| |
| public void testDeleteWhenCaretBeforeSoftWrap() throws IOException { |
| final String text = |
| "text 1234"; |
| init(7, text); |
| |
| final int offset = text.indexOf("123"); |
| checkSoftWraps(offset); |
| |
| final CaretModel caretModel = myEditor.getCaretModel(); |
| caretModel.moveToOffset(offset); |
| caretModel.moveCaretRelatively(-1, 0, false, false, false); |
| assertEquals(offset, caretModel.getOffset()); // Navigating from 'after soft wrap' to the 'before soft wrap' position. |
| delete(); |
| assertEquals("text 234", myEditor.getDocument().getText()); |
| } |
| |
| public void testSelectionOfLineWithSoftWrapAndFoldRegion() throws IOException { |
| final String text = |
| "123\n" + |
| "fold line 1\n" + |
| "fold line 2 456"; |
| init(6, text); |
| addCollapsedFoldRegion(2, text.indexOf("4"), "..."); |
| myEditor.getSelectionModel().selectLineAtCaret(); |
| assertEquals(text, myEditor.getSelectionModel().getSelectedText()); |
| } |
| |
| public void testCaretPositionAfterLineRemoval() throws IOException { |
| final String text = |
| "123456\n" + |
| "123456\n" + |
| "line towrapbecauseitislong\n" + |
| "923456"; |
| init(8, text); |
| final CaretModel caretModel = myEditor.getCaretModel(); |
| caretModel.moveToOffset(text.indexOf("e")); |
| final VisualPosition position = caretModel.getVisualPosition(); |
| deleteLine(); |
| assertEquals(text.substring(0, text.indexOf("line")) + text.substring(text.indexOf('9')), myEditor.getDocument().getText()); |
| assertEquals(position, caretModel.getVisualPosition()); |
| } |
| |
| public void testNoUnnecessaryHorizontalScrollBar() throws IOException { |
| // Inspired by IDEA-87184 |
| final String text = "12345678 abcdefgh"; |
| init(15, 7, text); |
| myEditor.getCaretModel().moveToOffset(text.length()); |
| final Ref<Boolean> fail = new Ref<Boolean>(true); |
| SoftWrapApplianceManager applianceManager = ((SoftWrapModelImpl)myEditor.getSoftWrapModel()).getApplianceManager(); |
| SoftWrapAwareDocumentParsingListener listener = new SoftWrapAwareDocumentParsingListenerAdapter() { |
| @Override |
| public void beforeSoftWrapLineFeed(@NotNull EditorPosition position) { |
| if (position.x == text.indexOf("a") * 7) { |
| fail.set(false); |
| } |
| } |
| }; |
| applianceManager.addListener(listener); |
| try { |
| backspace(); |
| } |
| finally { |
| applianceManager.removeListener(listener); |
| } |
| assertFalse(fail.get()); |
| } |
| |
| public void testCaretInsideFoldRegionOnCollapse() throws IOException { |
| // IDEA-89874 |
| String text = "one two three"; |
| init(30, text); |
| int foldStart = text.indexOf("two"); |
| addFoldRegion(foldStart, foldStart + "two".length(), "..."); |
| myEditor.getCaretModel().moveToOffset(foldStart + 1); |
| FoldRegion foldRegion = getFoldRegion(foldStart); |
| assertNotNull(foldRegion); |
| assertTrue(foldRegion.isExpanded()); |
| |
| toggleFoldRegionState(foldRegion, false); |
| |
| assertFalse(foldRegion.isExpanded()); |
| assertEquals(foldStart, myEditor.getCaretModel().getOffset()); |
| } |
| |
| public void testFoldRegionEndingAtLineStart() throws IOException { |
| init(100, "aaa\nbbb\nccc\nddd"); |
| addCollapsedFoldRegion(4, 8, "..."); |
| addCollapsedFoldRegion(13, 15, "..."); |
| |
| myEditor.getDocument().insertString(10, "C"); |
| |
| // verify that cached layout data is intact after document change and position recalculation is done correctly |
| assertEquals(new LogicalPosition(0, 0), myEditor.visualToLogicalPosition(new VisualPosition(0, 0))); |
| } |
| |
| private void init(final int visibleWidthInColumns, @NotNull String fileText) throws IOException { |
| init(visibleWidthInColumns, 7, fileText); |
| } |
| |
| private void init(final int visibleWidthInColumns, final int symbolWidthInPixels, @NotNull String fileText) throws IOException { |
| init(visibleWidthInColumns * symbolWidthInPixels, fileText, symbolWidthInPixels); |
| } |
| |
| private void init(final int visibleWidth, @NotNull String fileText, int symbolWidth) throws IOException { |
| init(visibleWidth, fileText, TestFileType.TEXT, symbolWidth); |
| } |
| |
| private void init(final int visibleWidth, @NotNull String fileText, @NotNull TestFileType fileType, final int symbolWidth) throws IOException { |
| init(fileText, fileType); |
| EditorTestUtil.configureSoftWraps(myEditor, visibleWidth, symbolWidth); |
| } |
| |
| private static void checkSoftWraps(int... startOffsets) { |
| final List<? extends SoftWrap> softWraps = getSoftWrapModel().getRegisteredSoftWraps(); |
| assertEquals("soft wraps number", startOffsets.length, softWraps.size()); |
| for (int i = 0; i < startOffsets.length; i++) { |
| assertEquals(startOffsets[i], softWraps.get(i).getStart()); |
| } |
| } |
| |
| private static SoftWrapModelImpl getSoftWrapModel() { |
| return (SoftWrapModelImpl)myEditor.getSoftWrapModel(); |
| } |
| } |