Fix block map colon offset and support updating explicit keys without colons
diff --git a/pkgs/yaml_edit/CHANGELOG.md b/pkgs/yaml_edit/CHANGELOG.md
index 7e14667..dd4350d 100644
--- a/pkgs/yaml_edit/CHANGELOG.md
+++ b/pkgs/yaml_edit/CHANGELOG.md
@@ -1,6 +1,8 @@
 ## 2.2.5-wip
 
 - Fix `YamlEditor.appendToList` and map updates when adding to flow collections with a trailing comma.
+- Fix updating block map entries when keys have whitespace before the colon.
+- Support updating the value of explicit keys that have no colon.
 
 ## 2.2.4
 
diff --git a/pkgs/yaml_edit/lib/src/map_mutations.dart b/pkgs/yaml_edit/lib/src/map_mutations.dart
index 6fa71d6..3a374a2 100644
--- a/pkgs/yaml_edit/lib/src/map_mutations.dart
+++ b/pkgs/yaml_edit/lib/src/map_mutations.dart
@@ -173,9 +173,27 @@
     valueAsString = ' $valueAsString';
   }
 
-  /// +1 accounts for the colon
-  // TODO: What if here is a whitespace following the key, before the colon?
-  final start = keyNode.span.end.offset + 1;
+  /// Find the association colon after the key, skipping whitespace and
+  /// comments. If no colon exists for this key (e.g. an explicit key without
+  /// a value), insert a new association line with the colon and value.
+  final colonIndex = _findAssociationColon(yaml, keyNode.span.end.offset);
+  if (colonIndex == -1) {
+    final mapIndent = getMapIndentation(yaml, map);
+    final nextNewLine = yaml.indexOf('\n', keyNode.span.end.offset);
+    if (nextNewLine == -1) {
+      return SourceEdit(
+        yaml.length,
+        0,
+        '$lineEnding${' ' * mapIndent}:$valueAsString$lineEnding',
+      );
+    }
+    return SourceEdit(
+      nextNewLine + 1,
+      0,
+      '${' ' * mapIndent}:$valueAsString$lineEnding',
+    );
+  }
+  final start = colonIndex + 1;
   var end = getContentSensitiveEnd(map.nodes[key]!);
 
   /// `package:yaml` parses empty nodes in a way where the start/end of the
@@ -186,6 +204,36 @@
   return SourceEdit(start, end - start, valueAsString);
 }
 
+/// Finds the index of the association colon (`:`) belonging to a key in
+/// [yaml], starting from [startOffset].
+///
+/// Skips whitespace and comments. Returns `-1` if no colon is found before
+/// encountering another token or reaching the end of the input.
+int _findAssociationColon(String yaml, int startOffset) {
+  var i = startOffset;
+  while (i < yaml.length) {
+    final c = yaml.codeUnitAt(i);
+    if (c == 0x20 /* space */ ||
+        c == 0x09 /* tab */ ||
+        c == 0x0A /* LF */ ||
+        c == 0x0D /* CR */) {
+      i++;
+    } else if (c == 0x23 /* # */) {
+      i++;
+      while (i < yaml.length &&
+          yaml.codeUnitAt(i) != 0x0A &&
+          yaml.codeUnitAt(i) != 0x0D) {
+        i++;
+      }
+    } else if (c == 0x3A /* : */) {
+      return i;
+    } else {
+      return -1;
+    }
+  }
+  return -1;
+}
+
 /// Performs the string operation on [yamlEdit] to achieve the effect of
 /// replacing the value at [key] with [newValue] when reparsed, bearing in mind
 /// that this is a flow map.
diff --git a/pkgs/yaml_edit/test/update_test.dart b/pkgs/yaml_edit/test/update_test.dart
index 20ab3b9..1746229 100644
--- a/pkgs/yaml_edit/test/update_test.dart
+++ b/pkgs/yaml_edit/test/update_test.dart
@@ -961,6 +961,71 @@
             doc, {'Sammy Sosa': null, 'Ken Griff': null, 'Mark McGwire': null});
       });
 
+      test('updating explicit key without colon succeeds', () {
+        final doc1 = YamlEditor('? key\n');
+        doc1.update(['key'], 123);
+        expect(doc1.toString(), equals('? key\n: 123\n'));
+        expectYamlBuilderValue(doc1, {'key': 123});
+
+        final doc2 = YamlEditor('? key');
+        doc2.update(['key'], 123);
+        expect(doc2.toString(), equals('? key\n: 123\n'));
+        expectYamlBuilderValue(doc2, {'key': 123});
+
+        final doc3 = YamlEditor('''
+? key
+foo: bar
+''');
+        doc3.update(['key'], 123);
+        expect(doc3.toString(), equals('''
+? key
+: 123
+foo: bar
+'''));
+        expectYamlBuilderValue(doc3, {'key': 123, 'foo': 'bar'});
+
+        final doc4 = YamlEditor('''
+? key # comment with : here
+foo: bar
+''');
+        doc4.update(['key'], 123);
+        expect(doc4.toString(), equals('''
+? key # comment with : here
+: 123
+foo: bar
+'''));
+        expectYamlBuilderValue(doc4, {'key': 123, 'foo': 'bar'});
+
+        final doc5 = YamlEditor('''
+parent:
+  ? key
+  foo: bar
+''');
+        doc5.update(['parent', 'key'], 123);
+        expect(doc5.toString(), equals('''
+parent:
+  ? key
+  : 123
+  foo: bar
+'''));
+        expectYamlBuilderValue(doc5, {
+          'parent': {'key': 123, 'foo': 'bar'}
+        });
+      });
+
+      test('updating explicit key with colon succeeds', () {
+        final doc = YamlEditor('''
+? key
+: old
+''');
+        doc.update(['key'], 'new');
+        expect(doc.toString(), equals('''
+? key
+: new
+'''));
+        expectYamlBuilderValue(doc, {'key': 'new'});
+      });
+
       test('with trailing newline', () {
         final doc = YamlEditor('''
 a: 1
@@ -1106,4 +1171,43 @@
       });
     });
   });
+
+  group('quoted map keys with spaces before colon', () {
+    test('preserves colon when updating double-quoted key with space', () {
+      final doc = YamlEditor('''
+"key" : 123
+other: 456
+''');
+      doc.update(['key'], 789);
+      expect(doc.toString(), equals('''
+"key" : 789
+other: 456
+'''));
+      expect(doc.parseAt(['key']).value, equals(789));
+    });
+
+    test('preserves colon when updating single-quoted key with space', () {
+      final doc = YamlEditor('''
+'my quoted key'   : 100
+''');
+      doc.update(['my quoted key'], 200);
+      expect(doc.toString(), equals('''
+'my quoted key'   : 200
+'''));
+      expect(doc.parseAt(['my quoted key']).value, equals(200));
+    });
+
+    test('preserves colon when replacing with block collection', () {
+      final doc = YamlEditor('''
+"key"  : 123
+''');
+      doc.update(['key'], ['a', 'b']);
+      expect(doc.toString(), equals('''
+"key"  :
+  - a
+  - b
+'''));
+      expect(doc.parseAt(['key', 0]).value, equals('a'));
+    });
+  });
 }