Repository: akvelon/flutter-code-editor (dart)

Issue:
CodeController.foldImports does not fold a multiline import in Go
Reproducible with MinimalWordCount example in Playground. In short:

```go
// license
// license
package main

import (
  "context"
  "fmt"
  "regexp"

  "github.com/apache/beam/sdks/v2/go/pkg/beam"
  "github.com/apache/beam/sdks/v2/go/pkg/beam/io/textio"
  "github.com/apache/beam/sdks/v2/go/pkg/beam/runners/direct"
  "github.com/apache/beam/sdks/v2/go/pkg/beam/transforms/stats"

  _ "github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/gcs"
  _ "github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/local"
)

```

Pull request:
- Resolves #136

Patch:
diff --git a/lib/src/code/code.dart b/lib/src/code/code.dart
index 58cefe97..cfe5bea0 100644
--- a/lib/src/code/code.dart
+++ b/lib/src/code/code.dart
@@ -246,13 +246,19 @@ class Code {
     }
     final lastLine = section.lastLine;
     if (lastLine != null && lastLine < lines.lines.length - 1) {
-      result['ending'] = HiddenRange(
-        lines.lines[lastLine].textRange.end,
-        lines.lines.last.textRange.end,
-        firstLine: lastLine,
-        lastLine: lines.lines.length - 1,
-        wholeFirstLine: false,
-      );
+      final start = lines.lines[lastLine].textRange.end;
+      final end = lines.lines.last.textRange.end;
+
+      // Can be equal if the last line has no \n.
+      if (end > start) {
+        result['ending'] = HiddenRange(
+          start,
+          end,
+          firstLine: lastLine,
+          lastLine: lines.lines.length - 1,
+          wholeFirstLine: false,
+        );
+      }
     }
     return result;
   }
diff --git a/lib/src/code_field/text_editing_value.dart b/lib/src/code_field/text_editing_value.dart
index b79ff844..0afa37d6 100644
--- a/lib/src/code_field/text_editing_value.dart
+++ b/lib/src/code_field/text_editing_value.dart
@@ -152,4 +152,39 @@ extension TextEditingValueExtension on TextEditingValue {
     }
     return text.substring(selection.end);
   }
+
+  TextEditingValue typed(String text) {
+    final lengthDiff = text.length - selected.length;
+
+    return replaced(selection, text).copyWith(
+      selection: TextSelection.collapsed(offset: selection.end + lengthDiff),
+    );
+  }
+
+  TextEditingValue? select(Pattern pattern, [int start = 0]) {
+    if (pattern == '') {
+      throw AssertionError('Cannot search for an empty pattern');
+    }
+
+    final position = text.indexOf(pattern, start);
+
+    if (position == -1) {
+      return null;
+    }
+
+    final match = pattern.matchAsPrefix(text, position);
+
+    if (match == null) {
+      throw AssertionError('');
+    }
+
+    return TextEditingValue(
+      composing: composing,
+      selection: TextSelection(
+        baseOffset: position,
+        extentOffset: match.end,
+      ),
+      text: text,
+    );
+  }
 }
diff --git a/lib/src/folding/foldable_block.dart b/lib/src/folding/foldable_block.dart
index 2ac0cb5c..e27261ac 100644
--- a/lib/src/folding/foldable_block.dart
+++ b/lib/src/folding/foldable_block.dart
@@ -55,6 +55,30 @@ class FoldableBlock extends InclusiveRange with EquatableMixin {
 
     return false;
   }
