Skip to content

Commit dce5f45

Browse files
committed
FIxes microsoft#19436: Add support to edit the php executable path as a non sharable workspace setting
1 parent ac59c51 commit dce5f45

2 files changed

Lines changed: 159 additions & 13 deletions

File tree

extensions/php/src/features/validationProvider.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export default class PHPValidationProvider {
9494
private diagnosticCollection: vscode.DiagnosticCollection;
9595
private delayers: { [key: string]: ThrottledDelayer<void> };
9696

97-
constructor() {
97+
constructor(private workspaceExecutablePath: string) {
9898
this.executable = null;
9999
this.validationEnabled = true;
100100
this.trigger = RunTrigger.onSave;
@@ -114,6 +114,17 @@ export default class PHPValidationProvider {
114114
}, null, subscriptions);
115115
}
116116

117+
public updateWorkspaceExecutablePath(workspaceExecutablePath: string, loadConfig: boolean = false): void {
118+
if (workspaceExecutablePath && workspaceExecutablePath.length === 0) {
119+
this.workspaceExecutablePath = undefined;
120+
} else {
121+
this.workspaceExecutablePath = workspaceExecutablePath;
122+
}
123+
if (loadConfig) {
124+
this.loadConfiguration();
125+
}
126+
}
127+
117128
public dispose(): void {
118129
this.diagnosticCollection.clear();
119130
this.diagnosticCollection.dispose();
@@ -124,7 +135,7 @@ export default class PHPValidationProvider {
124135
let oldExecutable = this.executable;
125136
if (section) {
126137
this.validationEnabled = section.get<boolean>('validate.enable', true);
127-
this.executable = section.get<string>('validate.executablePath', null);
138+
this.executable = this.workspaceExecutablePath || section.get<string>('validate.executablePath', null);
128139
this.trigger = RunTrigger.from(section.get<string>('validate.run', RunTrigger.strings.onSave));
129140
}
130141
this.delayers = Object.create(null);
@@ -226,7 +237,11 @@ export default class PHPValidationProvider {
226237
private showError(error: any, executable: string): void {
227238
let message: string = null;
228239
if (error.code === 'ENOENT') {
229-
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\'');
240+
if (executable) {
241+
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);
242+
} else {
243+
message = localize('noExecutable', 'Cannot validate since no PHP executable is set. Click on the Path status bar item to configure the executable.');
244+
}
230245
} else {
231246
message = error.message ? error.message : localize('unknownReason', 'Failed to run php using path: {0}. Reason is unknown.', executable);
232247
}

extensions/php/src/phpMain.ts

Lines changed: 141 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,161 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
5-
65
'use strict';
76

7+
import * as fs from 'fs';
8+
import * as path from 'path';
9+
810
import PHPCompletionItemProvider from './features/completionItemProvider';
911
import PHPHoverProvider from './features/hoverProvider';
1012
import PHPSignatureHelpProvider from './features/signatureHelpProvider';
1113
import PHPValidationProvider from './features/validationProvider';
12-
import { ExtensionContext, languages, env } from 'vscode';
14+
import * as vscode from 'vscode';
1315

1416
import * as nls from 'vscode-nls';
17+
nls.config({ locale: vscode.env.language });
18+
let localize = nls.loadMessageBundle();
19+
20+
const MigratedKey = 'php.validate.executablePaht.migrated';
21+
const PathKey = 'php.validate.executablePath';
22+
23+
namespace is {
24+
const toString = Object.prototype.toString;
25+
26+
export function string(value: any): value is string {
27+
return toString.call(value) === '[object String]';
28+
}
29+
}
30+
31+
let statusBarItem: vscode.StatusBarItem;
32+
33+
export function activate(context: vscode.ExtensionContext): any {
34+
35+
let workspaceExecutablePath = context.workspaceState.get<string>(PathKey, undefined);
36+
let migrated = context.workspaceState.get<boolean>(MigratedKey, false);
37+
let validator = new PHPValidationProvider(workspaceExecutablePath);
38+
context.subscriptions.push(vscode.commands.registerCommand('_php.onPathClicked', () => {
39+
onPathClicked(context, validator);
40+
}));
1541

16-
export function activate(context: ExtensionContext): any {
17-
nls.config({ locale: env.language });
42+
statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE);
43+
statusBarItem.text = localize('php.path', 'Path');
44+
statusBarItem.color = 'white';
45+
statusBarItem.command = '_php.onPathClicked';
46+
updateStatusBarItem(context);
47+
vscode.workspace.onDidChangeConfiguration(() => updateStatusBarItem(context));
48+
statusBarItem.show();
49+
50+
if (workspaceExecutablePath === void 0 && !migrated) {
51+
let settingsExecutablePath = readLocalExecutableSetting();
52+
if (settingsExecutablePath) {
53+
migrateExecutablePath(settingsExecutablePath).then((value) => {
54+
// User has pressed escape;
55+
if (!value) {
56+
// activate the validator with the current settings.
57+
validator.activate(context.subscriptions);
58+
return;
59+
}
60+
context.workspaceState.update(MigratedKey, true);
61+
context.workspaceState.update(PathKey, value);
62+
validator.updateWorkspaceExecutablePath(value, false);
63+
validator.activate(context.subscriptions);
64+
updateStatusBarItem(context);
65+
});
66+
} else {
67+
validator.activate(context.subscriptions);
68+
}
69+
} else {
70+
validator.activate(context.subscriptions);
71+
}
1872

1973
// add providers
20-
context.subscriptions.push(languages.registerCompletionItemProvider('php', new PHPCompletionItemProvider(), '.', '$'));
21-
context.subscriptions.push(languages.registerHoverProvider('php', new PHPHoverProvider()));
22-
context.subscriptions.push(languages.registerSignatureHelpProvider('php', new PHPSignatureHelpProvider(), '(', ','));
74+
context.subscriptions.push(vscode.languages.registerCompletionItemProvider('php', new PHPCompletionItemProvider(), '.', '$'));
75+
context.subscriptions.push(vscode.languages.registerHoverProvider('php', new PHPHoverProvider()));
76+
context.subscriptions.push(vscode.languages.registerSignatureHelpProvider('php', new PHPSignatureHelpProvider(), '(', ','));
2377

24-
let validator = new PHPValidationProvider();
25-
validator.activate(context.subscriptions);
2678

2779
// need to set in the extension host as well as the completion provider uses it.
28-
languages.setLanguageConfiguration('php', {
80+
vscode.languages.setLanguageConfiguration('php', {
2981
wordPattern: /(-?\d*\.\d\w*)|([^\-\`\~\!\@\#\%\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g
3082
});
83+
}
84+
85+
function updateStatusBarItem(context: vscode.ExtensionContext): void {
86+
statusBarItem.tooltip = context.workspaceState.get<string>(PathKey, undefined) || vscode.workspace.getConfiguration('php.validate').get('executablePath', undefined);
87+
}
88+
89+
function onPathClicked(context: vscode.ExtensionContext, validator: PHPValidationProvider) {
90+
let value = context.workspaceState.get<string>(PathKey);
91+
vscode.window.showInputBox({ prompt: localize('php.enterPath', 'The path to the PHP executable'), value: value || '' }).then(value => {
92+
if (!value) {
93+
// User pressed Escape
94+
return;
95+
}
96+
context.workspaceState.update(PathKey, value);
97+
validator.updateWorkspaceExecutablePath(value, true);
98+
updateStatusBarItem(context);
99+
}, (error) => {
100+
});
101+
}
102+
103+
function migrateExecutablePath(settingsExecutablePath: string): Thenable<string> {
104+
return vscode.window.showInputBox(
105+
{
106+
prompt: localize('php.migrateExecutablePath', 'Use the above path as the PHP executable path?'),
107+
value: settingsExecutablePath
108+
}
109+
);
110+
}
111+
112+
function readLocalExecutableSetting(): string {
113+
function stripComments(content: string): string {
114+
/**
115+
* First capturing group matches double quoted string
116+
* Second matches single quotes string
117+
* Third matches block comments
118+
* Fourth matches line comments
119+
*/
120+
var regexp: RegExp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
121+
let result = content.replace(regexp, (match, m1, m2, m3, m4) => {
122+
// Only one of m1, m2, m3, m4 matches
123+
if (m3) {
124+
// A block comment. Replace with nothing
125+
return '';
126+
} else if (m4) {
127+
// A line comment. If it ends in \r?\n then keep it.
128+
let length = m4.length;
129+
if (length > 2 && m4[length - 1] === '\n') {
130+
return m4[length - 2] === '\r' ? '\r\n' : '\n';
131+
} else {
132+
return '';
133+
}
134+
} else {
135+
// We match a string
136+
return match;
137+
}
138+
});
139+
return result;
140+
};
141+
142+
try {
143+
let rootPath = vscode.workspace.rootPath;
144+
if (!rootPath) {
145+
return undefined;
146+
}
147+
let settingsFile = path.join(rootPath, '.vscode', 'settings.json');
148+
if (!fs.existsSync(settingsFile)) {
149+
return undefined;
150+
}
151+
let content = fs.readFileSync(settingsFile, 'utf8');
152+
if (!content || content.length === 0) {
153+
return undefined;
154+
}
155+
content = stripComments(content);
156+
let json = JSON.parse(content);
157+
let value = json['php.validate.executablePath'];
158+
return is.string(value) ? value : undefined;
159+
} catch (error) {
160+
}
161+
return undefined;
31162
}

0 commit comments

Comments
 (0)