import * as monaco from 'monaco-editor'; import type { editor } from 'monaco-editor'; import { parseAndLocate } from '../parser/locate'; import { offsetToPosition } from '../parser/lexer'; import { buildCompletions } from './completions'; import type { LabelMatcher } from '../types'; import type { MutableRefObject } from 'react'; export interface ProviderOptions { fetchLabelNamesRef: MutableRefObject<((currentMatchers: LabelMatcher[]) => Promise) | undefined>; fetchLabelValuesRef: MutableRefObject<((labelName: string, currentMatchers: LabelMatcher[]) => Promise) | undefined>; variablesRef: MutableRefObject; editorRef: MutableRefObject; } export function getLokiCompletionProvider(opts: ProviderOptions): monaco.languages.CompletionItemProvider { return { triggerCharacters: ['{', '|', '=', '~', ',', '(', ' ', '$', '"', "'"], provideCompletionItems: async (model, position) => { // Only serve requests from this editor's own model if (opts.editorRef.current?.getModel()?.id !== model.id) { return { suggestions: [] }; } const value = model.getValue(); const offset = model.getOffsetAt(position); // Parse and locate const { ast, situation } = parseAndLocate(value, offset); // Extract current matchers from the AST stream selector for context, // excluding the matcher currently being edited (identified by matcherIndex). let currentMatchers: LabelMatcher[] = ast.streamSelector?.matchers.map((m) => ({ label: m.label, operator: m.operator, value: m.value, })) ?? []; if (situation.matcherIndex !== undefined) { currentMatchers = currentMatchers.filter((_, i) => i !== situation.matcherIndex); } // Build the Monaco range from the situation's replaceRange const startPos = offsetToPosition(value, situation.replaceRange.start); const endPos = position; // current cursor position const range = { startLineNumber: startPos.lineNumber, startColumn: startPos.column, endLineNumber: endPos.lineNumber, endColumn: endPos.column, }; const suggestions = await buildCompletions({ situation, range, variables: opts.variablesRef.current, currentMatchers, fetchLabelNames: opts.fetchLabelNamesRef.current, fetchLabelValues: opts.fetchLabelValuesRef.current, }); return { suggestions }; }, }; }