Skip to content

Commit

Permalink
Fix FoldableBlock.joinIntersecting(), fix newline handling after a se…
Browse files Browse the repository at this point in the history
…rvice comment, export TextEditingValue.typed, add TextEditingValue.select (#136)
  • Loading branch information
alexeyinkin committed Jan 16, 2023
1 parent 0930d20 commit 42edccc
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 198 deletions.
20 changes: 13 additions & 7 deletions lib/src/code/code.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
35 changes: 35 additions & 0 deletions lib/src/code_field/text_editing_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}
}
104 changes: 84 additions & 20 deletions lib/src/folding/foldable_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,38 +55,102 @@ 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> {
void sortByStartLine() {
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 - 1; --ancestorIndex >= 0;) {
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];
}
}
}
}
Expand Down
45 changes: 31 additions & 14 deletions test/src/code_field/code_controller_visible_sections_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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, '''
Expand All @@ -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, '''
Expand All @@ -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, '''
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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');
});
});
}
Loading

0 comments on commit 42edccc

Please sign in to comment.