blob: 78c9f938357307fab441cb089ddb1f5e9796cc1d [file] [log] [blame]
'use strict';
import * as vscode from 'vscode';
import { spawnSync } from 'child_process';
import * as fs from 'fs';
const notDefined = "FIDL format tool not specified. Please set the path to fidl-format in Settings.";
const notExecutable = "FIDL format tool doesn't exist or can't be run. Please verify the path to fidl-format in Settings.";
const formatterError = "Error formatting FIDL";
const editSettingLabel = "Edit setting";
enum FormatToolErrorCause {
ToolNotSpecified, ToolNotExecutable, FormatterError
}
class FormatToolError extends Error {
cause: FormatToolErrorCause;
formatterMessage: string | undefined;
constructor(cause: FormatToolErrorCause) {
super();
this.cause = cause;
}
}
/**
* Checks whether the path to fidl-format has been filled out. If the default
* value of an empty string is found, an error is shown as a notification.
*/
function getFidlFormatToolPath(): string {
const formatTool: string | undefined | null = vscode.workspace.getConfiguration('fidl').get('formatTool');
if (!formatTool) {
throw new FormatToolError(FormatToolErrorCause.ToolNotSpecified);
}
try {
// Throws if tool is not present or not executable.
fs.accessSync(formatTool, fs.constants.X_OK);
} catch (e) {
throw new FormatToolError(FormatToolErrorCause.ToolNotExecutable);
}
return formatTool as string;
}
async function showError(e: FormatToolError) {
let message;
let showEdit = false;
switch (e.cause) {
case FormatToolErrorCause.ToolNotSpecified:
message = notDefined;
showEdit = true;
break;
case FormatToolErrorCause.ToolNotExecutable:
message = notExecutable;
showEdit = true;
break;
case FormatToolErrorCause.FormatterError:
message = `${formatterError}: ${e.formatterMessage}`
break;
default:
// This shouldn't happen. Since we don't know what kind of error we
// have, we rethrow so this error appears in the console during
// development.
throw e;
}
let items: string[] = [];
if (showEdit) {
items.push(editSettingLabel);
}
let item = await vscode.window.showErrorMessage(message, ...items);
switch (item) {
case undefined:
case null:
// The user did not choose to edit setting.
return;
case editSettingLabel:
vscode.commands.executeCommand('workbench.action.openSettings', 'fidl.formatTool');
}
}
/**
* A formatting edit provider that runs fidl-format over the code in the
* current FIDL file. This happens indirectly by the use of a temporary file to
* work around fidl-format's limitation of not accepting FIDL code via standard
* input.
*
* Objects of this class will check for the presence of the fidl-format path in
* settings when instantiated and on every formatting request. If the path is
* left empty (the default) and the path to fidl-format isn't specified,
* objects of this class will show a notification to the user each time they
* try to format a FIDL file.
*/
export default class FidlFormattingEditProvider implements vscode.DocumentFormattingEditProvider {
constructor() {
try {
getFidlFormatToolPath();
} catch (e) {
showError(e);
}
}
provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] {
let formatTool: string;
try {
formatTool = getFidlFormatToolPath();
} catch (e) {
showError(e);
return [];
}
// FIDL files are assumed to always be UTF-8.
const format = spawnSync(formatTool, ["-"], { encoding: 'utf8', input: document.getText() });
if (format.status) {
// fidl-format exited with an error.
const e = new FormatToolError(FormatToolErrorCause.FormatterError);
e.formatterMessage = format.stderr;
showError(e);
// Return no edits. i.e. no changes to document.
return [];
}
const fileStart = new vscode.Position(0, 0);
const fileEnd = document.lineAt(document.lineCount - 1).range.end;
// Casting: because spawnSync was called with an encoding,
// format.stdout is already a string.
return [new vscode.TextEdit(new vscode.Range(fileStart, fileEnd), format.stdout as string)];
}
}