@api-client/ui 0.5.12 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/build/src/elements/code-editor/{ui-code-editor.d.ts → code-editor.d.ts} +2 -2
  2. package/build/src/elements/code-editor/code-editor.d.ts.map +1 -0
  3. package/build/src/elements/code-editor/{ui-code-editor.js → code-editor.js} +1 -1
  4. package/build/src/elements/code-editor/code-editor.js.map +1 -0
  5. package/build/src/elements/code-editor/internals/CodeEditor.d.ts +21 -59
  6. package/build/src/elements/code-editor/internals/CodeEditor.d.ts.map +1 -1
  7. package/build/src/elements/code-editor/internals/CodeEditor.js +136 -105
  8. package/build/src/elements/code-editor/internals/CodeEditor.js.map +1 -1
  9. package/build/src/elements/code-editor/internals/CodeEditor.styles.d.ts.map +1 -1
  10. package/build/src/elements/code-editor/internals/CodeEditor.styles.js +4 -0
  11. package/build/src/elements/code-editor/internals/CodeEditor.styles.js.map +1 -1
  12. package/build/src/elements/code-editor/internals/Linter.d.ts +2 -2
  13. package/build/src/elements/code-editor/internals/Linter.d.ts.map +1 -1
  14. package/build/src/elements/code-editor/internals/Linter.js +32 -37
  15. package/build/src/elements/code-editor/internals/Linter.js.map +1 -1
  16. package/build/src/elements/code-editor/internals/PlaceholderWidget.d.ts +20 -0
  17. package/build/src/elements/code-editor/internals/PlaceholderWidget.d.ts.map +1 -0
  18. package/build/src/elements/code-editor/internals/PlaceholderWidget.js +46 -0
  19. package/build/src/elements/code-editor/internals/PlaceholderWidget.js.map +1 -0
  20. package/build/src/elements/code-editor/internals/SuggestionMatchDecorator.d.ts +17 -0
  21. package/build/src/elements/code-editor/internals/SuggestionMatchDecorator.d.ts.map +1 -0
  22. package/build/src/elements/code-editor/internals/SuggestionMatchDecorator.js +31 -0
  23. package/build/src/elements/code-editor/internals/SuggestionMatchDecorator.js.map +1 -0
  24. package/build/src/elements/code-editor/internals/types.d.ts +51 -0
  25. package/build/src/elements/code-editor/internals/types.d.ts.map +1 -0
  26. package/build/src/elements/code-editor/internals/types.js +2 -0
  27. package/build/src/elements/code-editor/internals/types.js.map +1 -0
  28. package/build/src/index.d.ts +2 -2
  29. package/build/src/index.d.ts.map +1 -1
  30. package/build/src/index.js +1 -1
  31. package/build/src/index.js.map +1 -1
  32. package/demo/elements/code-editor/CodeEditorDemo.ts +3 -3
  33. package/package.json +2 -2
  34. package/src/elements/code-editor/README.md +1 -1
  35. package/src/elements/code-editor/{ui-code-editor.ts → code-editor.ts} +1 -1
  36. package/src/elements/code-editor/internals/CodeEditor.styles.ts +4 -0
  37. package/src/elements/code-editor/internals/CodeEditor.ts +166 -172
  38. package/src/elements/code-editor/internals/Linter.ts +37 -39
  39. package/src/elements/code-editor/internals/PlaceholderWidget.ts +50 -0
  40. package/src/elements/code-editor/internals/SuggestionMatchDecorator.ts +36 -0
  41. package/src/elements/code-editor/internals/types.ts +54 -0
  42. package/src/index.ts +2 -2
  43. package/build/src/elements/code-editor/ui-code-editor.d.ts.map +0 -1
  44. package/build/src/elements/code-editor/ui-code-editor.js.map +0 -1
@@ -1,8 +1,15 @@
1
1
  import { html, LitElement, PropertyValues, TemplateResult, nothing } from 'lit'
