diff --git a/CHANGELOG.md b/CHANGELOG.md index 1700f43..8a1617b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Change Log +## 0.0.3 +- Fix phpcbf hanging issue by closing stdin [@shivanandwp](https://github.com/shivanandwp) [#2](https://github.com/soderlind/vscode-phpcbf/issues/2) +- For relative links in settings executablePath, add support for multi-root workspaces, i.e. will look for phpcbf in all workspaces +- Add support for period in the path: `{ "phpcbf.executablePath" : "./vendor/bin/phpcbf" }`, i,e, the period is the workspace root ## 0.0.2 - Update documentation about the `phpcbf.executablePath` setting. - Add credits, copyright and license. diff --git a/README.md b/README.md index e0cd5b0..892ca94 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ This extension has the following settings: * `phpcbf.standard`: [ Optional | Default: `null` ]. The formatting standard. * When `null`, phpcbf will use, if it's set, the `default_standard`, otherwise fallback to `Pear`. * By default, the following standards are available: `PEAR`, `Zend`, `PSR2`, `MySource`, `Squiz` and `PSR1` - * If you add a standard to phpcs, it will be available for phpcbf, eg the [Drupal](https://github.com/klausi/coder), [WordPress](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards), [Yii2](https://github.com/yiisoft/yii2-coding-standards), and [Magento](https://github.com/magento/marketplace-eqp) extensions. + * If you add a standard to phpcs, it will be available for phpcbf, eg [Drupal](https://github.com/klausi/coder), [WordPress](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards), [Yii2](https://github.com/yiisoft/yii2-coding-standards), [Magento](https://github.com/magento/marketplace-eqp), [Symfony](https://github.com/djoos/Symfony-coding-standard) etc. * You can also point to a [phpcs.xml rules file](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml), eg: `"phpcbf.standard": "/file/path/phpcs.xml"` The default settings are diff --git a/extension.js b/extension.js index 5c51681..d3269bc 100644 --- a/extension.js +++ b/extension.js @@ -1,195 +1,227 @@ "use strict"; -// const { -// html_beautify -// } = require('./js-beautify/beautify-html'); const vscode = require("vscode"); const { commands, workspace, window, languages, Range, Position } = vscode; const fs = require("fs"); const os = require("os"); const cp = require("child_process"); const TmpDir = os.tmpdir(); -let autoFixing = false; class PHPCBF { - constructor() { - this.loadSettings(); - } - - loadSettings() { - let config = workspace.getConfiguration("phpcbf"); - if (!config.get("enable") == true) { - return; + constructor() { + this.loadSettings(); } - this.onsave = config.get("onsave", true); - this.executablePath = config.get( - "executablePath", - process.platform === "win32" ? "php-cbf.bat" : "phpcbf" - ); - if ( - process.platform == "win32" && - config.has("executablePathWindows") && - config.get("executablePathWindows").length > 0 - ) { - this.executablePath = config.get("executablePathWindows"); - } - if (workspace.rootPath != undefined) { - this.executablePath = this.executablePath.replace( - "${workspaceRoot}", - workspace.rootPath - ); - } - this.executablePath = this.executablePath.replace( - /^~\//, - os.homedir() + "/" - ); - this.standard = config.get("standard", null); - - this.documentFormattingProvider = config.get( - "documentFormattingProvider", - true - ); - } - - getArgs(fileName) { - let args = ["-lq", fileName]; - if (this.standard) { - args.push("--standard=" + this.standard); + loadSettings() { + let config = workspace.getConfiguration("phpcbf"); + if (!config.get("enable") == true) { + return; + } + this.onsave = config.get("onsave", false); + + this.executablePath = config.get( + "executablePath", + process.platform === "win32" ? "php-cbf.bat" : "phpcbf" + ); + + // relative paths? + if (this.executablePath.startsWith("${workspaceRoot}")) { + this.addRootPath("${workspaceRoot}"); + } + if (this.executablePath.startsWith(".")) { + this.addRootPath("."); + } + if (this.executablePath.startsWith("~")) { + this.executablePath = this.executablePath.replace( + /^~\//, + os.homedir() + "/" + ); + } + + this.standard = config.get("standard", null); + + this.documentFormattingProvider = config.get( + "documentFormattingProvider", + true + ); } - return args; - } - - format(text) { - autoFixing = true; - - let fileName = - TmpDir + - "/temp-" + - Math.random() - .toString(36) - .replace(/[^a-z]+/g, "") - .substr(0, 10) + - ".php"; - fs.writeFileSync(fileName, text); - - let exec = cp.spawn(this.executablePath, this.getArgs(fileName)); - - let promise = new Promise((resolve, reject) => { - exec.on("error", err => { - reject(); - autoFixing = false; - console.log(err); - if (err.code == "ENOENT") { - window.showErrorMessage( - "PHPCBF: " + err.message + ". executablePath not found." - ); + + getArgs(fileName) { + let args = ["-lq", fileName]; + if (this.standard) { + args.push("--standard=" + this.standard); } - }); - exec.on("exit", code => { - /* phpcbf exit codes: + return args; + } + + format(text) { + console.time("phpcbf"); + phpcbfError = false; + let fileName = + TmpDir + + "/temp-" + + Math.random() + .toString(36) + .replace(/[^a-z]+/g, "") + .substr(0, 10) + + ".php"; + fs.writeFileSync(fileName, text); + let exec = cp.spawn(this.executablePath, this.getArgs(fileName)); + let promise = new Promise((resolve, reject) => { + exec.on("error", err => { + reject(); + console.log(err); + if (err.code == "ENOENT") { + window.showErrorMessage( + "PHPCBF: " + err.message + ". executablePath not found." + ); + } + }); + exec.on("exit", code => { + /* phpcbf exit codes: Exit code 0 is used to indicate that no fixable errors were found, so nothing was fixed Exit code 1 is used to indicate that all fixable errors were fixed correctly Exit code 2 is used to indicate that PHPCBF failed to fix some of the fixable errors it found Exit code 3 is used for general script execution errors - */ - switch (code) { - case 0: - break; - case 1: - case 2: - let fixed = fs.readFileSync(fileName, "utf-8"); - if (fixed.length > 0) { - resolve(fixed); - } else { - reject(); - } - break; - - default: - let msgs = { - 16: "PHPCBF: Configuration error of the application.", - 32: "PHPCBF: Configuration error of a Fixer.", - 64: "PHPCBF: Exception raised within the application." - }; - window.showErrorMessage(msgs[code]); - reject(); - break; + */ + switch (code) { + case 0: + break; + case 1: + case 2: + let fixed = fs.readFileSync(fileName, "utf-8"); + if (fixed.length > 0) { + resolve(fixed); + } else { + reject(); + } + break; + case 3: + phpcbfError = true; + break; + default: + let msgs = { + 3: "PHPCBF: general script execution errors.", + 16: "PHPCBF: Configuration error of the application.", + 32: "PHPCBF: Configuration error of a Fixer.", + 64: "PHPCBF: Exception raised within the application." + }; + window.showErrorMessage(msgs[code]); + reject(); + break; + } + + fs.unlink(fileName, function(err) {}); + }); + }); + exec.stdin.end(); + + if (phpcbfError) { + exec.stdout.on("data", buffer => { + console.log(buffer.toString()); + window.showErrorMessage(buffer.toString()); + }); } + exec.stderr.on("data", buffer => { + console.log(buffer.toString()); + }); + // exec.on("close", code => { + // // console.log(code); + // }); + + return promise; + } - fs.unlink(fileName, function(err) {}); - autoFixing = false; - }); - }); - - exec.stdout.on("data", buffer => { - // console.log(buffer.toString()); - }); - exec.stderr.on("data", buffer => { - console.log(buffer.toString()); - }); - exec.on("close", code => { - // console.log(code); - }); - - return promise; - } + addRootPath(prefix) { + const resources = []; + if (workspace.workspaceFolders) { + for (let wsFolder of workspace.workspaceFolders) { + resources.push(wsFolder.uri); + } + } else { + const editor = window.activeTextEditor; + if (editor) { + resources.push(editor.document.uri); + } + } + for (let resource of resources) { + if (resource.scheme === "file") { + const folder = workspace.getWorkspaceFolder(resource); + if (folder) { + const rootPath = folder.uri.fsPath; + let tmpExecutablePath = this.executablePath.replace( + prefix, + rootPath + ); + fs.exists(tmpExecutablePath, exists => { + if (exists) { + this.executablePath = tmpExecutablePath; + } + }); + } + } + } + } } exports.activate = context => { - let phpcbf = new PHPCBF(); - - context.subscriptions.push( - workspace.onWillSaveTextDocument(event => { - if ( - event.document.languageId == "php" && - phpcbf.onsave /*&& workspace.getConfiguration('editor').get('formatOnSave') == false*/ - ) { - event.waitUntil( - commands.executeCommand("editor.action.formatDocument") - ); - } - }) - ); - - context.subscriptions.push( - commands.registerTextEditorCommand("phpcbf-soderlind", textEditor => { - if (textEditor.document.languageId == "php") { - commands.executeCommand("editor.action.formatDocument"); - } - }) - ); - - context.subscriptions.push( - workspace.onDidChangeConfiguration(() => { - phpcbf.loadSettings(); - }) - ); - - if (phpcbf.documentFormattingProvider) { + let phpcbf = new PHPCBF(); + context.subscriptions.push( - languages.registerDocumentFormattingEditProvider("php", { - provideDocumentFormattingEdits: (document, options, token) => { - autoFixing = false; - return new Promise((resolve, reject) => { - let originalText = document.getText(); - let lastLine = document.lineAt(document.lineCount - 1); - let range = new Range(new Position(0, 0), lastLine.range.end); - phpcbf - .format(originalText) - .then(text => { - if (text != originalText) { - resolve([new vscode.TextEdit(range, text)]); - } else { - reject(); - } - }) - .catch(err => { - console.log(err); - reject(); - }); - }); - } - }) + workspace.onWillSaveTextDocument(event => { + if ( + event.document.languageId == "php" && + phpcbf.onsave /*&& + workspace.getConfiguration("editor").get("formatOnSave") == + false*/ + ) { + event.waitUntil( + commands.executeCommand("editor.action.formatDocument") + ); + } + }) + ); + + context.subscriptions.push( + commands.registerTextEditorCommand("phpcbf-soderlind", textEditor => { + if (textEditor.document.languageId == "php") { + commands.executeCommand("editor.action.formatDocument"); + } + }) + ); + + context.subscriptions.push( + workspace.onDidChangeConfiguration(() => { + phpcbf.loadSettings(); + }) ); - } + + if (phpcbf.documentFormattingProvider) { + context.subscriptions.push( + languages.registerDocumentFormattingEditProvider("php", { + provideDocumentFormattingEdits: (document, options, token) => { + return new Promise((resolve, reject) => { + let originalText = document.getText(); + let lastLine = document.lineAt(document.lineCount - 1); + let range = new Range( + new Position(0, 0), + lastLine.range.end + ); + phpcbf + .format(originalText) + .then(text => { + if (text != originalText) { + resolve([new vscode.TextEdit(range, text)]); + } else { + reject(); + } + }) + .catch(err => { + console.log(err); + reject(); + }); + }); + } + }) + ); + } };