Skip to content
This repository has been archived by the owner on Jul 11, 2022. It is now read-only.

Integrate lezer-promql v0.19.0 with alternative top #142

Merged
merged 8 commits into from
May 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 25 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"homepage": "https://github.com/prometheus-community/codemirror-promql/blob/master/README.md",
"dependencies": {
"lezer-promql": "^0.18.0",
"lezer-promql": "^0.19.0",
"lru-cache": "^6.0.0"
},
"devDependencies": {
Expand Down
6 changes: 6 additions & 0 deletions src/app/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ <h3>CodeMirror Mode PromQL</h3>
<option selected value="offline">Offline</option>
<option value="prometheus">Prometheus</option>
</select>
<br>
<label for="languageType">Language to complete</label>
<select name="languageType" id="languageType">
<option selected value="promql">Full PromQL</option>
<option value="metricName">Metric names</option>
</select>

<button id="apply">apply</button>

Expand Down
17 changes: 15 additions & 2 deletions src/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,25 @@
import { basicSetup } from '@codemirror/basic-setup';
import { EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { PromQLExtension } from '../lang-promql';
import { LanguageType, PromQLExtension } from '../lang-promql';
import { customTheme, promQLHighlightMaterialTheme } from './theme';

const promqlExtension = new PromQLExtension();
let editor: EditorView;

function getLanguageType(): LanguageType {
const completionSelect = document.getElementById('languageType') as HTMLSelectElement;
const completionValue = completionSelect.options[completionSelect.selectedIndex].value;
switch (completionValue) {
case 'promql':
return LanguageType.PromQL;
case 'metricName':
return LanguageType.MetricName;
default:
return LanguageType.PromQL;
}
}

function setCompletion() {
const completionSelect = document.getElementById('completion') as HTMLSelectElement;
const completionValue = completionSelect.options[completionSelect.selectedIndex].value;
Expand Down Expand Up @@ -59,7 +72,7 @@ function createEditor() {
}
editor = new EditorView({
state: EditorState.create({
extensions: [basicSetup, promqlExtension.asExtension(), promQLHighlightMaterialTheme, customTheme],
extensions: [basicSetup, promqlExtension.asExtension(getLanguageType()), promQLHighlightMaterialTheme, customTheme],
doc: doc,
}),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand Down
42 changes: 22 additions & 20 deletions src/lang-promql/complete/hybrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ import {
} from 'lezer-promql';
import { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import { EditorState } from '@codemirror/state';
import { containsAtLeastOneChild, containsChild, retrieveAllRecursiveNodes, walkBackward, walkThrough, buildLabelMatchers } from '../parser';
import { buildLabelMatchers, containsAtLeastOneChild, containsChild, retrieveAllRecursiveNodes, walkBackward, walkThrough } from '../parser';
import {
aggregateOpModifierTerms,
aggregateOpTerms,
Expand Down Expand Up @@ -220,13 +220,6 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
result.push({ kind: ContextKind.Duration });
break;
}
if (node.parent?.type.id === StepInvariantExpr) {
// we are likely in the given situation:
// `expr @ s`
// we can autocomplete start / end
result.push({ kind: ContextKind.AtModifiers });
break;
}
if (node.parent?.type.id === SubqueryExpr && containsAtLeastOneChild(node.parent, Duration)) {
// we are likely in the given situation:
// `rate(foo[5d:5])`
Expand All @@ -246,14 +239,22 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
case Identifier:
// sometimes an Identifier has an error has parent. This should be treated in priority
if (node.parent?.type.id === 0) {
if (node.parent.parent?.type.id === AggregateExpr) {
const parent = node.parent;
if (parent.parent?.type.id === StepInvariantExpr) {
// we are likely in the given situation:
// `expr @ s`
// we can autocomplete start / end
result.push({ kind: ContextKind.AtModifiers });
break;
}
if (parent.parent?.type.id === AggregateExpr) {
// it matches 'sum() b'. So here we can autocomplete:
// - the aggregate operation modifier
// - the binary operation (since it's not mandatory to have an aggregate operation modifier)
result.push({ kind: ContextKind.AggregateOpModifier }, { kind: ContextKind.BinOp });
break;
}
if (node.parent.parent?.type.id === VectorSelector) {
if (parent.parent?.type.id === VectorSelector) {
// it matches 'sum b'. So here we also have to autocomplete the aggregate operation modifier only
// if the associated metricIdentifier is matching an aggregation operation.
// Note: here is the corresponding tree in order to understand the situation:
Expand Down Expand Up @@ -296,14 +297,9 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context

const parent = node.parent?.parent?.parent?.parent;
if (!parent) {
// this case is normally impossible since by definition, the identifier has 3 parents,
// and in Lexer, there is always a default parent in top of everything.
result.push(
{ kind: ContextKind.MetricName, metricName: state.sliceDoc(node.from, node.to) },
{ kind: ContextKind.Function },
{ kind: ContextKind.Aggregation },
{ kind: ContextKind.Number }
);
// this case can be possible if the topNode is not anymore PromQL but MetricName.
// In this particular case, then we just want to autocomplete the metric
result.push({ kind: ContextKind.MetricName, metricName: state.sliceDoc(node.from, node.to) });
break;
}
// now we have to know if we have two Expr in the direct children of the `parent`
Expand Down Expand Up @@ -396,7 +392,12 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
const metricName = getMetricNameInVectorSelector(node, state);
// finally get the full matcher available
const labelMatchers = buildLabelMatchers(retrieveAllRecursiveNodes(walkBackward(node, LabelMatchList), LabelMatchList, LabelMatcher), state);
result.push({ kind: ContextKind.LabelValue, metricName: metricName, labelName: labelName, matchers: labelMatchers });
result.push({
kind: ContextKind.LabelValue,
metricName: metricName,
labelName: labelName,
matchers: labelMatchers,
});
}
break;
case NumberLiteral:
Expand Down Expand Up @@ -482,11 +483,13 @@ export class HybridComplete implements CompleteStrategy {
for (const context of contexts) {
switch (context.kind) {
case ContextKind.Aggregation:
completeSnippet = true;
asyncResult = asyncResult.then((result) => {
return result.concat(autocompleteNodes.aggregateOp);
});
break;
case ContextKind.Function:
completeSnippet = true;
asyncResult = asyncResult.then((result) => {
return result.concat(autocompleteNodes.functionIdentifier);
});
Expand Down Expand Up @@ -539,7 +542,6 @@ export class HybridComplete implements CompleteStrategy {
break;
case ContextKind.MetricName:
asyncResult = asyncResult.then((result) => {
completeSnippet = true;
return this.autocompleteMetricName(result, context);
});
break;
Expand Down
2 changes: 1 addition & 1 deletion src/lang-promql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@
export { PrometheusClient } from './client';
export { CompleteConfiguration, CompleteStrategy } from './complete';
export { LintStrategy } from './lint';
export { PromQLExtension, promQLLanguage } from './promql';
export { PromQLExtension, LanguageType, promQLLanguage } from './promql';
73 changes: 41 additions & 32 deletions src/lang-promql/promql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,35 +28,43 @@ import { LintStrategy, newLintStrategy, promQLLinter } from './lint';
import { CompletionContext } from '@codemirror/autocomplete';
import { LezerLanguage } from '@codemirror/language';

export const promQLLanguage = LezerLanguage.define({
parser: parser.configure({
props: [
styleTags({
LineComment: tags.comment,
LabelName: tags.labelName,
StringLiteral: tags.string,
NumberLiteral: tags.number,
Duration: tags.number,
'Abs Absent AbsentOverTime AvgOverTime Ceil Changes Clamp ClampMax ClampMin CountOverTime DaysInMonth DayOfMonth DayOfWeek Delta Deriv Exp Floor HistogramQuantile HoltWinters Hour Idelta Increase Irate LabelReplace LabelJoin LastOverTime Ln Log10 Log2 MaxOverTime MinOverTime Minute Month PredictLinear QuantileOverTime Rate Resets Round Scalar Sgn Sort SortDesc Sqrt StddevOverTime StdvarOverTime SumOverTime Time Timestamp Vector Year': tags.function(
tags.variableName
),
'Avg Bottomk Count Count_values Group Max Min Quantile Stddev Stdvar Sum Topk': tags.operatorKeyword,
'By Without Bool On Ignoring GroupLeft GroupRight Offset Start End': tags.modifier,
'And Unless Or': tags.logicOperator,
'Sub Add Mul Mod Div Eql Neq Lte Lss Gte Gtr EqlRegex EqlSingle NeqRegex Pow At': tags.operator,
UnaryOp: tags.arithmeticOperator,
'( )': tags.paren,
'[ ]': tags.squareBracket,
'{ }': tags.brace,
'⚠': tags.invalid,
}),
],
}),
languageData: {
closeBrackets: { brackets: ['(', '[', '{', "'", '"', '`'] },
commentTokens: { line: '#' },
},
});
export enum LanguageType {
PromQL = 'PromQL',
MetricName = 'MetricName',
}

export function promQLLanguage(top: LanguageType) {
return LezerLanguage.define({
parser: parser.configure({
top: top,
props: [
styleTags({
LineComment: tags.comment,
LabelName: tags.labelName,
StringLiteral: tags.string,
NumberLiteral: tags.number,
Duration: tags.number,
'Abs Absent AbsentOverTime AvgOverTime Ceil Changes Clamp ClampMax ClampMin CountOverTime DaysInMonth DayOfMonth DayOfWeek Delta Deriv Exp Floor HistogramQuantile HoltWinters Hour Idelta Increase Irate LabelReplace LabelJoin LastOverTime Ln Log10 Log2 MaxOverTime MinOverTime Minute Month PredictLinear QuantileOverTime Rate Resets Round Scalar Sgn Sort SortDesc Sqrt StddevOverTime StdvarOverTime SumOverTime Time Timestamp Vector Year': tags.function(
tags.variableName
),
'Avg Bottomk Count Count_values Group Max Min Quantile Stddev Stdvar Sum Topk': tags.operatorKeyword,
'By Without Bool On Ignoring GroupLeft GroupRight Offset Start End': tags.modifier,
'And Unless Or': tags.logicOperator,
'Sub Add Mul Mod Div Eql Neq Lte Lss Gte Gtr EqlRegex EqlSingle NeqRegex Pow At': tags.operator,
UnaryOp: tags.arithmeticOperator,
'( )': tags.paren,
'[ ]': tags.squareBracket,
'{ }': tags.brace,
'⚠': tags.invalid,
}),
],
}),
languageData: {
closeBrackets: { brackets: ['(', '[', '{', "'", '"', '`'] },
commentTokens: { line: '#' },
},
});
}

/**
* This class holds the state of the completion extension for CodeMirror and allow hot-swapping the complete strategy.
Expand Down Expand Up @@ -102,10 +110,11 @@ export class PromQLExtension {
return this;
}

asExtension(): Extension {
let extension: Extension = [promQLLanguage];
asExtension(languageType = LanguageType.PromQL): Extension {
const language = promQLLanguage(languageType);
let extension: Extension = [language];
if (this.enableCompletion) {
const completion = promQLLanguage.data.of({
const completion = language.data.of({
autocomplete: (context: CompletionContext) => {
return this.complete.promQL(context);
},
Expand Down