2
2
  import { property, query, state } from 'lit/decorators.js'
3
3
  import { classMap } from 'lit/directives/class-map.js'
4
+ import { linter } from '@codemirror/lint'
4
5
  import { EditorState, Extension } from '@codemirror/state'
5
- import { autocompletion, CompletionContext, CompletionResult, CompletionSource } from '@codemirror/autocomplete'
6
+ import {
7
+ autocompletion,
8
+ CompletionContext,
9
+ type CompletionResult,
10
+ type CompletionSource,
11
+ type Completion,
12
+ } from '@codemirror/autocomplete'
6
13
  import { javascript } from '@codemirror/lang-javascript'
7
14
  import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'
8
15
  import { oneDark } from '@codemirror/theme-one-dark'
@@ -10,120 +17,16 @@ import { keymap } from '@codemirror/view'
10
17
  import { defaultKeymap } from '@codemirror/commands'
11
18
  import {
12
19
  EditorView,
13
- MatchDecorator,
14
20
  Decoration,
15
21
  DecorationSet,
16
22
  ViewPlugin,
17
23
  ViewUpdate,
18
- WidgetType,
24
+ hoverTooltip,
25
+ type Tooltip,
19
26
  } from '@codemirror/view'
20
-
21
- export interface FunctionSchema {
22
- /** Unique identifier for the function */
23
- id: string
24
- /** Function name */
25
- name: string
26
- /** Function description */
27
- description?: string
28
- /** Function parameters */
29
- parameters?: FunctionParameter[]
30
- /** Return type description */
31
- returns?: string
32
- /** Additional metadata */
33
- metadata?: Record<string, unknown>
34
- }
35
-
36
- export interface FunctionParameter {
37
- /** Parameter name */
38
- name: string
39
- /** Parameter type */
40
- type: string
41
- /** Parameter description */
42
- description?: string
43
- /** Whether parameter is required */
44
- required?: boolean
45
- /** Default value */
46
- defaultValue?: unknown
47
- }
48
-
49
- export interface Suggestion {
50
- /** Unique identifier for the suggestion */
51
- id: string
52
- /** Main label displayed */
53
- label: string
54
- /** Supporting description text */
55
- description?: string
56
- /** Suffix text (e.g., type, category) */
57
- suffix?: string
58
- /** Additional data associated with the suggestion */
59
- data?: Record<string, unknown>
60
- }
61
-
62
- export interface FunctionInsertEvent {
63
- /** The inserted function schema */
64
- functionSchema: FunctionSchema
65
- /** The position where the function was inserted */
66
- position: number
67
- }
68
-
69
- export interface SuggestionInsertEvent {
70
- /** The inserted suggestion */
71
- suggestion: Suggestion
72
- /** The position where the suggestion was inserted */
73
- position: number
74
- }
75
-
76
- class PlaceholderWidget extends WidgetType {
77
- constructor(public placeholderText: string) {
78
- super()
79
- }
80
-
81
- override eq(other: WidgetType): boolean {
82
- return this.placeholderText == (other as PlaceholderWidget).placeholderText
83
- }
84
-
85
- toDOM(): HTMLElement {
86
- const placeholder = document.createElement('span')
87
- placeholder.className = 'cm-pill'
88
- placeholder.textContent = this.placeholderText
89
- return placeholder
90
- }
91
-
92
- override ignoreEvent(): boolean {
93
- return false
94
- }
95
- }
96
-
97
- // We do variables Salesforce style, with double curly braces.
98
- // This decorator replaces {{variable}} with a placeholder widget.
99
- const placeholderMatcher = new MatchDecorator({
100
- regexp: /\{\{(\w+)\}\}/g,
101
- decoration: (match) =>
102
- Decoration.replace({
103
- widget: new PlaceholderWidget(match[1]),
104
- }),
105
- })
106
-
107
- /**
108
- * @see https://codemirror.net/examples/decoration/
109
- */
110
- class AtomicDecorationRange {
111
- placeholders: DecorationSet
112
- constructor(view: EditorView) {
113
- this.placeholders = placeholderMatcher.createDeco(view)
114
- }
115
- update(update: ViewUpdate) {
116
- this.placeholders = placeholderMatcher.updateDeco(update, this.placeholders)
117
- }
118
- }
119
-
120
- const placeholders = ViewPlugin.fromClass(AtomicDecorationRange, {
121
- decorations: (instance) => instance.placeholders,
122
- provide: (plugin) =>
123
- EditorView.atomicRanges.of((view) => {
124
- return view.plugin(plugin)?.placeholders || Decoration.none
125
- }),
126
- })
27
+ import { functionLinter } from './Linter.js'
28
+ import type { FunctionInsertEvent, FunctionSchema, Suggestion, SuggestionInsertEvent } from './types.js'
29
+ import { SuggestionMatchDecorator } from './SuggestionMatchDecorator.js'
127
30
 
