Hello Code

This commit is contained in:
Erich Gamma
2015-11-13 14:39:38 +01:00
commit 8f35cc4768
1897 changed files with 704173 additions and 0 deletions

View 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);
}
}

View 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);
}
}
}

File diff suppressed because it is too large Load Diff

View 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;
}
}

View 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);
}
}

View 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();
}
});
}
}