From 60ebeea6c0a0015a03fde4e688a1ed870f460649 Mon Sep 17 00:00:00 2001
From: Yaroslav Lepenkin <yaroslav.lepenkin@jetbrains.com>
Date: Thu, 4 Aug 2016 19:23:28 +0300
Subject: [PATCH] range merge initial

---
 .../intellij/formatting/FormatTextRange.java  |  4 +
 .../intellij/formatting/FormatTextRanges.kt   | 38 ++++++++-
 .../formatting/FormatTextRangesTest.kt        | 81 +++++++++++++++++++
 3 files changed, 119 insertions(+), 4 deletions(-)

diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatTextRange.java b/platform/lang-impl/src/com/intellij/formatting/FormatTextRange.java
index 4915c0a3350f..b240d2c557d3 100644
--- a/platform/lang-impl/src/com/intellij/formatting/FormatTextRange.java
+++ b/platform/lang-impl/src/com/intellij/formatting/FormatTextRange.java
@@ -44,6 +44,10 @@ public class FormatTextRange {
   public int getStartOffset() {
     return formattingRange.getStartOffset();
   }
+  
+  public int getEndOffset() {
+    return formattingRange.getEndOffset();
+  }
 
   public boolean isReadOnly(@NotNull TextRange range) {
     return range.getStartOffset() > formattingRange.getEndOffset() || range.getEndOffset() < formattingRange.getStartOffset();
diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatTextRanges.kt b/platform/lang-impl/src/com/intellij/formatting/FormatTextRanges.kt
index ca19337534bb..f022de23ad23 100644
--- a/platform/lang-impl/src/com/intellij/formatting/FormatTextRanges.kt
+++ b/platform/lang-impl/src/com/intellij/formatting/FormatTextRanges.kt
@@ -19,14 +19,44 @@ import com.intellij.openapi.util.TextRange
 import java.util.*
 
 
-internal class FormatRangesStorage {
+class FormatRangesStorage {
   private val rangesByStartOffset = TreeMap<Int, FormatTextRange>()
 
   fun add(range: TextRange, processHeadingWhitespace: Boolean) {
-    val formatRange = FormatTextRange(range, processHeadingWhitespace)
-    rangesByStartOffset.put(formatRange.startOffset, formatRange)
+    if (range.isEmpty) return
+    val newRange = FormatTextRange(range, processHeadingWhitespace)
+
+    val nearestBefore = getClosestOrSiblingRange(range)
+    if (nearestBefore != null && canBeMerged(nearestBefore, range)) {
+      val mergedRange = merge(newRange, nearestBefore)
+      rangesByStartOffset.remove(nearestBefore.startOffset)
+      rangesByStartOffset.put(mergedRange.startOffset, mergedRange)
+      return
+    }
+    
+    assert(rangesByStartOffset[newRange.startOffset] == null)
+    rangesByStartOffset.put(newRange.startOffset, newRange)
   }
-  
+
+  private fun canBeMerged(nearestBefore: FormatTextRange, newRange: TextRange): Boolean {
+    return newRange.endOffset < nearestBefore.endOffset || newRange.startOffset < nearestBefore.endOffset
+  }
+
+  private fun getClosestOrSiblingRange(range: TextRange): FormatTextRange? {
+    val closest = rangesByStartOffset.floorEntry(range.endOffset)?.value ?: return null
+    if (range.endOffset == closest.startOffset && !closest.isProcessHeadingWhitespace) {
+      return rangesByStartOffset.floorEntry(range.endOffset - 1)?.value
+    }
+    return closest
+  }
+
+  private fun merge(first: FormatTextRange, second: FormatTextRange): FormatTextRange {
+    val firstByStartOffset = listOf(first, second).sortedBy { it.startOffset }.first()
+    val endOffset = Math.max(first.endOffset, second.endOffset)
+    val range = TextRange(firstByStartOffset.startOffset, endOffset)
+    return FormatTextRange(range, firstByStartOffset.isProcessHeadingWhitespace)
+  }
+
   fun isWhiteSpaceReadOnly(range: TextRange): Boolean {
     return rangesByStartOffset.values.find { !it.isWhitespaceReadOnly(range) } == null
   }
diff --git a/platform/platform-tests/testSrc/com/intellij/formatting/FormatTextRangesTest.kt b/platform/platform-tests/testSrc/com/intellij/formatting/FormatTextRangesTest.kt
index 0e9e4c37347b..3f3d923e79c6 100644
--- a/platform/platform-tests/testSrc/com/intellij/formatting/FormatTextRangesTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/formatting/FormatTextRangesTest.kt
@@ -28,4 +28,85 @@ class FormatTextRangesTest {
     assertThat(ranges.isWhitespaceReadOnly(TextRange(25, 35))).isFalse()
   }
   
+}
+
+
+class FormatRangesStorageTest {
+
+  private fun FormatTextRange.assertRange(start: Int, end: Int, isProcessHeadingSpace: Boolean) {
+    assertThat(textRange).isEqualTo(TextRange(start, end))
+    assertThat(isProcessHeadingWhitespace).isEqualTo(isProcessHeadingSpace)
+  }
+
+  @Test
+  fun `check fully contained text range is not added`() {
+    val storage = FormatRangesStorage()
+
+    storage.add(TextRange(10, 20), false)
+    storage.add(TextRange(15, 18), false)
+    val ranges = storage.getRanges()
+
+    assertThat(ranges).hasSize(1)
+    ranges[0].assertRange(10, 20, false)
+  }
+
+  @Test
+  fun `check left boundary is not formatted`() {
+    val storage = FormatRangesStorage()
+
+    storage.add(TextRange(10, 20), false)
+    storage.add(TextRange(5, 10), false)
+    val ranges = storage.getRanges()
+    assertThat(ranges).hasSize(2)
+  }
+
+
+  @Test
+  fun `check left boundary is formatted`() {
+    val storage = FormatRangesStorage()
+
+    storage.add(TextRange(10, 20), true)
+    storage.add(TextRange(5, 10), false)
+    val ranges = storage.getRanges()
+    
+    assertThat(ranges).hasSize(1)
+    ranges[0].assertRange(5, 20, false)
+  }
+
+  @Test
+  fun `check right boundary is not formatted`() {
+    val storage = FormatRangesStorage()
+
+    storage.add(TextRange(10, 20), false)
+    storage.add(TextRange(20, 30), false)
+    val ranges = storage.getRanges()
+    assertThat(ranges).hasSize(2)
+  }
+
+
+  @Test
+  fun `check right boundary is formatted`() {
+    val storage = FormatRangesStorage()
+
+    storage.add(TextRange(10, 20), false)
+    storage.add(TextRange(19, 30), false)
+    val ranges = storage.getRanges()
+    assertThat(ranges).hasSize(1)
+    ranges[0].assertRange(10, 30, false)
+  }
+
+
+  @Test
+  fun `ensure isProcessHeading space state merged`() {
+    val storage = FormatRangesStorage()
+    
+    storage.add(TextRange(10, 20), false)
+    storage.add(TextRange(10, 20), true)
+    val ranges = storage.getRanges()
+    assertThat(ranges).hasSize(1)
+    ranges[0].assertRange(10, 20, true)
+  }
+  
+
+
 }
\ No newline at end of file
-- 
GitLab