import * as monaco from 'monaco-editor'; import type { Situation } from '../parser/types'; import type { LabelMatcher } from '../types'; export interface BuildCompletionsArgs { situation: Situation; range: monaco.IRange; variables: string[]; currentMatchers: LabelMatcher[]; fetchLabelNames?: (currentMatchers: LabelMatcher[]) => Promise; fetchLabelValues?: (labelName: string, currentMatchers: LabelMatcher[]) => Promise; } const SORT = { variable: '0', labelValue: '1', labelName: '2', keyword: '4', function: '5', }; // ─── Line filter operators ──────────────────────────────────────── const LINE_FILTER_OPERATORS = [ { label: '|=', detail: 'Line contains', insertText: '|= ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '!=', detail: 'Line does not contain', insertText: '!= ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '|~', detail: 'Line matches regex', insertText: '|~ ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '!~', detail: 'Line does not match regex', insertText: '!~ ', kind: monaco.languages.CompletionItemKind.Operator }, ]; // ─── Pipeline stage keywords ────────────────────────────────────── const PIPELINE_STAGES = [ { label: 'json', detail: 'Parse JSON from log line', insertText: 'json', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'logfmt', detail: 'Parse logfmt-encoded log line', insertText: 'logfmt', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'regexp', detail: 'Parse using regex pattern', insertText: 'regexp ', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'pattern', detail: 'Parse using log pattern', insertText: 'pattern ', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'unpack', detail: 'Unpack packed log line', insertText: 'unpack', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'line_format', detail: 'Format log line content', insertText: 'line_format ', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'label_format', detail: 'Rename or format labels', insertText: 'label_format ', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'drop', detail: 'Drop labels by name or value', insertText: 'drop ', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'keep', detail: 'Keep only specified labels', insertText: 'keep ', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'unwrap', detail: 'Extract a label as sample data', insertText: 'unwrap ', kind: monaco.languages.CompletionItemKind.Keyword }, { label: 'decolorize', detail: 'Remove ANSI color codes', insertText: 'decolorize', kind: monaco.languages.CompletionItemKind.Keyword }, ]; // ─── Metric functions ───────────────────────────────────────────── const METRIC_FUNCTIONS = [ { label: 'rate', detail: 'Per-second rate of log lines', insertText: 'rate', kind: monaco.languages.CompletionItemKind.Function }, { label: 'rate_counter', detail: 'Rate as counter metric', insertText: 'rate_counter', kind: monaco.languages.CompletionItemKind.Function }, { label: 'count_over_time', detail: 'Count log lines over time', insertText: 'count_over_time', kind: monaco.languages.CompletionItemKind.Function, }, { label: 'bytes_rate', detail: 'Bytes per second', insertText: 'bytes_rate', kind: monaco.languages.CompletionItemKind.Function }, { label: 'bytes_over_time', detail: 'Bytes over time', insertText: 'bytes_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'sum_over_time', detail: 'Sum over time', insertText: 'sum_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'avg_over_time', detail: 'Average over time', insertText: 'avg_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'max_over_time', detail: 'Maximum over time', insertText: 'max_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'min_over_time', detail: 'Minimum over time', insertText: 'min_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'first_over_time', detail: 'First value over time', insertText: 'first_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'last_over_time', detail: 'Last value over time', insertText: 'last_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'stdvar_over_time', detail: 'Variance over time', insertText: 'stdvar_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'stddev_over_time', detail: 'Std dev over time', insertText: 'stddev_over_time', kind: monaco.languages.CompletionItemKind.Function }, { label: 'quantile_over_time', detail: 'Quantile over time', insertText: 'quantile_over_time', kind: monaco.languages.CompletionItemKind.Function, }, { label: 'absent_over_time', detail: '1 if no data', insertText: 'absent_over_time', kind: monaco.languages.CompletionItemKind.Function }, ]; // ─── Aggregation functions ──────────────────────────────────────── const AGGREGATION_FUNCTIONS = [ { label: 'sum', insertText: 'sum', kind: monaco.languages.CompletionItemKind.Function }, { label: 'avg', insertText: 'avg', kind: monaco.languages.CompletionItemKind.Function }, { label: 'min', insertText: 'min', kind: monaco.languages.CompletionItemKind.Function }, { label: 'max', insertText: 'max', kind: monaco.languages.CompletionItemKind.Function }, { label: 'count', insertText: 'count', kind: monaco.languages.CompletionItemKind.Function }, { label: 'stddev', insertText: 'stddev', kind: monaco.languages.CompletionItemKind.Function }, { label: 'stdvar', insertText: 'stdvar', kind: monaco.languages.CompletionItemKind.Function }, { label: 'topk', insertText: 'topk', kind: monaco.languages.CompletionItemKind.Function }, { label: 'bottomk', insertText: 'bottomk', kind: monaco.languages.CompletionItemKind.Function }, { label: 'sort', insertText: 'sort', kind: monaco.languages.CompletionItemKind.Function }, { label: 'sort_desc', insertText: 'sort_desc', kind: monaco.languages.CompletionItemKind.Function }, ]; // ─── Helpers ────────────────────────────────────────────────────── function variableItems(variables: string[], range: monaco.IRange, wrapInQuotes?: boolean): monaco.languages.CompletionItem[] { return variables.map((v) => ({ label: `\${${v}}`, kind: monaco.languages.CompletionItemKind.Variable, insertText: wrapInQuotes ? `"\${${v}}"` : `\${${v}}`, range, sortText: SORT.variable, })); } function keywordItems( items: Array<{ label: string; detail?: string; insertText: string; kind: monaco.languages.CompletionItemKind }>, range: monaco.IRange, ): monaco.languages.CompletionItem[] { return items.map((item) => ({ label: item.label, kind: item.kind, detail: item.detail, insertText: item.insertText, range, sortText: SORT.keyword, })); } function functionItems(items: Array<{ label: string; insertText: string; kind: monaco.languages.CompletionItemKind }>, range: monaco.IRange): monaco.languages.CompletionItem[] { return items.map((item) => ({ label: item.label, kind: item.kind, insertText: item.insertText, range, sortText: SORT.function, })); } // ─── Build completions ──────────────────────────────────────────── export async function buildCompletions(args: BuildCompletionsArgs): Promise { const { situation, range, variables } = args; const items: monaco.languages.CompletionItem[] = []; switch (situation.kind) { case 'NONE': break; case 'IN_SELECTOR_LABEL': { // Offer async label names only — no variables (they belong in value context) if (args.fetchLabelNames) { try { const names = await args.fetchLabelNames(args.currentMatchers); const seen = new Set(); for (const name of names) { if (seen.has(name)) continue; seen.add(name); items.push({ label: name, kind: monaco.languages.CompletionItemKind.Property, insertText: name, range, sortText: SORT.labelName, }); } } catch { // Silently ignore fetch errors } } break; } case 'IN_SELECTOR_VALUE': { // Offer variables (quoted when outside a string, raw when inside) + async label values items.push(...variableItems(variables, range, !situation.insideString)); if (args.fetchLabelValues && situation.labelName) { try { const values = await args.fetchLabelValues(situation.labelName, args.currentMatchers); const seen = new Set(); for (const v of values) { if (seen.has(v)) continue; seen.add(v); items.push({ label: v, kind: monaco.languages.CompletionItemKind.Value, // When inside a string the quotes are already in the editor, // so insert the raw value. Otherwise wrap in quotes. insertText: situation.insideString ? v : `"${v}"`, range, sortText: SORT.labelValue, }); } } catch { // ignore } } break; } case 'AFTER_PIPE': { // Offer pipeline stages and line filter operators (no variables) items.push(...keywordItems(LINE_FILTER_OPERATORS, range)); items.push(...keywordItems(PIPELINE_STAGES, range)); break; } case 'IN_PIPELINE_ARG': { break; } case 'IN_LABEL_FILTER': { items.push( ...keywordItems( [ { label: '>=', detail: 'Greater or equal', insertText: '>= ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '<=', detail: 'Less or equal', insertText: '<= ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '>', detail: 'Greater than', insertText: '> ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '<', detail: 'Less than', insertText: '< ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '=', detail: 'Equals', insertText: '= ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '!=', detail: 'Not equals', insertText: '!= ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '=~', detail: 'Regex matches', insertText: '=~ ', kind: monaco.languages.CompletionItemKind.Operator }, { label: '!~', detail: 'Regex not matches', insertText: '!~ ', kind: monaco.languages.CompletionItemKind.Operator }, ], range, ), ); break; } case 'IN_FUNCTION_PARENS': { // Offer metric / aggregation functions (no variables, no stream selector snippet) items.push(...functionItems(METRIC_FUNCTIONS, range)); items.push(...functionItems(AGGREGATION_FUNCTIONS, range)); break; } case 'IN_GROUPING': { if (args.fetchLabelNames) { try { const names = await args.fetchLabelNames(args.currentMatchers); const seen = new Set(); for (const name of names) { if (seen.has(name)) continue; seen.add(name); items.push({ label: name, kind: monaco.languages.CompletionItemKind.Property, insertText: name, range, sortText: SORT.labelName, }); } } catch { // ignore } } break; } } return items; }