Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions pkgs/yaml_edit/lib/src/equality.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ YamlNode getKeyNode(YamlMap map, Object? key) {
return map.nodes.keys.firstWhere((node) => deepEquals(node, key)) as YamlNode;
}

/// Returns the entry associated with a [mapKey] and its index in the [map].
({int index, YamlNode keyNode, YamlNode valueNode}) getYamlMapEntry(
YamlMap map,
Object? mapKey,
) {
for (final (index, MapEntry(:key, :value)) in map.nodes.entries.indexed) {
if (deepEquals(key, mapKey)) {
return (index: index, keyNode: key, valueNode: value);
}
}

throw YamlException('$mapKey not found in map', map.span);
}

/// Returns the [YamlNode] after the [YamlNode] corresponding to the provided
/// [key].
YamlNode? getNextKeyNode(YamlMap map, Object? key) {
Expand Down
95 changes: 33 additions & 62 deletions pkgs/yaml_edit/lib/src/list_mutations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -306,72 +306,43 @@ SourceEdit _insertInFlowList(
/// [index] should be non-negative and less than or equal to `list.length`.
SourceEdit _removeFromBlockList(
YamlEditor yamlEdit, YamlList list, YamlNode nodeToRemove, int index) {
RangeError.checkValueInInterval(index, 0, list.length - 1);

var end = getContentSensitiveEnd(nodeToRemove);

/// If we are removing the last element in a block list, convert it into a
/// flow empty list.
if (list.length == 1) {
final start = list.span.start.offset;

return SourceEdit(start, end - start, '[]');
}
final listSize = list.length;
RangeError.checkValueInInterval(index, 0, listSize - 1);

final yaml = yamlEdit.toString();
final span = nodeToRemove.span;

/// Adjust the end to clear the new line after the end too.
///
/// We do this because we suspect that our users will want the inline
/// comments to disappear too.
final nextNewLine = yaml.indexOf('\n', end);
if (nextNewLine != -1) {
end = nextNewLine + 1;
}

/// If the value is empty
if (span.length == 0) {
var start = span.start.offset;
return SourceEdit(start, end - start, '');
}

/// -1 accounts for the fact that the content can start with a dash
var start = yaml.lastIndexOf('-', span.start.offset - 1);

/// Check if there is a `-` before the node
if (start > 0) {
final lastHyphen = yaml.lastIndexOf('-', start - 1);
final lastNewLine = yaml.lastIndexOf('\n', start - 1);
if (lastHyphen > lastNewLine) {
start = lastHyphen + 2;

/// If there is a `-` before the node, we need to check if we have
/// to update the indentation of the next node.
if (index < list.length - 1) {
/// Since [end] is currently set to the next new line after the current
/// node, check if we see a possible comment first, or a hyphen first.
/// Note that no actual content can appear here.
///
/// We check this way because the start of a span in a block list is
/// the start of its value, and checking from the back leaves us
/// easily confused if there are comments that have dashes in them.
final nextHash = yaml.indexOf('#', end);
final nextHyphen = yaml.indexOf('-', end);
final nextNewLine = yaml.indexOf('\n', end);

/// If [end] is on the same line as the hyphen of the next node
if ((nextHash == -1 || nextHyphen < nextHash) &&
nextHyphen < nextNewLine) {
end = nextHyphen;
}
}
} else if (lastNewLine > lastHyphen) {
start = lastNewLine + 1;
}
}

return SourceEdit(start, end - start, '');
return removeBlockCollectionEntry(
yaml,
blockCollection: list,
isFirstEntry: index == 0,
isSingleEntry: listSize == 1,
isLastEntry: index >= listSize - 1,
nodeToRemoveOffset: (
start: span.length == 0
? span.start.offset
: yaml.lastIndexOf('-', span.start.offset - 1),
end: getContentSensitiveEnd(nodeToRemove)
),
lineEnding: getLineEnding(yaml),
nextBlockNodeInfo: () {
final nextNode = list.nodes[index + 1];
final nextNodeSpan = nextNode.span;
final offset = nextNodeSpan.start.offset;

final hyphenOffset = yaml.lastIndexOf(
'-',
nextNodeSpan.length == 0 ? offset : offset - 1,
);

final nearestLineEnding = yaml.lastIndexOf('\n', hyphenOffset);

return (
nearestLineEnding: nearestLineEnding,
nextNodeColStart: hyphenOffset - (nearestLineEnding + 1),
);
},
);
}

/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
Expand Down
101 changes: 36 additions & 65 deletions pkgs/yaml_edit/lib/src/map_mutations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,11 @@ SourceEdit updateInMap(
/// removing the element at [key] when re-parsed.
SourceEdit removeInMap(YamlEditor yamlEdit, YamlMap map, Object? key) {
assert(containsKey(map, key));
final keyNode = getKeyNode(map, key);
final valueNode = map.nodes[keyNode]!;

if (map.style == CollectionStyle.FLOW) {
return _removeFromFlowMap(yamlEdit, map, keyNode, valueNode);
return _removeFromFlowMap(yamlEdit, map, key);
} else {
return _removeFromBlockMap(yamlEdit, map, keyNode, valueNode);
return _removeFromBlockMap(yamlEdit, map, key);
}
}

Expand Down Expand Up @@ -169,74 +167,47 @@ SourceEdit _replaceInFlowMap(
}

/// Performs the string operation on [yamlEdit] to achieve the effect of
/// removing the [keyNode] from the map, bearing in mind that this is a block
/// removing the [key] from the map, bearing in mind that this is a block
/// map.
SourceEdit _removeFromBlockMap(
YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode valueNode) {
final keySpan = keyNode.span;
var end = getContentSensitiveEnd(valueNode);
SourceEdit _removeFromBlockMap(YamlEditor yamlEdit, YamlMap map, Object? key) {
final (index: entryIndex, :keyNode, :valueNode) = getYamlMapEntry(map, key);
final yaml = yamlEdit.toString();
final lineEnding = getLineEnding(yaml);

if (map.length == 1) {
final start = map.span.start.offset;
final nextNewLine = yaml.indexOf(lineEnding, end);
if (nextNewLine != -1) {
// Remove everything up to the next newline, this strips comments that
// follows on the same line as the value we're removing.
// It also ensures we consume colon when [valueNode.value] is `null`
// because there is no value (e.g. `key: \n`). Because [valueNode.span] in
// such cases point to the colon `:`.
end = nextNewLine;
} else {
// Remove everything until the end of the document, if there is no newline
end = yaml.length;
}
return SourceEdit(start, end - start, '{}');
}

var start = keySpan.start.offset;

/// Adjust the end to clear the new line after the end too.
///
/// We do this because we suspect that our users will want the inline
/// comments to disappear too.
final nextNewLine = yaml.indexOf(lineEnding, end);
if (nextNewLine != -1) {
end = nextNewLine + lineEnding.length;
} else {
// Remove everything until the end of the document, if there is no newline
end = yaml.length;
}

final nextNode = getNextKeyNode(map, keyNode);

if (start > 0) {
final lastHyphen = yaml.lastIndexOf('-', start - 1);
final lastNewLine = yaml.lastIndexOf(lineEnding, start - 1);
if (lastHyphen > lastNewLine) {
start = lastHyphen + 2;

/// If there is a `-` before the node, and the end is on the same line
/// as the next node, we need to add the necessary offset to the end to
/// make sure the next node has the correct indentation.
if (nextNode != null &&
nextNode.span.start.offset - end <= nextNode.span.start.column) {
end += nextNode.span.start.column;
}
} else if (lastNewLine > lastHyphen) {
start = lastNewLine + lineEnding.length;
}
}
final mapSize = map.length;
final keySpan = keyNode.span;

return SourceEdit(start, end - start, '');
return removeBlockCollectionEntry(
yaml,
blockCollection: map,
isFirstEntry: entryIndex == 0,
isSingleEntry: mapSize == 1,
isLastEntry: entryIndex >= mapSize - 1,
nodeToRemoveOffset: (
start: keySpan.start.offset,
end: valueNode.span.length == 0
? keySpan.end.offset + 2 // Null value have no span. Skip ":".
: getContentSensitiveEnd(valueNode),
),
lineEnding: getLineEnding(yaml),

// Only called when the next node is present. Never before.
nextBlockNodeInfo: () {
final nextKeyNode = map.nodes.keys.elementAt(entryIndex + 1) as YamlNode;
final nextKeySpan = nextKeyNode.span.start;

return (
nearestLineEnding: yaml.lastIndexOf('\n', nextKeySpan.offset),
nextNodeColStart: nextKeySpan.column
);
},
);
}

/// Performs the string operation on [yamlEdit] to achieve the effect of
/// removing the [keyNode] from the map, bearing in mind that this is a flow
/// removing the [key] from the map, bearing in mind that this is a flow
/// map.
SourceEdit _removeFromFlowMap(
YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode valueNode) {
SourceEdit _removeFromFlowMap(YamlEditor yamlEdit, YamlMap map, Object? key) {
final (index: _, :keyNode, :valueNode) = getYamlMapEntry(map, key);

var start = keyNode.span.start.offset;
var end = valueNode.span.end.offset;
final yaml = yamlEdit.toString();
Expand Down
Loading