diff --git a/extensions/php/src/features/validationProvider.ts b/extensions/php/src/features/validationProvider.ts index 911b4448d38..64b9086ac7a 100644 --- a/extensions/php/src/features/validationProvider.ts +++ b/extensions/php/src/features/validationProvider.ts @@ -94,7 +94,7 @@ export default class PHPValidationProvider { private diagnosticCollection: vscode.DiagnosticCollection; private delayers: { [key: string]: ThrottledDelayer }; - constructor() { + constructor(private workspaceExecutablePath: string) { this.executable = null; this.validationEnabled = true; this.trigger = RunTrigger.onSave; @@ -114,6 +114,17 @@ export default class PHPValidationProvider { }, null, subscriptions); } + public updateWorkspaceExecutablePath(workspaceExecutablePath: string, loadConfig: boolean = false): void { + if (workspaceExecutablePath && workspaceExecutablePath.length === 0) { + this.workspaceExecutablePath = undefined; + } else { + this.workspaceExecutablePath = workspaceExecutablePath; + } + if (loadConfig) { + this.loadConfiguration(); + } + } + public dispose(): void { this.diagnosticCollection.clear(); this.diagnosticCollection.dispose(); @@ -124,7 +135,7 @@ export default class PHPValidationProvider { let oldExecutable = this.executable; if (section) { this.validationEnabled = section.get('validate.enable', true); - this.executable = section.get('validate.executablePath', null); + this.executable = this.workspaceExecutablePath || section.get('validate.executablePath', null); this.trigger = RunTrigger.from(section.get('validate.run', RunTrigger.strings.onSave)); } this.delayers = Object.create(null); @@ -226,7 +237,11 @@ export default class PHPValidationProvider { private showError(error: any, executable: string): void { let message: string = null; if (error.code === 'ENOENT') { - message = localize('noPHP', 'Cannot validate the php file. The php program was not found. Use the \'php.validate.executablePath\' setting to configure the location of \'php\''); + if (executable) { + message = localize('wrongExecutable', 'Cannot validate since {0} is not a valid php executable. Click on the Path status bar item to configure the executable.', executable); + } else { + message = localize('noExecutable', 'Cannot validate since no PHP executable is set. Click on the Path status bar item to configure the executable.'); + } } else { message = error.message ? error.message : localize('unknownReason', 'Failed to run php using path: {0}. Reason is unknown.', executable); } diff --git a/extensions/php/src/phpMain.ts b/extensions/php/src/phpMain.ts index 4ebc68e31da..d5fdf30e8ea 100644 --- a/extensions/php/src/phpMain.ts +++ b/extensions/php/src/phpMain.ts @@ -2,30 +2,161 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - 'use strict'; +import * as fs from 'fs'; +import * as path from 'path'; + import PHPCompletionItemProvider from './features/completionItemProvider'; import PHPHoverProvider from './features/hoverProvider'; import PHPSignatureHelpProvider from './features/signatureHelpProvider'; import PHPValidationProvider from './features/validationProvider'; -import { ExtensionContext, languages, env } from 'vscode'; +import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; +nls.config({ locale: vscode.env.language }); +let localize = nls.loadMessageBundle(); -export function activate(context: ExtensionContext): any { - nls.config({ locale: env.language }); +const MigratedKey = 'php.validate.executablePaht.migrated'; +const PathKey = 'php.validate.executablePath'; + +namespace is { + const toString = Object.prototype.toString; + + export function string(value: any): value is string { + return toString.call(value) === '[object String]'; + } +} + +let statusBarItem: vscode.StatusBarItem; + +export function activate(context: vscode.ExtensionContext): any { + + let workspaceExecutablePath = context.workspaceState.get(PathKey, undefined); + let migrated = context.workspaceState.get(MigratedKey, false); + let validator = new PHPValidationProvider(workspaceExecutablePath); + context.subscriptions.push(vscode.commands.registerCommand('_php.onPathClicked', () => { + onPathClicked(context, validator); + })); + + statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE); + statusBarItem.text = localize('php.path', 'Path'); + statusBarItem.color = 'white'; + statusBarItem.command = '_php.onPathClicked'; + updateStatusBarItem(context); + vscode.workspace.onDidChangeConfiguration(() => updateStatusBarItem(context)); + statusBarItem.show(); + + if (workspaceExecutablePath === void 0 && !migrated) { + let settingsExecutablePath = readLocalExecutableSetting(); + if (settingsExecutablePath) { + migrateExecutablePath(settingsExecutablePath).then((value) => { + // User has pressed escape; + if (!value) { + // activate the validator with the current settings. + validator.activate(context.subscriptions); + return; + } + context.workspaceState.update(MigratedKey, true); + context.workspaceState.update(PathKey, value); + validator.updateWorkspaceExecutablePath(value, false); + validator.activate(context.subscriptions); + updateStatusBarItem(context); + }); + } else { + validator.activate(context.subscriptions); + } + } else { + validator.activate(context.subscriptions); + } // add providers - context.subscriptions.push(languages.registerCompletionItemProvider('php', new PHPCompletionItemProvider(), '.', '$')); - context.subscriptions.push(languages.registerHoverProvider('php', new PHPHoverProvider())); - context.subscriptions.push(languages.registerSignatureHelpProvider('php', new PHPSignatureHelpProvider(), '(', ',')); + context.subscriptions.push(vscode.languages.registerCompletionItemProvider('php', new PHPCompletionItemProvider(), '.', '$')); + context.subscriptions.push(vscode.languages.registerHoverProvider('php', new PHPHoverProvider())); + context.subscriptions.push(vscode.languages.registerSignatureHelpProvider('php', new PHPSignatureHelpProvider(), '(', ',')); - let validator = new PHPValidationProvider(); - validator.activate(context.subscriptions); // need to set in the extension host as well as the completion provider uses it. - languages.setLanguageConfiguration('php', { + vscode.languages.setLanguageConfiguration('php', { wordPattern: /(-?\d*\.\d\w*)|([^\-\`\~\!\@\#\%\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g }); +} + +function updateStatusBarItem(context: vscode.ExtensionContext): void { + statusBarItem.tooltip = context.workspaceState.get(PathKey, undefined) || vscode.workspace.getConfiguration('php.validate').get('executablePath', undefined); +} + +function onPathClicked(context: vscode.ExtensionContext, validator: PHPValidationProvider) { + let value = context.workspaceState.get(PathKey); + vscode.window.showInputBox({ prompt: localize('php.enterPath', 'The path to the PHP executable'), value: value || '' }).then(value => { + if (!value) { + // User pressed Escape + return; + } + context.workspaceState.update(PathKey, value); + validator.updateWorkspaceExecutablePath(value, true); + updateStatusBarItem(context); + }, (error) => { + }); +} + +function migrateExecutablePath(settingsExecutablePath: string): Thenable { + return vscode.window.showInputBox( + { + prompt: localize('php.migrateExecutablePath', 'Use the above path as the PHP executable path?'), + value: settingsExecutablePath + } + ); +} + +function readLocalExecutableSetting(): string { + function stripComments(content: string): string { + /** + * First capturing group matches double quoted string + * Second matches single quotes string + * Third matches block comments + * Fourth matches line comments + */ + var regexp: RegExp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; + let result = content.replace(regexp, (match, m1, m2, m3, m4) => { + // Only one of m1, m2, m3, m4 matches + if (m3) { + // A block comment. Replace with nothing + return ''; + } else if (m4) { + // A line comment. If it ends in \r?\n then keep it. + let length = m4.length; + if (length > 2 && m4[length - 1] === '\n') { + return m4[length - 2] === '\r' ? '\r\n' : '\n'; + } else { + return ''; + } + } else { + // We match a string + return match; + } + }); + return result; + }; + + try { + let rootPath = vscode.workspace.rootPath; + if (!rootPath) { + return undefined; + } + let settingsFile = path.join(rootPath, '.vscode', 'settings.json'); + if (!fs.existsSync(settingsFile)) { + return undefined; + } + let content = fs.readFileSync(settingsFile, 'utf8'); + if (!content || content.length === 0) { + return undefined; + } + content = stripComments(content); + let json = JSON.parse(content); + let value = json['php.validate.executablePath']; + return is.string(value) ? value : undefined; + } catch (error) { + } + return undefined; } \ No newline at end of file