diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemTracking.java b/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemTracking.java index 53137251b..3418b132e 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemTracking.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemTracking.java @@ -13,7 +13,15 @@ import software.coley.recaf.ui.control.richtext.Editor; import software.coley.recaf.ui.control.richtext.EditorComponent; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; import java.util.function.Predicate; @@ -210,6 +218,9 @@ public List getAllProblems() { @Override public void accept(PlainTextChange change) { + // Skip if there is no associated editor, or there are no problems to update + if (editor == null || problems.isEmpty()) return; + // TODO: There are some edge cases where the tracking of problem indicators will fail, and we just // delete them. Deleting an empty line before a line with an error will void it. // I'm not really sure how to make a clean fix for that, but because the rest of @@ -251,6 +262,15 @@ public void accept(PlainTextChange change) { } } + /** + * Shifts problems beyond the given range by {@code 1 + (endLine - startLine)}. + * Problems in the removed range are deleted. + * + * @param startLine + * Starting range of lines inserted (inclusive). + * @param endLine + * Ending range of lines inserted (inclusive). + */ protected void onLinesInserted(int startLine, int endLine) { logger.debugging(l -> l.trace("Lines inserted: {}-{}", startLine, endLine)); TreeSet>> set = @@ -272,6 +292,15 @@ protected void onLinesInserted(int startLine, int endLine) { }); } + /** + * Shifts problems beyond the given range by {@code endLine - startLine}. + * Problems in the removed range are deleted. + * + * @param startLine + * Starting range of lines removed (inclusive). + * @param endLine + * Ending range of lines removed (exclusive). + */ protected void onLinesRemoved(int startLine, int endLine) { logger.debugging(l -> l.trace("Lines removed: {}-{}", startLine, endLine)); diff --git a/recaf-ui/src/test/java/software/coley/recaf/ui/control/richtext/problem/ProblemTrackingTest.java b/recaf-ui/src/test/java/software/coley/recaf/ui/control/richtext/problem/ProblemTrackingTest.java index 13d973ffe..b60e38f6d 100644 --- a/recaf-ui/src/test/java/software/coley/recaf/ui/control/richtext/problem/ProblemTrackingTest.java +++ b/recaf-ui/src/test/java/software/coley/recaf/ui/control/richtext/problem/ProblemTrackingTest.java @@ -1,9 +1,13 @@ package software.coley.recaf.ui.control.richtext.problem; import org.junit.jupiter.api.Test; -import static software.coley.recaf.ui.control.richtext.problem.ProblemLevel.*; -import static software.coley.recaf.ui.control.richtext.problem.ProblemPhase.*; + +import java.util.List; + import static org.junit.jupiter.api.Assertions.*; +import static software.coley.recaf.ui.control.richtext.problem.ProblemLevel.ERROR; +import static software.coley.recaf.ui.control.richtext.problem.ProblemLevel.WARN; +import static software.coley.recaf.ui.control.richtext.problem.ProblemPhase.*; /** * Tests for {@link ProblemTracking} @@ -61,9 +65,72 @@ void removeByLine() { } @Test - void foo() { + void onLinesRemoved() { + Problem problem0 = new Problem(0, 0, 0, ERROR, LINT, "message"); + Problem problem10 = new Problem(10, 0, 0, ERROR, LINT, "message"); + ProblemTracking tracking = new ProblemTracking(); + tracking.add(problem0); + tracking.add(problem10); + + // Initial state + assertSame(problem0, tracking.getFirstProblemOnLine(0), "Invalid initial state"); + assertSame(problem10, tracking.getFirstProblemOnLine(10), "Invalid initial state"); + + // Remove line 3 + // - End range is exclusive so this is just removing one line + tracking.onLinesRemoved(3, 4); + + // Validate line0 problem not moved, problem10 moved to line 9 + assertEquals(2, tracking.getAllProblems().size()); + assertSame(problem0, tracking.getFirstProblemOnLine(0), "Line 0 should not have moved"); + assertNull(tracking.getFirstProblemOnLine(10), "Line 10 should have moved"); + Problem problem9 = tracking.getFirstProblemOnLine(9); + assertNotEquals(problem10, problem9, "Moved problem should be different reference + have different line number"); + assertEquals(9, problem9.line(), "Line 10 problem should have moved to line 9"); + } + + @Test + void onLinesInserted() { + Problem problem0 = new Problem(0, 0, 0, ERROR, LINT, "message"); + Problem problem10 = new Problem(10, 0, 0, ERROR, LINT, "message"); + ProblemTracking tracking = new ProblemTracking(); + tracking.add(problem0); + tracking.add(problem10); + + // Initial state + assertSame(problem0, tracking.getFirstProblemOnLine(0), "Invalid initial state"); + assertSame(problem10, tracking.getFirstProblemOnLine(10), "Invalid initial state"); + + // Insert new line at line 3 + // - End range is inclusive + tracking.onLinesInserted(3, 3); + + // Validate line0 problem not moved, problem10 moved to line 11 + assertEquals(2, tracking.getAllProblems().size()); + assertSame(problem0, tracking.getFirstProblemOnLine(0), "Line 0 should not have moved"); + assertNull(tracking.getFirstProblemOnLine(10), "Line 10 should have moved"); + Problem problem11 = tracking.getFirstProblemOnLine(11); + assertNotEquals(problem10, problem11, "Moved problem should be different reference + have different line number"); + assertEquals(11, problem11.line(), "Line 10 problem should have moved to line 11"); + } + + @Test + void multipleOnLine() { ProblemTracking tracking = new ProblemTracking(); - tracking.onLinesInserted(1, 2); - tracking.add(new Problem(5, 0, 0, ERROR, LINT, "message")); + tracking.add(new Problem(10, 1, 0, ERROR, LINT, "foo")); + tracking.add(new Problem(10, 0, 0, ERROR, LINT, "fizz")); + tracking.add(new Problem(10, 1, 0, ERROR, LINT, "buzz")); + + // 3 total + assertEquals(3, tracking.getAllProblems().size()); + assertEquals(3, tracking.getProblemsByLevel(ERROR).size()); + assertEquals(3, tracking.getProblemsByPhase(LINT).size()); + + // 1 map entry since they all are on a single line + assertEquals(1, tracking.getProblems().size()); + + // 3 on line 10 + List problemsOnLine = tracking.getProblemsOnLine(10); + assertEquals(3, problemsOnLine.size()); } } \ No newline at end of file