UNPKG

@neo4j-cypher/language-support

Version:
217 lines (182 loc) 5.59 kB
import { SemanticTokenTypes } from 'vscode-languageserver-types'; import { Token } from 'antlr4'; import CypherLexer from '../generated-parser/CypherCmdLexer'; import { isCommentOpener } from '../helpers'; import { CypherTokenType, lexerSymbols } from '../lexerSymbols'; export interface TokenPosition { line: number; startCharacter: number; startOffset: number; } export function tokenPositionToString(tokenPosition: TokenPosition): string { return `${tokenPosition.line},${tokenPosition.startCharacter}`; } export interface ParsedCypherToken { position: TokenPosition; length: number; tokenType: CypherTokenType; token: string; bracketInfo?: BracketInfo; } export interface BracketInfo { bracketType: BracketType; bracketLevel: number; } export enum BracketType { bracket = 'bracket', parenthesis = 'parenthesis', curly = 'curly', } export interface ColouredToken { position: TokenPosition; length: number; tokenColour: SemanticTokenTypes; token: string; } export function getTokenPosition(token: Token): TokenPosition { return { line: token.line - 1, startCharacter: token.column, startOffset: token.start, }; } function getBracketType(token: Token): BracketType | undefined { const bracketType: Record<number, BracketType> = { [CypherLexer.LPAREN]: BracketType.parenthesis, [CypherLexer.RPAREN]: BracketType.parenthesis, [CypherLexer.LBRACKET]: BracketType.bracket, [CypherLexer.RBRACKET]: BracketType.bracket, [CypherLexer.LCURLY]: BracketType.curly, [CypherLexer.RCURLY]: BracketType.curly, }; return bracketType[token.type]; } function isClosingBracket(token: Token): boolean { return [ CypherLexer.RPAREN, CypherLexer.RBRACKET, CypherLexer.RCURLY, ].includes(token.type); } function isOpeningBracket(token: Token): boolean { return [ CypherLexer.LPAREN, CypherLexer.LBRACKET, CypherLexer.LCURLY, ].includes(token.type); } function computeBracketInfo( token: Token, bracketsLevel?: Map<BracketType, number>, ): BracketInfo | undefined { let bracketInfo: BracketInfo | undefined = undefined; if (bracketsLevel) { const bracketType = getBracketType(token); if (bracketType !== undefined) { let bracketLevel = bracketsLevel.get(bracketType) ?? 0; if (isOpeningBracket(token)) { bracketsLevel.set(bracketType, ++bracketLevel); } bracketInfo = { bracketLevel: bracketLevel, bracketType: bracketType, }; if (isClosingBracket(token)) { bracketsLevel.set(bracketType, --bracketLevel); } } } return bracketInfo; } export function toParsedTokens( tokenPosition: TokenPosition, tokenType: CypherTokenType, tokenStr: string, token: Token, bracketsLevel?: Map<BracketType, number>, ): ParsedCypherToken[] { let prevLen = 0; return tokenStr.split('\n').flatMap((tokenChunk, i) => { const position = i == 0 ? tokenPosition : { line: tokenPosition.line + i, startCharacter: 0, startOffset: token.start + prevLen, }; prevLen += tokenChunk.length + '\n'.length; // If the token is empty no need to create a token if (tokenChunk.length === 0) { return []; } const bracketInfo: BracketInfo | undefined = computeBracketInfo( token, bracketsLevel, ); return [ { position: position, length: tokenChunk.length, tokenType: tokenType, token: tokenChunk, bracketInfo: bracketInfo, }, ]; }); } export function getCypherTokenType( token: Token, nextToken: Token | undefined, ): { tokenType: CypherTokenType; finished: boolean; } { const tokenText = token.text; if (token.type === CypherLexer.ErrorChar) { if (tokenText === '"' || tokenText === "'") { return { tokenType: CypherTokenType.stringLiteral, finished: false }; } else if (tokenText.startsWith('`')) { return { tokenType: CypherTokenType.symbolicName, finished: false }; } } if (isCommentOpener(token, nextToken)) { return { tokenType: CypherTokenType.comment, finished: false }; } return { tokenType: lexerSymbols[token.type] ?? CypherTokenType.none, finished: true, }; } export function shouldAssignTokenType(token: Token): boolean { const nonEOF = token.type !== Token.EOF || token.text !== '<EOF>'; const inMainChannel = token.channel == 0; const isComment = token.type === CypherLexer.SINGLE_LINE_COMMENT || token.type === CypherLexer.MULTI_LINE_COMMENT; return nonEOF && (inMainChannel || isComment); } export function sortTokens(tokens: ParsedCypherToken[]) { return tokens.sort((a, b) => { const lineDiff = a.position.line - b.position.line; if (lineDiff !== 0) return lineDiff; return a.position.startCharacter - b.position.startCharacter; }); } // Assumes the tokens are already sorted export function removeOverlappingTokens(tokens: ParsedCypherToken[]) { const result: ParsedCypherToken[] = []; let prev: TokenPosition = { line: -1, startCharacter: -1, startOffset: -1 }; let prevEndCharacter = 0; tokens.forEach((token) => { const current = token.position; if (current.line > prev.line || current.startCharacter > prevEndCharacter) { // Add current token to the list and in further iterations // remove tokens overlapping with it result.push(token); prev = current; prevEndCharacter = prev.startCharacter + (token.token?.length ?? 0) - 1; } }); return result; }