* Generates autocomplete suggestions based on the input expression and cursor offset position.
(
expr: string,
cursorOffset: number,
context: SymbolsTable
)
| 45 | * Generates autocomplete suggestions based on the input expression and cursor offset position. |
| 46 | */ |
| 47 | public getSuggestions( |
| 48 | expr: string, |
| 49 | cursorOffset: number, |
| 50 | context: SymbolsTable |
| 51 | ): AutocompleteSuggestions { |
| 52 | if (!expr.length) { |
| 53 | return []; |
| 54 | } |
| 55 | |
| 56 | try { |
| 57 | const { result, invalidNodes } = this.#parser.parse(expr, { loose: true }); |
| 58 | |
| 59 | // Locate the node at the cursor position. |
| 60 | const nodeAtCursorFound = walk.findNodeAround(result, cursorOffset, (_type, node) => |
| 61 | isNodeAtCursor(node, cursorOffset) |
| 62 | ); |
| 63 | |
| 64 | if (!nodeAtCursorFound) { |
| 65 | // When we can't find one we might be in the boundary of the program/expression. |
| 66 | // We could possibly be in a whitespace at the end of the expression or in a situation where the parsed |
| 67 | // tree may contain 2 top level ExpressionStatement (second one returned as invalid node by the parser). |
| 68 | // |
| 69 | // In this case we want to provide operators as suggestions and refine the search of the "cursor" node to either: |
| 70 | // - using the end position of the whole expression AST (e.g white space at the very end of the expression string) |
| 71 | // - or using the second top level ExpressionStatement node found by the parser as the cursor is at the end of that node. |
| 72 | if (cursorOffset > result.end) { |
| 73 | const ast = invalidNodes.length > 0 ? invalidNodes[0]?.expression : result; |
| 74 | |
| 75 | if (!ast) { |
| 76 | return []; |
| 77 | } |
| 78 | |
| 79 | const lastNodeFound = walk.findNodeAround(ast, ast.end, (_type, node) => |
| 80 | isNodeAtCursor(node, ast.end) |
| 81 | ); |
| 82 | if (!lastNodeFound) { |
| 83 | return []; |
| 84 | } |
| 85 | const { node } = lastNodeFound; |
| 86 | |
| 87 | if (!isAnyNode(node)) { |
| 88 | throw Error(`Unexpected node type ${node.type}`); |
| 89 | } |
| 90 | |
| 91 | return this.getOperatorSuggestionsForNode(ast, node, result.end, context); |
| 92 | } |
| 93 | return []; |
| 94 | } |
| 95 | |
| 96 | const { node } = nodeAtCursorFound; |
| 97 | |
| 98 | if (!isAnyNode(node)) { |
| 99 | throw Error(`Unexpected node type ${node.type}`); |
| 100 | } |
| 101 | |
| 102 | return this.getSuggestionsForNode(result, node, expr, cursorOffset, context); |
| 103 | } catch (error) { |
| 104 | this.#logger.error('Error while computing autocomplete suggestions', error); |
no test coverage detected