UNPKG

perlnavigator-server

Version:

Perl language server

170 lines (143 loc) 6.59 kB
import { DefinitionParams, Location, WorkspaceFolder } from "vscode-languageserver/node"; import { TextDocument } from "vscode-languageserver-textdocument"; import { PerlDocument, PerlElem, NavigatorSettings, ElemSource, ParseType } from "./types"; import Uri from "vscode-uri"; import { realpathSync, existsSync, realpath, promises } from "fs"; import { getIncPaths, async_execFile, getSymbol, lookupSymbol, nLog, isFile } from "./utils"; import { dirname, join } from "path"; import { getPerlAssetsPath } from "./assets"; import { refineElement } from "./refinement"; export async function getDefinition(params: DefinitionParams, perlDoc: PerlDocument, txtDoc: TextDocument, modMap: Map<string, string>): Promise<Location[] | undefined> { let position = params.position; const symbol = getSymbol(position, txtDoc); if (!symbol) return; const foundElems = lookupSymbol(perlDoc, modMap, symbol, position.line); if (foundElems.length == 0) { return; } let locationsFound: Location[] = []; for (const elem of foundElems) { const elemResolved: PerlElem | undefined = await resolveElemForNav(perlDoc, elem, symbol); if (!elemResolved) continue; let uri: string; if (perlDoc.uri !== elemResolved.uri) { // If sending to a different file, let's make sure it exists and clean up the path const file = Uri.parse(elemResolved.uri).fsPath; if (!(await isFile(file))) continue; // Make sure the file exists and hasn't been deleted. uri = Uri.file(realpathSync(file)).toString(); // Resolve symlinks } else { // Sending to current file (including untitled files) uri = perlDoc.uri; } const newLoc: Location = { uri: uri, range: { start: { line: elemResolved.line, character: 0 }, end: { line: elemResolved.line, character: 500 }, }, }; locationsFound.push(newLoc); } // const count = locationsFound return locationsFound; } async function resolveElemForNav(perlDoc: PerlDocument, elem: PerlElem, symbol: string): Promise<PerlElem | undefined> { let refined = await refineElement(elem, perlDoc); elem = refined || elem; if (!badFile(elem.uri)) { if (perlDoc.uri == elem.uri && symbol.includes("->")) { // Corinna methods don't have line numbers. Let's hunt for them. If you dont find anything better, just return the original element. const method = symbol.split("->").pop(); if (method) { // Shouldn't this always be defined? Double check const found = perlDoc.elems.get(method); if (found) { if (elem.line == 0 && elem.type == "x") { if (found[0].uri == perlDoc.uri) return found[0]; } else if (elem.line > 0 && elem.type == "t") { // Solve the off-by-one error at least for these. Eventually, you could consult a tagger for this step. for (let potentialElem of found) { if (Math.abs(potentialElem.line - elem.line) <= 1) { return potentialElem; } } } } } // Otherwise give-up } // Normal path; file is good return elem; } else { // Try looking it up by package instead of file. // Happens with XS subs and Moo subs if (elem.package) { const elemResolved = perlDoc.elems.get(elem.package); if (elemResolved) { for (let potentialElem of elemResolved) { if (potentialElem.uri && !badFile(potentialElem.uri)) { return potentialElem; } } } } // Finding the module with the stored mod didn't work. Let's try navigating to the package itself instead of Foo::Bar->method(). // Many Moose methods end up here. // Not very helpful, since the user can simply click on the module manually if they want // const base_module = symbol.match(/^([\w:]+)->\w+$/); // if(base_module){ // const elemResolved = perlDoc.elems.get(base_module); // if(elemResolved && elemResolved.file && !badFile(elem.file)){ // return elemResolved; // } // } } return; } function badFile(uri: string): boolean { if (!uri) { return true; } const fsPath = Uri.parse(uri).fsPath; if (!fsPath || fsPath.length <= 1) { // Single forward slashes seem to sneak in here. return true; } return /(?:Sub[\\\/]Defer\.pm|Moo[\\\/]Object\.pm|Moose[\\\/]Object\.pm|\w+\.c|Inspectorito\.pm)$/.test(fsPath); } export async function getAvailableMods(workspaceFolders: WorkspaceFolder[] | null, settings: NavigatorSettings): Promise<Map<string, string>> { let perlParams = settings.perlParams; perlParams = perlParams.concat(getIncPaths(workspaceFolders, settings)); const modHunterPath = join(await getPerlAssetsPath(), "lib_bs22", "ModHunter.pl"); perlParams.push(modHunterPath); nLog("Starting to look for perl modules with " + perlParams.join(" "), settings); const mods: Map<string, string> = new Map(); let output: string; try { // This can be slow, especially if reading modules over a network or on windows. const out = await async_execFile(settings.perlPath, perlParams, { timeout: 90000, maxBuffer: 20 * 1024 * 1024 }); output = out.stdout; nLog("Success running mod hunter", settings); } catch (error: any) { nLog("ModHunter failed. You will lose autocomplete on importing modules. Not a huge deal", settings); nLog(error, settings); return mods; } output.split("\n").forEach((mod) => { var items = mod.split("\t"); if (items.length != 5 || items[1] != "M" || !items[2] || !items[3]) { return; } // Load file realpath(items[3], function (err, path) { if (err) { // Skip if error } else { if (!path) return; // Could file be empty, but no error? let uri = Uri.file(path).toString(); // Resolve symlinks mods.set(items[2], uri); } }); }); return mods; }