mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 20:13:32 +01:00
Hello Code
This commit is contained in:
69
extensions/php/src/features/completionItemProvider.ts
Normal file
69
extensions/php/src/features/completionItemProvider.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 {CompletionItemProvider, CompletionItem, CompletionItemKind, CancellationToken, TextDocument, Range, Position} from 'vscode';
|
||||
import phpGlobals = require('./phpGlobals');
|
||||
|
||||
export default class PHPCompletionItemProvider implements CompletionItemProvider {
|
||||
|
||||
public triggerCharacters = ['.', ':', '$'];
|
||||
|
||||
public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise<CompletionItem[]> {
|
||||
let result: CompletionItem[] = [];
|
||||
|
||||
var added : any = {};
|
||||
var createNewProposal = function(kind: CompletionItemKind, name: string, entry: phpGlobals.IEntry) : CompletionItem {
|
||||
var proposal : CompletionItem = new CompletionItem(name);
|
||||
proposal.kind = kind;
|
||||
if (entry) {
|
||||
if (entry.description) {
|
||||
proposal.documentation= entry.description;
|
||||
}
|
||||
if (entry.signature) {
|
||||
proposal.detail = entry.signature;
|
||||
}
|
||||
}
|
||||
return proposal;
|
||||
};
|
||||
|
||||
for (var name in phpGlobals.globalvariables) {
|
||||
if (phpGlobals.globalvariables.hasOwnProperty(name)) {
|
||||
added[name] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Variable, name, phpGlobals.globalvariables[name]));
|
||||
}
|
||||
}
|
||||
for (var name in phpGlobals.globalfunctions) {
|
||||
if (phpGlobals.globalfunctions.hasOwnProperty(name)) {
|
||||
added[name] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Function, name, phpGlobals.globalfunctions[name]));
|
||||
}
|
||||
}
|
||||
for (var name in phpGlobals.compiletimeconstants) {
|
||||
if (phpGlobals.compiletimeconstants.hasOwnProperty(name)) {
|
||||
added[name] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Field, name, phpGlobals.compiletimeconstants[name]));
|
||||
}
|
||||
}
|
||||
for (var name in phpGlobals.keywords) {
|
||||
if (phpGlobals.keywords.hasOwnProperty(name)) {
|
||||
added[name] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Keyword, name, phpGlobals.keywords[name]));
|
||||
}
|
||||
}
|
||||
|
||||
var text = document.getText();
|
||||
var variableMatch = /\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/g;
|
||||
var match : RegExpExecArray = null;
|
||||
while (match = variableMatch.exec(text)) {
|
||||
var word = match[0];
|
||||
if (!added[word]) {
|
||||
result.push(createNewProposal(CompletionItemKind.Variable, name, null));
|
||||
}
|
||||
}
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
28
extensions/php/src/features/hoverProvider.ts
Normal file
28
extensions/php/src/features/hoverProvider.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 {HoverProvider, Hover, MarkedString, TextDocument, CancellationToken, Range, Position} from 'vscode';
|
||||
import phpGlobals = require('./phpGlobals');
|
||||
|
||||
export default class PHPHoverProvider implements HoverProvider {
|
||||
|
||||
public provideHover(document: TextDocument, position: Position, token: CancellationToken): Hover {
|
||||
let wordRange = document.getWordRangeAtPosition(position);
|
||||
if (!wordRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
let name = document.getText(wordRange);
|
||||
|
||||
var entry = phpGlobals.globalfunctions[name] || phpGlobals.compiletimeconstants[name] || phpGlobals.globalvariables[name] || phpGlobals.keywords[name];
|
||||
if (entry && entry.description) {
|
||||
let signature = name + (entry.signature || '');
|
||||
let contents: MarkedString[] = [entry.description, { language: 'php', value: signature }];
|
||||
return new Hover(contents, wordRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
5759
extensions/php/src/features/phpGlobals.ts
Normal file
5759
extensions/php/src/features/phpGlobals.ts
Normal file
File diff suppressed because it is too large
Load Diff
175
extensions/php/src/features/signatureHelpProvider.ts
Normal file
175
extensions/php/src/features/signatureHelpProvider.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 {SignatureHelpProvider, SignatureHelp, SignatureInformation, ParameterInformation, Uri, CancellationToken, TextLine, TextDocument, Position} from 'vscode';
|
||||
import phpGlobals = require('./phpGlobals');
|
||||
|
||||
var _NL = '\n'.charCodeAt(0);
|
||||
var _TAB = '\t'.charCodeAt(0);
|
||||
var _WSB = ' '.charCodeAt(0);
|
||||
var _LBracket = '['.charCodeAt(0);
|
||||
var _RBracket = ']'.charCodeAt(0);
|
||||
var _LCurly = '{'.charCodeAt(0);
|
||||
var _RCurly = '}'.charCodeAt(0);
|
||||
var _LParent = '('.charCodeAt(0);
|
||||
var _RParent = ')'.charCodeAt(0);
|
||||
var _Comma = ','.charCodeAt(0);
|
||||
var _Quote = '\''.charCodeAt(0);
|
||||
var _DQuote = '"'.charCodeAt(0);
|
||||
var _USC = '_'.charCodeAt(0);
|
||||
var _a = 'a'.charCodeAt(0);
|
||||
var _z = 'z'.charCodeAt(0);
|
||||
var _A = 'A'.charCodeAt(0);
|
||||
var _Z = 'Z'.charCodeAt(0);
|
||||
var _0 = '0'.charCodeAt(0);
|
||||
var _9 = '9'.charCodeAt(0);
|
||||
|
||||
var BOF = 0;
|
||||
|
||||
|
||||
class BackwardIterator {
|
||||
private lineNumber: number;
|
||||
private offset: number;
|
||||
private line: string;
|
||||
private model : TextDocument;
|
||||
|
||||
constructor(model: TextDocument, offset: number, lineNumber: number) {
|
||||
this.lineNumber = lineNumber;
|
||||
this.offset = offset;
|
||||
this.line = model.lineAt(this.lineNumber).text;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public hasNext() : boolean {
|
||||
return this.lineNumber >= 0;
|
||||
}
|
||||
|
||||
public next() : number {
|
||||
if (this.offset < 0) {
|
||||
if (this.lineNumber > 0) {
|
||||
this.lineNumber--;
|
||||
this.line = this.model.lineAt(this.lineNumber).text;
|
||||
this.offset = this.line.length - 1;
|
||||
return _NL;
|
||||
}
|
||||
this.lineNumber = 0;
|
||||
return BOF;
|
||||
}
|
||||
var ch = this.line.charCodeAt(this.offset);
|
||||
this.offset--;
|
||||
return ch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default class PHPSignatureHelpProvider implements SignatureHelpProvider {
|
||||
|
||||
public provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken): Promise<SignatureHelp> {
|
||||
console.log('provideSignatureHelp');
|
||||
var iterator = new BackwardIterator(document, position.character - 1, position.line);
|
||||
|
||||
var paramCount = this.readArguments(iterator);
|
||||
if (paramCount < 0) {
|
||||
console.log('paramCount ' + 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
var ident = this.readIdent(iterator);
|
||||
if (!ident) {
|
||||
console.log('ident ' + ident);
|
||||
return null;
|
||||
}
|
||||
|
||||
var entry = phpGlobals.globalfunctions[ident] || phpGlobals.keywords[ident];
|
||||
if (!entry || !entry.signature) {
|
||||
console.log('no entry ');
|
||||
return null;
|
||||
}
|
||||
var paramsString = entry.signature.substring(0, entry.signature.lastIndexOf(')') + 1);
|
||||
let signatureInfo = new SignatureInformation(ident + paramsString, entry.description);
|
||||
|
||||
var re = /\w*\s+\&?\$[\w_\.]+|void/g;
|
||||
var match: RegExpExecArray = null;
|
||||
while ((match = re.exec(paramsString)) !== null) {
|
||||
signatureInfo.parameters.push({ label: match[0], documentation: ''});
|
||||
}
|
||||
|
||||
let ret = new SignatureHelp();
|
||||
ret.signatures.push(signatureInfo);
|
||||
ret.activeSignature = 0;
|
||||
ret.activeParameter = Math.min(paramCount, signatureInfo.parameters.length - 1);
|
||||
console.log(ret);
|
||||
return Promise.resolve(ret);
|
||||
}
|
||||
|
||||
private readArguments(iterator: BackwardIterator) : number {
|
||||
var parentNesting = 0;
|
||||
var bracketNesting = 0;
|
||||
var curlyNesting = 0;
|
||||
var paramCount = 0;
|
||||
while (iterator.hasNext()) {
|
||||
var ch = iterator.next();
|
||||
switch (ch) {
|
||||
case _LParent:
|
||||
parentNesting--;
|
||||
if (parentNesting < 0) {
|
||||
return paramCount;
|
||||
}
|
||||
break;
|
||||
case _RParent: parentNesting++; break;
|
||||
case _LCurly: curlyNesting--; break;
|
||||
case _RCurly: curlyNesting++; break;
|
||||
case _LBracket: bracketNesting--; break;
|
||||
case _RBracket: bracketNesting++; break;
|
||||
case _DQuote:
|
||||
case _Quote:
|
||||
while (iterator.hasNext() && ch !== iterator.next()) {
|
||||
// find the closing quote or double quote
|
||||
}
|
||||
break;
|
||||
case _Comma:
|
||||
if (!parentNesting && !bracketNesting && !curlyNesting) {
|
||||
paramCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private isIdentPart(ch:number) : boolean {
|
||||
if(ch === _USC || // _
|
||||
ch >= _a && ch <= _z || // a-z
|
||||
ch >= _A && ch <= _Z || // A-Z
|
||||
ch >= _0 && ch <= _9 || // 0/9
|
||||
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private readIdent(iterator: BackwardIterator) : string {
|
||||
var identStarted = false;
|
||||
var ident = '';
|
||||
while (iterator.hasNext()) {
|
||||
var ch = iterator.next();
|
||||
if (!identStarted && (ch === _WSB || ch === _TAB || ch === _NL)) {
|
||||
continue;
|
||||
}
|
||||
if (this.isIdentPart(ch)) {
|
||||
identStarted = true;
|
||||
ident= String.fromCharCode(ch) + ident;
|
||||
} else if (identStarted) {
|
||||
return ident;
|
||||
}
|
||||
}
|
||||
return ident;
|
||||
}
|
||||
|
||||
}
|
||||
185
extensions/php/src/features/utils/async.ts
Normal file
185
extensions/php/src/features/utils/async.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface ITask<T> {
|
||||
(): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to prevent accumulation of sequential async tasks.
|
||||
*
|
||||
* Imagine a mail man with the sole task of delivering letters. As soon as
|
||||
* a letter submitted for delivery, he drives to the destination, delivers it
|
||||
* and returns to his base. Imagine that during the trip, N more letters were submitted.
|
||||
* When the mail man returns, he picks those N letters and delivers them all in a
|
||||
* single trip. Even though N+1 submissions occurred, only 2 deliveries were made.
|
||||
*
|
||||
* The throttler implements this via the queue() method, by providing it a task
|
||||
* factory. Following the example:
|
||||
*
|
||||
* var throttler = new Throttler();
|
||||
* var letters = [];
|
||||
*
|
||||
* function letterReceived(l) {
|
||||
* letters.push(l);
|
||||
* throttler.queue(() => { return makeTheTrip(); });
|
||||
* }
|
||||
*/
|
||||
export class Throttler<T> {
|
||||
|
||||
private activePromise: Promise<T>;
|
||||
private queuedPromise: Promise<T>;
|
||||
private queuedPromiseFactory: ITask<Promise<T>>;
|
||||
|
||||
constructor() {
|
||||
this.activePromise = null;
|
||||
this.queuedPromise = null;
|
||||
this.queuedPromiseFactory = null;
|
||||
}
|
||||
|
||||
public queue(promiseFactory: ITask<Promise<T>>): Promise<T> {
|
||||
if (this.activePromise) {
|
||||
this.queuedPromiseFactory = promiseFactory;
|
||||
|
||||
if (!this.queuedPromise) {
|
||||
var onComplete = () => {
|
||||
this.queuedPromise = null;
|
||||
|
||||
var result = this.queue(this.queuedPromiseFactory);
|
||||
this.queuedPromiseFactory = null;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
this.queuedPromise = new Promise<T>((resolve, reject) => {
|
||||
this.activePromise.then(onComplete, onComplete).then(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.queuedPromise.then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
this.activePromise = promiseFactory();
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.activePromise.then((result: T) => {
|
||||
this.activePromise = null;
|
||||
resolve(result);
|
||||
}, (err: any) => {
|
||||
this.activePromise = null;
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to delay execution of a task that is being requested often.
|
||||
*
|
||||
* Following the throttler, now imagine the mail man wants to optimize the number of
|
||||
* trips proactively. The trip itself can be long, so the he decides not to make the trip
|
||||
* as soon as a letter is submitted. Instead he waits a while, in case more
|
||||
* letters are submitted. After said waiting period, if no letters were submitted, he
|
||||
* decides to make the trip. Imagine that N more letters were submitted after the first
|
||||
* one, all within a short period of time between each other. Even though N+1
|
||||
* submissions occurred, only 1 delivery was made.
|
||||
*
|
||||
* The delayer offers this behavior via the trigger() method, into which both the task
|
||||
* to be executed and the waiting period (delay) must be passed in as arguments. Following
|
||||
* the example:
|
||||
*
|
||||
* var delayer = new Delayer(WAITING_PERIOD);
|
||||
* var letters = [];
|
||||
*
|
||||
* function letterReceived(l) {
|
||||
* letters.push(l);
|
||||
* delayer.trigger(() => { return makeTheTrip(); });
|
||||
* }
|
||||
*/
|
||||
export class Delayer<T> {
|
||||
|
||||
public defaultDelay: number;
|
||||
private timeout: number;
|
||||
private completionPromise: Promise<T>;
|
||||
private onResolve: (value: T | Thenable<T>) => void;
|
||||
private task: ITask<T>;
|
||||
|
||||
constructor(defaultDelay: number) {
|
||||
this.defaultDelay = defaultDelay;
|
||||
this.timeout = null;
|
||||
this.completionPromise = null;
|
||||
this.onResolve = null;
|
||||
this.task = null;
|
||||
}
|
||||
|
||||
public trigger(task: ITask<T>, delay: number = this.defaultDelay): Promise<T> {
|
||||
this.task = task;
|
||||
this.cancelTimeout();
|
||||
|
||||
if (!this.completionPromise) {
|
||||
this.completionPromise = new Promise<T>((resolve, reject) => {
|
||||
this.onResolve = resolve;
|
||||
}).then(() => {
|
||||
this.completionPromise = null;
|
||||
this.onResolve = null;
|
||||
|
||||
var result = this.task();
|
||||
this.task = null;
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.timeout = null;
|
||||
this.onResolve(null);
|
||||
}, delay);
|
||||
|
||||
return this.completionPromise;
|
||||
}
|
||||
|
||||
public isTriggered(): boolean {
|
||||
return this.timeout !== null;
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.cancelTimeout();
|
||||
|
||||
if (this.completionPromise) {
|
||||
this.completionPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
private cancelTimeout(): void {
|
||||
if (this.timeout !== null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to delay execution of a task that is being requested often, while
|
||||
* preventing accumulation of consecutive executions, while the task runs.
|
||||
*
|
||||
* Simply combine the two mail man strategies from the Throttler and Delayer
|
||||
* helpers, for an analogy.
|
||||
*/
|
||||
export class ThrottledDelayer<T> extends Delayer<Promise<T>> {
|
||||
|
||||
private throttler: Throttler<T>;
|
||||
|
||||
constructor(defaultDelay: number) {
|
||||
super(defaultDelay);
|
||||
|
||||
this.throttler = new Throttler();
|
||||
}
|
||||
|
||||
public trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<Promise<T>> {
|
||||
return super.trigger(() => this.throttler.queue(promiseFactory), delay);
|
||||
}
|
||||
}
|
||||
225
extensions/php/src/features/validationProvider.ts
Normal file
225
extensions/php/src/features/validationProvider.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 path from 'path';
|
||||
import * as cp from 'child_process';
|
||||
import ChildProcess = cp.ChildProcess;
|
||||
import { NodeStringDecoder, StringDecoder } from 'string_decoder';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { ThrottledDelayer } from './utils/async';
|
||||
|
||||
export class LineDecoder {
|
||||
private stringDecoder: NodeStringDecoder;
|
||||
private remaining: string;
|
||||
|
||||
constructor(encoding: string = 'utf8') {
|
||||
this.stringDecoder = new StringDecoder(encoding);
|
||||
this.remaining = null;
|
||||
}
|
||||
|
||||
public write(buffer: NodeBuffer): string[] {
|
||||
var result: string[] = [];
|
||||
var value = this.remaining
|
||||
? this.remaining + this.stringDecoder.write(buffer)
|
||||
: this.stringDecoder.write(buffer);
|
||||
|
||||
if (value.length < 1) {
|
||||
return result;
|
||||
}
|
||||
var start = 0;
|
||||
var ch: number;
|
||||
while (start < value.length && ((ch = value.charCodeAt(start)) === 13 || ch === 10)) {
|
||||
start++;
|
||||
}
|
||||
var idx = start;
|
||||
while (idx < value.length) {
|
||||
ch = value.charCodeAt(idx);
|
||||
if (ch === 13 || ch === 10) {
|
||||
result.push(value.substring(start, idx));
|
||||
idx++;
|
||||
while (idx < value.length && ((ch = value.charCodeAt(idx)) === 13 || ch === 10)) {
|
||||
idx++;
|
||||
}
|
||||
start = idx;
|
||||
} else {
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
this.remaining = start < value.length ? value.substr(start) : null;
|
||||
return result;
|
||||
}
|
||||
|
||||
public end(): string {
|
||||
return this.remaining;
|
||||
}
|
||||
}
|
||||
|
||||
enum RunTrigger {
|
||||
onSave,
|
||||
onType
|
||||
}
|
||||
|
||||
namespace RunTrigger {
|
||||
export let strings = {
|
||||
onSave: 'onSave',
|
||||
onType: 'onType'
|
||||
}
|
||||
export let from = function(value: string): RunTrigger {
|
||||
if (value === 'onType') {
|
||||
return RunTrigger.onType;
|
||||
} else {
|
||||
return RunTrigger.onSave;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class PHPValidationProvider {
|
||||
|
||||
private static MatchExpression: RegExp = /(?:(?:Parse|Fatal) error): (.*)(?: in )(.*?)(?: on line )(\d+)/;
|
||||
private static BufferArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off'];
|
||||
private static FileArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off', '-f'];
|
||||
|
||||
private executable: string;
|
||||
private trigger: RunTrigger;
|
||||
private executableNotFound: boolean;
|
||||
|
||||
private documentListener: vscode.Disposable;
|
||||
private diagnosticCollection: vscode.DiagnosticCollection;
|
||||
private delayers: { [key: string]: ThrottledDelayer<void> };
|
||||
|
||||
constructor() {
|
||||
this.executable = null;
|
||||
this.trigger = RunTrigger.onSave;
|
||||
this.executableNotFound = false;
|
||||
}
|
||||
|
||||
public activate(subscriptions: vscode.Disposable[]) {
|
||||
this.diagnosticCollection = vscode.languages.createDiagnosticCollection();
|
||||
subscriptions.push(this);
|
||||
vscode.workspace.onDidChangeConfiguration(this.loadConfiguration, this, subscriptions);
|
||||
this.loadConfiguration();
|
||||
|
||||
vscode.workspace.onDidOpenTextDocument(this.triggerValidate, this, subscriptions);
|
||||
vscode.workspace.onDidCloseTextDocument((textDocument)=> {
|
||||
this.diagnosticCollection.delete(textDocument.uri);
|
||||
delete this.delayers[textDocument.uri.toString()];
|
||||
}, null, subscriptions);
|
||||
|
||||
// Validate all open php documents
|
||||
vscode.workspace.textDocuments.forEach(this.triggerValidate, this);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.diagnosticCollection.clear();
|
||||
this.diagnosticCollection.dispose();
|
||||
}
|
||||
|
||||
private loadConfiguration(): void {
|
||||
let section = vscode.workspace.getConfiguration('php');
|
||||
let oldExecutable = this.executable;
|
||||
if (section) {
|
||||
this.executable = section.get<string>('validate.executablePath', null);
|
||||
this.trigger = RunTrigger.from(section.get<string>('validate.run', RunTrigger.strings.onSave));
|
||||
}
|
||||
this.delayers = Object.create(null);
|
||||
if (this.executableNotFound) {
|
||||
this.executableNotFound = oldExecutable === this.executable;
|
||||
}
|
||||
if (this.documentListener) {
|
||||
this.documentListener.dispose();
|
||||
}
|
||||
if (this.trigger === RunTrigger.onType) {
|
||||
this.documentListener = vscode.workspace.onDidChangeTextDocument((e) => {
|
||||
this.triggerValidate(e.document);
|
||||
});
|
||||
} else {
|
||||
this.documentListener = vscode.workspace.onDidSaveTextDocument(this.triggerValidate, this);
|
||||
}
|
||||
// Configuration has changed. Reevaluate all documents.
|
||||
vscode.workspace.textDocuments.forEach(this.triggerValidate, this);
|
||||
}
|
||||
|
||||
private triggerValidate(textDocument: vscode.TextDocument): void {
|
||||
if (textDocument.languageId !== 'php' || this.executableNotFound) {
|
||||
return;
|
||||
}
|
||||
let key = textDocument.uri.toString();
|
||||
let delayer = this.delayers[key];
|
||||
if (!delayer) {
|
||||
delayer = new ThrottledDelayer<void>(this.trigger === RunTrigger.onType ? 250 : 0);
|
||||
this.delayers[key];
|
||||
}
|
||||
delayer.trigger(() => this.doValidate(textDocument) );
|
||||
}
|
||||
|
||||
private doValidate(textDocument: vscode.TextDocument): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let executable = this.executable || 'php';
|
||||
let filePath = textDocument.fileName;
|
||||
let decoder = new LineDecoder();
|
||||
let diagnostics: vscode.Diagnostic[] = [];
|
||||
let processLine = (line: string) => {
|
||||
let matches = line.match(PHPValidationProvider.MatchExpression);
|
||||
if (matches) {
|
||||
let message = matches[1];
|
||||
let file = matches[2];
|
||||
let line = parseInt(matches[3]) - 1;
|
||||
let diagnostic: vscode.Diagnostic = new vscode.Diagnostic(
|
||||
new vscode.Range(line, 0, line, Number.MAX_VALUE),
|
||||
message
|
||||
)
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
let options = vscode.workspace.rootPath ? { cwd: vscode.workspace.rootPath } : undefined;
|
||||
let args: string[];
|
||||
if (this.trigger === RunTrigger.onSave) {
|
||||
args = PHPValidationProvider.FileArgs.slice(0);
|
||||
args.push(textDocument.fileName);
|
||||
} else {
|
||||
args = PHPValidationProvider.BufferArgs;
|
||||
}
|
||||
let childProcess = cp.spawn(executable, args, options);
|
||||
childProcess.on('error', (error: Error) => {
|
||||
if (this.executableNotFound) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
let message: string = null;
|
||||
if ((<any>error).code === 'ENOENT') {
|
||||
message = `Cannot validate the php file. The php program was not found. Use the 'php.validate.executablePath' setting to configure the location of 'php'`;
|
||||
} else {
|
||||
message = error.message ? error.message : `Failed to run php using path: ${executable}. Reason is unknown.`;
|
||||
}
|
||||
vscode.window.showInformationMessage(message);
|
||||
this.executableNotFound = true;
|
||||
resolve();
|
||||
});
|
||||
if (childProcess.pid) {
|
||||
if (this.trigger === RunTrigger.onType) {
|
||||
childProcess.stdin.write(textDocument.getText());
|
||||
childProcess.stdin.end();
|
||||
}
|
||||
childProcess.stdout.on('data', (data: Buffer) => {
|
||||
decoder.write(data).forEach(processLine);
|
||||
});
|
||||
childProcess.stdout.on('end', () => {
|
||||
let line = decoder.end();
|
||||
if (line) {
|
||||
processLine(line);
|
||||
}
|
||||
this.diagnosticCollection.set(textDocument.uri, diagnostics);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user