Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Console command support (:use, :params, :clear, history) #165

Merged
merged 31 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
db244c1
create gramamr for client commands
OskarDamkjaer Jan 12, 2024
097b5c6
some basic commands
OskarDamkjaer Jan 12, 2024
6eb5adc
consider
OskarDamkjaer Jan 12, 2024
e8b3a04
add test file
OskarDamkjaer Jan 15, 2024
0a43995
mellan
OskarDamkjaer Jan 17, 2024
de69b4c
rename
OskarDamkjaer Jan 17, 2024
d46b6d1
add unit tests
OskarDamkjaer Jan 18, 2024
99eb47a
cleanuptodos
OskarDamkjaer Jan 18, 2024
7e1cba4
todos
OskarDamkjaer Jan 18, 2024
788f8ab
fix syntax highlighting for console commands
OskarDamkjaer Jan 19, 2024
532ef7a
add tests
OskarDamkjaer Jan 19, 2024
e133ef5
fix completions
OskarDamkjaer Jan 19, 2024
108adef
add errors when cmd disabled
OskarDamkjaer Jan 19, 2024
fbd2004
semantic analysis handles multiple queries
OskarDamkjaer Jan 19, 2024
4d6ac31
comment
OskarDamkjaer Jan 19, 2024
94a0614
self review
OskarDamkjaer Jan 19, 2024
6a9ee9a
unused variable
OskarDamkjaer Jan 19, 2024
45ac58d
fix unit tests
OskarDamkjaer Jan 22, 2024
31bb59a
self review
OskarDamkjaer Jan 22, 2024
a194b3b
rename parser
OskarDamkjaer Jan 29, 2024
7a866ee
rename rules
OskarDamkjaer Jan 29, 2024
5987f9d
rephrase comment
OskarDamkjaer Jan 29, 2024
6c81588
fix crash and add tests
OskarDamkjaer Jan 29, 2024
345c704
remove todo
OskarDamkjaer Jan 30, 2024
1713dfa
mellan
OskarDamkjaer Feb 5, 2024
02c40b9
properly merge main
OskarDamkjaer Feb 5, 2024
efc964e
mellan
OskarDamkjaer Feb 6, 2024
d6e8359
improve error messages for console commands
OskarDamkjaer Feb 6, 2024
d44bb69
add changeset
OskarDamkjaer Feb 6, 2024
b42237a
Merge branch 'main' into relevant_client_cmds
OskarDamkjaer Feb 7, 2024
4933929
fix bad merge
OskarDamkjaer Feb 7, 2024
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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"typescript.tsc.autoDetect": "off",
"typescript.preferences.quoteStyle": "single",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/language-support/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"vscode-languageserver-types": "^3.17.3"
},
"scripts": {
"gen-parser": "antlr4 -Dlanguage=TypeScript -visitor src/antlr-grammar/CypherParser.g4 src/antlr-grammar/CypherLexer.g4 -o src/generated-parser/ -Xexact-output-dir",
"gen-parser": "antlr4 -Dlanguage=TypeScript -visitor src/antlr-grammar/CypherCmdLexer.g4 src/antlr-grammar/CypherCmdParser.g4 -o src/generated-parser/ -Xexact-output-dir",
"build": "npm run gen-parser && concurrently 'npm:build-types' 'npm:build-esm' 'npm:build-commonjs'",
"build-types": "tsc --emitDeclarationOnly --outDir dist/types",
"build-esm": "esbuild ./src/index.ts --bundle --format=esm --sourcemap --outfile=dist/esm/index.mjs",
Expand Down
9 changes: 9 additions & 0 deletions packages/language-support/src/antlr-grammar/CypherCmdLexer.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
lexer grammar CypherCmdLexer;

import CypherLexer;

PARAM : P A R A M S?;

CLEAR: C L E A R;

HISTORY: H I S T O R Y;
28 changes: 28 additions & 0 deletions packages/language-support/src/antlr-grammar/CypherCmdParser.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
parser grammar CypherCmdParser;

import CypherParser;

options { tokenVocab = CypherCmdLexer; }

statementsOrCommands: statementOrCommand (SEMICOLON statementOrCommand)* SEMICOLON? EOF;

statementOrCommand: (statement | consoleCommand);

consoleCommand: COLON (clearCmd | historyCmd | useCmd | paramsCmd);

paramsCmd: PARAM paramsArgs?;

paramsArgs: (CLEAR | listCompletionRule | map | lambda);

lambda: unescapedSymbolicNameString EQ GT expression;

clearCmd: CLEAR;

historyCmd: HISTORY;

useCmd: useCompletionRule symbolicAliasName?;

// These rules are needed to distinguish cypher <-> commands, for exapmle `USE` and `:use` in autocompletion
listCompletionRule: LIST;

useCompletionRule: USE;
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,9 @@ export function autocomplete(
parsingResult = parserWrapper.parse(lastStatement);
}

return completionCoreCompletion(parsingResult, dbSchema);
return completionCoreCompletion(
parsingResult,
dbSchema,
parserWrapper.enableConsoleCommands,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
CompletionItemKind,
} from 'vscode-languageserver-types';
import { DbSchema } from '../dbSchema';
import CypherLexer from '../generated-parser/CypherLexer';
import CypherLexer from '../generated-parser/CypherCmdLexer';
import CypherParser, {
Expression2Context,
} from '../generated-parser/CypherParser';
} from '../generated-parser/CypherCmdParser';
import { rulesDefiningVariables } from '../helpers';
import {
CypherTokenType,
Expand Down Expand Up @@ -125,17 +125,27 @@ const namespacedCompletion = (
}
};

