Skip to content

fix: keep comments and layout when the editor saves a YAML file - #16

Merged
penspanic merged 3 commits into
mainfrom
fix/preserve-yaml-comments
Sep 28, 2026
Merged

penspanic merged 3 commits into
mainfrom
fix/preserve-yaml-comments

Conversation

@penspanic

Copy link
Copy Markdown
Owner

Data Editor 에서 YAML 테이블을 저장하면 파일에 적어 둔 # 주석이 전부 사라졌습니다. KeyValueDataRepository / SingleDataRepository 가 저장할 때마다 객체에서 문서를 통째로 다시 만들고, YamlDotNet 객체 모델에는 주석을 둘 자리가 없기 때문입니다. Spill 에서는 이 때문에 값 설명을 별도 문서로 옮겨야 했고, Breakline 도 같은 길을 가게 됩니다.

방식

객체 모델이 주석을 들고 다니게 만드는 대신, 저장 시점에 텍스트 세 개를 비교합니다.

  • 디스크에 있는 지금 파일 (주석 포함)
  • 그 파일을 읽어 다시 직렬화한 정규 텍스트
  • 저장할 데이터의 정규 텍스트 (지금까지 그대로 쓰던 것)

뒤의 둘을 구조 단위로 비교해 바뀐 부분만 원본 텍스트에 되돌려 씁니다 (Datra/Serializers/YamlCommentPreserver.cs).

  • 안 바뀐 행·필드는 원본을 바이트 그대로 복사합니다. 주석, 빈 줄, 따옴표, flow 리스트([1, 2]), 3.0 같은 표기, 타입에 없는 키까지 남습니다.
  • 바뀐 스칼라는 그 자리만 고칩니다. 줄 끝 주석과 원래 따옴표 스타일이 유지되고, flow 리스트는 flow 로 다시 씁니다.
  • 행은 Id 로 짝을 맞춥니다. 삭제된 행은 자기 주석(바로 위, 행 안, 줄 끝)을 데리고 사라지고, 순서가 바뀌면 주석도 같이 움직입니다. Id 가 없는 리스트는 안 바뀐 항목을 기준(LCS)으로 맞춥니다.
  • 파일에 없던 필드(기본값이라 생략했던 것)가 바뀌면 앞 필드 다음 줄에 넣고, 새 행은 파일의 행 간격을 따라 마지막 행 뒤(꼬리 주석 앞)에 붙습니다.
  • 주석 소속 규칙: 빈 줄 없이 바로 위에 붙은 주석은 그 행/필드 것, 빈 줄로 떨어진 주석 블록은 섹션 주석이라 제자리에 남음, 첫 행 주석보다 위는 파일 헤더.

라운드트립 모델(주석을 노드에 붙여 들고 다니는 방식)은 YamlDotNet 직렬화기를 대체해야 하고 polymorphic $type·커스텀 컨버터와 두 번 맞춰야 해서 택하지 않았습니다. 이 방식은 기존 직렬화기를 그대로 두고 그 출력만 비교하므로 컨버터나 생성된 serializer 가 무엇이든 똑같이 동작합니다.

안전장치: 합친 결과를 다시 읽어 직렬화한 값이 기존 방식으로 저장했을 때와 다르면, 또는 패치할 수 없는 구조(여러 문서, 파싱 불가 등)면 예전과 똑같은 정규 텍스트를 씁니다. 버그가 있어도 잃는 건 주석이지 데이터가 아닙니다. 아무것도 안 바뀐 저장(강제 저장 포함)은 파일을 그대로 둡니다.

범위

  • YAML 만 해당합니다. CSV·JSON 저장 경로는 손대지 않았습니다 (IsYaml() 로 분기, JSON 은 테스트로 확인).
  • 저장 직전에 provider 로 파일을 다시 읽으므로 에디터가 열려 있는 동안 밖에서 추가한 주석도 유지됩니다. 읽을 수 없으면 마지막으로 읽거나 쓴 텍스트를 씁니다.
  • Unity(AssetDatabaseRawDataProvider), WebEditor, 공유 에디터 레이어 모두 EditableKeyValueDataSource / EditableSingleDataSource → repository SaveAsync() 를 거치므로 별도 writer 없이 이 경로를 탑니다.
  • 정적 SerializeYaml / IDataSerializer.SerializeTable 은 원본 파일을 모르므로 그대로입니다. MultiFileKeyValueDataRepository(저장 미구현), AssetRepository 도 이번 범위 밖입니다.
  • 성능: 3000행(약 600KB) 표에서 한 값 변경 저장에 추가 비용 약 0.5초(파싱 3회 + 검증 재직렬화). 안 바뀐 저장은 추가 비용이 비교 한 번뿐입니다.

