@@ -17,6 +17,87 @@ const RGBG = new RegExp(colorRegex.anyGlobal);
1717
1818const enumColorType = { hex : "hex" , rgb : "rgb" , hsl : "hsl" , named : "named" } ;
1919
20+ const disallowedBoundaryBefore = new Set ( [ "-" , "." , "/" , "#" ] ) ;
21+ const disallowedBoundaryAfter = new Set ( [ "-" , "." , "/" ] ) ;
22+ const ignoredLeadingWords = new Set ( [ "url" ] ) ;
23+
24+ function isWhitespace ( char ) {
25+ return (
26+ char === " " ||
27+ char === "\t" ||
28+ char === "\n" ||
29+ char === "\r" ||
30+ char === "\f"
31+ ) ;
32+ }
33+
34+ function isAlpha ( char ) {
35+ if ( ! char ) return false ;
36+ const code = char . charCodeAt ( 0 ) ;
37+ return (
38+ ( code >= 65 && code <= 90 ) || // A-Z
39+ ( code >= 97 && code <= 122 )
40+ ) ;
41+ }
42+
43+ function charAt ( doc , index ) {
44+ if ( index < 0 || index >= doc . length ) return "" ;
45+ return doc . sliceString ( index , index + 1 ) ;
46+ }
47+
48+ function findPrevNonWhitespace ( doc , index ) {
49+ for ( let i = index - 1 ; i >= 0 ; i -- ) {
50+ if ( ! isWhitespace ( charAt ( doc , i ) ) ) return i ;
51+ }
52+ return - 1 ;
53+ }
54+
55+ function findNextNonWhitespace ( doc , index ) {
56+ for ( let i = index ; i < doc . length ; i ++ ) {
57+ if ( ! isWhitespace ( charAt ( doc , i ) ) ) return i ;
58+ }
59+ return doc . length ;
60+ }
61+
62+ function readWordBefore ( doc , index ) {
63+ let pos = index ;
64+ while ( pos >= 0 && isWhitespace ( charAt ( doc , pos ) ) ) pos -- ;
65+ if ( pos < 0 ) return "" ;
66+ if ( charAt ( doc , pos ) === "(" ) {
67+ pos -- ;
68+ }
69+ while ( pos >= 0 && isWhitespace ( charAt ( doc , pos ) ) ) pos -- ;
70+ let end = pos ;
71+ while ( pos >= 0 && isAlpha ( charAt ( doc , pos ) ) ) pos -- ;
72+ const start = pos + 1 ;
73+ if ( end < start ) return "" ;
74+ return doc . sliceString ( start , end + 1 ) . toLowerCase ( ) ;
75+ }
76+
77+ function shouldRenderColor ( doc , start , end ) {
78+ const immediatePrev = charAt ( doc , start - 1 ) ;
79+ if ( disallowedBoundaryBefore . has ( immediatePrev ) ) return false ;
80+
81+ const immediateNext = charAt ( doc , end ) ;
82+ if ( disallowedBoundaryAfter . has ( immediateNext ) ) return false ;
83+
84+ const prevNonWhitespaceIndex = findPrevNonWhitespace ( doc , start ) ;
85+ if ( prevNonWhitespaceIndex !== - 1 ) {
86+ const prevNonWhitespaceChar = charAt ( doc , prevNonWhitespaceIndex ) ;
87+ if ( disallowedBoundaryBefore . has ( prevNonWhitespaceChar ) ) return false ;
88+ const prevWord = readWordBefore ( doc , prevNonWhitespaceIndex ) ;
89+ if ( ignoredLeadingWords . has ( prevWord ) ) return false ;
90+ }
91+
92+ const nextNonWhitespaceIndex = findNextNonWhitespace ( doc , end ) ;
93+ if ( nextNonWhitespaceIndex < doc . length ) {
94+ const nextNonWhitespaceChar = charAt ( doc , nextNonWhitespaceIndex ) ;
95+ if ( disallowedBoundaryAfter . has ( nextNonWhitespaceChar ) ) return false ;
96+ }
97+
98+ return true ;
99+ }
100+
20101class ColorWidget extends WidgetType {
21102 constructor ( { color, colorRaw, ...state } ) {
22103 super ( ) ;
@@ -59,14 +140,16 @@ class ColorWidget extends WidgetType {
59140function colorDecorations ( view ) {
60141 const deco = [ ] ;
61142 const ranges = view . visibleRanges ;
143+ const doc = view . state . doc ;
62144 for ( const { from, to } of ranges ) {
63- const text = view . state . doc . sliceString ( from , to ) ;
145+ const text = doc . sliceString ( from , to ) ;
64146 // Any color using global matcher from utils (captures named/rgb/rgba/hsl/hsla/hex)
65147 RGBG . lastIndex = 0 ;
66148 for ( let m ; ( m = RGBG . exec ( text ) ) ; ) {
67149 const raw = m [ 2 ] ;
68150 const start = from + m . index + m [ 1 ] . length ;
69151 const end = start + raw . length ;
152+ if ( ! shouldRenderColor ( doc , start , end ) ) continue ;
70153 const c = color ( raw ) ;
71154 const colorHex = c . hex . toString ( false ) ;
72155 deco . push (
0 commit comments