128
31
  /**
129
32
  * A CodeMirror 6 based editor component that supports function autocomplete and suggestion placeholders.
@@ -232,9 +135,11 @@ export default class CodeEditor extends LitElement {
232
135
  private accessor isEditorFocus = false
233
136
 
234
137
  private editorView?: EditorView
235
- private suggestionMap = new Map<string, Suggestion>()
236
- private functionMap = new Map<string, FunctionSchema>()
237
138
  private _previousValue = ''
139
+ /**
140
+ * Matcher for suggestion placeholders in the editor.
141
+ */
142
+ placeholderMatcher?: SuggestionMatchDecorator
238
143
 
239
144
  /**
240
145
  * Get all suggestions (placeholders) currently in the editor
@@ -254,11 +159,6 @@ export default class CodeEditor extends LitElement {
254
159
  return suggestions
255
160
  }
256
161
 
257
- override connectedCallback(): void {
258
- super.connectedCallback()
259
- this.setupSuggestionMaps()
260
- }
261
-
262
162
  override disconnectedCallback(): void {
263
163
  super.disconnectedCallback()
264
164
  this.editorView?.destroy()
@@ -272,8 +172,10 @@ export default class CodeEditor extends LitElement {
272
172
  override willUpdate(changedProperties: PropertyValues): void {
273
173
  super.willUpdate(changedProperties)
274
174
 
275
- if (changedProperties.has('suggestions') || changedProperties.has('functionSchemas')) {
276
- this.setupSuggestionMaps()
175
+ if (changedProperties.has('suggestions')) {
176
+ if (this.placeholderMatcher) {
177
+ this.placeholderMatcher.suggestions = this.suggestions || []
178
+ }
277
179
  }
278
180
 
279
181
  if (changedProperties.has('value') && this.editorView) {
@@ -305,8 +207,9 @@ export default class CodeEditor extends LitElement {
305
207
  this.handleFocusChange(update.view.hasFocus)
306
208
  }
307
209
  }),
308
- placeholders,
309
- // linter((view) => functionLinter(view as unknown as EditorView, this.functionSchemas)),
210
+ this.createPlaceholderPlugin(),
211
+ hoverTooltip(this.createHoverTooltipSource),
212
+ linter((view) => functionLinter(view, this.functionSchemas, (e) => this.dispatchEvent(e))),
310
213
  ]
311
214
 
312
215
  // Add language support
@@ -339,6 +242,30 @@ export default class CodeEditor extends LitElement {
339
242
  })
340
243
  }
341
244
 
245
+ /**
246
+ * Creates the ViewPlugin for rendering suggestion placeholders.
247
+ * This is created as a method to get access to the component's `suggestions`.
248
+ */
249
+ private createPlaceholderPlugin(): Extension {
250
+ const placeholderMatcher = new SuggestionMatchDecorator(/\{\{(\w+)\}\}/g, this.suggestions)
251
+ this.placeholderMatcher = placeholderMatcher
252
+ // This class needs to be defined here to have access to the `placeholderMatcher`
253
+ class AtomicDecorationRange {
254
+ placeholders: DecorationSet
255
+ constructor(view: EditorView) {
256
+ this.placeholders = placeholderMatcher.createDeco(view)
257
+ }
258
+ update(update: ViewUpdate) {
259
+ this.placeholders = placeholderMatcher.updateDeco(update, this.placeholders)
260
+ }
261
+ }
262
+
263
+ return ViewPlugin.fromClass(AtomicDecorationRange, {
264
+ decorations: (instance) => instance.placeholders,
265
+ provide: (plugin) => EditorView.atomicRanges.of((view) => view.plugin(plugin)?.placeholders || Decoration.none),
266
+ })
267
+ }
268
+
342
269
  /**
343
270
  * Create completion source for functions and suggestions
344
271
  */
