|
| 1 | +import { Hover, HoverParams, Position, _Connection } from 'vscode-languageserver/node.js'; |
| 2 | +import { StateManager } from '../../services/StateManager.js'; |
| 3 | +import { TextDocument } from 'vscode-languageserver-textdocument'; |
| 4 | +import { IAnalyzer } from '../document-text-changed-handler/interface.js'; |
| 5 | +import { TSymbolTableSemantics } from '@bitloops/bl-transpiler'; |
| 6 | + |
| 7 | +export const handleHover = ( |
| 8 | + stateManager: StateManager, |
| 9 | + analyzer: IAnalyzer, |
| 10 | + params: HoverParams, |
| 11 | +): Hover => { |
| 12 | + const fileContent = stateManager.getFileContent(params.textDocument.uri); |
| 13 | + const document = TextDocument.create(params.textDocument.uri, 'bitloops', 1, fileContent); |
| 14 | + const position = params.position; |
| 15 | + const boundedContext = stateManager.getBoundedContext(params.textDocument.uri); |
| 16 | + |
| 17 | + let word = findWord(document, position); |
| 18 | + const symbolTable = analyzer.getSymbolTable() as TSymbolTableSemantics; |
| 19 | + const testSymbolTable = symbolTable.symbolTables[boundedContext]; |
| 20 | + const typeOfKeyword = testSymbolTable?.findTypeOfKeyword(word, { |
| 21 | + line: position.line, |
| 22 | + column: position.character, |
| 23 | + }); |
| 24 | + let { type, isConst } = typeOfKeyword || { type: null, isConst: null }; |
| 25 | + if (type) { |
| 26 | + if (type === '') type = 'unknown'; //if the type is empty string, it means that the variable is unknown |
| 27 | + if (isConst) word = 'const ' + word; //add const keyword to the hover info if the word is a constant |
| 28 | + const hoverInfo: Hover = { |
| 29 | + contents: { |
| 30 | + kind: 'markdown', |
| 31 | + value: `${word}: ${type}`, |
| 32 | + }, |
| 33 | + }; |
| 34 | + |
| 35 | + const hoverResponse: Hover = hoverInfo; |
| 36 | + return hoverResponse; |
| 37 | + } |
| 38 | + return null; |
| 39 | +}; |
| 40 | + |
| 41 | +const findWord = (document: TextDocument, position: Position): string => { |
| 42 | + const line = document.getText().split('\n')[position.line]; //get the current line |
| 43 | + let currentPosition = line.indexOf(line.trimStart()[0]); //get the position of the first non-whitespace character |
| 44 | + const wordRegExp = /\b[\w.]+\b\(?/g; //find all words, including those which contain dots or opening parentheses |
| 45 | + const matches = line.match(wordRegExp) || []; |
| 46 | + const allMatches = []; //this array will contain all the matches, including the words which contain dots incrementally |
| 47 | + //for example: if the word is this.accountRepo.getById(accountId).ifError(), the array will contain the following elements: |
| 48 | + //[this, this.accountRepo, this.accountRepo.getById(), accountId, this.accountRepo.getById().ifError()] in this order |
| 49 | + for (const match of matches) { |
| 50 | + if (match.includes('.')) { |
| 51 | + const separateMatches = match.split('.'); |
| 52 | + let i = 1; |
| 53 | + allMatches.push(separateMatches[0]); |
| 54 | + while (i < separateMatches.length - 1) { |
| 55 | + allMatches.push(allMatches[allMatches.length - 1] + '.' + separateMatches[i]); |
| 56 | + i++; |
| 57 | + } |
| 58 | + } |
| 59 | + if (match === 'ifError(') allMatches.push(allMatches[allMatches.length - 2] + '.ifError()'); |
| 60 | + //special case for ifError: we want to concatenate with the previous method called, but without the variable in the parenthesis |
| 61 | + else if (match.includes('(')) allMatches.push(match + ')'); |
| 62 | + //for methods: we keep the opening parenthesis in the matched word so we can separate the method from the variable and then we add the closing parenthesis |
| 63 | + //because in the symbolTable it is stored as a method call, for example this.accountRepo.getById() |
| 64 | + else allMatches.push(match); |
| 65 | + } |
| 66 | + for (let i = 0; i < allMatches.length; i++) { |
| 67 | + let myLength = allMatches[i].length; |
| 68 | + if (allMatches[i].includes('.')) { |
| 69 | + myLength -= allMatches[i - 1].length; |
| 70 | + } //if the word contains a dot, we subtract the length of the previous word from the length of the current word, as we keep the dot member in the previous word |
| 71 | + if (currentPosition + myLength >= position.character && currentPosition <= position.character) { |
| 72 | + return allMatches[i]; |
| 73 | + } else { |
| 74 | + currentPosition += myLength + 1; |
| 75 | + } |
| 76 | + } |
| 77 | + return undefined; |
| 78 | +}; |
0 commit comments