+
+  bool isSameLines(FoldableBlock other) {
+    return firstLine == other.firstLine && lastLine == other.lastLine;
+  }
+
+  FoldableBlock join(FoldableBlock other) {
+    return FoldableBlock(
+      firstLine: min(firstLine, other.firstLine),
+      lastLine: max(lastLine, other.lastLine),
+      type: _getJoinType(this, other),
+    );
+  }
+
+  static FoldableBlockType _getJoinType(FoldableBlock a, FoldableBlock b) {
+    if (a.type == FoldableBlockType.imports) {
+      return FoldableBlockType.imports;
+    }
+
+    if (b.type == FoldableBlockType.imports) {
+      return FoldableBlockType.imports;
+    }
+
+    return FoldableBlockType.union;
+  }
 }
 
 extension FoldableBlockList on List<FoldableBlock> {
@@ -62,31 +86,73 @@ extension FoldableBlockList on List<FoldableBlock> {
     sort((a, b) => a.firstLine - b.firstLine);
   }
 
-  /// Joins intersecting blocks in list.
-  /// Expected that list is sorted by start line.
+  /// Joins intersecting blocks.
+  ///
+  /// This list must be sorted by firstLine.
   void joinIntersecting() {
     if (length < 2) {
       return;
     }
 
-    for (int i = 1; i < length; i++) {
-      final currentBlock = this[i];
-      final previousBlock = this[i - 1];
-
-      final areIntersected = previousBlock.lastLine >= currentBlock.firstLine &&
-          previousBlock.lastLine < currentBlock.lastLine;
-
-      final isDuplicate = previousBlock.firstLine == currentBlock.firstLine &&
-          previousBlock.lastLine == currentBlock.lastLine;
-
-      if (areIntersected || isDuplicate) {
-        this[i - 1] = FoldableBlock(
-          firstLine: previousBlock.firstLine,
-          lastLine: currentBlock.lastLine,
-          type: FoldableBlockType.union,
-        );
-        removeAt(i);
-        i = max(i - 2, 0);
+    // A working list to lay down blocks to compare.
+    // As we iterate blocks, it will contain the current nesting hierarchy.
+    final ancestors = <FoldableBlock>[];
+    final ancestorIndexToOverallIndex = <int>[];
+
+    for (int overallIndex = 0; overallIndex < length; overallIndex++) {
+      FoldableBlock bubble = this[overallIndex];
+
+      // Throw the block into the current nesting hierarchy...
+      ancestors.add(bubble);
+      ancestorIndexToOverallIndex.add(overallIndex);
+
+      int bubbleIndexInAncestors = ancestors.length - 1;
+      int bubbleOverallIndex = overallIndex;
+
+      // And fix every violation of the hierarchy by bubbling the block up,
+      // removing non-ancestors from the working list, and joining when needed.
+      for (int ancestorIndex = ancestors.length - 2;
+          ancestorIndex >= 0;
+          ancestorIndex--) {
+        final ancestor = ancestors[ancestorIndex];
+
+        if (ancestor.lastLine < bubble.firstLine) {
+          // `bubble` is not nested in `ancestor`.
+          // Remove `ancestor` from the working list and try the upper one.
+          ancestors.removeAt(ancestorIndex);
+          ancestorIndexToOverallIndex.removeAt(ancestorIndex);
+          bubbleIndexInAncestors--;
+          continue;
+        }
+
+        final isDuplicate = bubble.isSameLines(ancestor);
+
+        final areIntersecting = ancestor.lastLine >= bubble.firstLine &&
+            ancestor.lastLine < bubble.lastLine;
+
+        if (isDuplicate || areIntersecting) {
+          final joined = ancestor.join(bubble);
+
+          this[ancestorIndexToOverallIndex[ancestorIndex]] = joined;
+          ancestors[ancestorIndex] = joined;
+
+          removeAt(bubbleOverallIndex);
+          ancestors.removeAt(bubbleIndexInAncestors);
+          ancestorIndexToOverallIndex.removeAt(bubbleIndexInAncestors);
+          overallIndex--;
+
+          if (!areIntersecting) {
+            // `bubble` was a duplicate lines-wise.
+            // Do not go up the hierarchy because it is laid down alright there.
+            break; // to the top level, try the next block.
+          }
+
+          // Continue bubbling up the new joined block
+          // because it may still violate the hierarchy above.
+          bubbleIndexInAncestors = ancestorIndex;
+          bubbleOverallIndex = ancestorIndexToOverallIndex[ancestorIndex];
+          bubble = ancestors[bubbleIndexInAncestors];
+        }
       }
     }
   }


Test patch:
diff --git a/test/src/code_field/code_controller_visible_sections_test.dart b/test/src/code_field/code_controller_visible_sections_test.dart
index 81011386..739dd126 100644
--- a/test/src/code_field/code_controller_visible_sections_test.dart
+++ b/test/src/code_field/code_controller_visible_sections_test.dart
@@ -7,13 +7,17 @@ import 'package:highlight/languages/dart.dart';
 
 import '../common/create_app.dart';
 
+//                                    separate      separate      separate      trailing      trailing      trailing      no          no
+//                                    start         start         start         start         start         start         start       start
+//                                    no            separate      trailing      no            separate      trailing      trailing    separate
+//                                    end           end           end           end           end           end           end         end
 const _fullText = '''
-class MyClass {//    0 [START whole]
-  //                 1 [START separate_start_no_end][START separate_start_separate_end][START separate_start_trailing_end]
-  void method() {//  2 [START trailing_start_no_end][START trailing_start_separate_end][START trailing_start_trailing_end]
-  }//                3 [END separate_start_trailing_end][END trailing_start_trailing_end][END no_start_trailing_end]
-  //                 4 [END separate_start_separate_end][END trailing_start_separate_end][END no_start_separate_end]
-}//                  5 [END whole]''';
+class MyClass {//    0 [START whole]                                                                                      |           |
+  //                 1 |              [START ss_ne] [START ss_se] [START ss_te]                                           |           |
+  void method() {//  2 |              |             |             |             [START ts_ne] [START ts_se] [START ts_te] |           |
+  }//                3 |              |             |             [END ss_te]   |             |             [END ts_te]   [END ns_te] |
+  //                 4 |              |             [END ss_se]                 |             [END ts_se]                             [END ns_se]
+}//                  5 [END whole]    |                                         |''';
 
 const _fullVisibleText = '''
 class MyClass {
@@ -53,7 +57,7 @@ void main() {
     });
 
     test('Separate start no end', () {
-      final controller = createTestController({'separate_start_no_end'});
+      final controller = createTestController({'ss_ne'});
 
       expect(controller.value.text, '''
   
@@ -67,7 +71,7 @@ $_method
     });
 
     test('Separate start separate end', () {
-      final controller = createTestController({'separate_start_separate_end'});
+      final controller = createTestController({'ss_se'});
 
       expect(controller.value.text, '''
   
@@ -81,7 +85,7 @@ $_method
     });
 
     test('Separate start trailing end', () {
-      final controller = createTestController({'separate_start_trailing_end'});
+      final controller = createTestController({'ss_te'});
 
       expect(controller.value.text, '''
   
@@ -94,7 +98,7 @@ $_method
     });
 
     test('Trailing start no end', () {
-      final controller = createTestController({'trailing_start_no_end'});
+      final controller = createTestController({'ts_ne'});
 
       expect(controller.value.text, '''
 $_method
@@ -107,7 +111,7 @@ $_method
     });
 
     test('Trailing start separate end', () {
-      final controller = createTestController({'trailing_start_separate_end'});
+      final controller = createTestController({'ts_se'});
 
       expect(controller.value.text, '''
 $_method
@@ -120,7 +124,7 @@ $_method
     });
 
     test('Trailing start trailing end', () {
-      final controller = createTestController({'trailing_start_trailing_end'});
+      final controller = createTestController({'ts_te'});
 
       expect(controller.value.text, '''
 $_method
@@ -132,7 +136,7 @@ $_method
     });
 
     test('No start separate end', () {
-      final controller = createTestController({'no_start_separate_end'});
+      final controller = createTestController({'ns_se'});
 
       expect(controller.value.text, '''
 class MyClass {
@@ -147,7 +151,7 @@ $_method
     });
 
     test('No start trailing end', () {
-      final controller = createTestController({'no_start_trailing_end'});
+      final controller = createTestController({'ns_te'});
 
       expect(controller.value.text, '''
 class MyClass {
@@ -224,5 +228,18 @@ void method1() {
 
 void method2() {''');
     });
+
+    test('Newline after closing comment on the last line', () {
+      final controller = createController(
+        '''
+//[START show]
+//[END show]
+''',
+        language: dart,
+        visibleSectionNames: {'show'},
+      );
+
+      expect(controller.value.text, '\n\n');
+    });
   });
 }
diff --git a/test/src/code_field/text_editing_value_select_test.dart b/test/src/code_field/text_editing_value_select_test.dart
new file mode 100644
index 00000000..c81a81fa
--- /dev/null
+++ b/test/src/code_field/text_editing_value_select_test.dart
@@ -0,0 +1,100 @@
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: prefer_const_constructors
+
+import 'package:flutter/widgets.dart';
+import 'package:flutter_code_editor/flutter_code_editor.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('TextEditingValue ', () {
+    test('select', () {
+      final examples = [
+        //
+        _Example(
+          'Empty value -> null',
+          value: TextEditingValue.empty,
+          pattern: 'pattern',
+          start: 0,
+          expected: null,
+        ),
+
+        _Example(
+          'Empty search -> Error',
+          value: TextEditingValue(text: 'text'),
+          pattern: '',
+          start: 0,
+          throws: true,
+        ),
+
+        _Example(
+          'Not found -> null',
+          value: TextEditingValue(text: 'text'),
+          pattern: RegExp('not found'),
+          start: 0,
+          expected: null,
+        ),
+
+        _Example(
+          'First occurrence',
+          value: TextEditingValue(text: 'aba'),
+          pattern: 'a',
+          start: 0,
+          expected: TextEditingValue(
+            text: 'aba',
+            selection: TextSelection(baseOffset: 0, extentOffset: 1),
+          ),
+        ),
+
+        _Example(
+          'First occurrence starting at, preserves composing',
+          value: TextEditingValue(
+            text: 'aba',
+            composing: TextRange(start: 1, end: 1),
+          ),
+          pattern: RegExp('a'),
+          start: 1,
+          expected: TextEditingValue(
+            text: 'aba',
+            selection: TextSelection(baseOffset: 2, extentOffset: 3),
+            composing: TextRange(start: 1, end: 1),
+          ),
+        ),
+      ];
+
+      for (final example in examples) {
+        TextEditingValue? call() {
+          return example.value.select(example.pattern, example.start);
+        }
+
+        if (example.throws) {
+          expect(call, throwsAssertionError);
+        } else {
+          expect(
+            call(),
+            example.expected,
+            reason: example.name,
+          );
+        }
+      }
+    });
+  });
+}
+
+class _Example {
+  final String name;
+
+  final TextEditingValue? expected;
+  final Pattern pattern;
+  final int start;
+  final bool throws;
+  final TextEditingValue value;
+
+  const _Example(
+    this.name, {
+    required this.pattern,
+    required this.start,
+    required this.value,
+    this.expected,
+    this.throws = false,
+  });
+}
diff --git a/test/src/code_field/text_editing_value_test.dart b/test/src/code_field/text_editing_value_test.dart
index 8555dac3..c369a102 100644
--- a/test/src/code_field/text_editing_value_test.dart
+++ b/test/src/code_field/text_editing_value_test.dart
@@ -6,76 +6,76 @@ import '../common/lorem_ipsum.dart';
 
 void main() {
   group('TextEditingValue ', () {
-    test('wordAtCursorStart', () {
+    test('wordAtCursor', () {
       const examples = [
         //
-        _IntExample(
+        _StringExample(
           'Empty -> null',
           value: TextEditingValue.empty,
           expected: null,
         ),
 
-        _IntExample(
+        _StringExample(
           'No selection -> null',
           value: TextEditingValue(text: loremIpsum),
           expected: null,
         ),
 
-        _IntExample(
+        _StringExample(
           'Word start',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 2),
           ),
-          expected: 2,
+          expected: 'Lorem',
         ),
 
-        _IntExample(
+        _StringExample(
           '1 char',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 3),
           ),
-          expected: 2,
+          expected: 'Lorem',
         ),
 
-        _IntExample(
+        _StringExample(
           '2 chars',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 4),
           ),
-          expected: 2,
+          expected: 'Lorem',
         ),
 
-        _IntExample(
+        _StringExample(
           '3 chars',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 5),
           ),
-          expected: 2,
+          expected: 'Lorem',
         ),
 
-        _IntExample(
+        _StringExample(
           '4 chars',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 6),
           ),
-          expected: 2,
+          expected: 'Lorem',
         ),
 
-        _IntExample(
+        _StringExample(
           'Just after a word',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 7),
           ),
-          expected: 2,
+          expected: 'Lorem',
         ),
 
-        _IntExample(
+        _StringExample(
           'Non-collapsed -> null',
           value: TextEditingValue(
             text: loremIpsum,
@@ -84,16 +84,16 @@ void main() {
           expected: null,
         ),
 
-        _IntExample(
+        _StringExample(
           'String start',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 0),
           ),
-          expected: 0,
+          expected: '1',
         ),
 
-        _IntExample(
+        _StringExample(
           'Non-word string start',
           value: TextEditingValue(
             text: '   ',
@@ -102,102 +102,102 @@ void main() {
           expected: null,
         ),
 
-        _IntExample(
+        _StringExample(
           'At the last word',
           value: TextEditingValue(
             text: 'abc def',
             selection: TextSelection.collapsed(offset: 6),
           ),
-          expected: 4,
+          expected: 'def',
         ),
 
-        _IntExample(
+        _StringExample(
           'After the last word',
           value: TextEditingValue(
             text: 'abc def',
             selection: TextSelection.collapsed(offset: 7),
           ),
-          expected: 4,
+          expected: 'def',
         ),
       ];
 
       for (final example in examples) {
-        final result = example.value.wordAtCursorStart;
+        final result = example.value.wordAtCursor;
 
         expect(result, example.expected, reason: example.name);
       }
     });
 
-    test('wordAtCursor', () {
+    test('wordAtCursorStart', () {
       const examples = [
         //
-        _StringExample(
+        _IntExample(
           'Empty -> null',
           value: TextEditingValue.empty,
           expected: null,
         ),
 
-        _StringExample(
+        _IntExample(
           'No selection -> null',
           value: TextEditingValue(text: loremIpsum),
           expected: null,
         ),
 
-        _StringExample(
+        _IntExample(
           'Word start',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 2),
           ),
-          expected: 'Lorem',
+          expected: 2,
         ),
 
-        _StringExample(
+        _IntExample(
           '1 char',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 3),
           ),
-          expected: 'Lorem',
+          expected: 2,
         ),
 
-        _StringExample(
+        _IntExample(
           '2 chars',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 4),
           ),
-          expected: 'Lorem',
+          expected: 2,
         ),
 
-        _StringExample(
+        _IntExample(
           '3 chars',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 5),
           ),
-          expected: 'Lorem',
+          expected: 2,
         ),
 
-        _StringExample(
+        _IntExample(
           '4 chars',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 6),
           ),
-          expected: 'Lorem',
+          expected: 2,
         ),
 
-        _StringExample(
+        _IntExample(
           'Just after a word',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 7),
           ),
-          expected: 'Lorem',
+          expected: 2,
         ),
 
-        _StringExample(
+        _IntExample(
           'Non-collapsed -> null',
           value: TextEditingValue(
             text: loremIpsum,
@@ -206,16 +206,16 @@ void main() {
           expected: null,
         ),
 
-        _StringExample(
+        _IntExample(
           'String start',
           value: TextEditingValue(
             text: loremIpsum,
             selection: TextSelection.collapsed(offset: 0),
           ),
-          expected: '1',
+          expected: 0,
         ),
 
-        _StringExample(
+        _IntExample(
           'Non-word string start',
           value: TextEditingValue(
             text: '   ',
@@ -224,27 +224,27 @@ void main() {
           expected: null,
         ),
 
-        _StringExample(
+        _IntExample(
           'At the last word',
           value: TextEditingValue(
             text: 'abc def',
             selection: TextSelection.collapsed(offset: 6),
           ),
-          expected: 'def',
+          expected: 4,
         ),
 
-        _StringExample(
+        _IntExample(
           'After the last word',
           value: TextEditingValue(
             text: 'abc def',
             selection: TextSelection.collapsed(offset: 7),
           ),
-          expected: 'def',
+          expected: 4,
         ),
       ];
 
       for (final example in examples) {
-        final result = example.value.wordAtCursor;
+        final result = example.value.wordAtCursorStart;
 
         expect(result, example.expected, reason: example.name);
       }
@@ -356,24 +356,24 @@ void main() {
   });
 }
 
-class _StringExample {
+class _IntExample {
   final String name;
   final TextEditingValue value;
-  final String? expected;
+  final int? expected;
 
-  const _StringExample(
+  const _IntExample(
     this.name, {
     required this.value,
     required this.expected,
   });
 }
 
-class _IntExample {
+class _StringExample {
   final String name;
   final TextEditingValue value;
-  final int? expected;
+  final String? expected;
 
-  const _IntExample(
+  const _StringExample(
     this.name, {
     required this.value,
     required this.expected,
diff --git a/test/src/common/text_editing_value.dart b/test/src/common/text_editing_value.dart
index d326d294..ef73555e 100644
--- a/test/src/common/text_editing_value.dart
+++ b/test/src/common/text_editing_value.dart
@@ -5,12 +5,4 @@ extension TextEditingValueTestExtension on TextEditingValue {
   TextEditingValue replacedSelection(String text) {
     return replaced(selection, text);
   }
-
-  TextEditingValue typed(String text) {
-    final lengthDiff = text.length - selected.length;
-
-    return replaced(selection, text).copyWith(
-      selection: TextSelection.collapsed(offset: selection.end + lengthDiff),
-    );
-  }
 }
diff --git a/test/src/folding/foldable_block_test.dart b/test/src/folding/foldable_block_test.dart
index 9841f7e6..3403891f 100644
--- a/test/src/folding/foldable_block_test.dart
+++ b/test/src/folding/foldable_block_test.dart
@@ -4,132 +4,141 @@ import 'package:flutter_test/flutter_test.dart';
 import 'parsers/test_executor.dart';
 
 void main() {
-  group('FoldableBlockList.joinIntersecting', () {
-    test('Empty -> Empty', () {
-      const blocks = <FoldableBlock>[];
+  group('FoldableBlock.join', () {
+    test('Lines', () {
+      const block14 = FB(firstLine: 1, lastLine: 4, type: FBT.union);
+      const block12 = FB(firstLine: 1, lastLine: 2, type: FBT.union);
+      const block13 = FB(firstLine: 1, lastLine: 3, type: FBT.union);
+      const block23 = FB(firstLine: 2, lastLine: 3, type: FBT.union);
 
-      final actual = [...blocks]..joinIntersecting();
+      expect(block14.join(block14), block14);
 
-      expect(actual, blocks);
+      expect(block14.join(block23), block14);
+      expect(block23.join(block14), block14);
+
+      expect(block12.join(block23), block13);
+      expect(block23.join(block12), block13);
     });
 
-    test('Single -> Single', () {
-      const blocks = [
-        FoldableBlock(firstLine: 0, lastLine: 1, type: FBT.braces),
-      ];
+    test('Anything + Imports = Imports', () {
+      const imports = FB(firstLine: 0, lastLine: 0, type: FBT.imports);
 
-      final actual = [...blocks]..joinIntersecting();
+      for (final type in FBT.values) {
+        final block = FB(firstLine: 0, lastLine: 0, type: type);
+        expect(block.join(imports), imports, reason: type.name);
+        expect(imports.join(block), imports, reason: type.name);
+      }
+    });
 
-      expect(actual, blocks);
+    test('Anything else + Anything else = Union', () {
+      const expected = FB(firstLine: 0, lastLine: 0, type: FBT.union);
+
+      for (final typeA in FBT.values) {
+        for (final typeB in FBT.values) {
+          if (typeA == FBT.imports || typeB == FBT.imports) {
+            continue;
+          }
+
+          final a = FB(firstLine: 0, lastLine: 0, type: typeA);
+          final b = FB(firstLine: 0, lastLine: 0, type: typeB);
+          expect(a.join(b), expected, reason: '$a + $b');
+          expect(b.join(a), expected, reason: '$b + $a');
+        }
+      }
     });
+  });
 
-    test('Not intersected blocks do not join', () {
-      const blocks = [
-        FB(firstLine: 0, lastLine: 3, type: FBT.braces),
-        FB(firstLine: 4, lastLine: 5, type: FBT.imports),
-        FB(firstLine: 8, lastLine: 10, type: FBT.parentheses),
-      ];
+  group('FoldableBlockList.joinIntersecting', () {
+    test('Empty -> Empty', () {
+      const blocks = <FoldableBlock>[];
 
       final actual = [...blocks]..joinIntersecting();
 
       expect(actual, blocks);
     });
 
-    test('Mixed blocks join correctly', () {
+    test('Single -> Single', () {
       const blocks = [
-        FB(firstLine: 0, lastLine: 3, type: FBT.braces),
-        FB(firstLine: 2, lastLine: 5, type: FBT.imports),
-        FB(firstLine: 6, lastLine: 10, type: FBT.parentheses),
-      ];
-      const expected = [
-        FB(firstLine: 0, lastLine: 5, type: FBT.union),
-        FB(firstLine: 6, lastLine: 10, type: FBT.parentheses),
+        FoldableBlock(firstLine: 0, lastLine: 1, type: FBT.braces),
       ];
 
       final actual = [...blocks]..joinIntersecting();
 
-      expect(actual, expected);
+      expect(actual, blocks);
     });
 
-    test('Multiple intersected blocks will join in single', () {
+    test('Not intersecting blocks', () {
       const blocks = [
         FB(firstLine: 0, lastLine: 3, type: FBT.braces),
-        FB(firstLine: 2, lastLine: 5, type: FBT.imports),
-        FB(firstLine: 4, lastLine: 10, type: FBT.parentheses),
-      ];
-      const expected = [
-        FB(firstLine: 0, lastLine: 10, type: FBT.union),
-      ];
-
-      final actual = [...blocks]..joinIntersecting();
-
-      expect(actual, expected);
-    });
-
-    test('Nested intersected blocks will join', () {
-      const blocks = [
-        FB(firstLine: 0, lastLine: 5, type: FBT.braces),
-        FB(firstLine: 1, lastLine: 3, type: FBT.imports),
-        FB(firstLine: 3, lastLine: 5, type: FBT.parentheses),
-      ];
-      const expected = [
-        FB(firstLine: 0, lastLine: 5, type: FBT.braces),
-        FB(firstLine: 1, lastLine: 5, type: FBT.union),
+        FB(firstLine: 4, lastLine: 5, type: FBT.imports),
       ];
 
       final actual = [...blocks]..joinIntersecting();
 
-      expect(actual, expected);
+      expect(actual, blocks);
     });
 
-    test('Duplicates are removed', () {
+    //    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 -> 0 1 2 3 4 5 6 7 8 9
+    //  0 ]                                    ]
+    //  1 ]                                    ]
+    //  2   }                                    }
+    //  3   } )                                  } |
+    //  4   } ) ]                                } | |
+    //  5   } ) ] }                              } | |
+    //  6   } )   }                              } | |
+    //  7   } )     )                            } |
+    //  8   }       )                            } |
+    //  9   }                                    }
+    // 10   }         ]                          }     |
+    // 11   }         ] } )                      }     | |
+    // 12   }         ] } )                      }     | |
+    // 13   }         ]     ]                    }     |
+    // 14   }               ] }                  }     |   }
+    // 15   }               ] }                  }     |   }
+    // 16   }               ]   )                }     |
+    // 17   }                   )                }     |
+    // 18                         ]                          ]
+    // 19                         ] } )                      ] |
+    // 20                         ] } ) ]                    ] |
+    // 21                         ] }   ]                    ] |
+    // 22                         ]                          ]
+    // 23                                 }                      }
+    test('Intersecting blocks', () {
       const blocks = [
-        FB(firstLine: 0, lastLine: 1, type: FBT.singleLineComment),
-        FB(firstLine: 3, lastLine: 5, type: FBT.parentheses),
-        FB(firstLine: 3, lastLine: 5, type: FBT.braces),
+        FB(firstLine: 0, lastLine: 1, type: FBT.brackets),
+        FB(firstLine: 2, lastLine: 17, type: FBT.braces),
+        FB(firstLine: 3, lastLine: 7, type: FBT.parentheses),
+        FB(firstLine: 4, lastLine: 5, type: FBT.brackets),
+        FB(firstLine: 5, lastLine: 6, type: FBT.braces),
+        FB(firstLine: 7, lastLine: 8, type: FBT.parentheses),
+        FB(firstLine: 10, lastLine: 13, type: FBT.brackets),
+        FB(firstLine: 11, lastLine: 12, type: FBT.braces),
+        FB(firstLine: 11, lastLine: 12, type: FBT.parentheses),
+        FB(firstLine: 13, lastLine: 16, type: FBT.brackets),
+        FB(firstLine: 14, lastLine: 15, type: FBT.braces),
+        FB(firstLine: 16, lastLine: 17, type: FBT.parentheses),
+        FB(firstLine: 18, lastLine: 22, type: FBT.brackets),
+        FB(firstLine: 19, lastLine: 21, type: FBT.braces),
+        FB(firstLine: 19, lastLine: 20, type: FBT.parentheses),
+        FB(firstLine: 20, lastLine: 21, type: FBT.brackets),
+        FB(firstLine: 23, lastLine: 23, type: FBT.braces),
       ];
       const expected = [
-        FB(firstLine: 0, lastLine: 1, type: FBT.singleLineComment),
-        FB(firstLine: 3, lastLine: 5, type: FBT.union),
+        FB(firstLine: 0, lastLine: 1, type: FBT.brackets),
+        FB(firstLine: 2, lastLine: 17, type: FBT.braces),
+        FB(firstLine: 3, lastLine: 8, type: FBT.union),
+        FB(firstLine: 4, lastLine: 6, type: FBT.union),
+        FB(firstLine: 10, lastLine: 17, type: FBT.union),
+        FB(firstLine: 11, lastLine: 12, type: FBT.union),
+        FB(firstLine: 14, lastLine: 15, type: FBT.braces),
+        FB(firstLine: 18, lastLine: 22, type: FBT.brackets),
+        FB(firstLine: 19, lastLine: 21, type: FBT.union),
+        FB(firstLine: 23, lastLine: 23, type: FBT.braces),
       ];
 
       final actual = [...blocks]..joinIntersecting();
 
       expect(actual, expected);
     });
-
-    // TODO(Malarg): fix this. It's not desired behavior
-    // This test represents situation, shown on diagram below:
-    //    0 1 2 3 4 5
-    //  0 |
-    //  1 | |
-    //  2 | |
-    //  3 | |
-    //  4 |   |
-    //  5 |   |
-    //  6 |     |
-    //  7       | |
-    //  8       | |
-    //  9       | |
-    // 10       |   |
-    // 11       |   |
-    // 12       |
-    //
-    // blocks[0] and blocks[3] should be joined, but they aren't.
-    test('Containing children blocks will not join', () {
-      const blocks = [
-        FB(firstLine: 0, lastLine: 6, type: FBT.braces), //        0
-        FB(firstLine: 1, lastLine: 3, type: FBT.parentheses), //   1
-        FB(firstLine: 4, lastLine: 5, type: FBT.parentheses), //   2
-        FB(firstLine: 6, lastLine: 12, type: FBT.braces), //       3
-        FB(firstLine: 7, lastLine: 9, type: FBT.parentheses), //   4
-        FB(firstLine: 10, lastLine: 11, type: FBT.parentheses), // 5
-      ];
-
-      // 0 intersects with 3, not detected.
-      final actual = [...blocks]..joinIntersecting();
-
-      expect(actual, blocks);
-    });
   });
 }
diff --git a/test/src/folding/parsers/highlight_parser_go_test.dart b/test/src/folding/parsers/highlight_parser_go_test.dart
index b5f3d5fe..3cb169d5 100644
--- a/test/src/folding/parsers/highlight_parser_go_test.dart
+++ b/test/src/folding/parsers/highlight_parser_go_test.dart
@@ -146,7 +146,23 @@ import (
 
 func main() {}''';
       const expectedBlocks = [
-        FB(firstLine: 0, lastLine: 6, type: FBT.union),
+        FB(firstLine: 0, lastLine: 6, type: FBT.imports),
+      ];
+      _Tester.parseAndCheck(mode: go, code: code, expected: expectedBlocks);
+    });
+
+    test('Comments inside multiline imports', () {
+      const code = '''
+package main
+//
+//
+import (
+	"fmt"
+	"strings"
+)''';
+      const expectedBlocks = [
+        FB(firstLine: 0, lastLine: 6, type: FBT.imports),
+        FB(firstLine: 1, lastLine: 2, type: FBT.singleLineComment),
       ];
       _Tester.parseAndCheck(mode: go, code: code, expected: expectedBlocks);
     });
@@ -177,7 +193,7 @@ func main() {
       _Tester.parseAndCheck(mode: go, code: code, expected: expectedBlocks);
     });
 
-    test('', () {
+    test('Service comment sequences do not form a foldable block.', () {
       const code = '''
 func main() {
 // [START section1]
diff --git a/test/src/folding/parsers/highlight_parser_scala_test.dart b/test/src/folding/parsers/highlight_parser_scala_test.dart
index 0e464cb6..e8998fb2 100644
--- a/test/src/folding/parsers/highlight_parser_scala_test.dart
+++ b/test/src/folding/parsers/highlight_parser_scala_test.dart
@@ -146,13 +146,12 @@ import users.{ User, UserPreferences}  // Only imports selected members''';
       //TODO(Malarg): handle scala multiline blocks
       //https://github.com/akvelon/flutter-code-editor/issues/78.
       const code = '''
-import users.User  // import the class User
+import users.User // Without this, it is 'braces' block.
 import users.{ 
-  User, 
-  UserPreferences
-}  // Only imports selected members''';
+  User
+}''';
       const expected = [
-        FB(firstLine: 0, lastLine: 4, type: FBT.union),
+        FB(firstLine: 0, lastLine: 3, type: FBT.imports),
       ];
       _Tester.parseAndCheck(mode: scala, code: code, expected: expected);
     });