@@ -354,19 +281,7 @@ export default class CodeEditor extends LitElement {
354
281
  const prefix = functionMatch[1]
355
282
  const functions = this.functionSchemas
356
283
  .filter((fn) => fn.name.toLowerCase().startsWith(prefix.toLowerCase()))
357
- .map((fn) => ({
358
- label: fn.name,
359
- detail: fn.description || '',
360
- info: this.formatFunctionInfo(fn),
361
- apply: (view: EditorView, completion: unknown, from: number, to: number) => {
362
- const functionCall = this.formatFunctionCall(fn)
363
- view.dispatch({
364
- changes: { from, to, insert: functionCall },
365
- selection: { anchor: from + functionCall.length },
366
- })
367
- this.dispatchFunctionInsert(fn, from)
368
- },
369
- }))
284
+ .map((fn) => this.createFunctionCompletion(fn))
370
285
 
371
286
  if (functions.length > 0) {
372
287
  return {
@@ -382,19 +297,7 @@ export default class CodeEditor extends LitElement {
382
297
  const prefix = suggestionMatch[1]
383
298
  const suggestions = this.suggestions
384
299
  .filter((suggestion) => suggestion.label.toLowerCase().startsWith(prefix.toLowerCase()))
385
- .map((suggestion) => ({
386
- label: suggestion.label,
387
- detail: suggestion.description || '',
388
- info: suggestion.suffix || '',
389
- apply: (view: EditorView, completion: unknown, from: number, to: number) => {
390
- const placeholderText = `{{${suggestion.label}}}`
391
- view.dispatch({
392
- changes: { from: from - 2, to, insert: placeholderText }, // -2 to include the {{
393
- selection: { anchor: from - 2 + placeholderText.length },
394
- })
395
- this.dispatchSuggestionInsert(suggestion, from - 2)
396
- },
397
- }))
300
+ .map((suggestion) => this.createSuggestionCompletion(suggestion))
398
301
 
399
302
  if (suggestions.length > 0) {
400
303
  return {
@@ -408,25 +311,132 @@ export default class CodeEditor extends LitElement {
408
311
  }
409
312
  }
410
313
 
314
+ private createFunctionCompletion(schema: FunctionSchema): Completion {
315
+ const result: Completion = {
316
+ label: schema.name,
317
+ detail: schema.description || '',
318
+ info: () => this.createFunctionInfoElement(schema),
319
+ apply: (view: EditorView, completion: unknown, from: number, to: number) => {
320
+ const functionCall = this.formatFunctionCall(schema)
321
+ view.dispatch({
322
+ changes: { from, to, insert: functionCall },
323
+ selection: { anchor: from + functionCall.length },
324
+ })
325
+ this.dispatchFunctionInsert(schema, from)
326
+ },
327
+ }
328
+
329
+ return result
330
+ }
331
+
332
+ private createSuggestionCompletion(suggestion: Suggestion): Completion {
333
+ const result: Completion = {
334
+ label: suggestion.label,
335
+ detail: suggestion.description || '',
336
+ info: suggestion.suffix || '',
337
+ apply: (view: EditorView, completion: unknown, from: number, to: number) => {
338
+ const placeholderText = `{{${suggestion.label}}}`
339
+ view.dispatch({
340
+ changes: { from: from - 2, to, insert: placeholderText }, // -2 to include the {{
341
+ selection: { anchor: from - 2 + placeholderText.length },
342
+ })
343
+ this.dispatchSuggestionInsert(suggestion, from - 2)
344
+ },
345
+ }
346
+
347
+ return result
348
+ }
349
+
350
+ /**
351
+ * Creates the source for the hover tooltips.
352
+ * This is an arrow function to automatically bind `this`.
353
+ */
354
+ private createHoverTooltipSource = (view: EditorView, pos: number): Tooltip | null => {
355
+ const word = view.state.wordAt(pos)
356
+ if (!word) {
357
+ return null
358
+ }
359
+
360
+ const functionName = view.state.doc.sliceString(word.from, word.to)
361
+ const fnSchema = this.functionSchemas.find((schema) => schema.name === functionName)
362
+
363
+ if (!fnSchema) {
364
+ return null
365
+ }
366
+
367
+ return {
368
+ pos: word.from,
369
+ end: word.to,
370
+ above: true,
371
+ create: () => ({ dom: this.createFunctionInfoElement(fnSchema) }),
372
+ }
373
+ }
374
+
411
375
  /**
412
- * Format function information for autocomplete
376
+ * Creates a styled HTML element to display function documentation.
377
+ * This is used for both hover tooltips and autocomplete info panels.
413
378
  */
414
- private formatFunctionInfo(fn: FunctionSchema): string {
415
- let info = fn.description || ''
379
+ private createFunctionInfoElement(fn: FunctionSchema): HTMLElement {
380
+ const container = document.createElement('div')
381
+ container.className = 'function-info' // for styling
382
+
383
+ if (fn.description) {
384
+ const description = document.createElement('p')
385
+ description.className = 'description'
386
+ description.textContent = fn.description
387
+ container.appendChild(description)
388
+ }
389
+
416
390
  if (fn.parameters && fn.parameters.length > 0) {
417
- info += '\n\nParameters:\n'
391
+ const paramsContainer = document.createElement('div')
392
+ paramsContainer.className = 'parameters'
393
+
394
+ const paramsHeader = document.createElement('h4')
395
+ paramsHeader.textContent = 'Parameters'
396
+ paramsContainer.appendChild(paramsHeader)
397
+
398
+ const paramsList = document.createElement('ul')
418
399
  fn.parameters.forEach((param) => {
419
- info += `• ${param.name}: ${param.type}`
400
+ const listItem = document.createElement('li')
401
+
402
+ const name = document.createElement('span')
403
+ name.className = 'param-name'
404
+ name.textContent = param.name
405
+
406
+ const type = document.createElement('span')
407
+ type.className = 'param-type'
408
+ type.textContent = `: ${param.type}`
409
+
410
+ listItem.appendChild(name)
411
+ listItem.appendChild(type)
412
+
420
413
  if (param.description) {
421
- info += ` - ${param.description}`
414
+ const paramDesc = document.createElement('p')
415
+ paramDesc.className = 'param-description'
416
+ paramDesc.textContent = param.description
417
+ listItem.appendChild(paramDesc)
422
418
  }
423
- info += '\n'
419
+ paramsList.appendChild(listItem)
424
420
  })
421
+ paramsContainer.appendChild(paramsList)
422
+ container.appendChild(paramsContainer)
425
423
  }
424
+
426
425
  if (fn.returns) {
427
- info += `\nReturns: ${fn.returns}`
426
+ const returnsContainer = document.createElement('div')
427
+ returnsContainer.className = 'returns'
428
+
429
+ const returnsHeader = document.createElement('h4')
430
+ returnsHeader.textContent = 'Returns'
431
+ returnsContainer.appendChild(returnsHeader)
432
+
433
+ const returnsDesc = document.createElement('p')
434
+ returnsDesc.textContent = fn.returns
435
+ returnsContainer.appendChild(returnsDesc)
436
+ container.appendChild(returnsContainer)
428
437
  }
429
- return info
438
+
439
+ return container
430
440
  }
431
441
 
432
442
  /**
@@ -449,22 +459,6 @@ export default class CodeEditor extends LitElement {
449
459
  return `${fn.name}(${params})`
450
460
  }
451
461
 
452
- /**
453
- * Setup suggestion and function maps for quick lookup
454
- */
455
- private setupSuggestionMaps(): void {
456
- this.suggestionMap.clear()
457
- this.functionMap.clear()
458
-
459
- this.suggestions.forEach((suggestion) => {
460
- this.suggestionMap.set(suggestion.id, suggestion)
461
- })
462
-
463
- this.functionSchemas.forEach((fn) => {
464
- this.functionMap.set(fn.id, fn)
465
- })
466
- }
467
-
468
462
  /**
469
463
  * Update editor content when value changes
470
464
  */
@@ -1,13 +1,17 @@
1
1
  import type { Diagnostic } from '@codemirror/lint'
2
2
  import type { EditorView } from '@codemirror/view'
3
- import Element from './CodeEditor.js'
3
+ import type { FunctionSchema } from './types.js'
4
4
 
5
- export function functionLinter(view: EditorView, element: Element): readonly Diagnostic[] {
5
+ export function functionLinter(
6
+ view: EditorView,
7
+ functionSchemas: readonly FunctionSchema[],
8
+ dispatchEvent: (e: CustomEvent) => void
9
+ ): readonly Diagnostic[] {
6
10
  const diagnostics: Diagnostic[] = []
7
11
  const doc = view.state.doc
8
12
  const text = doc.toString()
9
13
 
10
- const functions = element.functionSchemas
14
+ const functions = functionSchemas
11
15
  if (!functions || functions.length === 0) {
12
16
  return diagnostics
13
17
  }
@@ -22,22 +26,21 @@ export function functionLinter(view: EditorView, element: Element): readonly Dia
22
26
  const endPos = startPos + functionName.length
23
27
 
24
28
  // Check if this function exists in our schemas
25
- const functionExists = functions.some((schema) => schema.name === functionName)
29
+ const fn = functions.find((schema) => schema.name === functionName)
26
30
 
27
- if (!functionExists) {
31
+ if (!fn) {
28
32
  diagnostics.push({
29
33
  from: startPos,
30
34
  to: endPos,
31
35
  severity: 'error',
32
- message: `Unknown function "${functionName}". Only functions defined in the schema are allowed.`,
36
+ message: `Unknown function "${functionName}". Make sure you have the correct syntax for the function call.`,
33
37
  actions: [
34
38
  {
35
39
  name: 'View available functions',
36
40
  apply: () => {
37
- // This could trigger showing a help dialog or documentation
38
- element.dispatchEvent(
41
+ dispatchEvent(
39
42
  new CustomEvent('show-available-functions', {
40
- detail: { availableFunctions: functions.map((s) => s.name) },
43
+ detail: { availableFunctions: [...functions] },
41
44
  bubbles: true,
42
45
  })
43
46
  )
@@ -46,38 +49,33 @@ export function functionLinter(view: EditorView, element: Element): readonly Dia
46
49
  ],
47
50
  })
48
51
  } else {
49
- // Optional: Validate function usage (parameters, etc.)
50
- const fn = functions.find((schema) => schema.name === functionName)
52
+ // Extract the function call content to validate parameters
53
+ const functionCallMatch = text.substring(startPos).match(/(\w+)\s*\(([^)]*)\)/)
54
+ if (functionCallMatch && fn.parameters && fn.parameters.length > 0) {
55
+ const argsString = functionCallMatch[2].trim()
56
+ const args = argsString ? argsString.split(',').map((arg) => arg.trim()) : []
51
57
 
52
- if (fn) {
53
- // Extract the function call content to validate parameters
54
- const functionCallMatch = text.substring(startPos).match(/(\w+)\s*\(([^)]*)\)/)
55
- if (functionCallMatch && fn.parameters) {
56
- const argsString = functionCallMatch[2].trim()
57
- const args = argsString ? argsString.split(',').map((arg) => arg.trim()) : []
58
-
59
- // Check required parameters
60
- const requiredParams = fn.parameters.filter((p) => p.required)
61
- if (args.length < requiredParams.length) {
62
- const functionEndPos = startPos + functionCallMatch[0].length
63
- diagnostics.push({
64
- from: startPos,
65
- to: functionEndPos,
66
- severity: 'error',
67
- message: `Function "${functionName}" requires ${requiredParams.length} parameters but got ${args.length}. Required: ${requiredParams.map((p) => p.name).join(', ')}`,
68
- })
69
- }
58
+ // Check required parameters
59
+ const requiredParams = fn.parameters.filter((p) => p.required)
60
+ if (args.length < requiredParams.length) {
61
+ const functionEndPos = startPos + functionCallMatch[0].length
62
+ diagnostics.push({
63
+ from: startPos,
64
+ to: functionEndPos,
65
+ severity: 'error',
66
+ message: `Function "${functionName}" requires ${requiredParams.length} parameters but got ${args.length}. Required: ${requiredParams.map((p) => p.name).join(', ')}`,
67
+ })
68
+ }
70
69
 
71
- // Check if too many parameters
72
- if (fn.parameters && args.length > fn.parameters.length) {
73
- const functionEndPos = startPos + functionCallMatch[0].length
74
- diagnostics.push({
75
- from: startPos,
76
- to: functionEndPos,
77
- severity: 'warning',
78
- message: `Function "${functionName}" expects at most ${fn.parameters.length} parameters but got ${args.length}`,
79
- })
80
- }
70
+ // Check if too many parameters
71
+ if (fn.parameters && args.length > fn.parameters.length) {
72
+ const functionEndPos = startPos + functionCallMatch[0].length
73
+ diagnostics.push({
74
+ from: startPos,
75
+ to: functionEndPos,
76
+ severity: 'warning',
77
+ message: `Function "${functionName}" expects at most ${fn.parameters.length} parameters but got ${args.length}`,
78
+ })
81
79
  }
82
80
  }
83
81
  }
@@ -0,0 +1,50 @@
1
+ import { EditorView, WidgetType } from '@codemirror/view'
2
+ import type { Suggestion } from './types.js'
3
+ import '../../../md/chip/ui-chip.js'
4
+
5
+ /**
6
+ * A widget that represents a placeholder in the code editor,
7
+ * specifically for suggestions that are replaced with chips.
8
+ *
9
+ * This widget is used to create a visual representation of a suggestion
10
+ * in the code editor, allowing users to see and interact with suggestions
11
+ * as chips. When a chip is removed, the corresponding text in the editor
12
+ * is also removed.
13
+ */
14
+ export class ChipWidget extends WidgetType {
15
+ constructor(public suggestion: Suggestion) {
16
+ super()
17
+ }
18
+
19
+ override eq(other: WidgetType): boolean {
20
+ return this.suggestion.id == (other as ChipWidget).suggestion.id
21
+ }
22
+
23
+ toDOM(view: EditorView): HTMLElement {
24
+ const wrapper = document.createElement('span')
25
+ wrapper.className = 'mention-chip'
26
+ wrapper.setAttribute('data-mention-id', this.suggestion.id)
27
+
28
+ const chip = document.createElement('ui-chip')
29
+ chip.setAttribute('type', 'input')
30
+ chip.setAttribute('removable', 'true')
31
+ chip.textContent = this.suggestion.label
32
+ chip.addEventListener('remove', () => {
33
+ const pos = view.posAtDOM(wrapper)
34
+ if (pos === null) return
35
+
36
+ const originalText = `{{${this.suggestion.label}}}`
37
+ view.dispatch({
38
+ changes: { from: pos, to: pos + originalText.length, insert: '' },
39
+ })
40
+ view.focus()
41
+ })
42
+
43
+ wrapper.appendChild(chip)
44
+ return wrapper
45
+ }
46
+
47
+ override ignoreEvent(): boolean {
48
+ return false
49
+ }
50
+ }
@@ -0,0 +1,36 @@
1
+ import { MatchDecorator, Decoration } from '@codemirror/view'
2
+ import type { Suggestion } from './types.js'
3
+ import { ChipWidget } from './PlaceholderWidget.js'
4
+
5
+ /**
6
+ * A class that specializes in creating and managing decorations for suggestion matches in a code editor.
7
+ */
8
+ export class SuggestionMatchDecorator extends MatchDecorator {
9
+ /**
10
+ * Creates a new instance of SuggestionMatchDecorator.
11
+ * @param regexp - The regular expression used to match suggestions in the code.
12
+ * @param suggestions - An array of suggestions that will be used to create decorations.
13
+ * Each suggestion should have a unique `id` and a `label` that will be displayed in the editor.
14
+ */
15
+ constructor(
16
+ regexp: RegExp,
17
+ public suggestions: Suggestion[]
18
+ ) {
19
+ super({
20
+ regexp,
21
+ decoration: (match) => this.#decoration(match),
22
+ })
23
+ }
24
+
25
+ #decoration(match: RegExpExecArray): Decoration {
26
+ const label = match[1]
27
+ const suggestion = this.suggestions.find((s) => s.label === label)
28
+
29
+ // If no suggestion is found, create a default one
30
+ const suggestionData: Suggestion = suggestion || { id: label, label }
31
+
32
+ return Decoration.replace({
33
+ widget: new ChipWidget(suggestionData),
34
+ })
35
+ }
36
+ }
@@ -0,0 +1,54 @@
1
+ export interface FunctionSchema {
2
+ /** Unique identifier for the function */
3
+ id: string
4
+ /** Function name */
5
+ name: string
6
+ /** Function description */
7
+ description?: string
8
+ /** Function parameters */
9
+ parameters?: FunctionParameter[]
10
+ /** Return type description */
11
+ returns?: string
12
+ /** Additional metadata */
13
+ metadata?: Record<string, unknown>
14
+ }
15
+
16
+ export interface FunctionParameter {
17
+ /** Parameter name */
18
+ name: string
19
+ /** Parameter type */
20
+ type: string
21
+ /** Parameter description */
22
+ description?: string
23
+ /** Whether parameter is required */
24
+ required?: boolean
25
+ /** Default value */
26
+ defaultValue?: unknown
27
+ }
28
+
29
+ export interface Suggestion {
30
+ /** Unique identifier for the suggestion */
31
+ id: string
32
+ /** Main label displayed */
33
+ label: string
34
+ /** Supporting description text */
35
+ description?: string
36
+ /** Suffix text (e.g., type, category) */
37
+ suffix?: string
38
+ /** Additional data associated with the suggestion */
39
+ data?: Record<string, unknown>
40
+ }
41
+
42
+ export interface FunctionInsertEvent {
43
+ /** The inserted function schema */
44
+ functionSchema: FunctionSchema
45
+ /** The position where the function was inserted */
46
+ position: number
47
+ }
48
+
49
+ export interface SuggestionInsertEvent {
50
+ /** The inserted suggestion */
51
+ suggestion: Suggestion
52
+ /** The position where the suggestion was inserted */
53
+ position: number
54
+ }