테스트

  • Datra.Tests/YamlCommentPreservationTests.cs (21) — 에디터 저장 경로(EditableKeyValueDataSource/EditableSingleDataSource → repository). 먼저 실패하는 테스트로 커밋(4b0b361, 20개 실패)한 뒤 수정했습니다.
    • 값 하나 변경 시 그 부분 외 바이트 동일, 줄 끝 주석 유지, flow 리스트 유지, 블록 리스트 안 주석 유지·항목 추가·항목 삭제, 따옴표 유지, 생략 필드 삽입, null 로 지운 필드 줄과 위 주석 제거, 변경 없는 저장은 파일 그대로, 두 번 저장, CRLF 유지
    • 행 추가(간격 유지, 꼬리 주석 앞), 주석 달린 첫 행 삭제(헤더 유지), 중간 행 삭제(행 안 주석 제거, 섹션 주석 유지), 마지막 행 삭제, 추가·삭제·변경 한 번에
    • SingleData(중첩 리스트 포함), 생성된 GameDataContext 의 polymorphic Skills.yaml, JSON 테이블 동작 불변, 3000행 테이블
  • Datra.Tests/YamlCommentPreserverTests.cs (14) — 저장 API 로는 만들 수 없는 경우: 행 재정렬, Id 없는 리스트 중간 삭제, 중첩 객체, 여러 줄 문자열, literal block 교체, 빈 필드 채우기, 타입에 없는 키 유지, 빈 테이블/전체 삭제, ---, anchor/alias, 여러 문서·파싱 불가 시 폴백
  • Datra.Unity.Sample/Assets/Tests/Editor/YamlCommentPreservationTests.cs — AssetDatabaseRawDataProvider 로 두 번 저장(두 번째는 재임포트된 TextAsset 을 다시 읽음). Unity 6000.7.0b2 최소 프로젝트(이 브랜치의 Datra / Datra.Editor / Datra.Unity 를 file: 로 참조)에서 EditMode 통과. 샘플 프로젝트 자체(6000.2.6f2)는 로컬에 해당 에디터가 없어 돌리지 못했습니다.
  • dotnet test Datra.sln -c Release: Datra.Tests 772, Datra.Editor.Tests 158, Datra.WebEditor.Tests 37, Datra.Analyzers.Tests 7 모두 통과.
  • 추가로 Spill 의 실제 YAML 6개(주석 없는 원본 + 헤더·행·줄 끝 주석을 넣은 버전)에 대해 필드별 변경·리스트 항목 추가/삭제·행 추가/삭제 974가지를 돌려 폴백 0, 데이터 불일치 0, 주석 유실 0 을 확인했습니다 (로컬 스크립트, 커밋하지 않음).

소비처

Spill / Breakline 은 com.penspanic.datra, com.penspanic.datra.editor, com.penspanic.datra.unity 세 패키지를 머지 커밋으로 함께 올리면 됩니다. 수정은 Datra 패키지 안에 있고(Generator DLL 변경 없음), 데이터 파일 형식이나 API 변경은 없습니다. 새 .cs 에는 .meta 를 같이 넣었습니다.

🤖 Generated with Claude Code

penspanic and others added 3 commits September 29, 2026 00:19
Failing tests for the bug: every save through EditableKeyValueDataSource /
EditableSingleDataSource -> repository rewrites the file from objects, so
header, row, section, trailing and in-list comments all disappear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KeyValueDataRepository and SingleDataRepository rebuilt YAML files from
objects on every save, so the editor erased every comment a designer wrote.

YamlCommentPreserver diffs the canonical rendering of the file as it is on
disk against the canonical rendering of the data being saved and replays
only the differences onto the original text: untouched rows stay byte for
byte, changed scalars are patched in place (keeping trailing comments and
quote style), flow lists stay flow, rows match by Id so deletes and
reorders take their own comments with them, and new rows follow the file's
spacing. The result is re-read and compared with what the plain save would
load as; anything it cannot patch falls back to the plain serializer
output, so data is never at risk. CSV and JSON are untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…L comments

AssetDatabaseRawDataProvider reads the file back through the AssetDatabase at
save time; this saves twice (the second time after the re-import) and checks
the file byte for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@penspanic
penspanic merged commit 1d81b07 into main Sep 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant