add lsp bridge services

This commit is contained in:
bobo.yang 2023-12-27 21:41:43 +08:00
parent fd4d213cbc
commit b895a6ffcf
3 changed files with 263 additions and 21 deletions

View File

@ -0,0 +1,131 @@
import * as vscode from "vscode";
interface Definition {
name: string;
abspath: string;
line: number; // 1-based
character: number; // 1-based
}
/**
* @param abspath: absolute path of the file
* @param line: line number, 1-based
* @param character: character number, 1-based
*
**/
async function findDefinitions(
abspath: string,
line: number,
character: number
): Promise<Definition[]> {
const uri = vscode.Uri.file(abspath);
const position = new vscode.Position(line - 1, character - 1);
// TODO: verify if the file & position is correct
// const document = await vscode.workspace.openTextDocument(uri);
const locations = await vscode.commands.executeCommand<vscode.Location[]>(
"vscode.executeDefinitionProvider",
uri,
position
);
const definitions: Definition[] = [];
if (locations) {
for (const location of locations) {
console.log(
`* Definition found in file: ${location.uri.fsPath}, line: ${location.range.start.line}, character: ${location.range.start.character}`
);
// use `map` & `Promise.all` to improve performance if needed
const doc = await vscode.workspace.openTextDocument(location.uri);
definitions.push({
name: doc.getText(location.range),
abspath: location.uri.fsPath,
line: location.range.start.line + 1,
character: location.range.start.character + 1,
});
}
} else {
console.log("No definition found");
}
console.log(`* Found ${definitions.length} definitions`);
console.log(definitions);
return definitions;
}
function findText(
document: vscode.TextDocument,
text: string
): vscode.Position[] {
const positions: vscode.Position[] = [];
if (!text) {
return positions;
}
const content = document.getText();
let index = content.indexOf(text);
while (index >= 0) {
const position = document.positionAt(index);
positions.push(position);
// Find the next occurrence
index = content.indexOf(text, index + 1);
}
return positions;
}
async function findDefinitionsOfToken(
abspath: string,
token: string
): Promise<Definition[]> {
const uri = vscode.Uri.file(abspath);
const document = await vscode.workspace.openTextDocument(uri);
const positions = findText(document, token);
console.log(`- Found ${positions.length} <${token}>`);
// TODO: verify if the file & position is correct
// const document = await vscode.workspace.openTextDocument(uri);
const definitions: Definition[] = [];
const visited = new Set<string>();
for (const position of positions) {
const locations = await vscode.commands.executeCommand<
vscode.Location[]
>("vscode.executeDefinitionProvider", uri, position);
if (!locations) {
console.log("No definition found");
continue;
}
for (const location of locations) {
const locationKey = `${location.uri.fsPath}:${location.range.start.line}:${location.range.start.character}`;
if (visited.has(locationKey)) {
continue;
}
visited.add(locationKey);
console.log(
`- <${token}> Definition found in file: ${location.uri.fsPath}, line: ${location.range.start.line}, character: ${location.range.start.character}`
);
// use `map` & `Promise.all` to improve performance if needed
const doc = await vscode.workspace.openTextDocument(location.uri);
definitions.push({
name: doc.getText(location.range),
abspath: location.uri.fsPath,
line: location.range.start.line + 1,
character: location.range.start.character + 1,
});
}
}
console.log(`* Found ${definitions.length} definitions`);
console.log(definitions);
return definitions;
}
export { findDefinitions, findDefinitionsOfToken };

View File

@ -0,0 +1,59 @@
import * as vscode from "vscode";
import * as path from "path";
// TODO: merge with find-defs.ts
interface Reference {
name: string;
abspath: string;
line: number; // 1-based
character: number; // 1-based
}
/**
* @param abspath: absolute path of the file
* @param line: line number, 1-based
* @param character: character number, 1-based
*
**/
async function findReferences(
abspath: string,
line: number,
character: number
): Promise<Reference[]> {
const uri = vscode.Uri.file(abspath);
const position = new vscode.Position(line - 1, character - 1);
// TODO: verify if the file & position is correct
// const document = await vscode.workspace.openTextDocument(uri);
const locations = await vscode.commands.executeCommand<vscode.Location[]>(
"vscode.executeReferenceProvider",
uri,
position
);
const references: Reference[] = [];
if (locations) {
for (const location of locations) {
console.log(
`* Reference found in file: ${location.uri.fsPath}, line: ${location.range.start.line}, character: ${location.range.start.character}`
);
// use `map` & `Promise.all` to improve performance if needed
const doc = await vscode.workspace.openTextDocument(location.uri);
references.push({
name: doc.getText(location.range),
abspath: location.uri.fsPath,
line: location.range.start.line + 1,
character: location.range.start.character + 1,
});
}
} else {
console.log("No reference found");
}
return references;
}
export { findReferences };

