174 lines
4.8 KiB
TypeScript
174 lines
4.8 KiB
TypeScript
|
/*---------------------------------------------------------------------------------------------
|
||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||
|
*--------------------------------------------------------------------------------------------*/
|
||
|
|
||
|
import { SignatureHelpProvider, SignatureHelp, SignatureInformation, CancellationToken, TextDocument, Position, workspace } from 'vscode';
|
||
|
import phpGlobals = require('./phpGlobals');
|
||
|
import phpGlobalFunctions = require('./phpGlobalFunctions');
|
||
|
|
||
|
const _NL = '\n'.charCodeAt(0);
|
||
|
const _TAB = '\t'.charCodeAt(0);
|
||
|
const _WSB = ' '.charCodeAt(0);
|
||
|
const _LBracket = '['.charCodeAt(0);
|
||
|
const _RBracket = ']'.charCodeAt(0);
|
||
|
const _LCurly = '{'.charCodeAt(0);
|
||
|
const _RCurly = '}'.charCodeAt(0);
|
||
|
const _LParent = '('.charCodeAt(0);
|
||
|
const _RParent = ')'.charCodeAt(0);
|
||
|
const _Comma = ','.charCodeAt(0);
|
||
|
const _Quote = '\''.charCodeAt(0);
|
||
|
const _DQuote = '"'.charCodeAt(0);
|
||
|
const _USC = '_'.charCodeAt(0);
|
||
|
const _a = 'a'.charCodeAt(0);
|
||
|
const _z = 'z'.charCodeAt(0);
|
||
|
const _A = 'A'.charCodeAt(0);
|
||
|
const _Z = 'Z'.charCodeAt(0);
|
||
|
const _0 = '0'.charCodeAt(0);
|
||
|
const _9 = '9'.charCodeAt(0);
|
||
|
|
||
|
const 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 = -1;
|
||
|
return BOF;
|
||
|
}
|
||
|
let 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> | null {
|
||
|
let enable = workspace.getConfiguration('php').get<boolean>('suggest.basic', true);
|
||
|
if (!enable) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
let iterator = new BackwardIterator(document, position.character - 1, position.line);
|
||
|
|
||
|
let paramCount = this.readArguments(iterator);
|
||
|
if (paramCount < 0) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
let ident = this.readIdent(iterator);
|
||
|
if (!ident) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
let entry = phpGlobalFunctions.globalfunctions[ident] || phpGlobals.keywords[ident];
|
||
|
if (!entry || !entry.signature) {
|
||
|
return null;
|
||
|
}
|
||
|
let paramsString = entry.signature.substring(0, entry.signature.lastIndexOf(')') + 1);
|
||
|
let signatureInfo = new SignatureInformation(ident + paramsString, entry.description);
|
||
|
|
||
|
let re = /\w*\s+\&?\$[\w_\.]+|void/g;
|
||
|
let match: RegExpExecArray | null = 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);
|
||
|
return Promise.resolve(ret);
|
||
|
}
|
||
|
|
||
|
private readArguments(iterator: BackwardIterator): number {
|
||
|
let parentNesting = 0;
|
||
|
let bracketNesting = 0;
|
||
|
let curlyNesting = 0;
|
||
|
let paramCount = 0;
|
||
|
while (iterator.hasNext()) {
|
||
|
let 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 {
|
||
|
let identStarted = false;
|
||
|
let ident = '';
|
||
|
while (iterator.hasNext()) {
|
||
|
let 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;
|
||
|
}
|
||
|
|
||
|
}
|