chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CompletionItemProvider, CompletionItem, CompletionItemKind, CancellationToken, TextDocument, Position, Range, TextEdit, workspace, CompletionContext } from 'vscode';
|
||||
import * as phpGlobals from './phpGlobals';
|
||||
import * as phpGlobalFunctions from './phpGlobalFunctions';
|
||||
|
||||
export default class PHPCompletionItemProvider implements CompletionItemProvider {
|
||||
|
||||
public provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken, context: CompletionContext): Promise<CompletionItem[]> {
|
||||
const result: CompletionItem[] = [];
|
||||
|
||||
const shouldProvideCompletionItems = workspace.getConfiguration('php').get<boolean>('suggest.basic', true);
|
||||
if (!shouldProvideCompletionItems) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
let range = document.getWordRangeAtPosition(position);
|
||||
const prefix = range ? document.getText(range) : '';
|
||||
if (!range) {
|
||||
range = new Range(position, position);
|
||||
}
|
||||
|
||||
if (context.triggerCharacter === '>') {
|
||||
const twoBeforeCursor = new Position(position.line, Math.max(0, position.character - 2));
|
||||
const previousTwoChars = document.getText(new Range(twoBeforeCursor, position));
|
||||
if (previousTwoChars !== '->') {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
const added: any = {};
|
||||
const createNewProposal = function (kind: CompletionItemKind, name: string, entry: phpGlobals.IEntry | null): CompletionItem {
|
||||
const 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;
|
||||
};
|
||||
|
||||
const matches = (name: string) => {
|
||||
return prefix.length === 0 || name.length >= prefix.length && name.substr(0, prefix.length) === prefix;
|
||||
};
|
||||
|
||||
if (matches('php') && range.start.character >= 2) {
|
||||
const twoBeforePosition = new Position(range.start.line, range.start.character - 2);
|
||||
const beforeWord = document.getText(new Range(twoBeforePosition, range.start));
|
||||
|
||||
if (beforeWord === '<?') {
|
||||
const proposal = createNewProposal(CompletionItemKind.Class, '<?php', null);
|
||||
proposal.textEdit = new TextEdit(new Range(twoBeforePosition, position), '<?php');
|
||||
result.push(proposal);
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
for (const globalvariables in phpGlobals.globalvariables) {
|
||||
if (phpGlobals.globalvariables.hasOwnProperty(globalvariables) && matches(globalvariables)) {
|
||||
added[globalvariables] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Variable, globalvariables, phpGlobals.globalvariables[globalvariables]));
|
||||
}
|
||||
}
|
||||
for (const globalfunctions in phpGlobalFunctions.globalfunctions) {
|
||||
if (phpGlobalFunctions.globalfunctions.hasOwnProperty(globalfunctions) && matches(globalfunctions)) {
|
||||
added[globalfunctions] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Function, globalfunctions, phpGlobalFunctions.globalfunctions[globalfunctions]));
|
||||
}
|
||||
}
|
||||
for (const compiletimeconstants in phpGlobals.compiletimeconstants) {
|
||||
if (phpGlobals.compiletimeconstants.hasOwnProperty(compiletimeconstants) && matches(compiletimeconstants)) {
|
||||
added[compiletimeconstants] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Field, compiletimeconstants, phpGlobals.compiletimeconstants[compiletimeconstants]));
|
||||
}
|
||||
}
|
||||
for (const keywords in phpGlobals.keywords) {
|
||||
if (phpGlobals.keywords.hasOwnProperty(keywords) && matches(keywords)) {
|
||||
added[keywords] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Keyword, keywords, phpGlobals.keywords[keywords]));
|
||||
}
|
||||
}
|
||||
|
||||
const text = document.getText();
|
||||
if (prefix[0] === '$') {
|
||||
const variableMatch = /\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/g;
|
||||
let match: RegExpExecArray | null = null;
|
||||
while (match = variableMatch.exec(text)) {
|
||||
const word = match[0];
|
||||
if (!added[word]) {
|
||||
added[word] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Variable, word, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
const functionMatch = /function\s+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*\(/g;
|
||||
let match2: RegExpExecArray | null = null;
|
||||
while (match2 = functionMatch.exec(text)) {
|
||||
const word2 = match2[1];
|
||||
if (!added[word2]) {
|
||||
added[word2] = true;
|
||||
result.push(createNewProposal(CompletionItemKind.Function, word2, null));
|
||||
}
|
||||
}
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { HoverProvider, Hover, MarkedString, TextDocument, CancellationToken, Position, workspace } from 'vscode';
|
||||
import { textToMarkedString } from './utils/markedTextUtil';
|
||||
import * as phpGlobals from './phpGlobals';
|
||||
import * as phpGlobalFunctions from './phpGlobalFunctions';
|
||||
|
||||
export default class PHPHoverProvider implements HoverProvider {
|
||||
|
||||
public provideHover(document: TextDocument, position: Position, _token: CancellationToken): Hover | undefined {
|
||||
const enable = workspace.getConfiguration('php').get<boolean>('suggest.basic', true);
|
||||
if (!enable) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const wordRange = document.getWordRangeAtPosition(position);
|
||||
if (!wordRange) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const name = document.getText(wordRange);
|
||||
|
||||
const entry = phpGlobalFunctions.globalfunctions[name] || phpGlobals.compiletimeconstants[name] || phpGlobals.globalvariables[name] || phpGlobals.keywords[name];
|
||||
if (entry && entry.description) {
|
||||
const signature = name + (entry.signature || '');
|
||||
const contents: MarkedString[] = [textToMarkedString(entry.description), { language: 'php', value: signature }];
|
||||
return new Hover(contents, wordRange);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,324 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// file generated from PHP53Schema.xml using php-exclude_generate_php_globals.js
|
||||
|
||||
export interface IEntry { description?: string; signature?: string }
|
||||
export interface IEntries { [name: string]: IEntry }
|
||||
|
||||
export const globalvariables: IEntries = {
|
||||
$GLOBALS: {
|
||||
description: 'An associative array containing references to all variables which are currently defined in the global scope of the script. The variable names are the keys of the array.',
|
||||
},
|
||||
$_SERVER: {
|
||||
description: '$_SERVER is an array containing information such as headers, paths, and script locations. The entries in this array are created by the web server. There is no guarantee that every web server will provide any of these; servers may omit some, or provide others not listed here. That said, a large number of these variables are accounted for in the CGI/1.1 specification, so you should be able to expect those.',
|
||||
},
|
||||
$_GET: {
|
||||
description: 'An associative array of variables passed to the current script via the URL parameters.',
|
||||
},
|
||||
$_POST: {
|
||||
description: 'An associative array of variables passed to the current script via the HTTP POST method.',
|
||||
},
|
||||
$_FILES: {
|
||||
description: 'An associative array of items uploaded to the current script via the HTTP POST method.',
|
||||
},
|
||||
$_REQUEST: {
|
||||
description: 'An associative array that by default contains the contents of $_GET, $_POST and $_COOKIE.',
|
||||
},
|
||||
$_SESSION: {
|
||||
description: 'An associative array containing session variables available to the current script. See the Session functions documentation for more information on how this is used.',
|
||||
},
|
||||
$_ENV: {
|
||||
description: 'An associative array of variables passed to the current script via the environment method. \r\n\r\nThese variables are imported into PHP\'s global namespace from the environment under which the PHP parser is running. Many are provided by the shell under which PHP is running and different systems are likely running different kinds of shells, a definitive list is impossible. Please see your shell\'s documentation for a list of defined environment variables. \r\n\r\nOther environment variables include the CGI variables, placed there regardless of whether PHP is running as a server module or CGI processor.',
|
||||
},
|
||||
$_COOKIE: {
|
||||
description: 'An associative array of variables passed to the current script via HTTP Cookies.',
|
||||
},
|
||||
$php_errormsg: {
|
||||
description: '$php_errormsg is a variable containing the text of the last error message generated by PHP. This variable will only be available within the scope in which the error occurred, and only if the track_errors configuration option is turned on (it defaults to off).',
|
||||
},
|
||||
$HTTP_RAW_POST_DATA: {
|
||||
description: '$HTTP_RAW_POST_DATA contains the raw POST data. See always_populate_raw_post_data',
|
||||
},
|
||||
$http_response_header: {
|
||||
description: 'The $http_response_header array is similar to the get_headers() function. When using the HTTP wrapper, $http_response_header will be populated with the HTTP response headers. $http_response_header will be created in the local scope.',
|
||||
},
|
||||
$argc: {
|
||||
description: 'Contains the number of arguments passed to the current script when running from the command line.',
|
||||
},
|
||||
$argv: {
|
||||
description: 'Contains an array of all the arguments passed to the script when running from the command line.',
|
||||
},
|
||||
$this: {
|
||||
description: 'Refers to the current object',
|
||||
},
|
||||
};
|
||||
export const compiletimeconstants: IEntries = {
|
||||
__CLASS__: {
|
||||
description: 'The class name. (Added in PHP 4.3.0) As of PHP 5 this constant returns the class name as it was declared (case-sensitive). In PHP 4 its value is always lowercased.',
|
||||
},
|
||||
__DIR__: {
|
||||
description: 'The directory of the file. If used inside an include, the directory of the included file is returned. This is equivalent to dirname(__FILE__). This directory name does not have a trailing slash unless it is the root directory. (Added in PHP 5.3.0.)',
|
||||
},
|
||||
__FILE__: {
|
||||
description: 'The full path and filename of the file. If used inside an include, the name of the included file is returned. Since PHP 4.0.2, __FILE__ always contains an absolute path with symlinks resolved whereas in older versions it contained relative path under some circumstances.',
|
||||
},
|
||||
__FUNCTION__: {
|
||||
description: 'The function name. (Added in PHP 4.3.0) As of PHP 5 this constant returns the function name as it was declared (case-sensitive). In PHP 4 its value is always lowercased.',
|
||||
},
|
||||
__LINE__: {
|
||||
description: 'The current line number of the file.',
|
||||
},
|
||||
__METHOD__: {
|
||||
description: 'The class method name. (Added in PHP 5.0.0) The method name is returned as it was declared (case-sensitive).',
|
||||
},
|
||||
__NAMESPACE__: {
|
||||
description: 'The name of the current namespace (case-sensitive). This constant is defined in compile-time (Added in PHP 5.3.0).',
|
||||
},
|
||||
TRUE: {
|
||||
},
|
||||
FALSE: {
|
||||
},
|
||||
NULL: {
|
||||
},
|
||||
M_PI: {
|
||||
description: 'The constant Pi: 3.14159265358979323846',
|
||||
},
|
||||
M_E: {
|
||||
description: 'The constant e: 2.7182818284590452354',
|
||||
},
|
||||
M_LOG2E: {
|
||||
description: 'The constant log_2 e: 1.4426950408889634074',
|
||||
},
|
||||
M_LOG10E: {
|
||||
description: 'The constant log_10 e: 0.43429448190325182765',
|
||||
},
|
||||
M_LN2: {
|
||||
description: 'The constant log_e 2: 0.69314718055994530942',
|
||||
},
|
||||
M_LN10: {
|
||||
description: 'The constant log_e 10: 2.30258509299404568402',
|
||||
},
|
||||
M_PI_2: {
|
||||
description: 'The constant pi/2: 1.57079632679489661923',
|
||||
},
|
||||
M_PI_4: {
|
||||
description: 'The constant pi/4: 0.78539816339744830962',
|
||||
},
|
||||
M_1_PI: {
|
||||
description: 'The constant 1/pi: 0.31830988618379067154',
|
||||
},
|
||||
M_2_PI: {
|
||||
description: 'The constant 2/pi: 0.63661977236758134308',
|
||||
},
|
||||
M_SQRTPI: {
|
||||
description: 'The constant sqrt(pi): 1.77245385090551602729',
|
||||
},
|
||||
M_2_SQRTPI: {
|
||||
description: 'The constant 2/sqrt(pi): 1.12837916709551257390',
|
||||
},
|
||||
M_SQRT2: {
|
||||
description: 'The constant sqrt(2): 1.41421356237309504880',
|
||||
},
|
||||
M_SQRT3: {
|
||||
description: 'The constant sqrt(3): 1.73205080756887729352',
|
||||
},
|
||||
M_SQRT1_2: {
|
||||
description: 'The constant 1/sqrt(2): 0.7071067811865475244',
|
||||
},
|
||||
M_LNPI: {
|
||||
description: 'The constant log_e(pi): 1.14472988584940017414',
|
||||
},
|
||||
M_EULER: {
|
||||
description: 'Euler constant: 0.57721566490153286061',
|
||||
},
|
||||
PHP_ROUND_HALF_UP: {
|
||||
description: 'Round halves up = 1',
|
||||
},
|
||||
PHP_ROUND_HALF_DOWN: {
|
||||
description: 'Round halves down = 2',
|
||||
},
|
||||
PHP_ROUND_HALF_EVEN: {
|
||||
description: 'Round halves to even numbers = 3',
|
||||
},
|
||||
PHP_ROUND_HALF_ODD: {
|
||||
description: 'Round halvesto odd numbers = 4',
|
||||
},
|
||||
NAN: {
|
||||
description: 'NAN (as a float): Not A Number',
|
||||
},
|
||||
INF: {
|
||||
description: 'INF (as a float): The infinite',
|
||||
},
|
||||
PASSWORD_BCRYPT: {
|
||||
description: 'PASSWORD_BCRYPT is used to create new password hashes using the CRYPT_BLOWFISH algorithm.',
|
||||
},
|
||||
PASSWORD_DEFAULT: {
|
||||
description: 'The default algorithm to use for hashing if no algorithm is provided. This may change in newer PHP releases when newer, stronger hashing algorithms are supported.',
|
||||
},
|
||||
};
|
||||
export const keywords: IEntries = {
|
||||
define: {
|
||||
description: 'Defines a named constant at runtime.',
|
||||
signature: '( string $name , mixed $value [, bool $case_insensitive = false ] ): bool'
|
||||
},
|
||||
die: {
|
||||
description: 'This language construct is equivalent to exit().',
|
||||
},
|
||||
echo: {
|
||||
description: 'Outputs all parameters. \r\n\r\necho is not actually a function (it is a language construct), so you are not required to use parentheses with it. echo (unlike some other language constructs) does not behave like a function, so it cannot always be used in the context of a function. Additionally, if you want to pass more than one parameter to echo, the parameters must not be enclosed within parentheses.\r\n\r\necho also has a shortcut syntax, where you can immediately follow the opening tag with an equals sign. This short syntax only works with the short_open_tag configuration setting enabled.',
|
||||
signature: '( string $arg1 [, string $... ] ): void'
|
||||
},
|
||||
empty: {
|
||||
description: 'Determine whether a variable is considered to be empty.',
|
||||
signature: '( mixed $var ): bool'
|
||||
},
|
||||
exit: {
|
||||
description: 'Terminates execution of the script. Shutdown functions and object destructors will always be executed even if exit() is called.',
|
||||
signature: '([ string $status ] )\r\nvoid exit ( int $status ): void'
|
||||
},
|
||||
eval: {
|
||||
description: 'Evaluates the string given in code_str as PHP code. Among other things, this can be useful for storing code in a database text field for later execution.\r\nThere are some factors to keep in mind when using eval(). Remember that the string passed must be valid PHP code, including things like terminating statements with a semicolon so the parser doesn\'t die on the line after the eval(), and properly escaping things in code_str. To mix HTML output and PHP code you can use a closing PHP tag to leave PHP mode.\r\nAlso remember that variables given values under eval() will retain these values in the main script afterwards.',
|
||||
signature: '( string $code_str ): mixed'
|
||||
},
|
||||
include: {
|
||||
description: 'The include statement includes and evaluates the specified file.',
|
||||
},
|
||||
include_once: {
|
||||
description: 'The include_once statement includes and evaluates the specified file during the execution of the script. This is a behavior similar to the include statement, with the only difference being that if the code from a file has already been included, it will not be included again. As the name suggests, it will be included just once. \r\n\r\ninclude_once may be used in cases where the same file might be included and evaluated more than once during a particular execution of a script, so in this case it may help avoid problems such as function redefinitions, variable value reassignments, etc.',
|
||||
},
|
||||
isset: {
|
||||
description: 'Determine if a variable is set and is not NULL. \r\n\r\nIf a variable has been unset with unset(), it will no longer be set. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a NULL byte is not equivalent to the PHP NULL constant. \r\n\r\nIf multiple parameters are supplied then isset() will return TRUE only if all of the parameters are set. Evaluation goes from left to right and stops as soon as an unset variable is encountered.',
|
||||
signature: '( mixed $var [, mixed $... ] ): bool'
|
||||
},
|
||||
list: {
|
||||
description: 'Like array(), this is not really a function, but a language construct. list() is used to assign a list of variables in one operation.',
|
||||
signature: '( mixed $varname [, mixed $... ] ): array'
|
||||
},
|
||||
require: {
|
||||
description: 'require is identical to include except upon failure it will also produce a fatal E_COMPILE_ERROR level error. In other words, it will halt the script whereas include only emits a warning (E_WARNING) which allows the script to continue.',
|
||||
},
|
||||
require_once: {
|
||||
description: 'The require_once statement is identical to require except PHP will check if the file has already been included, and if so, not include (require) it again.',
|
||||
},
|
||||
return: {
|
||||
description: 'If called from within a function, the return statement immediately ends execution of the current function, and returns its argument as the value of the function call. return will also end the execution of an eval() statement or script file. \r\n\r\nIf called from the global scope, then execution of the current script file is ended. If the current script file was included or required, then control is passed back to the calling file. Furthermore, if the current script file was included, then the value given to return will be returned as the value of the include call. If return is called from within the main script file, then script execution ends. If the current script file was named by the auto_prepend_file or auto_append_file configuration options in php.ini, then that script file\'s execution is ended.',
|
||||
},
|
||||
print: {
|
||||
description: 'Outputs arg. \r\n\r\nprint() is not actually a real function (it is a language construct) so you are not required to use parentheses with its argument list.',
|
||||
signature: '( string $arg ): int'
|
||||
},
|
||||
unset: {
|
||||
description: 'unset() destroys the specified variables. \r\n\r\nThe behavior of unset() inside of a function can vary depending on what type of variable you are attempting to destroy. \r\n\r\nIf a globalized variable is unset() inside of a function, only the local variable is destroyed. The variable in the calling environment will retain the same value as before unset() was called.',
|
||||
signature: '( mixed $var [, mixed $... ] ): void'
|
||||
},
|
||||
yield: {
|
||||
description: 'The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.',
|
||||
},
|
||||
abstract: {
|
||||
},
|
||||
and: {
|
||||
},
|
||||
array: {
|
||||
},
|
||||
as: {
|
||||
},
|
||||
break: {
|
||||
},
|
||||
case: {
|
||||
},
|
||||
catch: {
|
||||
},
|
||||
class: {
|
||||
},
|
||||
clone: {
|
||||
},
|
||||
const: {
|
||||
},
|
||||
continue: {
|
||||
},
|
||||
declare: {
|
||||
},
|
||||
default: {
|
||||
},
|
||||
do: {
|
||||
},
|
||||
else: {
|
||||
},
|
||||
elseif: {
|
||||
},
|
||||
enddeclare: {
|
||||
},
|
||||
endfor: {
|
||||
},
|
||||
endforeach: {
|
||||
},
|
||||
endif: {
|
||||
},
|
||||
endswitch: {
|
||||
},
|
||||
endwhile: {
|
||||
},
|
||||
extends: {
|
||||
},
|
||||
final: {
|
||||
},
|
||||
finally: {
|
||||
},
|
||||
for: {
|
||||
},
|
||||
foreach: {
|
||||
},
|
||||
function: {
|
||||
},
|
||||
global: {
|
||||
},
|
||||
goto: {
|
||||
},
|
||||
if: {
|
||||
},
|
||||
implements: {
|
||||
},
|
||||
interface: {
|
||||
},
|
||||
instanceof: {
|
||||
},
|
||||
insteadOf: {
|
||||
},
|
||||
namespace: {
|
||||
},
|
||||
new: {
|
||||
},
|
||||
or: {
|
||||
},
|
||||
parent: {
|
||||
},
|
||||
private: {
|
||||
},
|
||||
protected: {
|
||||
},
|
||||
public: {
|
||||
},
|
||||
self: {
|
||||
},
|
||||
static: {
|
||||
},
|
||||
switch: {
|
||||
},
|
||||
throw: {
|
||||
},
|
||||
trait: {
|
||||
},
|
||||
try: {
|
||||
},
|
||||
use: {
|
||||
},
|
||||
var: {
|
||||
},
|
||||
while: {
|
||||
},
|
||||
xor: {
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as phpGlobals from './phpGlobals';
|
||||
import * as phpGlobalFunctions from './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;
|
||||
}
|
||||
const 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 {
|
||||
const enable = workspace.getConfiguration('php').get<boolean>('suggest.basic', true);
|
||||
if (!enable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const iterator = new BackwardIterator(document, position.character - 1, position.line);
|
||||
|
||||
const paramCount = this.readArguments(iterator);
|
||||
if (paramCount < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ident = this.readIdent(iterator);
|
||||
if (!ident) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entry = phpGlobalFunctions.globalfunctions[ident] || phpGlobals.keywords[ident];
|
||||
if (!entry || !entry.signature) {
|
||||
return null;
|
||||
}
|
||||
const paramsString = entry.signature.substring(0, entry.signature.lastIndexOf(')') + 1);
|
||||
const signatureInfo = new SignatureInformation(ident + paramsString, entry.description);
|
||||
|
||||
const re = /\w*\s+\&?\$[\w_\.]+|void/g;
|
||||
let match: RegExpExecArray | null = null;
|
||||
while ((match = re.exec(paramsString)) !== null) {
|
||||
signatureInfo.parameters.push({ label: match[0], documentation: '' });
|
||||
}
|
||||
const 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()) {
|
||||
const 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()) {
|
||||
const 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> | null;
|
||||
private queuedPromise: Promise<T> | null;
|
||||
private queuedPromiseFactory: ITask<Promise<T>> | null;
|
||||
|
||||
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) {
|
||||
const onComplete = () => {
|
||||
this.queuedPromise = null;
|
||||
|
||||
const result = this.queue(this.queuedPromiseFactory!);
|
||||
this.queuedPromiseFactory = null;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
this.queuedPromise = new Promise<T>((resolve) => {
|
||||
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: NodeJS.Timer | null;
|
||||
private completionPromise: Promise<T> | null;
|
||||
private onResolve: ((value: T | PromiseLike<T> | undefined) => void) | null;
|
||||
private task: ITask<T> | null;
|
||||
|
||||
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 | undefined>((resolve) => {
|
||||
this.onResolve = resolve;
|
||||
}).then(() => {
|
||||
this.completionPromise = null;
|
||||
this.onResolve = null;
|
||||
|
||||
const result = this.task!();
|
||||
this.task = null;
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.timeout = null;
|
||||
this.onResolve!(undefined);
|
||||
}, 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<T>();
|
||||
}
|
||||
|
||||
public override trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<Promise<T>> {
|
||||
return super.trigger(() => this.throttler.queue(promiseFactory), delay);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MarkedString } from 'vscode';
|
||||
|
||||
export function textToMarkedString(text: string): MarkedString {
|
||||
return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import { StringDecoder } from 'string_decoder';
|
||||
import which from 'which';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { ThrottledDelayer } from './utils/async';
|
||||
|
||||
const enum Setting {
|
||||
Run = 'php.validate.run',
|
||||
Enable = 'php.validate.enable',
|
||||
ExecutablePath = 'php.validate.executablePath',
|
||||
}
|
||||
|
||||
export class LineDecoder {
|
||||
private stringDecoder: StringDecoder;
|
||||
private remaining: string | null;
|
||||
|
||||
constructor(encoding: BufferEncoding = 'utf8') {
|
||||
this.stringDecoder = new StringDecoder(encoding);
|
||||
this.remaining = null;
|
||||
}
|
||||
|
||||
public write(buffer: Buffer): string[] {
|
||||
const result: string[] = [];
|
||||
const value = this.remaining
|
||||
? this.remaining + this.stringDecoder.write(buffer)
|
||||
: this.stringDecoder.write(buffer);
|
||||
|
||||
if (value.length < 1) {
|
||||
return result;
|
||||
}
|
||||
let start = 0;
|
||||
let ch: number;
|
||||
while (start < value.length && ((ch = value.charCodeAt(start)) === 13 || ch === 10)) {
|
||||
start++;
|
||||
}
|
||||
let 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 | null {
|
||||
return this.remaining;
|
||||
}
|
||||
}
|
||||
|
||||
enum RunTrigger {
|
||||
onSave,
|
||||
onType
|
||||
}
|
||||
|
||||
namespace RunTrigger {
|
||||
export const strings = {
|
||||
onSave: 'onSave',
|
||||
onType: 'onType'
|
||||
};
|
||||
export const 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 validationEnabled: boolean;
|
||||
private pauseValidation: boolean;
|
||||
private config: IPhpConfig | undefined;
|
||||
private loadConfigP: Promise<void>;
|
||||
|
||||
private documentListener: vscode.Disposable | null = null;
|
||||
private diagnosticCollection?: vscode.DiagnosticCollection;
|
||||
private delayers?: { [key: string]: ThrottledDelayer<void> };
|
||||
|
||||
constructor() {
|
||||
this.validationEnabled = true;
|
||||
this.pauseValidation = false;
|
||||
this.loadConfigP = this.loadConfiguration();
|
||||
}
|
||||
|
||||
public activate(subscriptions: vscode.Disposable[]) {
|
||||
this.diagnosticCollection = vscode.languages.createDiagnosticCollection();
|
||||
subscriptions.push(this);
|
||||
subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => this.loadConfigP = this.loadConfiguration()));
|
||||
|
||||
vscode.workspace.onDidOpenTextDocument(this.triggerValidate, this, subscriptions);
|
||||
vscode.workspace.onDidCloseTextDocument((textDocument) => {
|
||||
this.diagnosticCollection!.delete(textDocument.uri);
|
||||
if (this.delayers) {
|
||||
delete this.delayers[textDocument.uri.toString()];
|
||||
}
|
||||
}, null, subscriptions);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.diagnosticCollection) {
|
||||
this.diagnosticCollection.clear();
|
||||
this.diagnosticCollection.dispose();
|
||||
}
|
||||
if (this.documentListener) {
|
||||
this.documentListener.dispose();
|
||||
this.documentListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadConfiguration(): Promise<void> {
|
||||
const section = vscode.workspace.getConfiguration();
|
||||
const oldExecutable = this.config?.executable;
|
||||
this.validationEnabled = section.get<boolean>(Setting.Enable, true);
|
||||
|
||||
this.config = await getConfig();
|
||||
|
||||
this.delayers = Object.create(null);
|
||||
if (this.pauseValidation) {
|
||||
this.pauseValidation = oldExecutable === this.config.executable;
|
||||
}
|
||||
if (this.documentListener) {
|
||||
this.documentListener.dispose();
|
||||
this.documentListener = null;
|
||||
}
|
||||
this.diagnosticCollection!.clear();
|
||||
if (this.validationEnabled) {
|
||||
if (this.config.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 async triggerValidate(textDocument: vscode.TextDocument): Promise<void> {
|
||||
await this.loadConfigP;
|
||||
if (textDocument.languageId !== 'php' || this.pauseValidation || !this.validationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vscode.workspace.isTrusted) {
|
||||
const key = textDocument.uri.toString();
|
||||
let delayer = this.delayers![key];
|
||||
if (!delayer) {
|
||||
delayer = new ThrottledDelayer<void>(this.config?.trigger === RunTrigger.onType ? 250 : 0);
|
||||
this.delayers![key] = delayer;
|
||||
}
|
||||
delayer.trigger(() => this.doValidate(textDocument));
|
||||
}
|
||||
}
|
||||
|
||||
private doValidate(textDocument: vscode.TextDocument): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
const executable = this.config!.executable;
|
||||
if (!executable) {
|
||||
this.showErrorMessage(vscode.l10n.t("Cannot validate since a PHP installation could not be found. Use the setting 'php.validate.executablePath' to configure the PHP executable."));
|
||||
this.pauseValidation = true;
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!path.isAbsolute(executable)) {
|
||||
// executable should either be resolved to an absolute path or undefined.
|
||||
// This is just to be sure.
|
||||
return;
|
||||
}
|
||||
|
||||
const decoder = new LineDecoder();
|
||||
const diagnostics: vscode.Diagnostic[] = [];
|
||||
const processLine = (line: string) => {
|
||||
const matches = line.match(PHPValidationProvider.MatchExpression);
|
||||
if (matches) {
|
||||
const message = matches[1];
|
||||
const line = parseInt(matches[3]) - 1;
|
||||
const diagnostic: vscode.Diagnostic = new vscode.Diagnostic(
|
||||
new vscode.Range(line, 0, line, 2 ** 31 - 1), // See https://github.com/microsoft/vscode/issues/80288#issuecomment-650636442 for discussion
|
||||
message
|
||||
);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
};
|
||||
|
||||
const options = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]) ? { cwd: vscode.workspace.workspaceFolders[0].uri.fsPath } : undefined;
|
||||
let args: string[];
|
||||
if (this.config!.trigger === RunTrigger.onSave) {
|
||||
args = PHPValidationProvider.FileArgs.slice(0);
|
||||
args.push(textDocument.fileName);
|
||||
} else {
|
||||
args = PHPValidationProvider.BufferArgs;
|
||||
}
|
||||
try {
|
||||
const childProcess = cp.spawn(executable, args, options);
|
||||
childProcess.on('error', (error: Error) => {
|
||||
if (this.pauseValidation) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.showError(error, executable);
|
||||
this.pauseValidation = true;
|
||||
resolve();
|
||||
});
|
||||
if (childProcess.pid) {
|
||||
if (this.config!.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', () => {
|
||||
const line = decoder.end();
|
||||
if (line) {
|
||||
processLine(line);
|
||||
}
|
||||
this.diagnosticCollection!.set(textDocument.uri, diagnostics);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error, executable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async showError(error: any, executable: string): Promise<void> {
|
||||
let message: string | null = null;
|
||||
if (error.code === 'ENOENT') {
|
||||
if (this.config!.executable) {
|
||||
message = vscode.l10n.t("Cannot validate since {0} is not a valid php executable. Use the setting 'php.validate.executablePath' to configure the PHP executable.", executable);
|
||||
} else {
|
||||
message = vscode.l10n.t("Cannot validate since no PHP executable is set. Use the setting 'php.validate.executablePath' to configure the PHP executable.");
|
||||
}
|
||||
} else {
|
||||
message = error.message ? error.message : vscode.l10n.t("Failed to run php using path: {0}. Reason is unknown.", executable);
|
||||
}
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.showErrorMessage(message);
|
||||
}
|
||||
|
||||
private async showErrorMessage(message: string): Promise<void> {
|
||||
const openSettings = vscode.l10n.t("Open Settings");
|
||||
if (await vscode.window.showInformationMessage(message, openSettings) === openSettings) {
|
||||
vscode.commands.executeCommand('workbench.action.openSettings', Setting.ExecutablePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IPhpConfig {
|
||||
readonly executable: string | undefined;
|
||||
readonly executableIsUserDefined: boolean | undefined;
|
||||
readonly trigger: RunTrigger;
|
||||
}
|
||||
|
||||
async function getConfig(): Promise<IPhpConfig> {
|
||||
const section = vscode.workspace.getConfiguration();
|
||||
|
||||
let executable: string | undefined;
|
||||
let executableIsUserDefined: boolean | undefined;
|
||||
const inspect = section.inspect<string>(Setting.ExecutablePath);
|
||||
if (inspect && inspect.workspaceValue) {
|
||||
executable = inspect.workspaceValue;
|
||||
executableIsUserDefined = false;
|
||||
} else if (inspect && inspect.globalValue) {
|
||||
executable = inspect.globalValue;
|
||||
executableIsUserDefined = true;
|
||||
} else {
|
||||
executable = undefined;
|
||||
executableIsUserDefined = undefined;
|
||||
}
|
||||
|
||||
if (executable && !path.isAbsolute(executable)) {
|
||||
const first = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0];
|
||||
if (first) {
|
||||
executable = vscode.Uri.joinPath(first.uri, executable).fsPath;
|
||||
} else {
|
||||
executable = undefined;
|
||||
}
|
||||
} else if (!executable) {
|
||||
executable = await getPhpPath();
|
||||
}
|
||||
|
||||
const trigger = RunTrigger.from(section.get<string>(Setting.Run, RunTrigger.strings.onSave));
|
||||
return {
|
||||
executable,
|
||||
executableIsUserDefined,
|
||||
trigger
|
||||
};
|
||||
}
|
||||
|
||||
async function getPhpPath(): Promise<string | undefined> {
|
||||
try {
|
||||
return await which('php');
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import PHPCompletionItemProvider from './features/completionItemProvider';
|
||||
import PHPHoverProvider from './features/hoverProvider';
|
||||
import PHPSignatureHelpProvider from './features/signatureHelpProvider';
|
||||
import PHPValidationProvider from './features/validationProvider';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): any {
|
||||
|
||||
const validator = new PHPValidationProvider();
|
||||
validator.activate(context.subscriptions);
|
||||
|
||||
// add providers
|
||||
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(), '(', ','));
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;
|
||||
declare function clearTimeout(timeoutId: NodeJS.Timer): void;
|
||||
declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;
|
||||
declare function clearInterval(intervalId: NodeJS.Timer): void;
|
||||
declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): any;
|
||||
declare function clearImmediate(immediateId: any): void;
|
||||
Reference in New Issue
Block a user