View File

@ -10,7 +10,12 @@ import { UiUtilWrapper } from '../util/uiUtil';
import { createEnvByConda, createEnvByMamba } from '../util/python_installer/app_install';
import { installRequirements } from '../util/python_installer/package_install';
import {
findDefinitions,
findDefinitionsOfToken,
} from "./lsp_bridge/feature/find-defs";
import { findReferences } from "./lsp_bridge/feature/find-refs";
const functionRegistry: any = {
@ -18,7 +23,35 @@ const functionRegistry: any = {
"/get_lsp_brige_port": {
"keys": [],
"handler": async () => {
return await UiUtilWrapper.getLSPBrigePort();
logger.channel()?.info(`get lsp bridge port: ${process.env.DEVCHAT_IDE_SERVICE_PORT}`);
// return await UiUtilWrapper.getLSPBrigePort();
return process.env.DEVCHAT_IDE_SERVICE_PORT;
}
},
"/definitions": {
"keys": ["abspath", "line", "character", "token"],
"handler": async (abspath: string, line: string|undefined = undefined, character: string|undefined = undefined, token: string|undefined = undefined) => {
logger.channel()?.info(`get definitions: ${abspath}, ${line}, ${character}, ${token}`);
if (token !== undefined) {
const definitions = await findDefinitionsOfToken(abspath, token);
return definitions;
} else {
const definitions = await findDefinitions(abspath, Number(line), Number(character));
return definitions;
}
}
},
"/references": {
"keys": ["abspath", "line", "character"],
"handler": async (abspath: string, line: number, character: number) => {
logger.channel()?.info(`get references: ${abspath}, ${line}, ${character}`);
const references = await findReferences(
abspath,
Number(line),
Number(character)
);
return references;
}
},
// eslint-disable-next-line @typescript-eslint/naming-convention
@ -128,6 +161,7 @@ let server: http.Server | null = null;
export async function startRpcServer() {
server = http.createServer((req, res) => {
const parsedUrl = new URL(req.url!, `http://${req.headers.host}`);
logger.channel()?.info(`request: ${parsedUrl}`)
if (parsedUrl.pathname === '/favicon.ico') {
res.writeHead(204);
res.end();
@ -168,30 +202,47 @@ export async function startRpcServer() {
});
async function handleRequest(parsedUrl: URL, params: any, res: http.ServerResponse) {
let responseResult = {};
if (functionRegistry[parsedUrl.pathname]) {
let keysExist = true;
let newParameters: any[] = [];
for (let key of functionRegistry[parsedUrl.pathname]['keys']) {
if (!params.hasOwnProperty(key)) {
keysExist = false;
break;
// log current datetime
const date = new Date();
logger.channel()?.info(`date: ${date.toISOString()}`);
try {
let responseResult = {};
if (functionRegistry[parsedUrl.pathname]) {
let keysExist = true;
let newParameters: any[] = [];
for (let key of functionRegistry[parsedUrl.pathname]['keys']) {
if (!params.hasOwnProperty(key)) {
// check whether key in functionRegistry[parsedUrl.pathname]['optional']
newParameters.push(undefined);
continue;
}
newParameters.push(params[key]);
}
if (!keysExist) {
responseResult['error'] = "Missing required parameters";
} else {
responseResult['result'] = await functionRegistry[parsedUrl.pathname]['handler'](...newParameters);
if (parsedUrl.pathname === "/definitions" || parsedUrl.pathname === "/references") {
responseResult = responseResult['result'];
}
}
newParameters.push(params[key]);
}
if (!keysExist) {
responseResult['error'] = "Missing required parameters";
} else {
responseResult['result'] = await functionRegistry[parsedUrl.pathname]['handler'](...newParameters);
responseResult['error'] = "Function not found";
}
} else {
responseResult['error'] = "Function not found";
// eslint-disable-next-line @typescript-eslint/naming-convention
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(responseResult));
logger.channel()?.info(`result:${responseResult}`);
} catch (error) {
logger.channel()?.error(`Error: ${error}`);
logger.channel()?.show();
}
// eslint-disable-next-line @typescript-eslint/naming-convention
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(responseResult));
const date2 = new Date();
logger.channel()?.info(`date finish: ${date2.toISOString()}`);
}
server.listen(0, () => {
@ -200,5 +251,6 @@ export async function startRpcServer() {
const port = typeof address === 'string' ? address : address?.port;
logger.channel()?.info(`Server running at http://localhost:${port}/`);
process.env.DEVCHAT_IDE_SERVICE_URL = `http://localhost:${port}`;
process.env.DEVCHAT_IDE_SERVICE_PORT = `${port}`;
});
}