Skip to content

Commit e0ae16d

Browse files
authored
Feat: Highlight know columns in editor (#761)
1 parent 1858c2a commit e0ae16d

6 files changed

Lines changed: 126 additions & 37 deletions

File tree

web/client/src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
--color-neutral: hsla(0, 0%, 50%, 1);
5050

5151
/* Brand */
52+
--color-brand-10: hsla(24, 100%, 60%, 0.1);
53+
--color-brand-20: hsla(24, 100%, 60%, 0.2);
54+
--color-brand-30: hsla(24, 100%, 60%, 0.3);
55+
--color-brand-40: hsla(24, 100%, 60%, 0.4);
5256
--color-brand-100: hsla(37, 100%, 92%, 1);
5357
--color-brand-200: hsla(34, 100%, 84%, 1);
5458
--color-brand-300: hsla(31, 100%, 76%, 1);

web/client/src/library/components/editor/Editor.css

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,44 @@
4545
border-radius: 1rem;
4646
}
4747

48-
.sqlmesh-model {
48+
.cm-tooltip {
49+
border: none !important;
50+
outline: none !important;
51+
background: var(--color-theme) !important;
52+
}
53+
54+
.sqlmesh-model,
55+
.sqlmesh-model > span {
56+
display: inline-block;
4957
color: var(--color-primary-500);
5058
background: var(--color-primary-10);
5159
box-shadow: inset 0 -2px 0 0 var(--color-primary-300);
52-
display: inline-block;
5360
cursor: pointer;
5461
font-weight: bold;
5562
}
5663

57-
.cm-tooltip {
58-
border: none !important;
59-
outline: none !important;
60-
background: var(--color-theme) !important;
61-
}
62-
6364
.sqlmesh-model:hover {
6465
opacity: 0.8;
6566
}
67+
68+
.sqlmesh-model.--is-active-model,
69+
.sqlmesh-model.--is-active-model:hover,
70+
.sqlmesh-model.--is-active-model > span,
71+
.sqlmesh-model.--is-active-model:hover > span {
72+
box-shadow: none;
73+
cursor: default;
74+
opacity: 1;
75+
}
76+
77+
.sqlmesh-model__column.--is-original,
78+
.sqlmesh-model__column.--is-original > span {
79+
display: inline-block;
80+
color: var(--color-accent-300);
81+
}
82+
83+
.sqlmesh-model__column.--is-active-model.--is-derived,
84+
.sqlmesh-model__column.--is-active-model.--is-derived > span {
85+
display: inline-block;
86+
color: var(--color-brand-300);
87+
background: var(--color-brand-10);
88+
}

web/client/src/library/components/editor/EditorCode.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ export default function CodeEditor({ tab }: { tab: EditorTab }): JSX.Element {
3838
const [SqlMeshDialect, SqlMeshDialectCleanUp] = useSqlMeshExtension()
3939

4040
const models = useStoreContext(s => s.models)
41-
4241
const files = useStoreFileTree(s => s.files)
4342
const selectFile = useStoreFileTree(s => s.selectFile)
4443

44+
const previewLineage = useStoreEditor(s => s.previewLineage)
4545
const tabs = useStoreEditor(s => s.tabs)
4646
const dialects = useStoreEditor(s => s.dialects)
4747
const engine = useStoreEditor(s => s.engine)
@@ -73,11 +73,17 @@ export default function CodeEditor({ tab }: { tab: EditorTab }): JSX.Element {
7373
)
7474

7575
const extensions = useMemo(() => {
76+
const model = models.get(tab.file.path)
77+
const columns = Object.keys(previewLineage ?? {})
78+
.map(model => models.get(model)?.columns.map(c => c.name))
79+
.flat()
80+
.filter(Boolean) as string[]
81+
7682
return [
7783
mode === EnumColorScheme.Dark ? dracula : tomorrow,
7884
HoverTooltip(models),
7985
events(models, files, selectFile),
80-
SqlMeshModel(models),
86+
model != null && SqlMeshModel(models, model, columns),
8187
tab.file.extension === '.py' && python(),
8288
tab.file.extension === '.yaml' && StreamLanguage.define(yaml),
8389
tab.file.extension === '.sql' &&

web/client/src/library/components/editor/extensions/SqlMeshDialect.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,13 @@ export function useSqlMeshExtension(): [
128128
])
129129
}
130130

131-
const SqlMeshDialectCleanUp: ExtensionCleanUp =
132-
function SqlMeshDialectCleanUp(): void {
133-
const handler = cache.get('message')
131+
function SqlMeshDialectCleanUp(): void {
132+
const handler = cache.get('message')
134133

135-
if (handler == null) return
134+
if (handler == null) return
136135

137-
sqlglotWorker.removeEventListener('message', handler)
138-
}
136+
sqlglotWorker.removeEventListener('message', handler)
137+
}
139138

140139
return [SqlMeshDialectExtension, SqlMeshDialectCleanUp]
141140
}

web/client/src/library/components/editor/extensions/index.ts

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,30 @@ import {
1111
} from '@codemirror/view'
1212
import { type Model } from '~/api/client'
1313
import { type ModelFile } from '~/models'
14-
import { isNil } from '~/utils'
1514

1615
import { useSqlMeshExtension } from './SqlMeshDialect'
1716

1817
export { useSqlMeshExtension }
1918

20-
export function SqlMeshModel(models: Map<string, Model>): Extension {
19+
export function SqlMeshModel(
20+
models: Map<string, Model>,
21+
model: Model,
22+
columns: string[],
23+
): Extension {
2124
return ViewPlugin.fromClass(
2225
class SqlMeshModelView {
2326
decorations: DecorationSet = Decoration.set([])
2427
constructor(readonly view: EditorView) {
25-
this.decorations = getDecorations(models, view)
28+
this.decorations = getDecorations(models, view, model, columns)
2629
}
2730

2831
update(viewUpdate: ViewUpdate): void {
29-
if (viewUpdate.docChanged) {
30-
this.decorations = getDecorations(models, viewUpdate.view)
31-
}
32+
this.decorations = getDecorations(
33+
models,
34+
viewUpdate.view,
35+
model,
36+
columns,
37+
)
3238
}
3339
},
3440
{
@@ -75,11 +81,11 @@ export function HoverTooltip(models: Map<string, Model>): Extension {
7581
create() {
7682
const dom = document.createElement('div')
7783
const template = `
78-
<div class="flex items-center">
79-
<span>Model Name:</span>
80-
<span class="px-2 py-1 inline-block ml-1 bg-alternative-100 text-alternative-500 rounded">${model.name}</span>
81-
</div>
82-
`
84+
<div class="flex items-center">
85+
<span>Model Name:</span>
86+
<span class="px-2 py-1 inline-block ml-1 bg-alternative-100 text-alternative-500 rounded">${model.name}</span>
87+
</div>
88+
`
8389

8490
dom.className =
8591
'text-xs font-bold px-3 py-3 bg-white border-2 border-secondary-100 rounded outline-none shadow-lg mb-2'
@@ -97,8 +103,11 @@ export function HoverTooltip(models: Map<string, Model>): Extension {
97103
function getDecorations(
98104
models: Map<string, Model>,
99105
view: EditorView,
106+
model: Model,
107+
columns: string[],
100108
): DecorationSet {
101109
const decorations: any = []
110+
const modelColumns = model.columns.map(c => c.name)
102111

103112
for (const range of view.visibleRanges) {
104113
syntaxTree(view.state).iterate({
@@ -107,21 +116,68 @@ function getDecorations(
107116
enter({ from, to }) {
108117
// In case model name represented in qoutes
109118
// like in python files, we need to remove qoutes
110-
const model = view.state.doc
111-
.sliceString(from, to)
119+
let maybeModelOrColumn = view.state.doc
120+
.sliceString(from - 1, to + 1)
112121
.replaceAll('"', '')
113122
.replaceAll("'", '')
123+
let isOriginal = false
124+
125+
if (
126+
maybeModelOrColumn.startsWith('.') ||
127+
maybeModelOrColumn.endsWith(':')
128+
) {
129+
isOriginal = true
130+
}
131+
132+
maybeModelOrColumn = maybeModelOrColumn.slice(
133+
1,
134+
maybeModelOrColumn.length - 1,
135+
)
114136

115-
if (isNil(models.get(model))) return true
137+
if (
138+
columns.includes(maybeModelOrColumn) &&
139+
!modelColumns.includes(maybeModelOrColumn)
140+
) {
141+
isOriginal = true
142+
}
116143

117-
const decoration = Decoration.mark({
118-
attributes: {
119-
class: 'sqlmesh-model',
120-
model,
121-
},
122-
}).range(from, to)
144+
let decoration
145+
146+
if (maybeModelOrColumn === model.name) {
147+
decoration = Decoration.mark({
148+
attributes: {
149+
class: 'sqlmesh-model --is-active-model',
150+
model: maybeModelOrColumn,
151+
},
152+
}).range(from, to)
153+
} else if (models.get(maybeModelOrColumn) != null) {
154+
decoration = Decoration.mark({
155+
attributes: {
156+
class: 'sqlmesh-model',
157+
model: maybeModelOrColumn,
158+
},
159+
}).range(from, to)
160+
} else if (modelColumns.includes(maybeModelOrColumn)) {
161+
decoration = Decoration.mark({
162+
attributes: {
163+
class: `sqlmesh-model__column --is-active-model ${
164+
isOriginal ? '--is-original' : ' --is-derived'
165+
}`,
166+
column: maybeModelOrColumn,
167+
},
168+
}).range(from, to)
169+
} else if (columns.includes(maybeModelOrColumn)) {
170+
decoration = Decoration.mark({
171+
attributes: {
172+
class: `sqlmesh-model__column ${
173+
isOriginal ? '--is-original' : ' --is-derived'
174+
}`,
175+
column: maybeModelOrColumn,
176+
},
177+
}).range(from, to)
178+
}
123179

124-
decorations.push(decoration)
180+
decoration != null && decorations.push(decoration)
125181
},
126182
})
127183
}

web/client/tailwind.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ module.exports = {
4343
DEFAULT: 'var(--color-divider)',
4444
},
4545
brand: {
46+
10: 'var(--color-brand-10)',
4647
100: 'var(--color-brand-100)',
4748
200: 'var(--color-brand-200)',
4849
300: 'var(--color-brand-300)',

0 commit comments

Comments
 (0)