perlnavigator-server
Version:
Perl language server
151 lines (136 loc) • 5.81 kB
text/typescript
import { DefinitionParams, DocumentFormattingParams, TextEdit, DocumentRangeFormattingParams, Position, Range, WorkspaceFolder } from "vscode-languageserver/node";
import { TextDocument } from "vscode-languageserver-textdocument";
import { PerlDocument, PerlElem, NavigatorSettings } from "./types";
import { async_execFile, getPerlimportsProfile, nLog } from "./utils";
import { dirname, join } from "path";
import Uri from "vscode-uri";
import { execFileSync } from "child_process";
import { getPerlAssetsPath } from "./assets";
import { startProgress, endProgress } from "./progress";
import { Connection } from "vscode-languageserver/node";
export async function formatDoc(
params: DocumentFormattingParams,
txtDoc: TextDocument,
settings: NavigatorSettings,
workspaceFolders: WorkspaceFolder[] | null,
connection: Connection
): Promise<TextEdit[] | undefined> {
return await maybeReturnEdits(Range.create(Position.create(0, 0), Position.create(txtDoc.lineCount, 0)), txtDoc, settings, workspaceFolders, connection);
}
export async function formatRange(
params: DocumentRangeFormattingParams,
txtDoc: TextDocument,
settings: NavigatorSettings,
workspaceFolders: WorkspaceFolder[] | null,
connection: Connection
): Promise<TextEdit[] | undefined> {
const offset = params.range.end.character > 0 ? 1 : 0;
return await maybeReturnEdits(
Range.create(Position.create(params.range.start.line, 0), Position.create(params.range.end.line + offset, 0)),
txtDoc,
settings,
workspaceFolders,
connection
);
}
async function maybeReturnEdits(
range: Range,
txtDoc: TextDocument,
settings: NavigatorSettings,
workspaceFolders: WorkspaceFolder[] | null,
connection: Connection
): Promise<TextEdit[] | undefined> {
const text = txtDoc.getText(range);
if (!text) {
return;
}
const progressToken = await startProgress(connection, "Formatting doc", settings);
let newSource: string = "";
const fixedImports = await perlimports(txtDoc, text, settings);
if (fixedImports) {
newSource = fixedImports;
}
const tidedSource = await perltidy(fixedImports || text, settings, workspaceFolders);
if (tidedSource) {
newSource = tidedSource;
}
endProgress(connection, progressToken);
if (!newSource) {
// If we failed on both tidy and imports
return;
}
const edits: TextEdit = {
range: range,
newText: newSource,
};
return [edits];
}
async function perlimports(doc: TextDocument, code: string, settings: NavigatorSettings): Promise<string | undefined> {
if (!settings.perlimportsTidyEnabled) return;
const importsPath = join(await getPerlAssetsPath(), "perlimportsWrapper.pl");
let cliParams: string[] = [importsPath].concat(getPerlimportsProfile(settings));
cliParams = cliParams.concat(["--filename", Uri.parse(doc.uri).fsPath]);
nLog("Now starting perlimports with: " + cliParams.join(" "), settings);
try {
const process = async_execFile(settings.perlPath, settings.perlParams.concat(cliParams), { timeout: 25000, maxBuffer: 20 * 1024 * 1024 });
process?.child?.stdin?.on("error", (error: any) => {
nLog("perlImports Error Caught: ", settings);
nLog(error, settings);
});
process?.child?.stdin?.write(code);
process?.child?.stdin?.end();
const out = await process;
return out.stdout;
} catch (error: any) {
nLog("Attempted to run perlimports tidy " + error.stdout, settings);
return;
}
}
async function perltidy(code: string, settings: NavigatorSettings, workspaceFolders: WorkspaceFolder[] | null): Promise<string | undefined> {
if (!settings.perltidyEnabled) return;
const tidy_path = join(await getPerlAssetsPath(), "tidyWrapper.pl");
let tidyParams: string[] = [tidy_path].concat(getTidyProfile(workspaceFolders, settings));
nLog("Now starting perltidy with: " + tidyParams.join(" "), settings);
let output: string | Buffer;
try {
const process = async_execFile(settings.perlPath, settings.perlParams.concat(tidyParams), { timeout: 25000, maxBuffer: 20 * 1024 * 1024 });
process?.child?.stdin?.on("error", (error: any) => {
nLog("PerlTidy Error Caught: ", settings);
nLog(error, settings);
});
process?.child?.stdin?.write(code);
process?.child?.stdin?.end();
const out = await process;
output = out.stdout;
} catch (error: any) {
nLog("Perltidy failed with unknown error", settings);
nLog(error, settings);
return;
}
let pieces = output.split("ee4ffa34-a14f-4609-b2e4-bf23565f3262");
if (pieces.length > 1) {
return pieces[1];
} else {
return;
}
}
function getTidyProfile(workspaceFolders: WorkspaceFolder[] | null, settings: NavigatorSettings): string[] {
let profileCmd: string[] = [];
if (settings.perltidyProfile) {
let profile = settings.perltidyProfile;
if (profile.indexOf("$workspaceFolder") != -1) {
if (workspaceFolders) {
// TODO: Fix this. Only uses the first workspace folder
const workspaceUri = Uri.parse(workspaceFolders[0].uri).fsPath;
profileCmd.push("--profile");
profileCmd.push(profile.replaceAll("$workspaceFolder", workspaceUri));
} else {
nLog("You specified $workspaceFolder in your perltidy path, but didn't include any workspace folders. Ignoring profile.", settings);
}
} else {
profileCmd.push("--profile");
profileCmd.push(profile);
}
}
return profileCmd;
}