function getTokenCandidates(
function getTokenCompletions(
candidates: CandidatesCollection,
ignoredTokens: Set<number>,
) {
): CompletionItem[] {
const tokenEntries = candidates.tokens.entries();

const tokenCandidates = Array.from(tokenEntries).flatMap((value) => {
const completions = Array.from(tokenEntries).flatMap((value) => {
const [tokenNumber, followUpList] = value;

if (!ignoredTokens.has(tokenNumber)) {
const firstToken = tokenNames[tokenNumber];
const isConsoleCommand =
lexerSymbols[tokenNumber] === CypherTokenType.consoleCommand;

const kind = isConsoleCommand
? CompletionItemKind.Event
: CompletionItemKind.Keyword;

const firstToken = isConsoleCommand
? tokenNames[tokenNumber].toLowerCase()
: tokenNames[tokenNumber];

const followUpIndexes = followUpList.indexes;
const firstIgnoredToken = followUpIndexes.findIndex((t) =>
ignoredTokens.has(t),
Expand All @@ -151,21 +161,28 @@ function getTokenCandidates(
if (firstToken === undefined) {
return [];
} else if (followUpString === '') {
return [firstToken];
return [{ label: firstToken, kind }];
} else {
const followUp = firstToken + ' ' + followUpString;
const followUp =
firstToken +
' ' +
(isConsoleCommand ? followUpString.toLowerCase() : followUpString);

if (followUpList.optional) {
return [firstToken, followUp];
return [
{ label: firstToken, kind },
{ label: followUp, kind },
];
}

return [followUp];
return [{ label: followUp, kind }];
}
} else {
return [];
}
});

return tokenCandidates;
return completions;
}

const parameterCompletions = (
Expand Down Expand Up @@ -265,6 +282,7 @@ function calculateNamespacePrefix(
export function completionCoreCompletion(
parsingResult: EnrichedParsingResult,
dbSchema: DbSchema,
enableConsoleCommands: boolean,
): CompletionItem[] {
const parser = parsingResult.parser;
const tokens = parsingResult.tokens;
Expand Down Expand Up @@ -306,6 +324,15 @@ export function completionCoreCompletion(
CypherParser.RULE_propertyKeyName,
CypherParser.RULE_variable,

// Either enable the helper rules for lexer clashes,
// or collect all console commands like below with symbolicNameString
...(enableConsoleCommands
? [
CypherParser.RULE_useCompletionRule,
CypherParser.RULE_listCompletionRule,
]
: [CypherParser.RULE_consoleCommand]),

// Because of the overlap of keywords and identifiers in cypher
// We will suggest keywords when users type identifiers as well
// To avoid this we want custom completion for identifiers
Expand All @@ -317,7 +344,11 @@ export function completionCoreCompletion(
// Keep only keywords as suggestions
const ignoredTokens = new Set<number>(
Object.entries(lexerSymbols)
.filter(([, type]) => type !== CypherTokenType.keyword)
.filter(
([, type]) =>
type !== CypherTokenType.keyword &&
type !== CypherTokenType.consoleCommand,
)
.map(([token]) => Number(token)),
);

Expand Down Expand Up @@ -427,17 +458,24 @@ export function completionCoreCompletion(
];
}
}

// These are simple tokens that get completed as the wrong kind, due to a lexer conlfict
if (ruleNumber === CypherParser.RULE_useCompletionRule) {
return [{ label: 'use', kind: CompletionItemKind.Event }];
}

if (ruleNumber === CypherParser.RULE_listCompletionRule) {
return [{ label: 'list', kind: CompletionItemKind.Event }];
}

return [];
},
);

const tokenCandidates = getTokenCandidates(candidates, ignoredTokens);
const tokenCompletions: CompletionItem[] = tokenCandidates.map((t) => ({
label: t,
kind: CompletionItemKind.Keyword,
}));

return [...ruleCompletions, ...tokenCompletions];
return [
...ruleCompletions,
...getTokenCompletions(candidates, ignoredTokens),
];
}

type CompletionHelperArgs = {
Expand Down
8 changes: 4 additions & 4 deletions packages/language-support/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import antlrDefaultExport, {
ParserRuleContext,
Token,
} from 'antlr4';
import CypherLexer from './generated-parser/CypherLexer';
import CypherLexer from './generated-parser/CypherCmdLexer';
import CypherParser, {
NodePatternContext,
RelationshipPatternContext,
StatementsContext,
} from './generated-parser/CypherParser';
StatementsOrCommandsContext,
} from './generated-parser/CypherCmdParser';
import { ParsingResult } from './parserWrapper';

export function findStopNode(root: StatementsContext) {
export function findStopNode(root: StatementsOrCommandsContext) {
let children = root.children;
let current: ParserRuleContext = root;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
AnyExpressionContext,
ArrowLineContext,
BooleanLiteralContext,
ConsoleCommandContext,
FunctionNameContext,
KeywordLiteralContext,
LabelNameContext,
Expand All @@ -14,6 +15,7 @@ import {
NoneExpressionContext,
NumberLiteralContext,
ParameterContext,
ParamsArgsContext,
ProcedureNameContext,
ProcedureResultItemContext,
PropertyKeyNameContext,
Expand All @@ -23,14 +25,15 @@ import {
StringsLiteralContext,
StringTokenContext,
SymbolicNameStringContext,
UseCompletionRuleContext,
VariableContext,
} from '../../generated-parser/CypherParser';
} from '../../generated-parser/CypherCmdParser';

import {
SemanticTokensLegend,
SemanticTokenTypes,
} from 'vscode-languageserver-types';
import CypherParserListener from '../../generated-parser/CypherParserListener';
import CypherParserListener from '../../generated-parser/CypherCmdParserListener';
import { CypherTokenType } from '../../lexerSymbols';
import { parserWrapper } from '../../parserWrapper';
import {
Expand Down Expand Up @@ -74,6 +77,7 @@ export function mapCypherToSemanticTokenIndex(
[CypherTokenType.label]: SemanticTokenTypes.type,
[CypherTokenType.variable]: SemanticTokenTypes.variable,
[CypherTokenType.symbolicName]: SemanticTokenTypes.variable,
[CypherTokenType.consoleCommand]: SemanticTokenTypes.macro,
};

const semanticTokenType = tokenMappings[cypherTokenType];
Expand Down Expand Up @@ -229,6 +233,43 @@ class SyntaxHighlighter extends CypherParserListener {
exitSymbolicNameString = (ctx: SymbolicNameStringContext) => {
this.addToken(ctx.start, CypherTokenType.symbolicName, ctx.getText());
};

// Fix coloring of colon in console commands (operator -> consoleCommand)
exitConsoleCommand = (ctx: ConsoleCommandContext) => {
const colon = ctx.COLON();
this.addToken(
colon.symbol,
CypherTokenType.consoleCommand,
colon.getText(),
);
};

// console commands that clash with cypher keywords
exitUseCompletionRule = (ctx: UseCompletionRuleContext) => {
const use = ctx.USE();

this.addToken(use.symbol, CypherTokenType.consoleCommand, use.getText());
};

exitParamsArgs = (ctx: ParamsArgsContext) => {
const clear = ctx.CLEAR();
if (clear) {
this.addToken(
clear.symbol,
CypherTokenType.consoleCommand,
clear.getText(),
);
}

const list = ctx.listCompletionRule()?.LIST();
if (list) {
this.addToken(
list.symbol,
CypherTokenType.consoleCommand,
list.getText(),
);
}
};
}

function colourLexerTokens(tokens: Token[]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SemanticTokenTypes } from 'vscode-languageserver-types';

import { Token } from 'antlr4';

import CypherLexer from '../../generated-parser/CypherLexer';
import CypherLexer from '../../generated-parser/CypherCmdLexer';

import { CypherTokenType, lexerSymbols } from '../../lexerSymbols';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Token } from 'antlr4';
import type { ParserRuleContext } from 'antlr4-c3';
import { CodeCompletionCore } from 'antlr4-c3';
import { distance } from 'fastest-levenshtein';
import CypherLexer from '../../generated-parser/CypherLexer';
import CypherParser from '../../generated-parser/CypherParser';
import CypherLexer from '../../generated-parser/CypherCmdLexer';
import CypherParser from '../../generated-parser/CypherCmdParser';
import { keywordNames, tokenNames } from '../../lexerSymbols';

/*
Expand Down Expand Up @@ -64,6 +64,12 @@ export function completionCoreErrormessage(
const tokenCandidates = Array.from(tokenEntries).flatMap(([tokenNumber]) => {
const tokenName = tokenNames[tokenNumber];

// We don't want to suggest the ":" of console cmds as it's not helpful even
// when console cmds are available
if (caretIndex === 0 && tokenNumber === CypherLexer.COLON) {
return [];
}

switch (tokenNumber) {
case CypherLexer.DECIMAL_DOUBLE:
humanReadableRulename.push('a decimal double');
Expand Down
Loading
Loading