Skip to content

Commit 155b75a

Browse files
committed
fix highlighting for env vars
1 parent d717338 commit 155b75a

File tree

6 files changed

+134
-28
lines changed

6 files changed

+134
-28
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/code/code.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import type { GenerationType } from '@/blocks/types'
3838
import { normalizeName } from '@/executor/constants'
3939
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
4040
import { useTagSelection } from '@/hooks/kb/use-tag-selection'
41+
import { createShouldHighlightEnvVar, useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
4142

4243
const logger = createLogger('Code')
4344

@@ -88,21 +89,28 @@ interface CodePlaceholder {
8889
/**
8990
* Creates a syntax highlighter function with custom reference and environment variable highlighting.
9091
* @param effectiveLanguage - The language to use for syntax highlighting
91-
* @param shouldHighlightReference - Function to determine if a reference should be highlighted
92+
* @param shouldHighlightReference - Function to determine if a block reference should be highlighted
93+
* @param shouldHighlightEnvVar - Function to determine if an env var should be highlighted
9294
* @returns A function that highlights code with syntax and custom highlights
9395
*/
9496
const createHighlightFunction = (
9597
effectiveLanguage: 'javascript' | 'python' | 'json',
96-
shouldHighlightReference: (part: string) => boolean
98+
shouldHighlightReference: (part: string) => boolean,
99+
shouldHighlightEnvVar: (varName: string) => boolean
97100
) => {
98101
return (codeToHighlight: string): string => {
99102
const placeholders: CodePlaceholder[] = []
100103
let processedCode = codeToHighlight
101104

102105
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
103-
const placeholder = `__ENV_VAR_${placeholders.length}__`
104-
placeholders.push({ placeholder, original: match, type: 'env' })
105-
return placeholder
106+
// Extract var name from {{VAR_NAME}}
107+
const varName = match.slice(2, -2).trim()
108+
if (shouldHighlightEnvVar(varName)) {
109+
const placeholder = `__ENV_VAR_${placeholders.length}__`
110+
placeholders.push({ placeholder, original: match, type: 'env' })
111+
return placeholder
112+
}
113+
return match
106114
})
107115

108116
processedCode = processedCode.replace(createReferencePattern(), (match) => {
@@ -212,6 +220,7 @@ export const Code = memo(function Code({
212220
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
213221
const emitTagSelection = useTagSelection(blockId, subBlockId)
214222
const [languageValue] = useSubBlockValue<string>(blockId, 'language')
223+
const availableEnvVars = useAvailableEnvVarKeys(workspaceId)
215224

216225
const effectiveLanguage = (languageValue as 'javascript' | 'python' | 'json') || language
217226

@@ -603,9 +612,15 @@ export const Code = memo(function Code({
603612
[generateCodeStream, isPromptVisible, isAiStreaming]
604613
)
605614

615+
const shouldHighlightEnvVar = useMemo(
616+
() => createShouldHighlightEnvVar(availableEnvVars),
617+
[availableEnvVars]
618+
)
619+
606620
const highlightCode = useMemo(
607-
() => createHighlightFunction(effectiveLanguage, shouldHighlightReference),
608-
[effectiveLanguage, shouldHighlightReference]
621+
() =>
622+
createHighlightFunction(effectiveLanguage, shouldHighlightReference, shouldHighlightEnvVar),
623+
[effectiveLanguage, shouldHighlightReference, shouldHighlightEnvVar]
609624
)
610625

611626
const handleValueChange = useCallback(

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/
3535
import { normalizeName } from '@/executor/constants'
3636
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
3737
import { useTagSelection } from '@/hooks/kb/use-tag-selection'
38+
import { createShouldHighlightEnvVar, useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
3839
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3940

4041
const logger = createLogger('ConditionInput')
@@ -123,6 +124,8 @@ export function ConditionInput({
123124

124125
const emitTagSelection = useTagSelection(blockId, subBlockId)
125126
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
127+
const availableEnvVars = useAvailableEnvVarKeys(workspaceId)
128+
const shouldHighlightEnvVar = createShouldHighlightEnvVar(availableEnvVars)
126129

127130
const containerRef = useRef<HTMLDivElement>(null)
128131
const inputRefs = useRef<Map<string, HTMLTextAreaElement>>(new Map())
@@ -1136,14 +1139,19 @@ export function ConditionInput({
11361139
let processedCode = codeToHighlight
11371140

11381141
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
1139-
const placeholder = `__ENV_VAR_${placeholders.length}__`
1140-
placeholders.push({
1141-
placeholder,
1142-
original: match,
1143-
type: 'env',
1144-
shouldHighlight: true,
1145-
})
1146-
return placeholder
1142+
// Extract var name from {{VAR_NAME}}
1143+
const varName = match.slice(2, -2).trim()
1144+
if (shouldHighlightEnvVar(varName)) {
1145+
const placeholder = `__ENV_VAR_${placeholders.length}__`
1146+
placeholders.push({
1147+
placeholder,
1148+
original: match,
1149+
type: 'env',
1150+
shouldHighlight: true,
1151+
})
1152+
return placeholder
1153+
}
1154+
return match
11471155
})
11481156

11491157
processedCode = processedCode.replace(

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { createCombinedPattern } from '@/executor/utils/reference-validation'
77

88
export interface HighlightContext {
99
accessiblePrefixes?: Set<string>
10+
availableEnvVars?: Set<string>
1011
highlightAll?: boolean
1112
}
1213

@@ -43,9 +44,17 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
4344
return false
4445
}
4546

47+
const shouldHighlightEnvVar = (varName: string): boolean => {
48+
if (context?.highlightAll) {
49+
return true
50+
}
51+
if (context?.availableEnvVars === undefined) {
52+
return true
53+
}
54+
return context.availableEnvVars.has(varName)
55+
}
56+
4657
const nodes: ReactNode[] = []
47-
// Match variable references without allowing nested brackets to prevent matching across references
48-
// e.g., "<3. text <real.ref>" should match "<3" and "<real.ref>", not the whole string
4958
const regex = createCombinedPattern()
5059
let lastIndex = 0
5160
let key = 0
@@ -65,11 +74,16 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
6574
}
6675

6776
if (matchText.startsWith(REFERENCE.ENV_VAR_START)) {
68-
nodes.push(
69-
<span key={key++} className='text-[var(--brand-secondary)]'>
70-
{matchText}
71-
</span>
72-
)
77+
const varName = matchText.slice(2, -2).trim()
78+
if (shouldHighlightEnvVar(varName)) {
79+
nodes.push(
80+
<span key={key++} className='text-[var(--brand-secondary)]'>
81+
{matchText}
82+
</span>
83+
)
84+
} else {
85+
nodes.push(<span key={key++}>{matchText}</span>)
86+
}
7387
} else {
7488
const split = splitReferenceSegment(matchText)
7589

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-subflow-editor.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useCallback, useRef, useState } from 'react'
1+
import { useCallback, useMemo, useRef, useState } from 'react'
2+
import { useParams } from 'next/navigation'
23
import { highlight, languages } from '@/components/emcn'
34
import {
45
isLikelyReferenceSegment,
@@ -9,6 +10,7 @@ import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/co
910
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
1011
import { normalizeName, REFERENCE } from '@/executor/constants'
1112
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
13+
import { createShouldHighlightEnvVar, useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
1214
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
1315
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1416
import type { BlockState } from '@/stores/workflows/workflow/types'
@@ -53,6 +55,9 @@ const SUBFLOW_CONFIG = {
5355
* @returns Subflow editor state and handlers
5456
*/
5557
export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId: string | null) {
58+
const params = useParams()
59+
const workspaceId = params.workspaceId as string
60+
5661
const textareaRef = useRef<HTMLTextAreaElement | null>(null)
5762
const editorContainerRef = useRef<HTMLDivElement>(null)
5863

@@ -81,6 +86,13 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
8186
// Get accessible prefixes for tag dropdown
8287
const accessiblePrefixes = useAccessibleReferencePrefixes(currentBlockId || '')
8388

89+
// Get available env vars for highlighting validation
90+
const availableEnvVars = useAvailableEnvVarKeys(workspaceId)
91+
const shouldHighlightEnvVar = useMemo(
92+
() => createShouldHighlightEnvVar(availableEnvVars),
93+
[availableEnvVars]
94+
)
95+
8496
// Collaborative actions
8597
const {
8698
collaborativeUpdateLoopType,
@@ -140,9 +152,13 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
140152
let processedCode = code
141153

142154
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
143-
const placeholder = `__ENV_VAR_${placeholders.length}__`
144-
placeholders.push({ placeholder, original: match, type: 'env' })
145-
return placeholder
155+
const varName = match.slice(2, -2).trim()
156+
if (shouldHighlightEnvVar(varName)) {
157+
const placeholder = `__ENV_VAR_${placeholders.length}__`
158+
placeholders.push({ placeholder, original: match, type: 'env' })
159+
return placeholder
160+
}
161+
return match
146162
})
147163

148164
// Use [^<>]+ to prevent matching across nested brackets (e.g., "<3 <real.ref>" should match separately)
@@ -174,7 +190,7 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
174190

175191
return highlightedCode
176192
},
177-
[shouldHighlightReference]
193+
[shouldHighlightReference, shouldHighlightEnvVar]
178194
)
179195

180196
/**

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
useRefreshMcpServer,
4040
useStoredMcpTools,
4141
} from '@/hooks/queries/mcp'
42+
import { useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
4243
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
4344
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
4445
import { FormField, McpServerSkeleton } from './components'
@@ -157,6 +158,7 @@ interface FormattedInputProps {
157158
scrollLeft: number
158159
showEnvVars: boolean
159160
envVarProps: EnvVarDropdownConfig
161+
availableEnvVars?: Set<string>
160162
className?: string
161163
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
162164
onScroll: (scrollLeft: number) => void
@@ -169,6 +171,7 @@ function FormattedInput({
169171
scrollLeft,
170172
showEnvVars,
171173
envVarProps,
174+
availableEnvVars,
172175
className,
173176
onChange,
174177
onScroll,
@@ -190,7 +193,7 @@ function FormattedInput({
190193
/>
191194
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden px-[8px] py-[6px] font-medium font-sans text-sm'>
192195
<div className='whitespace-nowrap' style={{ transform: `translateX(-${scrollLeft}px)` }}>
193-
{formatDisplayText(value)}
196+
{formatDisplayText(value, { availableEnvVars })}
194197
</div>
195198
</div>
196199
{showEnvVars && (
@@ -221,6 +224,7 @@ interface HeaderRowProps {
221224
envSearchTerm: string
222225
cursorPosition: number
223226
workspaceId: string
227+
availableEnvVars?: Set<string>
224228
onInputChange: (field: InputFieldType, value: string, index?: number) => void
225229
onHeaderScroll: (key: string, scrollLeft: number) => void
226230
onEnvVarSelect: (value: string) => void
@@ -238,6 +242,7 @@ function HeaderRow({
238242
envSearchTerm,
239243
cursorPosition,
240244
workspaceId,
245+
availableEnvVars,
241246
onInputChange,
242247
onHeaderScroll,
243248
onEnvVarSelect,
@@ -265,6 +270,7 @@ function HeaderRow({
265270
scrollLeft={headerScrollLeft[`key-${index}`] || 0}
266271
showEnvVars={isKeyActive}
267272
envVarProps={envVarProps}
273+
availableEnvVars={availableEnvVars}
268274
className='flex-1'
269275
onChange={(e) => onInputChange('header-key', e.target.value, index)}
270276
onScroll={(scrollLeft) => onHeaderScroll(`key-${index}`, scrollLeft)}
@@ -276,6 +282,7 @@ function HeaderRow({
276282
scrollLeft={headerScrollLeft[`value-${index}`] || 0}
277283
showEnvVars={isValueActive}
278284
envVarProps={envVarProps}
285+
availableEnvVars={availableEnvVars}
279286
className='flex-1'
280287
onChange={(e) => onInputChange('header-value', e.target.value, index)}
281288
onScroll={(scrollLeft) => onHeaderScroll(`value-${index}`, scrollLeft)}
@@ -371,6 +378,7 @@ export function MCP({ initialServerId }: MCPProps) {
371378
const deleteServerMutation = useDeleteMcpServer()
372379
const refreshServerMutation = useRefreshMcpServer()
373380
const { testResult, isTestingConnection, testConnection, clearTestResult } = useMcpServerTest()
381+
const availableEnvVars = useAvailableEnvVarKeys(workspaceId)
374382

375383
const urlInputRef = useRef<HTMLInputElement>(null)
376384

@@ -1061,6 +1069,7 @@ export function MCP({ initialServerId }: MCPProps) {
10611069
onSelect: handleEnvVarSelect,
10621070
onClose: resetEnvVarState,
10631071
}}
1072+
availableEnvVars={availableEnvVars}
10641073
onChange={(e) => handleInputChange('url', e.target.value)}
10651074
onScroll={(scrollLeft) => handleUrlScroll(scrollLeft)}
10661075
/>
@@ -1094,6 +1103,7 @@ export function MCP({ initialServerId }: MCPProps) {
10941103
envSearchTerm={envSearchTerm}
10951104
cursorPosition={cursorPosition}
10961105
workspaceId={workspaceId}
1106+
availableEnvVars={availableEnvVars}
10971107
onInputChange={handleInputChange}
10981108
onHeaderScroll={handleHeaderScroll}
10991109
onEnvVarSelect={handleEnvVarSelect}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useMemo } from 'react'
2+
import { usePersonalEnvironment, useWorkspaceEnvironment } from '@/hooks/queries/environment'
3+
4+
export function useAvailableEnvVarKeys(workspaceId?: string): Set<string> | undefined {
5+
const { data: personalEnv, isLoading: personalLoading } = usePersonalEnvironment()
6+
const { data: workspaceEnvData, isLoading: workspaceLoading } = useWorkspaceEnvironment(
7+
workspaceId || ''
8+
)
9+
10+
return useMemo(() => {
11+
if (personalLoading || (workspaceId && workspaceLoading)) {
12+
return undefined
13+
}
14+
15+
const keys = new Set<string>()
16+
17+
if (personalEnv) {
18+
Object.keys(personalEnv).forEach((key) => keys.add(key))
19+
}
20+
21+
if (workspaceId && workspaceEnvData) {
22+
if (workspaceEnvData.workspace) {
23+
Object.keys(workspaceEnvData.workspace).forEach((key) => keys.add(key))
24+
}
25+
if (workspaceEnvData.personal) {
26+
Object.keys(workspaceEnvData.personal).forEach((key) => keys.add(key))
27+
}
28+
}
29+
30+
return keys
31+
}, [personalEnv, workspaceEnvData, personalLoading, workspaceLoading, workspaceId])
32+
}
33+
34+
export function createShouldHighlightEnvVar(
35+
availableEnvVars: Set<string> | undefined
36+
): (varName: string) => boolean {
37+
return (varName: string): boolean => {
38+
if (availableEnvVars === undefined) {
39+
return true
40+
}
41+
return availableEnvVars.has(varName)
42+
}
43+
}

0 commit comments

Comments
 (0)