Skip to content

Commit 8ce66d2

Browse files
authored
Develop (#20)
1 parent d338f1a commit 8ce66d2

File tree

10 files changed

+115
-8
lines changed

10 files changed

+115
-8
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ All notable changes to the "bitloops-language" extension will be documented in t
44

55
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
66

7+
### 0.4.0
8+
9+
Added hover provider for type of variables
10+
711
### 0.3.3
812

9-
Refactored state manager, added rebounce, updated transpiler & validator, added restart command
13+
Refactored state manager, added debounce, updated transpiler & validator, added restart command
1014

1115
### 0.3.2
1216

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ No known issues.
3333

3434
## What's New
3535

36-
### 0.3.3
36+
### 0.4.0
3737

38-
Refactored state manager, added rebounce, updated transpiler & validator, added restart command
38+
Added hover provider for type of variables
3939

4040
---
4141

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
},
1010
"icon": "assets/images/bitloops-language-logo-256x256.png",
1111
"license": "MIT",
12-
"version": "0.3.3",
12+
"version": "0.4.0",
1313
"scripts": {
1414
"vs:package": "vsce package",
1515
"vs:publish": "vsce publish",

server/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "bitloops-lsp-server",
33
"description": "BitLoops Language Server",
4-
"version": "0.3.3",
4+
"version": "0.4.0",
55
"publisher": "Bitloops",
66
"license": "MIT",
77
"engines": {
@@ -10,7 +10,7 @@
1010
"type": "module",
1111
"scripts": {},
1212
"dependencies": {
13-
"@bitloops/bl-transpiler": "^0.6.0",
13+
"@bitloops/bl-transpiler": "^0.6.8",
1414
"debounce": "^1.2.1",
1515
"fs": "^0.0.1-security",
1616
"path": "^0.12.7",

server/src/lsp/connection.ts

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class LspConnection {
3838
this.connection.onCompletionResolve(server.completionResolve.bind(server));
3939

4040
this.connection.onDidChangeWatchedFiles(server.onDidChangeWatchedFiles.bind(server));
41+
this.connection.onHover(server.onHover.bind(server));
4142

4243
return this;
4344
}

server/src/lsp/handlers/document-text-changed-handler/bitloops-transpiler-analyzer.ts

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
Transpiler,
77
ValidationErrors,
88
ParserSyntacticErrors,
9+
TSymbolTableSemantics,
910
} from '@bitloops/bl-transpiler';
1011
import { Diagnostic } from 'vscode-languageserver/node.js';
1112
import { TextDocument } from 'vscode-languageserver-textdocument';
@@ -15,6 +16,7 @@ import { TFileId } from '../../../types.js';
1516
import { StateManager, TFileDiagnostics } from '../../services/StateManager.js';
1617

1718
export class BitloopsAnalyzer implements IAnalyzer {
19+
symbolTable: ParserSyntacticErrors | TSymbolTableSemantics;
1820
constructor(private stateManager: StateManager) {}
1921

2022
analyze(): TFileDiagnostics {
@@ -33,6 +35,8 @@ export class BitloopsAnalyzer implements IAnalyzer {
3335
// 'Info:',
3436
// transpilerInput.core.map((x) => ({ bc: x.boundedContext, mod: x.module })),
3537
// );
38+
this.symbolTable = transpiler.getSymbolTable(transpilerInput);
39+
3640
const intermediateModelOrErrors = transpiler.bitloopsCodeToIntermediateModel(transpilerInput);
3741
if (Transpiler.isTranspilerError(intermediateModelOrErrors)) {
3842
this.mapTranspilerErrorsToLSPDiagnostics(intermediateModelOrErrors);
@@ -124,4 +128,7 @@ export class BitloopsAnalyzer implements IAnalyzer {
124128
]);
125129
}
126130
}
131+
public getSymbolTable(): ParserSyntacticErrors | TSymbolTableSemantics {
132+
return this.symbolTable;
133+
}
127134
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { ParserSyntacticErrors, TSymbolTableSemantics } from '@bitloops/bl-transpiler';
12
import { TFileDiagnostics } from '../../services/StateManager.js';
23

34
export interface IAnalyzer {
45
/**
56
* It analyzes the document and returns a list of diagnostics.
67
*/
78
analyze(): TFileDiagnostics;
9+
getSymbolTable(): ParserSyntacticErrors | TSymbolTableSemantics;
810
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
};

server/src/lsp/server.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import {
88
DidChangeConfigurationNotification,
99
WorkspaceFolder,
1010
DidChangeWatchedFilesParams,
11+
TextDocumentPositionParams,
12+
Hover,
13+
HoverParams,
14+
Position,
1115
} from 'vscode-languageserver/node.js';
1216

1317
import { TextDocument } from 'vscode-languageserver-textdocument';
@@ -22,6 +26,7 @@ import { FileUtils } from '../utils/file.js';
2226
import { handleChangeOnWatchedFiles } from './handlers/watched-files-changed/index.js';
2327
import { fileURLToPath } from 'url';
2428
import { StateManager } from './services/StateManager.js';
29+
import { handleHover } from './handlers/hover-handler/hover.js';
2530

2631
// Create a connection for the server, using Node's IPC as a transport.
2732
// Also include all preview / proposed LSP features.
@@ -124,6 +129,7 @@ export class BitloopsServer {
124129
resolveProvider: true,
125130
triggerCharacters: ['.'],
126131
},
132+
hoverProvider: true,
127133
},
128134
};
129135
if (this.hasWorkspaceFolderCapability) {
@@ -170,10 +176,15 @@ export class BitloopsServer {
170176
);
171177
}
172178

179+
public onHover(params: HoverParams): Hover {
180+
return handleHover(this.stateManager, this.analyzer, params);
181+
}
182+
173183
private async validateWorkspace(workspaceFolders: WorkspaceFolder[]): Promise<void> {
174184
for (const workspaceFolder of workspaceFolders) {
175-
// TODO use fileURLToPath instead
176-
const workspaceRoot = path.resolve(workspaceFolder.uri.replace('file://', ''));
185+
// const workspaceRoot = path.resolve(workspaceFolder.uri.replace('file://', ''));
186+
const workspaceRoot = fileURLToPath(workspaceFolder.uri);
187+
177188
// For now we only handle 1 setup.bl file, consequently only 1 bl project
178189
// Perhaps we would isolate each project, having its own analyzer
179190

server/src/lsp/services/StateManager.ts

+4
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,8 @@ export class StateManager {
229229
const module = filePathParts[2];
230230
return { boundedContext, module };
231231
}
232+
233+
public getBoundedContext(fileUri: string): string {
234+
return this.extractFileBoundedContextAndModule(fileUri).boundedContext;
235+
}
232236
}

0 commit comments

Comments
 (0)