"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Formatter = void 0;
const child_process_1 = require("child_process");
const LSP = require("vscode-languageserver/node");
const logger_1 = require("../util/logger");
class Formatter {
    constructor({ cwd, executablePath }) {
        this._canFormat = true;
        this.cwd = cwd || process.cwd();
        this.executablePath = executablePath;
    }
    get canFormat() {
        return this._canFormat;
    }
    format(document, formatOptions, shfmtConfig) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this._canFormat) {
                return [];
            }
            return this.executeFormat(document, formatOptions, shfmtConfig);
        });
    }
    executeFormat(document, formatOptions, shfmtConfig) {
        return __awaiter(this, void 0, void 0, function* () {
            const documentText = document.getText();
            const result = yield this.runShfmt(documentText, formatOptions, shfmtConfig);
            if (!this._canFormat) {
                return [];
            }
            return [
                {
                    range: LSP.Range.create(LSP.Position.create(0, 0), LSP.Position.create(Number.MAX_VALUE, Number.MAX_VALUE)),
                    newText: result,
                },
            ];
        });
    }
    runShfmt(documentText, formatOptions, shfmtConfig) {
        return __awaiter(this, void 0, void 0, function* () {
            const indentation = (formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.insertSpaces) ? formatOptions.tabSize : 0;
            const args = [`-i=${indentation}`]; // --indent
            if (shfmtConfig === null || shfmtConfig === void 0 ? void 0 : shfmtConfig.binaryNextLine)
                args.push('-bn'); // --binary-next-line
            if (shfmtConfig === null || shfmtConfig === void 0 ? void 0 : shfmtConfig.caseIndent)
                args.push('-ci'); // --case-indent
            if (shfmtConfig === null || shfmtConfig === void 0 ? void 0 : shfmtConfig.funcNextLine)
                args.push('-fn'); // --func-next-line
            if (shfmtConfig === null || shfmtConfig === void 0 ? void 0 : shfmtConfig.spaceRedirects)
                args.push('-sr'); // --space-redirects
            logger_1.logger.debug(`Shfmt: running "${this.executablePath} ${args.join(' ')}"`);
            let out = '';
            let err = '';
            const proc = new Promise((resolve, reject) => {
                const proc = (0, child_process_1.spawn)(this.executablePath, [...args, '-'], { cwd: this.cwd });
                proc.on('error', reject);
                proc.on('close', resolve);
                proc.stdout.on('data', (data) => (out += data));
                proc.stderr.on('data', (data) => (err += data));
                proc.stdin.on('error', () => {
                    // NOTE: Ignore STDIN errors in case the process ends too quickly, before we try to
                    // write. If we write after the process ends without this, we get an uncatchable EPIPE.
                    // This is solved in Node >= 15.1 by the "on('spawn', ...)" event, but we need to
                    // support earlier versions.
                });
                proc.stdin.end(documentText);
            });
            // NOTE: do we care about exit code? 0 means "ok", 1 possibly means "errors",
            // but the presence of parseable errors in the output is also sufficient to
            // distinguish.
            let exit;
            try {
                exit = yield proc;
            }
            catch (e) {
                // TODO: we could do this up front?
                if (e.code === 'ENOENT') {
                    // shfmt path wasn't found, don't try to format any more:
                    logger_1.logger.warn(`Shfmt: disabling formatting as no executable was found at path '${this.executablePath}'`);
                    this._canFormat = false;
                    return '';
                }
                throw new Error(`Shfmt: failed with code ${exit}: ${e}\nout:\n${out}\nerr:\n${err}`);
            }
            return out;
        });
    }
}
exports.Formatter = Formatter;
//# sourceMappingURL=index.js.map