1- import { syntaxTree } from '@codemirror/language'
21import {
32 type EditorView ,
43 type DecorationSet ,
54 Decoration ,
65} from '@codemirror/view'
76import { type ModelSQLMeshModel } from '@models/sqlmesh-model'
8- import { isFalse } from '@utils/index'
7+ import { isFalse , isNil , isNotNil } from '@utils/index'
98import { type Column } from '@api/client'
109import clsx from 'clsx'
1110
11+ interface DecorationRange {
12+ from : number
13+ to : number
14+ }
15+ type MarkDecorationCallback = (
16+ options : DecorationRange & {
17+ name : string
18+ key : string
19+ } ,
20+ ) => void
21+
1222export function findModel (
1323 event : MouseEvent ,
1424 models : Map < string , ModelSQLMeshModel > ,
1525) : ModelSQLMeshModel | undefined {
16- if ( event . target == null ) return
26+ if ( isNil ( event . target ) ) return
1727
1828 const el = event . target as HTMLElement
1929 const modelName =
2030 el . getAttribute ( 'model' ) ?? el . parentElement ?. getAttribute ( 'model' )
2131
22- if ( modelName == null ) return
32+ if ( isNil ( modelName ) ) return
2333
2434 return models . get ( modelName )
2535}
@@ -28,13 +38,13 @@ export function findColumn(
2838 event : MouseEvent ,
2939 model : ModelSQLMeshModel ,
3040) : Column | undefined {
31- if ( event . target == null ) return
41+ if ( isNil ( event . target ) ) return
3242
3343 const el = event . target as HTMLElement
3444 const columnName =
3545 el . getAttribute ( 'column' ) ?? el . parentElement ?. getAttribute ( 'column' )
3646
37- if ( columnName == null ) return
47+ if ( isNil ( columnName ) ) return
3848
3949 return model . columns . find ( c => c . name === columnName )
4050}
@@ -46,89 +56,155 @@ export function getDecorations(
4656 columns : Set < string > ,
4757 isActionMode : boolean ,
4858) : DecorationSet {
49- const decorations : any = [ ]
50- const modelColumns = model . columns . map ( c => c . name )
59+ const ranges : any = [ ]
60+ const columnNames = Array . from ( columns )
61+ const modelNames = Array . from ( new Set ( models . values ( ) ) ) . map ( m => m . name )
62+ const modelColumns = model . columns . map ( c => c . name . toLowerCase ( ) )
63+ const validLeftCharColumn = new Set ( [ '.' , '(' , '[' , ' ' , '\n' ] )
64+ const validRightCharColumn = new Set ( [ ':' , ')' , ',' , ']' , ' ' , '\n' ] )
5165
5266 for ( const range of view . visibleRanges ) {
53- syntaxTree ( view . state ) . iterate ( {
54- from : range . from ,
55- to : range . to ,
56- enter ( { from, to } ) {
57- // In case model name represented in qoutes
58- // like in python files, we need to remove qoutes
59- let maybeModelOrColumn = view . state . doc
60- . sliceString ( from - 1 , to + 1 )
61- . replaceAll ( '"' , '' )
62- . replaceAll ( "'" , '' )
63- let isOriginal = false
64-
65- if (
66- maybeModelOrColumn . startsWith ( '.' ) ||
67- maybeModelOrColumn . endsWith ( ':' )
68- ) {
69- isOriginal = true
70- }
71-
72- maybeModelOrColumn = maybeModelOrColumn . slice (
73- 1 ,
74- maybeModelOrColumn . length - 1 ,
67+ getMarkDecorations (
68+ modelNames ,
69+ view . state . doc . sliceString ( range . from , range . to ) ,
70+ range ,
71+ ( { from, to, name } ) => {
72+ ranges . push (
73+ createMarkDecorationModel ( {
74+ model : name ,
75+ isActionMode,
76+ isActiveModel : model . name === name ,
77+ } ) . range ( from , to ) ,
7578 )
79+ } ,
80+ )
81+
82+ getMarkDecorations (
83+ columnNames ,
84+ view . state . doc . sliceString ( range . from , range . to ) ,
85+ range ,
86+ ( { from, to, key } ) => {
87+ const column = view . state . doc . sliceString ( from , to )
88+ const word = view . state . doc . sliceString ( from - 1 , to + 1 )
89+ const leftChar = view . state . doc . sliceString ( from - 1 , from )
90+ const rightChar = view . state . doc . sliceString ( to , to + 1 )
7691
7792 if (
78- isFalse ( isOriginal ) &&
79- columns . has ( maybeModelOrColumn ) &&
80- ! modelColumns . includes ( maybeModelOrColumn )
81- ) {
82- isOriginal = true
83- }
84-
85- let decoration
86-
87- if ( maybeModelOrColumn === model . name ) {
88- decoration = Decoration . mark ( {
89- attributes : {
90- class : clsx (
91- 'sqlmesh-model --is-active-model' ,
92- isActionMode && '--is-action-mode' ,
93- ) ,
94- model : maybeModelOrColumn ,
95- } ,
96- } ) . range ( from , to )
97- } else if ( models . get ( maybeModelOrColumn ) != null ) {
98- decoration = Decoration . mark ( {
99- attributes : {
100- class : clsx ( 'sqlmesh-model' , isActionMode && '--is-action-mode' ) ,
101- model : maybeModelOrColumn ,
102- } ,
103- } ) . range ( from , to )
104- } else if ( modelColumns . includes ( maybeModelOrColumn ) ) {
105- decoration = Decoration . mark ( {
106- attributes : {
107- class : clsx (
108- 'sqlmesh-model__column --is-active-model' ,
109- isOriginal ? '--is-original' : '--is-derived' ,
110- isActionMode && '--is-action-mode' ,
111- ) ,
112- column : maybeModelOrColumn ,
113- } ,
114- } ) . range ( from , to )
115- } else if ( columns . has ( maybeModelOrColumn ) ) {
116- decoration = Decoration . mark ( {
117- attributes : {
118- class : clsx (
119- 'sqlmesh-model__column' ,
120- isOriginal ? '--is-original' : '--is-derived' ,
121- isActionMode && '--is-action-mode' ,
122- ) ,
123- column : maybeModelOrColumn ,
124- } ,
125- } ) . range ( from , to )
126- }
127-
128- decoration != null && decorations . push ( decoration )
93+ ( isNotNil ( leftChar ) && isFalse ( validLeftCharColumn . has ( leftChar ) ) ) ||
94+ ( isNotNil ( rightChar ) && isFalse ( validRightCharColumn . has ( rightChar ) ) )
95+ )
96+ return
97+
98+ ranges . push (
99+ createMarkDecorationColumn ( {
100+ column,
101+ isOriginalColumn : isOriginalColumn ( word ) ,
102+ isActiveModel : modelColumns . includes ( key ) ,
103+ isActionMode,
104+ } ) . range ( from , to ) ,
105+ )
129106 } ,
130- } )
107+ )
108+ }
109+
110+ return Decoration . set (
111+ ranges . sort ( ( a : DecorationRange , b : DecorationRange ) => a . from - b . from ) ,
112+ )
113+ }
114+
115+ function getMarkDecorations (
116+ list : string [ ] ,
117+ doc : string ,
118+ range : DecorationRange ,
119+ callback : MarkDecorationCallback ,
120+ ) : void {
121+ const visisted = new Set ( )
122+
123+ for ( const name of list ) {
124+ const key = name . toLowerCase ( )
125+
126+ if ( visisted . has ( key ) ) continue
127+
128+ visisted . add ( key )
129+
130+ const regex = new RegExp ( name , 'ig' )
131+ const regex_normalized = new RegExp ( alternativeNameFormat ( name ) , 'ig' )
132+ let found
133+
134+ while ( isNotNil ( ( found = regex_normalized . exec ( doc ) ?? regex . exec ( doc ) ) ) ) {
135+ const from = range . from + found . index
136+ const to = from + found [ 0 ] . length
137+ const options = {
138+ from,
139+ to,
140+ name,
141+ key,
142+ }
143+
144+ isNotNil ( callback ) && callback ( options )
145+ }
131146 }
147+ }
148+
149+ function createMarkDecorationModel ( {
150+ model,
151+ isActionMode,
152+ isActiveModel,
153+ } : {
154+ model : string
155+ isActionMode : boolean
156+ isActiveModel : boolean
157+ } ) : Decoration {
158+ return Decoration . mark ( {
159+ attributes : {
160+ class : clsx (
161+ 'sqlmesh-model' ,
162+ isActiveModel && '--is-active-model' ,
163+ isActionMode && '--is-action-mode' ,
164+ ) ,
165+ model,
166+ } ,
167+ } )
168+ }
169+
170+ function createMarkDecorationColumn ( {
171+ column,
172+ isOriginalColumn,
173+ isActionMode,
174+ isActiveModel,
175+ } : {
176+ column : string
177+ isOriginalColumn : boolean
178+ isActiveModel : boolean
179+ isActionMode : boolean
180+ } ) : Decoration {
181+ return Decoration . mark ( {
182+ attributes : {
183+ class : clsx (
184+ 'sqlmesh-model__column' ,
185+ isOriginalColumn ? '--is-original' : '--is-alias' ,
186+ isActiveModel && '--is-active-model' ,
187+ isActionMode && '--is-action-mode' ,
188+ ) ,
189+ column,
190+ } ,
191+ } )
192+ }
193+
194+ function isOriginalColumn ( column : string ) : boolean {
195+ return (
196+ column . startsWith ( '.' ) ||
197+ column . endsWith ( ':' ) ||
198+ column . startsWith ( '(' ) ||
199+ column . endsWith ( ')' )
200+ )
201+ }
132202
133- return Decoration . set ( decorations )
203+ function alternativeNameFormat ( modelName : string ) : string {
204+ return modelName . includes ( '"' )
205+ ? modelName
206+ : modelName
207+ . split ( '.' )
208+ . map ( name => `"${ name } "` )
209+ . join ( '.' )
134210}
0 commit comments