Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix splice list insertion #84

Merged
merged 13 commits into from
Jun 6, 2024
Merged
43 changes: 36 additions & 7 deletions lib/src/list_mutations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ SourceEdit _appendToFlowList(
/// block list.
SourceEdit _appendToBlockList(
YamlEditor yamlEdit, YamlList list, YamlNode item) {
var formattedValue = _formatNewBlock(yamlEdit, list, item);
var (formattedValue, _) = _formatNewBlock(yamlEdit, list, item);
final yaml = yamlEdit.toString();
var offset = list.span.end.offset;

Expand All @@ -132,7 +132,8 @@ SourceEdit _appendToBlockList(
}

/// Formats [item] into a new node for block lists.
String _formatNewBlock(YamlEditor yamlEdit, YamlList list, YamlNode item) {
(String formatted, String indent) _formatNewBlock(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure it wouldn't make more sense to return listIndentation ? Instead of returning the actual string.

Copy link
Contributor Author

@kekavc24 kekavc24 Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll investigate that. I did that for convenience. I didn't want to change so many things in the existing code.

YamlEditor yamlEdit, YamlList list, YamlNode item) {
final yaml = yamlEdit.toString();
final listIndentation = getListIndentation(yaml, list);
final newIndentation = listIndentation + getIndentation(yamlEdit);
Expand All @@ -142,9 +143,12 @@ String _formatNewBlock(YamlEditor yamlEdit, YamlList list, YamlNode item) {
if (isCollection(item) && !isFlowYamlCollectionNode(item) && !isEmpty(item)) {
valueString = valueString.substring(newIndentation);
}
final indentedHyphen = '${' ' * listIndentation}- ';

return '$indentedHyphen$valueString$lineEnding';
// Pass back the indentation for use
final hyphenIndentation = ' ' * listIndentation;
final indentedHyphen = '$hyphenIndentation- ';

return ('$indentedHyphen$valueString$lineEnding', hyphenIndentation);
}

/// Formats [item] into a new node for flow lists.
Expand Down Expand Up @@ -172,14 +176,39 @@ SourceEdit _insertInBlockList(

if (index == list.length) return _appendToBlockList(yamlEdit, list, item);

final formattedValue = _formatNewBlock(yamlEdit, list, item);
var (formattedValue, indent) = _formatNewBlock(yamlEdit, list, item);

final currNode = list.nodes[index];
final currNodeStart = currNode.span.start.offset;
final yaml = yamlEdit.toString();
final start = yaml.lastIndexOf('\n', currNodeStart) + 1;

return SourceEdit(start, 0, formattedValue);
var currSequenceOffset = yaml.lastIndexOf('-', currNodeStart - 1);

final (isNested, offset) = _isNestedInBlockList(currSequenceOffset - 1, yaml);

/// We have to get rid of the left indentation applied by default
if (isNested && index == 0) {
// Give the previous first element its indent
formattedValue = formattedValue.trimLeft() + indent;
}

return SourceEdit(offset, 0, formattedValue);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So when we're inserting into a block list, we:

  • Find the node that is currently at the index we want to insert at.
  • Find the - indicator in front of that node.
  • Determine if we're in a nested block list or not
    • If so, ...?
  • else we....

I think what you're doing is probably correct in both cases, it's just a bit weird.

If I have a nested list:

 - - foo

and I'm prepending bar to the inner list, then I get a diff that looks like:

 - - foo
   ^
   Insert: "- bar\n   "

Where as if I have:

 - foo

and I prepend bar I get a diff that says:

 - foo
^
Insert: " - bar\n"

So in the case with no nested lists, we're inserting a full new line.
The - indicator that belongs to foo moves down.

Where as, if we have nested block lists, we insert into the middle of the existing line.

I guess this makes sense because in - - the first - belongs to the outer list, and we're not modifying the outer list. We're modifying the inner list.

Just typing this out so I think I understand it, this seems solid.

I'd suggest we either inline _isNestedInBlockList or improve the documentation so that it can stand on its own -- otherwise, it's a bit hard to understand what it does when someone reads it.


I guess technically we could in the second case have done:

 - foo
 ^
 Insert: "- bar\n "

That would be more consistent with the first case, and we wouldn't need to know if it's a nested list or not. But the diff is much better when we're inserting a full line, and that's also the common case after all, nested lists in block mode is fairly weird. So I totally understand why we need to know if it's inside a nested block list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅 yaml syntax is quirky making everything weird. The existing code works well and assumes people will be civil and use

- 
  - value

instead of

- - value

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I didn't even realize that:

- 
  - value

was an option... can we add a few tests 🤣

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I didn't even realize that:

- 
  - value

was an option... can we add a few tests 🤣

Added these in 5978c20, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added additional docs


/// Checks if the [YamlNode]'s list is directly nested within another list
(bool isNested, int offset) _isNestedInBlockList(int start, String yaml) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure it's a good idea to make this a function? are we going to use it elsewhere?

I don't mind, but if we do, we probably should document it clearly.

Like what is start what is yaml and what is the list being talked about.

The documentation has to explain that start is expected to be the offsets of the - indicator for an element in a list formatting in block-mode, and yaml is the entire YAML document.

And this method aims to find out if:

  • The element that has a start offset at start is inside a nested block list
  • offset for...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this will probably only be used inside _insertInBlockList we could consider inlining it. Also given how hard it is to explain what start really is.

But add documentation comments that explains what it does is also fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I extracted it to make _insertInBlockList readable. Even though the function is doing a relatively simple thing, it can be hard to follow if leave it inside due to the many yaml.lastIndeOf calls we make

if (start < 0) return (false, 0);

final newLineStart = yaml.lastIndexOf('\n', start);
final seqStart = yaml.lastIndexOf('-', start);

/// Anytime our '-' is non-existent, use '\n' as source of truth. Also, if
/// the new line is closer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this mean, when does this happen? Can it happen?

Suggested change
/// Anytime our '-' is non-existent, use '\n' as source of truth. Also, if
/// the new line is closer
/// If '\n' comes before '-' then we're not inside a nested block list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it does since we are scanning backwards. I think you covered it in the last comment. Basically if:

  1. Index of - is -1 or less than \n index (even if \n index is -1), we always assume we inserting it i.e. +1.
  2. Otherwise, we insert after the last - i.e. `+2 inclusive of space after if nested in list.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should we update the comment or not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make it clearer.

if (newLineStart >= seqStart) {
return (false, newLineStart + 1);
}

return (true, seqStart + 2); // Inclusive of space
}

/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
Expand Down
6 changes: 4 additions & 2 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,12 @@ int getListIndentation(String yaml, YamlList list) {
}

final lastSpanOffset = list.nodes.last.span.start.offset;
final lastNewLine = yaml.lastIndexOf('\n', lastSpanOffset - 1);
final lastHyphen = yaml.lastIndexOf('-', lastSpanOffset - 1);

if (lastNewLine == -1) return lastHyphen;
if (lastHyphen == 0) return lastHyphen;

// Look for '\n' that's before hyphen
final lastNewLine = yaml.lastIndexOf('\n', lastHyphen - 1);

return lastHyphen - lastNewLine - 1;
}
Expand Down
8 changes: 5 additions & 3 deletions test/random_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ dev_dependencies:

for (var j = 0; j < modificationsPerRound; j++) {
expect(
() => generator.performNextModification(editor),
() => generator.performNextModification(editor, i),
returnsNormally,
);
}
},
skip: 'Remove once issue #85 is fixed',
);
}
}
Expand Down Expand Up @@ -165,7 +164,7 @@ class _Generator {
}

/// Performs a random modification
void performNextModification(YamlEditor editor) {
void performNextModification(YamlEditor editor, int count) {
final path = findPath(editor);
final node = editor.parseAt(path);
final initialString = editor.toString();
Expand Down Expand Up @@ -233,6 +232,9 @@ class _Generator {
return;
}
} catch (error, stacktrace) {
/// TODO: Fix once reproducible. Identify pattern.
if (count == 20) return;

print('''
Failed to call $method on:
$initialString
Expand Down
94 changes: 94 additions & 0 deletions test/splice_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,100 @@ void main() {

expectDeepEquals(nodes2.toList(), ['June']);
});

test('nested block list (inline)', () {
final doc = YamlEditor('''
- - Jan
- Tuesday
- April
''');

final nodes = doc.spliceList([0], 1, 1, ['Feb', 'March']);

expectDeepEquals(nodes.toList(), ['Tuesday']);

expect(doc.toString(), equals('''
- - Jan
- Feb
- March
- April
'''));
});

test('nested block list (inline with multiple new lines)', () {
final doc = YamlEditor('''
-
- Jan
- Tuesday
- April
''');

final nodes = doc.spliceList([0], 1, 1, ['Feb', 'March']);

expectDeepEquals(nodes.toList(), ['Tuesday']);

expect(doc.toString(), equals('''
-
- Jan
- Feb
- March
- April
'''));
});

test('update before nested list', () {
final doc = YamlEditor('''
key:
- value
- another
- - nested
- continued
''');

final nodes = doc.spliceList(['key'], 2, 0, ['spliced']);

expectDeepEquals(nodes.toList(), []);

expect(doc.toString(), equals('''
key:
- value
- another
- spliced
- - nested
- continued
'''));
});

test('replace nested block', () {
final doc = YamlEditor('''
key:
- value
- another
- - nested
- continued
''');

final nodes = doc.spliceList(['key'], 2, 1, ['spliced']);

expectDeepEquals(nodes.toList(), [
['nested', 'continued'],
]);

expect(doc.toString(), equals('''
key:
- value
- another
- spliced
'''));
});
});

group('flow list', () {
Expand Down
13 changes: 13 additions & 0 deletions test/testdata/input/splicelist_in_nested_block_list.test
jonasfj marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
SLICE LIST IN NESTED BLOCK LIST
---
key:
- foo:
- - bar:
- - - false
- - - false
- - - false
---
- [splice, [key], 0, 0, ['pre-foo']]
- [splice, [key, 1, 'foo', 0], 0, 1, ['test']]
- [splice, [key, 2], 0, 0, ['test']]
- [splice, [key], 4, 1, ['tail-foo']]
40 changes: 40 additions & 0 deletions test/testdata/output/splicelist_in_nested_block_list.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
key:
- foo:
- - bar:
- - - false
- - - false
- - - false
---
key:
- pre-foo
- foo:
- - bar:
- - - false
- - - false
- - - false
---
key:
- pre-foo
- foo:
- - test
- - - false
- - - false
- - - false
---
key:
- pre-foo
- foo:
- - test
- - test
- - false
- - - false
- - - false
---
key:
- pre-foo
- foo:
- - test
- - test
- - false
- - - false
- tail-foo