Comparing version 2.1.1 to 2.1.2
@@ -10,3 +10,3 @@ /****************************************************************************** | ||
export * from './template-node.js'; | ||
export { expandToString, expandToStringWithNL, normalizeEOL } from './template-string.js'; | ||
export { expandToString, expandToStringLF, expandToStringLFWithNL, expandToStringWithNL, normalizeEOL } from './template-string.js'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -9,3 +9,3 @@ /****************************************************************************** | ||
export * from './template-node.js'; | ||
export { expandToString, expandToStringWithNL, normalizeEOL } from './template-string.js'; | ||
export { expandToString, expandToStringLF, expandToStringLFWithNL, expandToStringWithNL, normalizeEOL } from './template-string.js'; | ||
//# sourceMappingURL=index.js.map |
@@ -7,4 +7,6 @@ /****************************************************************************** | ||
export declare function expandToStringWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string; | ||
export declare function expandToStringLFWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string; | ||
/** | ||
* A tag function that automatically aligns embedded multiline strings. | ||
* Multiple lines are joined with the platform-specific line separator. | ||
* | ||
@@ -16,2 +18,11 @@ * @param staticParts the static parts of a tagged template literal | ||
export declare function expandToString(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string; | ||
/** | ||
* A tag function that automatically aligns embedded multiline strings. | ||
* Multiple lines are joined with the LINE_FEED (`\n`) line separator. | ||
* | ||
* @param staticParts the static parts of a tagged template literal | ||
* @param substitutions the variable parts of a tagged template literal | ||
* @returns an aligned string that consists of the given parts | ||
*/ | ||
export declare function expandToStringLF(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string; | ||
export declare const SNLE: string; | ||
@@ -18,0 +29,0 @@ export declare const NEWLINE_REGEXP: RegExp; |
@@ -10,4 +10,8 @@ /****************************************************************************** | ||
} | ||
export function expandToStringLFWithNL(staticParts, ...substitutions) { | ||
return expandToStringLF(staticParts, ...substitutions) + '\n'; | ||
} | ||
/** | ||
* A tag function that automatically aligns embedded multiline strings. | ||
* Multiple lines are joined with the platform-specific line separator. | ||
* | ||
@@ -19,2 +23,16 @@ * @param staticParts the static parts of a tagged template literal | ||
export function expandToString(staticParts, ...substitutions) { | ||
return internalExpandToString(EOL, staticParts, ...substitutions); | ||
} | ||
/** | ||
* A tag function that automatically aligns embedded multiline strings. | ||
* Multiple lines are joined with the LINE_FEED (`\n`) line separator. | ||
* | ||
* @param staticParts the static parts of a tagged template literal | ||
* @param substitutions the variable parts of a tagged template literal | ||
* @returns an aligned string that consists of the given parts | ||
*/ | ||
export function expandToStringLF(staticParts, ...substitutions) { | ||
return internalExpandToString('\n', staticParts, ...substitutions); | ||
} | ||
function internalExpandToString(lineSep, staticParts, ...substitutions) { | ||
let lines = substitutions | ||
@@ -49,5 +67,5 @@ // align substitutions and fuse them with static parts | ||
// shifts lines to the left | ||
.map(line => line.slice(indent).trimRight()) | ||
.map(line => line.slice(indent).trimEnd()) | ||
// convert lines to string | ||
.join(EOL); | ||
.join(lineSep); | ||
} | ||
@@ -54,0 +72,0 @@ export const SNLE = Object.freeze('__«SKIP^NEW^LINE^IF^EMPTY»__'); |
@@ -119,2 +119,3 @@ /****************************************************************************** | ||
protected buildContexts(document: LangiumDocument, position: Position): IterableIterator<CompletionContext>; | ||
protected performNextTokenCompletion(document: LangiumDocument, text: string, _offset: number, _end: number): boolean; | ||
protected findDataTypeRuleStart(cst: CstNode, offset: number): [number, number] | undefined; | ||
@@ -121,0 +122,0 @@ /** |
@@ -10,3 +10,3 @@ /****************************************************************************** | ||
import { assignMandatoryAstProperties, getContainerOfType } from '../../utils/ast-util.js'; | ||
import { findDeclarationNodeAtOffset, findLeafNodeAtOffset } from '../../utils/cst-util.js'; | ||
import { findDeclarationNodeAtOffset, findLeafNodeBeforeOffset } from '../../utils/cst-util.js'; | ||
import { getEntryRule } from '../../utils/grammar-util.js'; | ||
@@ -105,3 +105,3 @@ import { stream } from '../../utils/stream.js'; | ||
*buildContexts(document, position) { | ||
var _a, _b, _c, _d, _e; | ||
var _a, _b; | ||
const cst = document.parseResult.value.$cstNode; | ||
@@ -125,3 +125,3 @@ if (!cst) { | ||
const [ruleStart, ruleEnd] = dataTypeRuleOffsets; | ||
const parentNode = (_a = findLeafNodeAtOffset(cst, ruleStart)) === null || _a === void 0 ? void 0 : _a.astNode; | ||
const parentNode = (_a = findLeafNodeBeforeOffset(cst, ruleStart)) === null || _a === void 0 ? void 0 : _a.astNode; | ||
yield Object.assign(Object.assign({}, partialContext), { node: parentNode, tokenOffset: ruleStart, tokenEndOffset: ruleEnd, features: this.findFeaturesAt(textDocument, ruleStart) }); | ||
@@ -131,12 +131,21 @@ } | ||
const { nextTokenStart, nextTokenEnd, previousTokenStart, previousTokenEnd } = this.backtrackToAnyToken(text, offset); | ||
let astNode; | ||
let astNodeOffset = nextTokenStart; | ||
if (offset <= nextTokenStart && previousTokenStart !== undefined) { | ||
// This check indicates that the cursor is still before the next token, so we should use the previous AST node (if it exists) | ||
astNodeOffset = previousTokenStart; | ||
} | ||
const astNode = (_b = findLeafNodeBeforeOffset(cst, astNodeOffset)) === null || _b === void 0 ? void 0 : _b.astNode; | ||
let performNextCompletion = true; | ||
if (previousTokenStart !== undefined && previousTokenEnd !== undefined && previousTokenEnd === offset) { | ||
astNode = (_b = findLeafNodeAtOffset(cst, previousTokenStart)) === null || _b === void 0 ? void 0 : _b.astNode; | ||
// This context aims to complete the current feature | ||
yield Object.assign(Object.assign({}, partialContext), { node: astNode, tokenOffset: previousTokenStart, tokenEndOffset: previousTokenEnd, features: this.findFeaturesAt(textDocument, previousTokenStart) }); | ||
// This context aims to complete the immediate next feature (if one exists at the current cursor position) | ||
// It uses the previous AST node for that. | ||
yield Object.assign(Object.assign({}, partialContext), { node: astNode, tokenOffset: previousTokenEnd, tokenEndOffset: previousTokenEnd, features: this.findFeaturesAt(textDocument, previousTokenEnd) }); | ||
// The completion after the current token should be prevented in case we find out that the current token definitely isn't completed yet | ||
// This is usually the case when the current token ends on a letter. | ||
performNextCompletion = this.performNextTokenCompletion(document, text.substring(previousTokenStart, previousTokenEnd), previousTokenStart, previousTokenEnd); | ||
if (performNextCompletion) { | ||
// This context aims to complete the immediate next feature (if one exists at the current cursor position) | ||
// It uses the previous cst start/offset for that. | ||
yield Object.assign(Object.assign({}, partialContext), { node: astNode, tokenOffset: previousTokenEnd, tokenEndOffset: previousTokenEnd, features: this.findFeaturesAt(textDocument, previousTokenEnd) }); | ||
} | ||
} | ||
astNode = (_d = (_c = findLeafNodeAtOffset(cst, nextTokenStart)) === null || _c === void 0 ? void 0 : _c.astNode) !== null && _d !== void 0 ? _d : (previousTokenStart === undefined ? undefined : (_e = findLeafNodeAtOffset(cst, previousTokenStart)) === null || _e === void 0 ? void 0 : _e.astNode); | ||
if (!astNode) { | ||
@@ -150,7 +159,14 @@ const parserRule = getEntryRule(this.grammar); | ||
} | ||
else { | ||
// This context aims to complete the next feature, using the next ast node | ||
else if (performNextCompletion) { | ||
// This context aims to complete the next feature, using the next cst start/end | ||
yield Object.assign(Object.assign({}, partialContext), { node: astNode, tokenOffset: nextTokenStart, tokenEndOffset: nextTokenEnd, features: this.findFeaturesAt(textDocument, nextTokenStart) }); | ||
} | ||
} | ||
performNextTokenCompletion(document, text, _offset, _end) { | ||
// This regex returns false if the text ends with a letter. | ||
// We don't want to complete new text immediately after a keyword, ID etc. | ||
// We only care about the last character in the text, so we use $ here. | ||
// The \P{L} used here is a Unicode category that matches any character that is not a letter | ||
return /\P{L}$/u.test(text); | ||
} | ||
findDataTypeRuleStart(cst, offset) { | ||
@@ -309,3 +325,3 @@ var _a, _b; | ||
// Filter out keywords that do not contain any word character | ||
return keyword.value.match(/[\w]/) !== null; | ||
return /\p{L}/u.test(keyword.value); | ||
} | ||
@@ -312,0 +328,0 @@ fillCompletionItem(context, item) { |
@@ -6,6 +6,6 @@ /****************************************************************************** | ||
******************************************************************************/ | ||
import { Lexer, EOF } from 'chevrotain'; | ||
import { isKeyword, isParserRule, isTerminalRule, isEndOfFile } from '../grammar/generated/ast.js'; | ||
import { Lexer } from 'chevrotain'; | ||
import { isKeyword, isParserRule, isTerminalRule } from '../grammar/generated/ast.js'; | ||
import { terminalRegex } from '../grammar/internal-grammar-util.js'; | ||
import { streamAllContents, streamAst } from '../utils/ast-util.js'; | ||
import { streamAllContents } from '../utils/ast-util.js'; | ||
import { getAllReachableRules } from '../utils/grammar-util.js'; | ||
@@ -28,6 +28,4 @@ import { getCaseInsensitivePattern, isWhitespaceRegExp, partialMatches } from '../utils/regex-util.js'; | ||
}); | ||
//reminder: EOF should always be the last token, because it is very unlikely that it will be matched within the input | ||
if (reachableRules.some(r => streamAst(r.definition).some(isEndOfFile))) { | ||
tokens.push(EOF); | ||
} | ||
// We don't need to add the EOF token explicitly. | ||
// It is automatically available at the end of the token stream. | ||
return tokens; | ||
@@ -34,0 +32,0 @@ } |
@@ -46,3 +46,24 @@ /****************************************************************************** | ||
export declare function isCommentNode(cstNode: CstNode, commentNames: string[]): boolean; | ||
/** | ||
* Finds the leaf CST node at the specified 0-based string offset. | ||
* Note that the given offset will be within the range of the returned leaf node. | ||
* | ||
* If the offset does not point to a CST node (but just white space), this method will return `undefined`. | ||
* | ||
* @param node The CST node to search through. | ||
* @param offset The specified offset. | ||
* @returns The CST node at the specified offset. | ||
*/ | ||
export declare function findLeafNodeAtOffset(node: CstNode, offset: number): LeafCstNode | undefined; | ||
/** | ||
* Finds the leaf CST node at the specified 0-based string offset. | ||
* If no CST node exists at the specified position, it will return the leaf node before it. | ||
* | ||
* If there is no leaf node before the specified offset, this method will return `undefined`. | ||
* | ||
* @param node The CST node to search through. | ||
* @param offset The specified offset. | ||
* @returns The CST node closest to the specified offset. | ||
*/ | ||
export declare function findLeafNodeBeforeOffset(node: CstNode, offset: number): LeafCstNode | undefined; | ||
export declare function getPreviousNode(node: CstNode, hidden?: boolean): CstNode | undefined; | ||
@@ -49,0 +70,0 @@ export declare function getNextNode(node: CstNode, hidden?: boolean): CstNode | undefined; |
@@ -142,2 +142,12 @@ /****************************************************************************** | ||
} | ||
/** | ||
* Finds the leaf CST node at the specified 0-based string offset. | ||
* Note that the given offset will be within the range of the returned leaf node. | ||
* | ||
* If the offset does not point to a CST node (but just white space), this method will return `undefined`. | ||
* | ||
* @param node The CST node to search through. | ||
* @param offset The specified offset. | ||
* @returns The CST node at the specified offset. | ||
*/ | ||
export function findLeafNodeAtOffset(node, offset) { | ||
@@ -148,19 +158,27 @@ if (isLeafCstNode(node)) { | ||
else if (isCompositeCstNode(node)) { | ||
let firstChild = 0; | ||
let lastChild = node.content.length - 1; | ||
while (firstChild < lastChild) { | ||
const middleChild = Math.floor((firstChild + lastChild) / 2); | ||
const n = node.content[middleChild]; | ||
if (n.offset > offset) { | ||
lastChild = middleChild - 1; | ||
} | ||
else if (n.end <= offset) { | ||
firstChild = middleChild + 1; | ||
} | ||
else { | ||
return findLeafNodeAtOffset(n, offset); | ||
} | ||
const searchResult = binarySearch(node, offset, false); | ||
if (searchResult) { | ||
return findLeafNodeAtOffset(searchResult, offset); | ||
} | ||
if (firstChild === lastChild) { | ||
return findLeafNodeAtOffset(node.content[firstChild], offset); | ||
} | ||
return undefined; | ||
} | ||
/** | ||
* Finds the leaf CST node at the specified 0-based string offset. | ||
* If no CST node exists at the specified position, it will return the leaf node before it. | ||
* | ||
* If there is no leaf node before the specified offset, this method will return `undefined`. | ||
* | ||
* @param node The CST node to search through. | ||
* @param offset The specified offset. | ||
* @returns The CST node closest to the specified offset. | ||
*/ | ||
export function findLeafNodeBeforeOffset(node, offset) { | ||
if (isLeafCstNode(node)) { | ||
return node; | ||
} | ||
else if (isCompositeCstNode(node)) { | ||
const searchResult = binarySearch(node, offset, true); | ||
if (searchResult) { | ||
return findLeafNodeBeforeOffset(searchResult, offset); | ||
} | ||
@@ -170,2 +188,25 @@ } | ||
} | ||
function binarySearch(node, offset, closest) { | ||
let left = 0; | ||
let right = node.content.length - 1; | ||
let closestNode = undefined; | ||
while (left <= right) { | ||
const middle = Math.floor((left + right) / 2); | ||
const middleNode = node.content[middle]; | ||
if (middleNode.offset <= offset && middleNode.end > offset) { | ||
// Found an exact match | ||
return middleNode; | ||
} | ||
if (middleNode.end <= offset) { | ||
// Update the closest node (less than offset) and move to the right half | ||
closestNode = closest ? middleNode : undefined; | ||
left = middle + 1; | ||
} | ||
else { | ||
// Move to the left half | ||
right = middle - 1; | ||
} | ||
} | ||
return closestNode; | ||
} | ||
export function getPreviousNode(node, hidden = true) { | ||
@@ -172,0 +213,0 @@ while (node.container) { |
{ | ||
"name": "langium", | ||
"version": "2.1.1", | ||
"version": "2.1.2", | ||
"description": "A language engineering tool for the Language Server Protocol", | ||
@@ -55,2 +55,3 @@ "homepage": "https://langium.org", | ||
"lint": "eslint src test --ext .ts", | ||
"validate-exports": "tsc -p test/tsconfig.export-main.json", | ||
"langium:generate": "langium generate", | ||
@@ -57,0 +58,0 @@ "langium:generate:production": "langium generate --mode=production", |
@@ -11,2 +11,3 @@ /****************************************************************************** | ||
export * from './template-node.js'; | ||
export { expandToString, expandToStringWithNL, normalizeEOL } from './template-string.js'; | ||
export { expandToString, expandToStringLF, expandToStringLFWithNL, expandToStringWithNL, normalizeEOL } from './template-string.js'; | ||
@@ -13,4 +13,9 @@ /****************************************************************************** | ||
export function expandToStringLFWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { | ||
return expandToStringLF(staticParts, ...substitutions) + '\n'; | ||
} | ||
/** | ||
* A tag function that automatically aligns embedded multiline strings. | ||
* Multiple lines are joined with the platform-specific line separator. | ||
* | ||
@@ -22,2 +27,18 @@ * @param staticParts the static parts of a tagged template literal | ||
export function expandToString(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { | ||
return internalExpandToString(EOL, staticParts, ...substitutions); | ||
} | ||
/** | ||
* A tag function that automatically aligns embedded multiline strings. | ||
* Multiple lines are joined with the LINE_FEED (`\n`) line separator. | ||
* | ||
* @param staticParts the static parts of a tagged template literal | ||
* @param substitutions the variable parts of a tagged template literal | ||
* @returns an aligned string that consists of the given parts | ||
*/ | ||
export function expandToStringLF(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { | ||
return internalExpandToString('\n', staticParts, ...substitutions); | ||
} | ||
function internalExpandToString(lineSep: string, staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { | ||
let lines = substitutions | ||
@@ -56,5 +77,5 @@ // align substitutions and fuse them with static parts | ||
// shifts lines to the left | ||
.map(line => line.slice(indent).trimRight()) | ||
.map(line => line.slice(indent).trimEnd()) | ||
// convert lines to string | ||
.join(EOL); | ||
.join(lineSep); | ||
} | ||
@@ -82,2 +103,2 @@ | ||
return input.replace(NEWLINE_REGEXP, EOL); | ||
} | ||
} |
@@ -26,3 +26,3 @@ /****************************************************************************** | ||
import { assignMandatoryAstProperties, getContainerOfType } from '../../utils/ast-util.js'; | ||
import { findDeclarationNodeAtOffset, findLeafNodeAtOffset } from '../../utils/cst-util.js'; | ||
import { findDeclarationNodeAtOffset, findLeafNodeBeforeOffset } from '../../utils/cst-util.js'; | ||
import { getEntryRule } from '../../utils/grammar-util.js'; | ||
@@ -241,3 +241,3 @@ import { stream } from '../../utils/stream.js'; | ||
const [ruleStart, ruleEnd] = dataTypeRuleOffsets; | ||
const parentNode = findLeafNodeAtOffset(cst, ruleStart)?.astNode; | ||
const parentNode = findLeafNodeBeforeOffset(cst, ruleStart)?.astNode; | ||
yield { | ||
@@ -253,5 +253,10 @@ ...partialContext, | ||
const { nextTokenStart, nextTokenEnd, previousTokenStart, previousTokenEnd } = this.backtrackToAnyToken(text, offset); | ||
let astNode: AstNode | undefined; | ||
let astNodeOffset = nextTokenStart; | ||
if (offset <= nextTokenStart && previousTokenStart !== undefined) { | ||
// This check indicates that the cursor is still before the next token, so we should use the previous AST node (if it exists) | ||
astNodeOffset = previousTokenStart; | ||
} | ||
const astNode = findLeafNodeBeforeOffset(cst, astNodeOffset)?.astNode; | ||
let performNextCompletion = true; | ||
if (previousTokenStart !== undefined && previousTokenEnd !== undefined && previousTokenEnd === offset) { | ||
astNode = findLeafNodeAtOffset(cst, previousTokenStart)?.astNode; | ||
// This context aims to complete the current feature | ||
@@ -265,14 +270,22 @@ yield { | ||
}; | ||
// This context aims to complete the immediate next feature (if one exists at the current cursor position) | ||
// It uses the previous AST node for that. | ||
yield { | ||
...partialContext, | ||
node: astNode, | ||
tokenOffset: previousTokenEnd, | ||
tokenEndOffset: previousTokenEnd, | ||
features: this.findFeaturesAt(textDocument, previousTokenEnd), | ||
}; | ||
// The completion after the current token should be prevented in case we find out that the current token definitely isn't completed yet | ||
// This is usually the case when the current token ends on a letter. | ||
performNextCompletion = this.performNextTokenCompletion( | ||
document, | ||
text.substring(previousTokenStart, previousTokenEnd), | ||
previousTokenStart, | ||
previousTokenEnd | ||
); | ||
if (performNextCompletion) { | ||
// This context aims to complete the immediate next feature (if one exists at the current cursor position) | ||
// It uses the previous cst start/offset for that. | ||
yield { | ||
...partialContext, | ||
node: astNode, | ||
tokenOffset: previousTokenEnd, | ||
tokenEndOffset: previousTokenEnd, | ||
features: this.findFeaturesAt(textDocument, previousTokenEnd), | ||
}; | ||
} | ||
} | ||
astNode = findLeafNodeAtOffset(cst, nextTokenStart)?.astNode | ||
?? (previousTokenStart === undefined ? undefined : findLeafNodeAtOffset(cst, previousTokenStart)?.astNode); | ||
@@ -291,4 +304,4 @@ if (!astNode) { | ||
}; | ||
} else { | ||
// This context aims to complete the next feature, using the next ast node | ||
} else if (performNextCompletion) { | ||
// This context aims to complete the next feature, using the next cst start/end | ||
yield { | ||
@@ -304,2 +317,10 @@ ...partialContext, | ||
protected performNextTokenCompletion(document: LangiumDocument, text: string, _offset: number, _end: number): boolean { | ||
// This regex returns false if the text ends with a letter. | ||
// We don't want to complete new text immediately after a keyword, ID etc. | ||
// We only care about the last character in the text, so we use $ here. | ||
// The \P{L} used here is a Unicode category that matches any character that is not a letter | ||
return /\P{L}$/u.test(text); | ||
} | ||
protected findDataTypeRuleStart(cst: CstNode, offset: number): [number, number] | undefined { | ||
@@ -463,3 +484,3 @@ let containerNode: CstNode | undefined = findDeclarationNodeAtOffset(cst, offset, this.grammarConfig.nameRegexp); | ||
// Filter out keywords that do not contain any word character | ||
return keyword.value.match(/[\w]/) !== null; | ||
return /\p{L}/u.test(keyword.value); | ||
} | ||
@@ -466,0 +487,0 @@ |
@@ -10,6 +10,6 @@ /****************************************************************************** | ||
import type { Stream } from '../utils/stream.js'; | ||
import { Lexer, EOF } from 'chevrotain'; | ||
import { isKeyword, isParserRule, isTerminalRule, isEndOfFile } from '../grammar/generated/ast.js'; | ||
import { Lexer } from 'chevrotain'; | ||
import { isKeyword, isParserRule, isTerminalRule } from '../grammar/generated/ast.js'; | ||
import { terminalRegex } from '../grammar/internal-grammar-util.js'; | ||
import { streamAllContents, streamAst } from '../utils/ast-util.js'; | ||
import { streamAllContents } from '../utils/ast-util.js'; | ||
import { getAllReachableRules } from '../utils/grammar-util.js'; | ||
@@ -42,7 +42,4 @@ import { getCaseInsensitivePattern, isWhitespaceRegExp, partialMatches } from '../utils/regex-util.js'; | ||
}); | ||
//reminder: EOF should always be the last token, because it is very unlikely that it will be matched within the input | ||
if (reachableRules.some(r => streamAst(r.definition).some(isEndOfFile))) { | ||
tokens.push(EOF); | ||
} | ||
// We don't need to add the EOF token explicitly. | ||
// It is automatically available at the end of the token stream. | ||
return tokens; | ||
@@ -49,0 +46,0 @@ } |
@@ -158,2 +158,12 @@ /****************************************************************************** | ||
/** | ||
* Finds the leaf CST node at the specified 0-based string offset. | ||
* Note that the given offset will be within the range of the returned leaf node. | ||
* | ||
* If the offset does not point to a CST node (but just white space), this method will return `undefined`. | ||
* | ||
* @param node The CST node to search through. | ||
* @param offset The specified offset. | ||
* @returns The CST node at the specified offset. | ||
*/ | ||
export function findLeafNodeAtOffset(node: CstNode, offset: number): LeafCstNode | undefined { | ||
@@ -163,17 +173,27 @@ if (isLeafCstNode(node)) { | ||
} else if (isCompositeCstNode(node)) { | ||
let firstChild = 0; | ||
let lastChild = node.content.length - 1; | ||
while (firstChild < lastChild) { | ||
const middleChild = Math.floor((firstChild + lastChild) / 2); | ||
const n = node.content[middleChild]; | ||
if (n.offset > offset) { | ||
lastChild = middleChild - 1; | ||
} else if (n.end <= offset) { | ||
firstChild = middleChild + 1; | ||
} else { | ||
return findLeafNodeAtOffset(n, offset); | ||
} | ||
const searchResult = binarySearch(node, offset, false); | ||
if (searchResult) { | ||
return findLeafNodeAtOffset(searchResult, offset); | ||
} | ||
if (firstChild === lastChild) { | ||
return findLeafNodeAtOffset(node.content[firstChild], offset); | ||
} | ||
return undefined; | ||
} | ||
/** | ||
* Finds the leaf CST node at the specified 0-based string offset. | ||
* If no CST node exists at the specified position, it will return the leaf node before it. | ||
* | ||
* If there is no leaf node before the specified offset, this method will return `undefined`. | ||
* | ||
* @param node The CST node to search through. | ||
* @param offset The specified offset. | ||
* @returns The CST node closest to the specified offset. | ||
*/ | ||
export function findLeafNodeBeforeOffset(node: CstNode, offset: number): LeafCstNode | undefined { | ||
if (isLeafCstNode(node)) { | ||
return node; | ||
} else if (isCompositeCstNode(node)) { | ||
const searchResult = binarySearch(node, offset, true); | ||
if (searchResult) { | ||
return findLeafNodeBeforeOffset(searchResult, offset); | ||
} | ||
@@ -184,2 +204,29 @@ } | ||
function binarySearch(node: CompositeCstNode, offset: number, closest: boolean): CstNode | undefined { | ||
let left = 0; | ||
let right = node.content.length - 1; | ||
let closestNode: CstNode | undefined = undefined; | ||
while (left <= right) { | ||
const middle = Math.floor((left + right) / 2); | ||
const middleNode = node.content[middle]; | ||
if (middleNode.offset <= offset && middleNode.end > offset) { | ||
// Found an exact match | ||
return middleNode; | ||
} | ||
if (middleNode.end <= offset) { | ||
// Update the closest node (less than offset) and move to the right half | ||
closestNode = closest ? middleNode : undefined; | ||
left = middle + 1; | ||
} else { | ||
// Move to the left half | ||
right = middle - 1; | ||
} | ||
} | ||
return closestNode; | ||
} | ||
export function getPreviousNode(node: CstNode, hidden = true): CstNode | undefined { | ||
@@ -186,0 +233,0 @@ while (node.container) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 8 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 8 instances in 1 package
2872469
44743