@@ -11,6 +11,65 @@ import { Range, RangeSet, RangeSetBuilder, StateField, type Extension } from '@c
1111import { forEachDiagnostic , setDiagnosticsEffect } from '@codemirror/lint' ;
1212import { NESTED_KEY_REGEX } from '../helpers/constants' ;
1313
14+ const SYSTEM_FIELDS = [ '$id' , '$createdAt' , '$updatedAt' ] as const ;
15+
16+ function escapeRegExp ( source : string ) : string {
17+ return source . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
18+ }
19+
20+ // Match a system field key at the start of a line, either quoted ("$id") or unquoted ($id).
21+ const SYSTEM_FIELDS_SOURCE = SYSTEM_FIELDS . map ( escapeRegExp ) . join ( '|' ) ;
22+ const SYSTEM_FIELD_KEY_LINE_PATTERN = new RegExp (
23+ `^\\s*("(?:${ SYSTEM_FIELDS_SOURCE } )"|(?:${ SYSTEM_FIELDS_SOURCE } ))\\s*:`
24+ ) ;
25+
26+ function skipInlineWhitespace ( text : string , from : number ) : number {
27+ let i = from ;
28+ while ( i < text . length ) {
29+ const ch = text [ i ] ;
30+ // Don't cross lines; values for the system fields we style are expected to be on the same line.
31+ if ( ch !== ' ' && ch !== '\t' ) break ;
32+ i += 1 ;
33+ }
34+ return i ;
35+ }
36+
37+ function findSingleLineValueEnd ( text : string , from : number ) : number {
38+ if ( from >= text . length ) return from ;
39+
40+ const quote = text [ from ] ;
41+ if ( quote === '"' || quote === "'" ) {
42+ let escaped = false ;
43+ for ( let i = from + 1 ; i < text . length ; i += 1 ) {
44+ const ch = text [ i ] ;
45+ if ( escaped ) {
46+ escaped = false ;
47+ continue ;
48+ }
49+ if ( ch === '\\' ) {
50+ escaped = true ;
51+ continue ;
52+ }
53+ if ( ch === quote ) {
54+ return i + 1 ;
55+ }
56+ if ( ch === '\n' || ch === '\r' ) {
57+ return i ;
58+ }
59+ }
60+ return text . length ;
61+ }
62+
63+ // Scalar token: read until comma or newline.
64+ for ( let i = from ; i < text . length ; i += 1 ) {
65+ const ch = text [ i ] ;
66+ if ( ch === ',' || ch === '\n' || ch === '\r' ) {
67+ return i ;
68+ }
69+ }
70+ return text . length ;
71+ }
72+
1473// ViewPlugin to highlight nested keys (4+ spaces) only in visible ranges
1574export function createNestedKeyPlugin ( ) : Extension {
1675 return ViewPlugin . fromClass (
@@ -56,6 +115,8 @@ export function createNestedKeyPlugin(): Extension {
56115
57116// ViewPlugin to apply muted styling to system fields ($id, $createdAt, $updatedAt)
58117export function createSystemFieldStylePlugin ( getShouldStyle : ( ) => boolean ) : Extension {
118+ const mutedMark = Decoration . mark ( { class : 'cm-system-field-muted' } ) ;
119+
59120 return ViewPlugin . fromClass (
60121 class {
61122 decorations : DecorationSet ;
@@ -77,32 +138,24 @@ export function createSystemFieldStylePlugin(getShouldStyle: () => boolean): Ext
77138
78139 const doc = view . state . doc ;
79140 const text = doc . toString ( ) ;
80- const systemFields = [ '$id' , '$createdAt' , '$updatedAt' ] ;
81141 const decos : Range < Decoration > [ ] = [ ] ;
82142
83- // Find all occurrences of system field keys
84- for ( const field of systemFields ) {
85- // Match the key in format: "$id": or $id: (with or without quotes)
86- const quotedPattern = new RegExp ( `"${ field . replace ( '$' , '\\$' ) } "\\s*:` , 'g' ) ;
87- const unquotedPattern = new RegExp ( `${ field . replace ( '$' , '\\$' ) } \\s*:` , 'g' ) ;
88-
89- let match : RegExpExecArray ;
90- // Check quoted format
91- while ( ( match = quotedPattern . exec ( text ) ) !== null ) {
92- const from = match . index ;
93- const to = from + field . length + 2 ; // +2 for quotes
94- decos . push (
95- Decoration . mark ( { class : 'cm-system-field-muted' } ) . range ( from , to )
96- ) ;
97- }
143+ for ( let ln = 1 ; ln <= doc . lines ; ln += 1 ) {
144+ const line = doc . line ( ln ) ;
145+ const match = SYSTEM_FIELD_KEY_LINE_PATTERN . exec ( line . text ) ;
146+ if ( ! match ) continue ;
147+
148+ const keyToken = match [ 1 ] ; // either "$id" or $id
149+ const keyOffset = match [ 0 ] . indexOf ( keyToken ) ;
150+ const from = line . from + keyOffset ;
151+ const to = line . from + match [ 0 ] . length ;
152+
153+ decos . push ( mutedMark . range ( from , to ) ) ;
98154
99- // Check unquoted format
100- while ( ( match = unquotedPattern . exec ( text ) ) !== null ) {
101- const from = match . index ;
102- const to = from + field . length ;
103- decos . push (
104- Decoration . mark ( { class : 'cm-system-field-muted' } ) . range ( from , to )
105- ) ;
155+ const valueFrom = skipInlineWhitespace ( text , to ) ;
156+ const valueTo = findSingleLineValueEnd ( text , valueFrom ) ;
157+ if ( valueTo > valueFrom ) {
158+ decos . push ( mutedMark . range ( valueFrom , valueTo ) ) ;
106159 }
107160 }
108161
0 commit comments