├── .gitignore ├── .editorconfig ├── LICENSE ├── package.json ├── styles └── main.less ├── LICENSE-atom-autocomplete-php ├── lib ├── GlobalVariableProvider.coffee ├── DocblockAnnotationProvider.coffee ├── MagicConstantProvider.coffee ├── AtomConfig.coffee ├── Config.coffee ├── TypeHintNewVariableNameProvider.coffee ├── SnippetProvider.coffee ├── VariableProvider.coffee ├── KeywordProvider.coffee ├── GlobalConstantProvider.coffee ├── NamespaceProvider.coffee ├── DocblockTagProvider.coffee ├── Main.coffee ├── GlobalFunctionProvider.coffee ├── AbstractProvider.coffee ├── PHPUnitTagProvider.coffee ├── MemberProvider.coffee └── ClassProvider.coffee ├── README.md └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | indent_size = 4 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This program is free software: you can redistribute it and/or modify 2 | it under the terms of the GNU General Public License as published by 3 | the Free Software Foundation, either version 3 of the License, or 4 | (at your option) any later version. 5 | 6 | This program is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-integrator-autocomplete-plus-legacy-php56", 3 | "main": "./lib/Main", 4 | "version": "1.0.1", 5 | "description": "Provides autocompletion for your PHP source code.", 6 | "repository": "php-integrator/atom-autocompletion-legacy-php56", 7 | "license": "GPL-3.0", 8 | "engines": { 9 | "atom": ">=1.13.0 <2.0.0" 10 | }, 11 | "providedServices": { 12 | "autocomplete.provider": { 13 | "versions": { 14 | "3.0.0": "getProviders" 15 | } 16 | } 17 | }, 18 | "consumedServices": { 19 | "php-integrator.service": { 20 | "versions": { 21 | "^2.0": "setService" 22 | } 23 | } 24 | }, 25 | "dependencies": {}, 26 | "keywords": [ 27 | "php", 28 | "autocompletion", 29 | "autocomplete-plus", 30 | "integrator", 31 | "integration", 32 | "php-integrator" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /styles/main.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | @import "octicon-utf-codes"; 7 | 8 | .php-integrator-autocomplete-plus-strike, .php-integrator-autocomplete-plus-strike .word { 9 | text-decoration: line-through; 10 | } 11 | 12 | .php-integrator-autocomplete-plus-suggestion { 13 | &.php-integrator-autocomplete-plus-has-additional-icons { 14 | .icon-container { 15 | padding-right: 0; 16 | } 17 | } 18 | 19 | .left-label { 20 | .icon { 21 | float: left; 22 | padding-top: 0.25em; 23 | padding-left: 0.5em; 24 | padding-right: 2em; 25 | margin-right: 0.75em; 26 | } 27 | } 28 | 29 | .right-label { 30 | float: right; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE-atom-autocomplete-php: -------------------------------------------------------------------------------- 1 | This project was forked from atom-autocomplete-php, thus the original code base 2 | was licensed under the MIT license. It can still be found at [1]. The original 3 | license is located below. 4 | 5 | [1] https://github.com/Peekmo/atom-autocomplete-php 6 | 7 | The MIT License (MIT) 8 | 9 | Copyright (c) 2014-2015 Axel Anceau 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of 12 | this software and associated documentation files (the "Software"), to deal in 13 | the Software without restriction, including without limitation the rights to 14 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 15 | the Software, and to permit persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 24 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 25 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /lib/GlobalVariableProvider.coffee: -------------------------------------------------------------------------------- 1 | VariableProvider = require "./VariableProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for global variable names (such as superglobals). 7 | ## 8 | class GlobalVariableProvider extends VariableProvider 9 | ###* 10 | * @inheritdoc 11 | ### 12 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 13 | return [] if not @service 14 | 15 | prefix = @getPrefix(editor, bufferPosition) 16 | return [] unless prefix != null 17 | 18 | return @addSuggestions(prefix) 19 | 20 | ###* 21 | * @inheritdoc 22 | ### 23 | addSuggestions: (prefix) -> 24 | suggestions = [] 25 | 26 | variables = { 27 | '$argc' : 'int', 28 | '$argv' : 'array', 29 | '$GLOBALS' : 'array', 30 | '$_SERVER' : 'array', 31 | '$_GET' : 'array', 32 | '$_POST' : 'array', 33 | '$_FILES' : 'array', 34 | '$_COOKIE' : 'array', 35 | '$_SESSION' : 'array', 36 | '$_REQUEST' : 'array', 37 | '$_ENV' : 'array' 38 | } 39 | 40 | for variable,type of variables 41 | suggestions.push 42 | type : 'variable' 43 | text : variable 44 | leftLabel : type 45 | replacementPrefix : prefix 46 | 47 | return suggestions 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atom-autocompletion-legacy-php56 2 | ## Legacy 3 | This is a legacy version that requires PHP >= 5.6. Users that are on PHP 7.1 can and should use [the newer version](https://github.com/php-integrator/atom-base). 4 | 5 | This package only exists to cater towards users that are not in any position to upgrade their host PHP version. As a result, any issues that appear in this package will not be fixed, no new features will be added and no enhancements will be done. 6 | 7 | ## About 8 | This package provides autocompletion for your PHP source code using [PHP Integrator](https://github.com/php-integrator/atom-base-legacy-php56) as well as Atom's [autocomplete-plus](https://github.com/atom/autocomplete-plus). 9 | 10 | **Note that the [php-integrator-base](https://github.com/php-integrator/atom-base-legacy-php56) package is required and needs to be set up correctly for this package to function correctly.** 11 | 12 | What is included? 13 | * Autocompletion for local variable names. 14 | * Autocompletion for global functions and constants. 15 | * Autocompletion (snippets) for tag names in docblocks. 16 | * Autocompletion for class, interface and trait members. 17 | * Autocompletion for class, interface and trait names as well as their constructors. 18 | * Automatic adding of use statements when class names are autocompleted (with a somewhat intelligent positioning). 19 | * Included is a command to sort the current use statements. 20 | 21 | ![GPLv3 Logo](http://gplv3.fsf.org/gplv3-127x51.png) 22 | -------------------------------------------------------------------------------- /lib/DocblockAnnotationProvider.coffee: -------------------------------------------------------------------------------- 1 | ClassProvider = require "./ClassProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for docblock annotations. 7 | ## 8 | class DocblockAnnotationProvider extends ClassProvider 9 | ###* 10 | * @inheritdoc 11 | * 12 | * These can only appear in docblocks. Including the space in the capturing group ensures that autocompletion will 13 | * start right after putting down an asterisk instead of when the tag symbol '@' is entered. 14 | ### 15 | regex: /^\s*(?:\/\*)?\*\s@(\\?[a-zA-Z_]?[a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?)$/ 16 | 17 | ###* 18 | * @inheritdoc 19 | ### 20 | scopeSelector: '.comment.block.documentation.phpdoc.php' 21 | 22 | ###* 23 | * @inheritdoc 24 | ### 25 | disableForScopeSelector: '' 26 | 27 | ###* 28 | * @inheritdoc 29 | ### 30 | handleSuccessfulCacheRefresh: (classes) -> 31 | filteredClasses = {} 32 | 33 | for name, element of classes 34 | if element.isAnnotation 35 | filteredClasses[name] = element 36 | 37 | super(filteredClasses) 38 | 39 | ###* 40 | * @inheritdoc 41 | ### 42 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 43 | return [] if not @service 44 | 45 | matches = @getPrefixMatchesByRegex(editor, bufferPosition, @regex) 46 | 47 | return [] unless matches? 48 | 49 | successHandler = (classes) => 50 | return [] unless classes 51 | 52 | return @getClassSuggestions(classes, matches) 53 | 54 | failureHandler = () => 55 | # Just return no results. 56 | return [] 57 | 58 | return @fetchResults().then(successHandler, failureHandler) 59 | -------------------------------------------------------------------------------- /lib/MagicConstantProvider.coffee: -------------------------------------------------------------------------------- 1 | {Point} = require 'atom' 2 | 3 | AbstractProvider = require "./AbstractProvider" 4 | 5 | module.exports = 6 | 7 | ##* 8 | # Provides autocompletion for magic constants. 9 | ## 10 | class MagicConstantProvider extends AbstractProvider 11 | ###* 12 | * @inheritdoc 13 | * 14 | * "new" keyword or word starting with capital letter 15 | ### 16 | regex: /(__?(?:[A-Z]+_?_?)?)$/ 17 | 18 | ###* 19 | * @inheritdoc 20 | ### 21 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 22 | return [] if not @service 23 | 24 | prefix = @getPrefix(editor, bufferPosition) 25 | return [] unless prefix != null 26 | 27 | return @addSuggestions(prefix.trim()) 28 | 29 | ###* 30 | * Returns suggestions available matching the given prefix. 31 | * 32 | * @param {string} prefix 33 | * 34 | * @return {array} 35 | ### 36 | addSuggestions: (prefix) -> 37 | suggestions = [] 38 | 39 | constants = { 40 | # See also https://secure.php.net/manual/en/reserved.keywords.php. 41 | '__CLASS__' : 'string', 42 | '__DIR__' : 'string', 43 | '__FILE__' : 'string', 44 | '__FUNCTION__' : 'string', 45 | '__LINE__' : 'int', 46 | '__METHOD__' : 'string', 47 | '__NAMESPACE__' : 'string', 48 | '__TRAIT__' : 'string' 49 | } 50 | 51 | for name, type of constants 52 | suggestions.push 53 | type : 'constant' 54 | text : name 55 | leftLabel : type 56 | replacementPrefix : prefix 57 | 58 | return suggestions 59 | -------------------------------------------------------------------------------- /lib/AtomConfig.coffee: -------------------------------------------------------------------------------- 1 | Config = require './Config' 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Config that retrieves its settings from Atom's config. 7 | ## 8 | class AtomConfig extends Config 9 | ###* 10 | * The name of the package to use when searching for settings. 11 | ### 12 | packageName: null 13 | 14 | ###* 15 | * @inheritdoc 16 | ### 17 | constructor: (@packageName) -> 18 | super() 19 | 20 | @attachListeners() 21 | 22 | ###* 23 | * @inheritdoc 24 | ### 25 | load: () -> 26 | @set('disableBuiltinAutocompletion', atom.config.get("#{@packageName}.disableBuiltinAutocompletion")) 27 | @set('insertNewlinesForUseStatements', atom.config.get("#{@packageName}.insertNewlinesForUseStatements")) 28 | @set('enablePhpunitAnnotationTags', atom.config.get("#{@packageName}.enablePhpunitAnnotationTags")) 29 | @set('largeListRefreshTimeout', atom.config.get("#{@packageName}.automaticallyAddUseStatements")) 30 | @set('largeListRefreshTimeout', atom.config.get("#{@packageName}.largeListRefreshTimeout")) 31 | 32 | ###* 33 | * Attaches listeners to listen to Atom configuration changes. 34 | ### 35 | attachListeners: () -> 36 | atom.config.onDidChange "#{@packageName}.disableBuiltinAutocompletion", () => 37 | @set('disableBuiltinAutocompletion', atom.config.get("#{@packageName}.disableBuiltinAutocompletion")) 38 | 39 | atom.config.onDidChange "#{@packageName}.insertNewlinesForUseStatements", () => 40 | @set('insertNewlinesForUseStatements', atom.config.get("#{@packageName}.insertNewlinesForUseStatements")) 41 | 42 | atom.config.onDidChange "#{@packageName}.enablePhpunitAnnotationTags", () => 43 | @set('enablePhpunitAnnotationTags', atom.config.get("#{@packageName}.enablePhpunitAnnotationTags")) 44 | 45 | atom.config.onDidChange "#{@packageName}.largeListRefreshTimeout", () => 46 | @set('automaticallyAddUseStatements', atom.config.get("#{@packageName}.automaticallyAddUseStatements")) 47 | 48 | atom.config.onDidChange "#{@packageName}.largeListRefreshTimeout", () => 49 | @set('largeListRefreshTimeout', atom.config.get("#{@packageName}.largeListRefreshTimeout")) 50 | -------------------------------------------------------------------------------- /lib/Config.coffee: -------------------------------------------------------------------------------- 1 | 2 | module.exports = 3 | 4 | ##* 5 | # Abstract base class for managing configurations. 6 | ## 7 | class Config 8 | ###* 9 | * Raw configuration object. 10 | ### 11 | data: null 12 | 13 | ###* 14 | * Array of change listeners. 15 | ### 16 | listeners: null 17 | 18 | ###* 19 | * Constructor. 20 | ### 21 | constructor: () -> 22 | @listeners = {} 23 | 24 | @data = 25 | disableBuiltinAutocompletion : true 26 | insertNewlinesForUseStatements : false 27 | automaticallyAddUseStatements : true 28 | enablePhpunitAnnotationTags : true 29 | largeListRefreshTimeout : 5000 30 | 31 | # See also http://www.phpdoc.org/docs/latest/index.html . 32 | phpdoc_base_url : { 33 | prefix: 'http://www.phpdoc.org/docs/latest/references/phpdoc/tags/' 34 | suffix: '.html' 35 | } 36 | 37 | # See also https://phpunit.de/manual/current/en/index.html . 38 | phpunit_annotations_base_url : { 39 | prefix: 'https://phpunit.de/manual/current/en/appendixes.annotations.html' 40 | id_prefix: '#appendixes.annotations.' 41 | } 42 | 43 | # See also https://secure.php.net/urlhowto.php . 44 | php_documentation_base_urls : { 45 | classes : 'https://secure.php.net/class.' 46 | functions : 'https://secure.php.net/function.' 47 | keywords : 'https://secure.php.net/manual/en/reserved.php' 48 | } 49 | 50 | @load() 51 | 52 | ###* 53 | * Loads the configuration. 54 | ### 55 | load: () -> 56 | throw new Error("This method is abstract and must be implemented!") 57 | 58 | ###* 59 | * Registers a listener that is invoked when the specified property is changed. 60 | ### 61 | onDidChange: (name, callback) -> 62 | if name not of @listeners 63 | @listeners[name] = [] 64 | 65 | @listeners[name].push(callback) 66 | 67 | ###* 68 | * Retrieves the config setting with the specified name. 69 | * 70 | * @return {mixed} 71 | ### 72 | get: (name) -> 73 | return @data[name] 74 | 75 | ###* 76 | * Retrieves the config setting with the specified name. 77 | * 78 | * @param {string} name 79 | * @param {mixed} value 80 | ### 81 | set: (name, value) -> 82 | @data[name] = value 83 | 84 | if name of @listeners 85 | for listener in @listeners[name] 86 | listener(value, name) 87 | -------------------------------------------------------------------------------- /lib/TypeHintNewVariableNameProvider.coffee: -------------------------------------------------------------------------------- 1 | {Point} = require 'atom' 2 | 3 | AbstractProvider = require "./AbstractProvider" 4 | 5 | module.exports = 6 | 7 | ##* 8 | # Suggests new variable names after type hints. 9 | ## 10 | class TypeHintNewVariableNameProvider extends AbstractProvider 11 | ###* 12 | * @inheritdoc 13 | ### 14 | regex: /(\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\s+\$?(?:[a-zA-Z_][a-zA-Z0-9_]*)?)$/ 15 | 16 | ###* 17 | * @inheritdoc 18 | ### 19 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 20 | return [] if not @service 21 | 22 | prefix = @getPrefix(editor, bufferPosition) 23 | return [] unless prefix != null 24 | 25 | # Don't complete local variable names if we found something else than a type hint. 26 | newBufferPosition = new Point(bufferPosition.row, bufferPosition.column - prefix.length) 27 | 28 | return [] if editor.scopeDescriptorForBufferPosition(newBufferPosition).getScopeChain().indexOf('.support.class') == -1 29 | 30 | parts = prefix.split(/\s+/) 31 | 32 | typeHint = parts[0].trim() 33 | prefix = parts[1].trim() 34 | 35 | return [] if not typeHint 36 | 37 | return @addSuggestions(typeHint, prefix) 38 | 39 | ###* 40 | * Returns suggestions available matching the given prefix. 41 | * 42 | * @param {string} typeHint 43 | * @param {string} prefix 44 | * 45 | * @return {array} 46 | ### 47 | addSuggestions: (typeHint, prefix) -> 48 | suggestions = [] 49 | 50 | typeHintParts = typeHint.split('\\') 51 | shortTypeName = typeHintParts[typeHintParts.length - 1] 52 | 53 | shortTypeNameParts = shortTypeName.split(/([A-Z][^A-Z]+)/).filter (part) -> 54 | return part and part.length > 0 55 | 56 | # Example type hint: FooBarInterface 57 | nameSuggestions = [] 58 | 59 | # Suggest 'fooBarInterface': 60 | nameSuggestions.push(shortTypeName[0].toLowerCase() + shortTypeName.substr(1)) 61 | 62 | # Suggest 'fooBar': 63 | if shortTypeNameParts.length > 1 64 | shortTypeNameParts.pop() 65 | 66 | name = shortTypeNameParts.join('') 67 | 68 | nameSuggestions.push(name[0].toLowerCase() + name.substr(1)) 69 | 70 | for name in nameSuggestions 71 | suggestions.push 72 | type : 'variable' 73 | text : '$' + name 74 | leftLabel : null 75 | replacementPrefix : prefix 76 | rightLabel : 'New variable' 77 | 78 | return suggestions 79 | -------------------------------------------------------------------------------- /lib/SnippetProvider.coffee: -------------------------------------------------------------------------------- 1 | AbstractProvider = require "./AbstractProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides useful snippets. 7 | ## 8 | class SnippetProvider extends AbstractProvider 9 | ###* 10 | * @inheritdoc 11 | * 12 | * These can appear pretty much everywhere, but not in variable names or as class members. Note that functions can 13 | * also appear inside namespaces, hence the middle part. 14 | ### 15 | regex: /(?:^|[^\$:>\w])((?:[a-zA-Z_][a-zA-Z0-9_]*\\)*[a-z_]+)$/ 16 | 17 | ###* 18 | * @inheritdoc 19 | ### 20 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 21 | return [] if not @service 22 | 23 | prefix = @getPrefix(editor, bufferPosition) 24 | return [] unless prefix != null 25 | 26 | return @addSuggestions(@fetchTagList(), prefix.trim()) 27 | 28 | ###* 29 | * Returns suggestions available matching the given prefix. 30 | * 31 | * @param {array} tagList 32 | * @param {string} prefix 33 | * 34 | * @return {array} 35 | ### 36 | addSuggestions: (tagList, prefix) -> 37 | suggestions = [] 38 | 39 | for tag in tagList 40 | # NOTE: The description must not be empty for the 'More' button to show up. 41 | suggestions.push 42 | type : 'snippet', 43 | description : 'PHP snippet.' 44 | snippet : tag.snippet 45 | displayText : tag.name 46 | replacementPrefix : prefix 47 | 48 | return suggestions 49 | 50 | ###* 51 | * Retrieves a list of known docblock tags. 52 | * 53 | * @return {array} 54 | ### 55 | fetchTagList: () -> 56 | return [ 57 | # Useful snippets for keywords: 58 | {name : 'catch', snippet : 'catch (${1:\Exception} ${2:\$e}) {\n ${3:// TODO: Handling.}\n}'}, 59 | 60 | {name : '__halt_compiler', snippet : '__halt_compiler()'}, 61 | {name : 'array', snippet : 'array($1)$0'}, 62 | {name : 'die', snippet : 'die($1)$0'}, 63 | {name : 'empty', snippet : 'empty(${1:expression})$0'}, 64 | {name : 'eval', snippet : 'eval(${1:expression})$0'}, 65 | {name : 'exit', snippet : 'exit($1)$0'}, 66 | {name : 'isset', snippet : 'isset(${1:${2:\$array}[\'${3:value}\']})$0'}, 67 | {name : 'list', snippet : 'list(${1:\$a, \$b})$0'}, 68 | {name : 'unset', snippet : 'unset(${1:${2:\$array}[\'${3:value}\']})$0'} 69 | ] 70 | -------------------------------------------------------------------------------- /lib/VariableProvider.coffee: -------------------------------------------------------------------------------- 1 | {Point} = require 'atom' 2 | 3 | AbstractProvider = require "./AbstractProvider" 4 | 5 | module.exports = 6 | 7 | ##* 8 | # Provides autocompletion for local variable names. 9 | ## 10 | class VariableProvider extends AbstractProvider 11 | ###* 12 | * @inheritdoc 13 | * 14 | * Variables are allowed inside double quoted strings (see also 15 | * {@link https://secure.php.net/manual/en/language.types.string.php#language.types.string.parsing}). 16 | ### 17 | disableForScopeSelector: '.source.php .comment, .source.php .string.quoted.single' 18 | 19 | ###* 20 | * @inheritdoc 21 | ### 22 | regex: /((?:\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\s+)?\$(?:[a-zA-Z_][a-zA-Z0-9_]*)?)$/ 23 | 24 | ###* 25 | * @inheritdoc 26 | ### 27 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 28 | return [] if not @service 29 | 30 | prefix = @getPrefix(editor, bufferPosition) 31 | return [] unless prefix != null 32 | 33 | # Don't complete local variable names if we found a type hint. 34 | newBufferPosition = new Point(bufferPosition.row, bufferPosition.column - prefix.length) 35 | 36 | return [] if editor.scopeDescriptorForBufferPosition(newBufferPosition).getScopeChain().indexOf('.support.class') != -1 37 | 38 | parts = prefix.split(/\s+/) 39 | 40 | typeHint = parts[0] 41 | prefix = parts[1] 42 | 43 | if not prefix? 44 | prefix = typeHint 45 | 46 | prefix = prefix.trim() 47 | 48 | offset = editor.getBuffer().characterIndexForPosition(bufferPosition) 49 | 50 | # Don't include the variable we're completing. 51 | offset -= prefix.length 52 | 53 | text = editor.getBuffer().getText() 54 | 55 | # Strip out the text currently being completed, as when the user is typing a variable name, a syntax error may 56 | # ensue. The base service will start ignoring parts of the file if that happens, which causes inconsistent 57 | # results. 58 | text = text.substr(0, offset) + text.substr(offset + prefix.length) 59 | 60 | successHandler = (variables) => 61 | return @addSuggestions(variables, prefix) 62 | 63 | failureHandler = () => 64 | return [] 65 | 66 | return @service.getAvailableVariablesByOffset(null, text, offset).then(successHandler, failureHandler) 67 | 68 | ###* 69 | * Returns available suggestions. 70 | * 71 | * @param {array} variables 72 | * @param {string} prefix 73 | * 74 | * @return array 75 | ### 76 | addSuggestions: (variables, prefix) -> 77 | suggestions = [] 78 | 79 | for name, variable of variables 80 | type = null 81 | 82 | # Just show the last part of a class name with a namespace. 83 | if variable.type 84 | parts = variable.type.split('\\') 85 | type = parts.pop() 86 | 87 | suggestions.push 88 | type : 'variable' 89 | text : variable.name 90 | leftLabel : type 91 | replacementPrefix : prefix 92 | 93 | return suggestions 94 | -------------------------------------------------------------------------------- /lib/KeywordProvider.coffee: -------------------------------------------------------------------------------- 1 | AbstractProvider = require "./AbstractProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for keywords. 7 | ## 8 | class KeywordProvider extends AbstractProvider 9 | ###* 10 | * @inheritdoc 11 | * 12 | * These can appear pretty much everywhere, but not in variable names or as class members. Note that functions can 13 | * also appear inside namespaces, hence the middle part. 14 | ### 15 | regex: /(?:^|[^\$:>\w])((?:[a-zA-Z_][a-zA-Z0-9_]*\\)*[a-z_]+)$/ 16 | 17 | ###* 18 | * @inheritdoc 19 | ### 20 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 21 | return [] if not @service 22 | 23 | prefix = @getPrefix(editor, bufferPosition) 24 | return [] unless prefix != null 25 | 26 | return @addSuggestions(@fetchTagList(), prefix.trim()) 27 | 28 | ###* 29 | * Returns available suggestions. 30 | * 31 | * @param {array} tagList 32 | * @param {string} prefix 33 | * 34 | * @return {array} 35 | ### 36 | addSuggestions: (tagList, prefix) -> 37 | suggestions = [] 38 | 39 | for tag in tagList 40 | # NOTE: The description must not be empty for the 'More' button to show up. 41 | suggestions.push 42 | text : tag.name 43 | type : 'keyword', 44 | description : 'PHP keyword.' 45 | descriptionMoreURL : @config.get('php_documentation_base_urls').keywords 46 | replacementPrefix : prefix 47 | 48 | return suggestions 49 | 50 | ###* 51 | * Retrieves a list of known docblock tags. 52 | * 53 | * @return {array} 54 | ### 55 | fetchTagList: () -> 56 | return [ 57 | {name : 'self'}, 58 | {name : 'static'}, 59 | {name : 'parent'}, 60 | 61 | # From https://secure.php.net/manual/en/reserved.other-reserved-words.php. 62 | {name : 'int'}, 63 | {name : 'float'}, 64 | {name : 'bool'}, 65 | {name : 'string'}, 66 | {name : 'true'}, 67 | {name : 'false'}, 68 | {name : 'null'}, 69 | {name : 'void'}, 70 | {name : 'iterable'}, 71 | 72 | # From https://secure.php.net/manual/en/reserved.keywords.php. 73 | {name : '__halt_compiler'}, 74 | {name : 'abstract'}, 75 | {name : 'and'}, 76 | {name : 'array'}, 77 | {name : 'as'}, 78 | {name : 'break'}, 79 | {name : 'callable'}, 80 | {name : 'case'}, 81 | {name : 'catch'}, 82 | {name : 'class'}, 83 | {name : 'clone'}, 84 | {name : 'const'}, 85 | {name : 'continue'}, 86 | {name : 'declare'}, 87 | {name : 'default'}, 88 | {name : 'die'}, 89 | {name : 'do'}, 90 | {name : 'echo'}, 91 | {name : 'else'}, 92 | {name : 'elseif'}, 93 | {name : 'empty'}, 94 | {name : 'enddeclare'}, 95 | {name : 'endfor'}, 96 | {name : 'endforeach'}, 97 | {name : 'endif'}, 98 | {name : 'endswitch'}, 99 | {name : 'endwhile'}, 100 | {name : 'eval'}, 101 | {name : 'exit'}, 102 | {name : 'extends'}, 103 | {name : 'final'}, 104 | {name : 'finally'}, 105 | {name : 'for'}, 106 | {name : 'foreach'}, 107 | {name : 'function'}, 108 | {name : 'global'}, 109 | {name : 'goto'}, 110 | {name : 'if'}, 111 | {name : 'implements'}, 112 | {name : 'include'}, 113 | {name : 'include_once'}, 114 | {name : 'instanceof'}, 115 | {name : 'insteadof'}, 116 | {name : 'interface'}, 117 | {name : 'isset'}, 118 | {name : 'list'}, 119 | {name : 'namespace'}, 120 | {name : 'new'}, 121 | {name : 'or'}, 122 | {name : 'print'}, 123 | {name : 'private'}, 124 | {name : 'protected'}, 125 | {name : 'public'}, 126 | {name : 'require'}, 127 | {name : 'require_once'}, 128 | {name : 'return'}, 129 | {name : 'static'}, 130 | {name : 'switch'}, 131 | {name : 'throw'}, 132 | {name : 'trait'}, 133 | {name : 'try'}, 134 | {name : 'unset'}, 135 | {name : 'use'}, 136 | {name : 'var'}, 137 | {name : 'while'}, 138 | {name : 'xor'}, 139 | {name : 'yield'} 140 | ] 141 | -------------------------------------------------------------------------------- /lib/GlobalConstantProvider.coffee: -------------------------------------------------------------------------------- 1 | AbstractProvider = require "./AbstractProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for global PHP constants. 7 | ## 8 | class GlobalConstantProvider extends AbstractProvider 9 | ###* 10 | * @inheritdoc 11 | * 12 | * These can appear pretty much everywhere, but not in variable names or as class members. We just use the regex 13 | * here to validate, but not to filter out the correct bits, as autocomplete-plus already seems to do this 14 | * correctly. 15 | ### 16 | regex: /(?:^|[^\$:>\w\\])([A-Z_]+)$/ 17 | 18 | ###* 19 | # Cache object to help improve responsiveness of autocompletion. 20 | ### 21 | listCache: null 22 | 23 | ###* 24 | # A list of disposables to dispose on deactivation. 25 | ### 26 | disposables: null 27 | 28 | ###* 29 | # Keeps track of a currently pending promise to ensure only one is active at any given time. 30 | ### 31 | pendingPromise: null 32 | 33 | ###* 34 | # Keeps track of a currently pending timeout to ensure only one is active at any given time.. 35 | ### 36 | timeoutHandle: null 37 | 38 | ###* 39 | * @inheritdoc 40 | ### 41 | activate: (@service) -> 42 | {CompositeDisposable} = require 'atom' 43 | 44 | @disposables = new CompositeDisposable() 45 | 46 | @disposables.add(@service.onDidFinishIndexing(@onDidFinishIndexing.bind(this))) 47 | 48 | ###* 49 | * @inheritdoc 50 | ### 51 | deactivate: () -> 52 | if @disposables? 53 | @disposables.dispose() 54 | @disposables = null 55 | 56 | ###* 57 | * Called when reindexing successfully finishes. 58 | * 59 | * @param {Object} info 60 | ### 61 | onDidFinishIndexing: (info) -> 62 | # Only reindex a couple of seconds after the last reindex. This prevents constant refreshes being scheduled 63 | # while the user is still modifying the file. This is acceptable as this provider's data rarely changes and 64 | # it is fairly expensive to refresh the cache. 65 | if @timeoutHandle? 66 | clearTimeout(@timeoutHandle) 67 | @timeoutHandle = null 68 | 69 | @timeoutHandle = setTimeout ( => 70 | @timeoutHandle = null 71 | @refreshCache() 72 | ), @config.get('largeListRefreshTimeout') 73 | 74 | ###* 75 | * Refreshes the internal cache. Returns a promise that resolves with the cache once it has been refreshed. 76 | * 77 | * @return {Promise} 78 | ### 79 | refreshCache: () -> 80 | successHandler = (constants) => 81 | @pendingPromise = null 82 | 83 | return unless constants 84 | 85 | @listCache = constants 86 | 87 | return @listCache 88 | 89 | failureHandler = () => 90 | @pendingPromise = null 91 | 92 | return [] 93 | 94 | if not @pendingPromise? 95 | @pendingPromise = @service.getGlobalConstants().then(successHandler, failureHandler) 96 | 97 | return @pendingPromise 98 | 99 | ###* 100 | * Fetches a list of results that can be fed to the getSuggestions method. 101 | * 102 | * @return {Promise} 103 | ### 104 | fetchResults: () -> 105 | return new Promise (resolve, reject) => 106 | if @listCache? 107 | resolve(@listCache) 108 | return 109 | 110 | return @refreshCache() 111 | 112 | ###* 113 | * @inheritdoc 114 | ### 115 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 116 | return [] if not @service 117 | 118 | tmpPrefix = @getPrefix(editor, bufferPosition) 119 | return [] unless tmpPrefix != null 120 | 121 | successHandler = (constants) => 122 | return [] unless constants 123 | 124 | return @addSuggestions(constants, prefix.trim()) 125 | 126 | failureHandler = () => 127 | return [] 128 | 129 | return @fetchResults().then(successHandler, failureHandler) 130 | 131 | ###* 132 | * Returns available suggestions. 133 | * 134 | * @param {array} constants 135 | * @param {string} prefix 136 | * 137 | * @return {array} 138 | ### 139 | addSuggestions: (constants, prefix) -> 140 | suggestions = [] 141 | 142 | for fqcn, constant of constants 143 | suggestions.push 144 | text : constant.name 145 | type : 'constant' 146 | displayText : constant.name 147 | replacementPrefix : prefix 148 | leftLabel : @getTypeSpecificationFromTypeArray(constant.types) 149 | description : if constant.isBuiltin then 'Built-in PHP constant.' else constant.shortDescription 150 | className : 'php-integrator-autocomplete-plus-suggestion' + if constant.isDeprecated then ' php-integrator-autocomplete-plus-strike' else '' 151 | 152 | return suggestions 153 | -------------------------------------------------------------------------------- /lib/NamespaceProvider.coffee: -------------------------------------------------------------------------------- 1 | AbstractProvider = require "./AbstractProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for namespaces after the namespace keyword. 7 | ## 8 | class NamespaceProvider extends AbstractProvider 9 | ###* 10 | * @inheritdoc 11 | ### 12 | regex: /namespace\s+(\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?)?$/ 13 | 14 | ###* 15 | # Cache object to help improve responsiveness of autocompletion. 16 | ### 17 | listCache: null 18 | 19 | ###* 20 | # A list of disposables to dispose on deactivation. 21 | ### 22 | disposables: null 23 | 24 | ###* 25 | # Keeps track of a currently pending promise to ensure only one is active at any given time. 26 | ### 27 | pendingPromise: null 28 | 29 | ###* 30 | # Keeps track of a currently pending timeout to ensure only one is active at any given time.. 31 | ### 32 | timeoutHandle: null 33 | 34 | ###* 35 | * @inheritdoc 36 | ### 37 | activate: (@service) -> 38 | {CompositeDisposable} = require 'atom' 39 | 40 | @disposables = new CompositeDisposable() 41 | 42 | @disposables.add(@service.onDidFinishIndexing(@onDidFinishIndexing.bind(this))) 43 | 44 | ###* 45 | * @inheritdoc 46 | ### 47 | deactivate: () -> 48 | if @disposables? 49 | @disposables.dispose() 50 | @disposables = null 51 | 52 | ###* 53 | * Called when reindexing successfully finishes. 54 | * 55 | * @param {Object} info 56 | ### 57 | onDidFinishIndexing: (info) -> 58 | # Only reindex a couple of seconds after the last reindex. This prevents constant refreshes being scheduled 59 | # while the user is still modifying the file. This is acceptable as this provider's data rarely changes and 60 | # it is fairly expensive to refresh the cache. 61 | if @timeoutHandle? 62 | clearTimeout(@timeoutHandle) 63 | @timeoutHandle = null 64 | 65 | @timeoutHandle = setTimeout ( => 66 | @timeoutHandle = null 67 | @refreshCache() 68 | ), @config.get('largeListRefreshTimeout') 69 | 70 | ###* 71 | * Refreshes the internal cache. Returns a promise that resolves with the cache once it has been refreshed. 72 | * 73 | * @return {Promise} 74 | ### 75 | refreshCache: () -> 76 | successHandler = (namespaces) => 77 | @pendingPromise = null 78 | 79 | return unless namespaces 80 | 81 | @listCache = namespaces 82 | 83 | return @listCache 84 | 85 | failureHandler = () => 86 | @pendingPromise = null 87 | 88 | return [] 89 | 90 | if not @pendingPromise? 91 | @pendingPromise = @service.getNamespaceList().then(successHandler, failureHandler) 92 | 93 | return @pendingPromise 94 | 95 | ###* 96 | * Fetches a list of results that can be fed to the addSuggestions method. 97 | * 98 | * @return {Promise} 99 | ### 100 | fetchResults: () -> 101 | return new Promise (resolve, reject) => 102 | if @listCache? 103 | resolve(@listCache) 104 | return 105 | 106 | return @refreshCache() 107 | 108 | ###* 109 | * @inheritdoc 110 | ### 111 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 112 | return [] if not @service 113 | 114 | prefix = @getPrefix(editor, bufferPosition) 115 | return [] unless prefix != null 116 | 117 | successHandler = (namespaces) => 118 | return [] unless namespaces 119 | 120 | characterAfterPrefix = editor.getTextInRange([bufferPosition, [bufferPosition.row, bufferPosition.column + 1]]) 121 | insertParameterList = if characterAfterPrefix == '(' then false else true 122 | 123 | return @addSuggestions(namespaces, prefix.trim(), insertParameterList) 124 | 125 | failureHandler = () => 126 | return [] 127 | 128 | return @fetchResults().then(successHandler, failureHandler) 129 | 130 | ###* 131 | * Returns available suggestions. 132 | * 133 | * @param {array} namespaces 134 | * @param {string} prefix 135 | * @param {bool} insertParameterList Whether to insert a list of parameters or not. 136 | * 137 | * @return {array} 138 | ### 139 | addSuggestions: (namespaces, prefix, insertParameterList = true) -> 140 | suggestions = [] 141 | 142 | for namespace in namespaces 143 | continue if namespace.namespace.length == 0 144 | 145 | fqcnWithoutLeadingSlash = namespace.namespace 146 | 147 | if fqcnWithoutLeadingSlash[0] == '\\' 148 | fqcnWithoutLeadingSlash = fqcnWithoutLeadingSlash.substring(1) 149 | 150 | # NOTE: The description must not be empty for the 'More' button to show up. 151 | suggestions.push 152 | text : fqcnWithoutLeadingSlash 153 | type : 'import' 154 | leftLabel : 'namespace' 155 | displayText : fqcnWithoutLeadingSlash 156 | 157 | return suggestions 158 | -------------------------------------------------------------------------------- /lib/DocblockTagProvider.coffee: -------------------------------------------------------------------------------- 1 | AbstractProvider = require "./AbstractProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for docblock tags. 7 | ## 8 | class DocblockTagProvider extends AbstractProvider 9 | ###* 10 | * @inheritdoc 11 | * 12 | * These can only appear in docblocks. Including the space in the capturing group ensures that autocompletion will 13 | * start right after putting down an asterisk instead of when the tag symbol '@' is entered. 14 | ### 15 | regex: /^\s*(?:\/\*)?\*(\s@\S*)$/ 16 | 17 | ###* 18 | * @inheritdoc 19 | ### 20 | scopeSelector: '.comment.block.documentation.phpdoc.php' 21 | 22 | ###* 23 | * @inheritdoc 24 | ### 25 | disableForScopeSelector: '' 26 | 27 | ###* 28 | * @inheritdoc 29 | ### 30 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 31 | return [] if not @service 32 | 33 | prefix = @getPrefix(editor, bufferPosition) 34 | return [] unless prefix != null 35 | 36 | return @addSuggestions(@fetchTagList(), prefix.trim()) 37 | 38 | ###* 39 | * Returns available suggestions. 40 | * 41 | * @param {array} tagList 42 | * @param {string} prefix 43 | * 44 | * @return {array} 45 | ### 46 | addSuggestions: (tagList, prefix) -> 47 | suggestions = [] 48 | 49 | for tag in tagList 50 | documentationUrl = null 51 | 52 | if tag.documentationName 53 | documentationUrl = 54 | @config.get('phpdoc_base_url').prefix + 55 | tag.documentationName + 56 | @config.get('phpdoc_base_url').suffix 57 | 58 | # NOTE: The description must not be empty for the 'More' button to show up. 59 | suggestions.push 60 | type : 'tag', 61 | description : 'PHP docblock tag.' 62 | descriptionMoreURL : documentationUrl 63 | snippet : tag.snippet 64 | displayText : tag.name 65 | replacementPrefix : prefix 66 | 67 | return suggestions 68 | 69 | ###* 70 | * Retrieves a list of known docblock tags. 71 | * 72 | * @return {array} 73 | ### 74 | fetchTagList: () -> 75 | return [ 76 | {name: '@api', documentationName : 'api', snippet : '@api$0'} 77 | {name: '@author', documentationName : 'author', snippet : '@author ${1:name} ${2:[email]}$0'} 78 | {name: '@copyright', documentationName : 'copyright', snippet : '@copyright ${1:description}$0'} 79 | {name: '@deprecated', documentationName : 'deprecated', snippet : '@deprecated ${1:[vector]} ${2:[description]}$0'} 80 | {name: '@example', documentationName : 'example', snippet : '@example ${1:example}$0'} 81 | {name: '@filesource', documentationName : 'filesource', snippet : '@filesource$0'} 82 | {name: '@ignore', documentationName : 'ignore', snippet : '@ignore ${1:[description]}$0'} 83 | {name: '@inheritDoc', documentationName : 'inheritDoc', snippet : '@inheritDoc$0'} 84 | {name: '@internal', documentationName : 'internal', snippet : '@internal ${1:description}$0'} 85 | {name: '@license', documentationName : 'license', snippet : '@license ${1:[url]} ${2:name}$0'} 86 | {name: '@link', documentationName : 'link', snippet : '@link ${1:uri} ${2:[description]}$0'} 87 | {name: '@method', documentationName : 'method', snippet : '@method ${1:type} ${2:name}(${3:[parameter list]})$0'} 88 | {name: '@package', documentationName : 'package', snippet : '@package ${1:package name}$0'} 89 | {name: '@param', documentationName : 'param', snippet : '@param ${1:mixed} \$${2:parameter} ${3:[description]}$0'} 90 | {name: '@property', documentationName : 'property', snippet : '@property ${1:type} ${2:name} ${3:[description]}$0'} 91 | {name: '@property-read', documentationName : 'property-read', snippet : '@property-read ${1:type} ${2:name} ${3:[description]}$0'} 92 | {name: '@property-write', documentationName : 'property-write', snippet : '@property-write ${1:type} ${2:name} ${3:[description]}$0'} 93 | {name: '@return', documentationName : 'return', snippet : '@return ${1:type} ${2:[description]}$0'} 94 | {name: '@see', documentationName : 'see', snippet : '@see ${1:URI or FQSEN} ${2:description}$0'} 95 | {name: '@since', documentationName : 'since', snippet : '@since ${1:version} ${2:[description]}$0'} 96 | {name: '@source', documentationName : 'source', snippet : '@source ${1:start line} ${2:number of lines} ${3:[description]}$0'} 97 | {name: '@throws', documentationName : 'throws', snippet : '@throws ${1:exception type} ${2:[description]}$0'} 98 | {name: '@todo', documentationName : 'todo', snippet : '@todo ${1:description}$0'} 99 | {name: '@uses', documentationName : 'uses', snippet : '@uses ${1:FQSEN} ${2:[description]}$0'} 100 | {name: '@var', documentationName : 'var', snippet : '@var ${1:type} ${2:\$${3:[property]} ${4:[description]}}$0'} 101 | {name: '@version', documentationName : 'version', snippet : '@version ${1:vector} ${2:[description]}$0'} 102 | ] 103 | -------------------------------------------------------------------------------- /lib/Main.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | ###* 3 | * Configuration settings. 4 | ### 5 | config: 6 | disableBuiltinAutocompletion: 7 | title : 'Disable built-in PHP autocompletion from Atom' 8 | description : 'Atom also provides some default autocompletion for PHP, which includes function names for 9 | some common PHP functions, but without their parameters (just their names). If this is 10 | checked, these will be surpressed and not show up in autocompletion. If you uncheck this, 11 | function names may show up twice: once from this package and once from Atom itself.' 12 | type : 'boolean' 13 | default : true 14 | order : 1 15 | 16 | automaticallyAddUseStatements: 17 | title : 'Automatically add use statements when necessary' 18 | description : 'When enabled, a use statement will be added when autocompleting a class name (if it isn\'t 19 | already present).' 20 | type : 'boolean' 21 | default : true 22 | order : 2 23 | 24 | enablePhpunitAnnotationTags: 25 | title : 'Autocomplete PHPUnit annotation tags' 26 | description : 'When enabled, PHPUnit annotation tags will be autocompleted.' 27 | type : 'boolean' 28 | default : true 29 | order : 3 30 | 31 | largeListRefreshTimeout: 32 | title : 'Timeout before refreshing large data (global functions, global constants, class list, ...)' 33 | description : 'Because the contents of these large lists changes rarely in most code bases, they are 34 | refreshed less often than other items. The amount of time (in milliseconds) specified here 35 | will need to pass after the last reindexing occurs (in any editor).' 36 | type : 'string' 37 | default : '5000' 38 | order : 4 39 | 40 | ###* 41 | * The name of the package. 42 | ### 43 | packageName: 'php-integrator-autocomplete-plus-legacy-php56' 44 | 45 | ###* 46 | * The configuration object. 47 | ### 48 | configuration: null 49 | 50 | ###* 51 | * List of tooltip providers. 52 | ### 53 | providers: [] 54 | 55 | ###* 56 | * Activates the package. 57 | ### 58 | activate: -> 59 | AtomConfig = require './AtomConfig' 60 | MemberProvider = require './MemberProvider' 61 | SnippetProvider = require './SnippetProvider' 62 | KeywordProvider = require './KeywordProvider' 63 | NamespaceProvider = require './NamespaceProvider' 64 | ClassProvider = require './ClassProvider' 65 | GlobalConstantProvider = require './GlobalConstantProvider' 66 | VariableProvider = require './VariableProvider' 67 | GlobalVariableProvider = require './GlobalVariableProvider' 68 | TypeHintNewVariableNameProvider = require './TypeHintNewVariableNameProvider' 69 | MagicConstantProvider = require './MagicConstantProvider' 70 | GlobalFunctionProvider = require './GlobalFunctionProvider' 71 | DocblockAnnotationProvider = require './DocblockAnnotationProvider' 72 | DocblockTagProvider = require './DocblockTagProvider' 73 | PHPUnitTagProvider = require './PHPUnitTagProvider' 74 | 75 | @configuration = new AtomConfig(@packageName) 76 | 77 | @providers.push(new SnippetProvider(@configuration)) 78 | @providers.push(new KeywordProvider(@configuration)) 79 | @providers.push(new MemberProvider(@configuration)) 80 | @providers.push(new TypeHintNewVariableNameProvider(@configuration)) 81 | @providers.push(new VariableProvider(@configuration)) 82 | @providers.push(new GlobalFunctionProvider(@configuration)) 83 | @providers.push(new GlobalConstantProvider(@configuration)) 84 | @providers.push(new NamespaceProvider(@configuration)) 85 | @providers.push(new ClassProvider(@configuration)) 86 | @providers.push(new GlobalVariableProvider(@configuration)) 87 | @providers.push(new MagicConstantProvider(@configuration)) 88 | @providers.push(new DocblockAnnotationProvider(@configuration)) 89 | @providers.push(new DocblockTagProvider(@configuration)) 90 | @providers.push(new PHPUnitTagProvider(@configuration)) 91 | 92 | ###* 93 | * Deactivates the package. 94 | ### 95 | deactivate: -> 96 | @deactivateProviders() 97 | 98 | ###* 99 | * Activates the providers using the specified service. 100 | ### 101 | activateProviders: (service) -> 102 | for provider in @providers 103 | provider.activate(service) 104 | 105 | ###* 106 | * Deactivates any active providers. 107 | ### 108 | deactivateProviders: () -> 109 | for provider in @providers 110 | provider.deactivate() 111 | 112 | @providers = [] 113 | 114 | ###* 115 | * Sets the php-integrator service. 116 | * 117 | * @param {mixed} service 118 | * 119 | * @return {Disposable} 120 | ### 121 | setService: (service) -> 122 | @activateProviders(service) 123 | 124 | {Disposable} = require 'atom' 125 | 126 | return new Disposable => @deactivateProviders() 127 | 128 | ###* 129 | * Retrieves a list of supported autocompletion providers. 130 | * 131 | * @return {array} 132 | ### 133 | getProviders: -> 134 | return @providers 135 | -------------------------------------------------------------------------------- /lib/GlobalFunctionProvider.coffee: -------------------------------------------------------------------------------- 1 | AbstractProvider = require "./AbstractProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for global PHP functions. 7 | ## 8 | class GlobalFunctionProvider extends AbstractProvider 9 | ###* 10 | * @inheritdoc 11 | * 12 | * These can appear pretty much everywhere, but not in variable names or as class members. Note that functions can 13 | * also appear inside namespaces, hence the middle part. 14 | ### 15 | regex: /(?:^|[^\$:>\w])((?:[a-zA-Z_][a-zA-Z0-9_]*\\)*[a-zA-Z_]+)$/ 16 | 17 | ###* 18 | # Cache object to help improve responsiveness of autocompletion. 19 | ### 20 | listCache: null 21 | 22 | ###* 23 | # A list of disposables to dispose on deactivation. 24 | ### 25 | disposables: null 26 | 27 | ###* 28 | # Keeps track of a currently pending promise to ensure only one is active at any given time. 29 | ### 30 | pendingPromise: null 31 | 32 | ###* 33 | # Keeps track of a currently pending timeout to ensure only one is active at any given time.. 34 | ### 35 | timeoutHandle: null 36 | 37 | ###* 38 | * @inheritdoc 39 | ### 40 | activate: (@service) -> 41 | {CompositeDisposable} = require 'atom' 42 | 43 | @disposables = new CompositeDisposable() 44 | 45 | @disposables.add(@service.onDidFinishIndexing(@onDidFinishIndexing.bind(this))) 46 | 47 | ###* 48 | * @inheritdoc 49 | ### 50 | deactivate: () -> 51 | if @disposables? 52 | @disposables.dispose() 53 | @disposables = null 54 | 55 | ###* 56 | * Called when reindexing successfully finishes. 57 | * 58 | * @param {Object} info 59 | ### 60 | onDidFinishIndexing: (info) -> 61 | # Only reindex a couple of seconds after the last reindex. This prevents constant refreshes being scheduled 62 | # while the user is still modifying the file. This is acceptable as this provider's data rarely changes and 63 | # it is fairly expensive to refresh the cache. 64 | if @timeoutHandle? 65 | clearTimeout(@timeoutHandle) 66 | @timeoutHandle = null 67 | 68 | @timeoutHandle = setTimeout ( => 69 | @timeoutHandle = null 70 | @refreshCache() 71 | ), @config.get('largeListRefreshTimeout') 72 | 73 | ###* 74 | * Refreshes the internal cache. Returns a promise that resolves with the cache once it has been refreshed. 75 | * 76 | * @return {Promise} 77 | ### 78 | refreshCache: () -> 79 | successHandler = (functions) => 80 | @pendingPromise = null 81 | 82 | return unless functions 83 | 84 | @listCache = functions 85 | 86 | return @listCache 87 | 88 | failureHandler = () => 89 | @pendingPromise = null 90 | 91 | return [] 92 | 93 | if not @pendingPromise? 94 | @pendingPromise = @service.getGlobalFunctions().then(successHandler, failureHandler) 95 | 96 | return @pendingPromise 97 | 98 | ###* 99 | * Fetches a list of results that can be fed to the addSuggestions method. 100 | * 101 | * @return {Promise} 102 | ### 103 | fetchResults: () -> 104 | return new Promise (resolve, reject) => 105 | if @listCache? 106 | resolve(@listCache) 107 | return 108 | 109 | return @refreshCache() 110 | 111 | ###* 112 | * @inheritdoc 113 | ### 114 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 115 | return [] if not @service 116 | 117 | prefix = @getPrefix(editor, bufferPosition) 118 | return [] unless prefix != null 119 | 120 | successHandler = (functions) => 121 | return [] unless functions 122 | 123 | characterAfterPrefix = editor.getTextInRange([bufferPosition, [bufferPosition.row, bufferPosition.column + 1]]) 124 | insertParameterList = if characterAfterPrefix == '(' then false else true 125 | 126 | return @addSuggestions(functions, prefix.trim(), insertParameterList) 127 | 128 | failureHandler = () => 129 | return [] 130 | 131 | return @fetchResults().then(successHandler, failureHandler) 132 | 133 | ###* 134 | * Returns available suggestions. 135 | * 136 | * @param {array} functions 137 | * @param {string} prefix 138 | * @param {bool} insertParameterList Whether to insert a list of parameters or not. 139 | * 140 | * @return {array} 141 | ### 142 | addSuggestions: (functions, prefix, insertParameterList = true) -> 143 | suggestions = [] 144 | 145 | for fqcn, func of functions 146 | shortDescription = '' 147 | 148 | if func.shortDescription? and func.shortDescription.length > 0 149 | shortDescription = func.shortDescription 150 | 151 | else if func.isBuiltin 152 | shortDescription = 'Built-in PHP function.' 153 | 154 | # NOTE: The description must not be empty for the 'More' button to show up. 155 | suggestions.push 156 | text : func.name 157 | type : 'function' 158 | snippet : if insertParameterList then @getFunctionSnippet(func.name, func) else null 159 | displayText : func.name + @getFunctionParameterList(func) 160 | replacementPrefix : prefix 161 | leftLabel : @getTypeSpecificationFromTypeArray(func.returnTypes) 162 | rightLabelHTML : @getSuggestionRightLabel(func) 163 | description : shortDescription 164 | descriptionMoreURL : if func.isBuiltin then @config.get('php_documentation_base_urls').functions + func.name else null 165 | className : 'php-integrator-autocomplete-plus-suggestion' + if func.isDeprecated then ' php-integrator-autocomplete-plus-strike' else '' 166 | 167 | return suggestions 168 | -------------------------------------------------------------------------------- /lib/AbstractProvider.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | 3 | ##* 4 | # Base class for providers. 5 | ## 6 | class AbstractProvider 7 | ###* 8 | * The regular expression that is used for the prefix. 9 | ### 10 | regex: '' 11 | 12 | ###* 13 | * The class selectors for which autocompletion triggers. 14 | ### 15 | scopeSelector: '.source.php' 16 | 17 | ###* 18 | * The inclusion priority of the provider. 19 | ### 20 | inclusionPriority: 1 21 | 22 | ###* 23 | * Let base autocomplete-plus handle the actual filtering, that way we don't need to manually filter (e.g. using 24 | * fuzzaldrin) ourselves and the user can configure filtering settings on the base package. 25 | ### 26 | filterSuggestions: true 27 | 28 | ###* 29 | * The class selectors autocompletion is explicitly disabled for (overrules the {@see scopeSelector}). 30 | ### 31 | disableForScopeSelector: '.source.php .comment, .source.php .string' 32 | 33 | ###* 34 | * The service (that can be used to query the source code and contains utility methods). 35 | ### 36 | service: null 37 | 38 | ###* 39 | * Contains global package settings. 40 | ### 41 | config: null 42 | 43 | ###* 44 | * Constructor. 45 | * 46 | * @param {Config} config 47 | ### 48 | constructor: (@config) -> 49 | @excludeLowerPriority = @config.get('disableBuiltinAutocompletion') 50 | 51 | @config.onDidChange 'disableBuiltinAutocompletion', (newValue) => 52 | @excludeLowerPriority = newValue 53 | 54 | ###* 55 | * Initializes this provider. 56 | * 57 | * @param {mixed} service 58 | ### 59 | activate: (@service) -> 60 | 61 | ###* 62 | * Deactives the provider. 63 | ### 64 | deactivate: () -> 65 | 66 | ###* 67 | * Entry point for all requests from autocomplete-plus. 68 | * 69 | * @param {TextEditor} editor 70 | * @param {Point} bufferPosition 71 | * @param {string} scopeDescriptor 72 | * @param {string} prefix 73 | * 74 | * @return {Promise|array} 75 | ### 76 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 77 | throw new Error("This method is abstract and must be implemented!") 78 | 79 | ###* 80 | * Builds the signature for a PHP function or method. 81 | * 82 | * @param {array} info Information about the function or method. 83 | * 84 | * @return {string} 85 | ### 86 | getFunctionParameterList: (info) -> 87 | body = "(" 88 | 89 | isInOptionalList = false 90 | 91 | for param, index in info.parameters 92 | description = '' 93 | description += '[' if param.isOptional and not isInOptionalList 94 | description += ', ' if index != 0 95 | description += '...' if param.isVariadic 96 | description += '&' if param.isReference 97 | description += '$' + param.name 98 | description += ' = ' + param.defaultValue if param.defaultValue? 99 | description += ']' if param.isOptional and index == (info.parameters.length - 1) 100 | 101 | isInOptionalList = param.isOptional 102 | 103 | if not param.isOptional 104 | body += description 105 | 106 | else 107 | body += description 108 | 109 | body += ")" 110 | 111 | return body 112 | 113 | ###* 114 | * Builds the right label for a PHP function or method. 115 | * 116 | * @param {array} info Information about the function or method. 117 | * 118 | * @return {string} 119 | ### 120 | getSuggestionRightLabel: (info) -> 121 | # Determine the short name of the location where this item is defined. 122 | declaringStructureShortName = '' 123 | 124 | if info.declaringStructure and info.declaringStructure.name 125 | return @getClassShortName(info.declaringStructure.name) 126 | 127 | return declaringStructureShortName 128 | 129 | ###* 130 | * Builds the snippet for a PHP function or method. 131 | * 132 | * @param {string} name The name of the function or method. 133 | * @param {array} info Information about the function or method. 134 | * 135 | * @return {string} 136 | ### 137 | getFunctionSnippet: (name, info) -> 138 | if info.parameters.length > 0 139 | return name + '($0)' 140 | 141 | return name + '()$0' 142 | 143 | ###* 144 | * Retrieves the short name for the specified class name (i.e. the last segment, without the class namespace). 145 | * 146 | * @param {string} className 147 | * 148 | * @return {string} 149 | ### 150 | getClassShortName: (className) -> 151 | return null if not className 152 | 153 | parts = className.split('\\') 154 | return parts.pop() 155 | 156 | ###* 157 | * @param {Array} typeArray 158 | * 159 | * @return {String} 160 | ### 161 | getTypeSpecificationFromTypeArray: (typeArray) -> 162 | typeNames = typeArray.map (type) => 163 | return @getClassShortName(type.type) 164 | 165 | return typeNames.join('|') 166 | 167 | ###* 168 | * Retrieves the prefix matches using the specified buffer position and the specified regular expression. 169 | * 170 | * @param {TextEditor} editor 171 | * @param {Point} bufferPosition 172 | * @param {String} regex 173 | * 174 | * @return {Array|null} 175 | ### 176 | getPrefixMatchesByRegex: (editor, bufferPosition, regex) -> 177 | # Unfortunately the regex $ doesn't seem to match the end when using backwardsScanInRange, so we match the regex 178 | # manually. 179 | line = editor.getBuffer().getTextInRange([[bufferPosition.row, 0], bufferPosition]) 180 | 181 | matches = regex.exec(line) 182 | 183 | return matches if matches 184 | return null 185 | 186 | ###* 187 | * Retrieves the prefix using the specified buffer position and the current class' configured regular expression. 188 | * 189 | * @param {TextEditor} editor 190 | * @param {Point} bufferPosition 191 | * 192 | * @return {String|null} 193 | ### 194 | getPrefix: (editor, bufferPosition) -> 195 | matches = @getPrefixMatchesByRegex(editor, bufferPosition, @regex) 196 | 197 | if matches 198 | # We always want the last match, as that's closest to the cursor itself. 199 | match = matches[matches.length - 1] 200 | 201 | # Turn undefined, which happens if the capture group has nothing to catch, into a valid string. 202 | return '' if not match? 203 | return match 204 | 205 | return null 206 | -------------------------------------------------------------------------------- /lib/PHPUnitTagProvider.coffee: -------------------------------------------------------------------------------- 1 | DocblockTagProvider = require "./DocblockTagProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for PHP-unit annotation tags. 7 | ## 8 | class PHPUnitTagProvider extends DocblockTagProvider 9 | ###* 10 | * @inheritdoc 11 | ### 12 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 13 | return [] unless @config.get('enablePhpunitAnnotationTags') 14 | super 15 | 16 | ###* 17 | * @inheritdoc 18 | ### 19 | addSuggestions: (tagList, prefix) -> 20 | suggestions = [] 21 | 22 | for tag in tagList 23 | documentationUrl = @config.get('phpunit_annotations_base_url').prefix 24 | 25 | if tag.documentationName 26 | documentationUrl += @config.get('phpunit_annotations_base_url').id_prefix + tag.documentationName 27 | 28 | # NOTE: The description must not be empty for the 'More' button to show up. 29 | suggestions.push 30 | type : 'tag', 31 | description : tag.description 32 | descriptionMoreURL : documentationUrl 33 | snippet : tag.snippet 34 | displayText : tag.name 35 | replacementPrefix : prefix 36 | 37 | return suggestions 38 | 39 | ###* 40 | * Retrieves a list of known docblock tags. 41 | * 42 | * @return {array} 43 | ### 44 | fetchTagList: () -> 45 | return [ 46 | {name: '@after', documentationName: 'after', snippet: '@after', description: 'The method should be called after each test method in a test case class'} 47 | {name: '@afterClass', documentationName: 'afterClass', snippet: '@afterClass', description: 'The static method should be called after all test methods in a test class have been run to clean up shared fixtures'} 48 | {name: '@backupGlobals', documentationName: 'backupGlobals', snippet: '@backupGlobals ${1:disabled}', description: 'Completely enable or disable the backup and restore operations for global variables'} 49 | {name: '@backupStaticAttributes', documentationName: 'backupStaticAttributes', snippet: '@backupStaticAttributes ${1:disabled}', description: 'Back up all static property values in all declared classes before each test and restore them afterwards'} 50 | {name: '@before', documentationName: 'before', snippet: '@before', description: 'The method should be called before each test method in a test case class'} 51 | {name: '@beforeClass', documentationName: 'beforeClass', snippet: '@beforeClass', description: 'The static method should be called before any test methods in a test class are run to set up shared fixtures'} 52 | {name: '@codeCoverageIgnore', documentationName: 'codeCoverageIgnore', snippet: '@codeCoverageIgnore', description: 'Exclude lines of code from the coverage analysis'} 53 | {name: '@codeCoverageIgnoreEnd', documentationName: 'codeCoverageIgnoreEnd', snippet: '@codeCoverageIgnoreEnd', description: 'Exclude lines of code from the coverage analysis'} 54 | {name: '@codeCoverageIgnoreStart', documentationName: 'codeCoverageIgnoreStart', snippet: '@codeCoverageIgnoreStart', description: 'Exclude lines of code from the coverage analysis'} 55 | {name: '@covers', documentationName: 'covers', snippet: '@covers ${1:method}', description: 'Specify which method(s) the test method wants to test'} 56 | {name: '@coversDefaultClass', documentationName: 'coversDefaultClass', snippet: '@coversDefaultClass ${1:class}', description: 'Specify a default namespace or class name for @covers annotation'} 57 | {name: '@coversNothing', documentationName: 'coversNothing', snippet: '@coversNothing', description: 'Specify no code coverage information will be recorded for the annotated test case'} 58 | {name: '@dataProvider', documentationName: 'dataProvider', snippet: '@dataProvider ${1:provider}', description: 'Specify the data provider method'} 59 | {name: '@depends', documentationName: 'depends', snippet: '@depends ${1:test}', description: 'Specify the test method this test depends'} 60 | {name: '@expectedException', documentationName: 'expectedException', snippet: '@expectedException ${1:exception}', description: 'Specify the exception that must be thrown inside the test method'} 61 | {name: '@expectedExceptionCode', documentationName: 'expectedExceptionCode', snippet: '@expectedExceptionCode ${1:code}', description: 'Specify the code for exception set by @expectedException'} 62 | {name: '@expectedExceptionMessage', documentationName: 'expectedExceptionMessage', snippet: '@expectedExceptionMessage ${1:message}', description: 'Specify the messege for exception set by @expectedException'} 63 | {name: '@expectedExceptionMessageRegExp', documentationName: 'expectedExceptionMessageRegExp', snippet: '@expectedExceptionMessageRegExp ${1:message}', description: 'Specify the messege as a regular expression for exception set by @expectedException'} 64 | {name: '@group', documentationName: 'group', snippet: '@group ${1:group}', description: 'Tag a test as belonging to one or more groups'} 65 | {name: '@large', documentationName: 'large', snippet: '@large', description: 'An alias for @group large'} 66 | {name: '@medium', documentationName: 'medium', snippet: '@medium', description: 'An alias for @group medium'} 67 | {name: '@preserveGlobalState', documentationName: 'preserveGlobalState', snippet: '@preserveGlobalState ${1:disabled}', description: 'Prevent PHPUnit from preserving global state'} 68 | {name: '@requires', documentationName: 'requires', snippet: '@requires ${1:preconditions}', description: 'Skip tests when common preconditions are not met'} 69 | {name: '@runInSeparateProcess', documentationName: 'runInSeparateProcess', snippet: '@runInSeparateProcess', description: 'Indicates that a test should be run in a separate PHP process'} 70 | {name: '@runTestsInSeparateProcesses', documentationName: 'runTestsInSeparateProcesses', snippet: '@runTestsInSeparateProcesses', description: 'Indicates that all tests in a test class should be run in a separate PHP process'} 71 | {name: '@small', documentationName: 'small', snippet: '@small', description: 'An alias for @group small'} 72 | {name: '@testdox', documentationName: 'testdox', snippet: '@testdox', description: ''} 73 | {name: '@ticket', documentationName: 'ticket', snippet: '@ticket', description: ''} 74 | {name: '@uses', documentationName: 'uses', snippet: '@uses', description: 'Specify the code which will be executed by a test, but is not intended to be covered by the test'} 75 | ] 76 | -------------------------------------------------------------------------------- /lib/MemberProvider.coffee: -------------------------------------------------------------------------------- 1 | AbstractProvider = require "./AbstractProvider" 2 | 3 | module.exports = 4 | 5 | ##* 6 | # Provides autocompletion for members of variables such as after ->, ::. 7 | ## 8 | class MemberProvider extends AbstractProvider 9 | ###* 10 | * @inheritdoc 11 | * 12 | * Member autocompletion is allowed inside double quoted strings (see also 13 | * {@link https://secure.php.net/manual/en/language.types.string.php#language.types.string.parsing}). Static class 14 | * name access must always be wrapped as {${Class::staticAccess}}, our autocompletion will also autocomplete without 15 | * the extra curly brackets, but it's better to have some autocompletion in a few rare erroneous cases than no 16 | * autocompletion at all in the most used cases. 17 | ### 18 | disableForScopeSelector: '.source.php .comment, .source.php .string.quoted.single' 19 | 20 | ###* 21 | * @inheritdoc 22 | * 23 | * Autocompletion for class members, i.e. after a ::, ->, ... 24 | ### 25 | regex: /((?:\$?(?:[a-zA-Z0-9_]*)\s*(?:\(.*\))?\s*(?:->|::)\s*)+\$?[a-zA-Z0-9_]*)$/ 26 | 27 | ###* 28 | * @inheritdoc 29 | ### 30 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 31 | return [] if not @service 32 | 33 | prefix = @getPrefix(editor, bufferPosition) 34 | return [] unless prefix != null 35 | 36 | # We only autocomplete after splitters, so there must be at least one word, one splitter, and another word 37 | # (the latter which could be empty). 38 | elements = prefix.split(/(->|::)/) 39 | return [] unless elements.length > 2 40 | 41 | objectBeingCompleted = elements[elements.length - 3].trim() 42 | 43 | failureHandler = () => 44 | # Just return no results. 45 | return [] 46 | 47 | successHandler = (values) => 48 | currentClassInfo = values[0] 49 | getClassInfoResults = values[1] 50 | 51 | getClassInfoResults = getClassInfoResults.filter (item) -> 52 | return item? 53 | 54 | return [] if getClassInfoResults.length == 0 55 | 56 | mustBeStatic = false 57 | hasDoubleDotSeparator = false 58 | 59 | if elements[elements.length - 2] == '::' 60 | hasDoubleDotSeparator = true 61 | 62 | if objectBeingCompleted != 'parent' 63 | mustBeStatic = true 64 | 65 | characterAfterPrefix = editor.getTextInRange([bufferPosition, [bufferPosition.row, bufferPosition.column + 1]]) 66 | insertParameterList = if characterAfterPrefix == '(' then false else true 67 | 68 | currentClassParents = [] 69 | 70 | if currentClassInfo? 71 | currentClassParents = currentClassInfo.parents 72 | 73 | if not currentClassParents? 74 | currentClassParents = [] 75 | 76 | return @addSuggestions(getClassInfoResults, elements[elements.length - 1].trim(), hasDoubleDotSeparator, (element) => 77 | # Constants are only available when statically accessed (actually not entirely correct, they will 78 | # work in a non-static context as well, but it's not good practice). 79 | return false if mustBeStatic and not element.isStatic 80 | return true 81 | , insertParameterList) 82 | 83 | resultingTypesSuccessHandler = (types) => 84 | classTypePromises = [] 85 | 86 | getRelevantClassInfoDataHandler = (classInfo) => 87 | return classInfo 88 | 89 | getRelevantClassInfoDataFailureHandler = (classInfo) => 90 | return null 91 | 92 | for type in types 93 | continue if @service.isBasicType(type) 94 | 95 | classTypePromises.push @service.getClassInfo(type).then( 96 | getRelevantClassInfoDataHandler, 97 | getRelevantClassInfoDataFailureHandler 98 | ) 99 | 100 | return Promise.all(classTypePromises) 101 | 102 | resultingTypesAtPromise = @service.getResultingTypesAt(editor, bufferPosition, true).then( 103 | resultingTypesSuccessHandler, 104 | failureHandler 105 | ) 106 | 107 | if objectBeingCompleted != '$this' 108 | # We only need this data above under the same condition. 109 | currentClassNameGetClassInfoHandler = (currentClass) => 110 | return null if not currentClass 111 | 112 | getClassInfoHandler = (currentClassInfo) => 113 | return currentClassInfo 114 | 115 | return @service.getClassInfo(currentClass).then(getClassInfoHandler, failureHandler) 116 | 117 | determineCurrentClassNamePromise = @service.determineCurrentClassName(editor, bufferPosition).then( 118 | currentClassNameGetClassInfoHandler, 119 | failureHandler 120 | ) 121 | 122 | else 123 | determineCurrentClassNamePromise = null 124 | 125 | return Promise.all([determineCurrentClassNamePromise, resultingTypesAtPromise]).then(successHandler, failureHandler) 126 | 127 | ###* 128 | * Returns available suggestions. 129 | * 130 | * @param {Array} classInfoObjects 131 | * @param {string} prefix Prefix to match (may be left empty to list all members). 132 | * @param {boolean} hasDoubleDotSeparator 133 | * @param {callback} filterCallback A callback that should return true if the item should be added 134 | * to the suggestions list. 135 | * @param {bool} insertParameterList Whether to insert a list of parameters for methods. 136 | * 137 | * @return {array} 138 | ### 139 | addSuggestions: (classInfoObjects, prefix, hasDoubleDotSeparator, filterCallback, insertParameterList = true) -> 140 | suggestions = [] 141 | 142 | for classInfo in classInfoObjects 143 | processList = (list, type) => 144 | for name, member of list 145 | if filterCallback and not filterCallback(member) 146 | continue 147 | 148 | if type == 'constant' and not hasDoubleDotSeparator 149 | # Constants can only be accessed statically. Also, it would cause the built-in PHP 5.5 'class' 150 | # keyword to be listed as member after ->, overriding any property with the name 'class'. 151 | continue 152 | 153 | text = (if type == 'property' and hasDoubleDotSeparator then '$' else '') + member.name 154 | typesToDisplay = if type == 'method' then member.returnTypes else member.types 155 | 156 | displayText = text 157 | 158 | if 'parameters' of member 159 | displayText += @getFunctionParameterList(member) 160 | 161 | leftLabel = '' 162 | 163 | if member.isPublic 164 | leftLabel += ' ' 165 | 166 | else if member.isProtected 167 | leftLabel += ' ' 168 | 169 | else if member.isPrivate 170 | leftLabel += ' ' 171 | 172 | leftLabel += @getTypeSpecificationFromTypeArray(typesToDisplay) 173 | 174 | suggestions.push 175 | text : text 176 | type : type 177 | snippet : if type == 'method' and insertParameterList then @getFunctionSnippet(member.name, member) else null 178 | displayText : displayText 179 | replacementPrefix : prefix 180 | leftLabelHTML : leftLabel 181 | rightLabelHTML : @getSuggestionRightLabel(member) 182 | description : if member.shortDescription then member.shortDescription else '' 183 | className : 'php-integrator-autocomplete-plus-suggestion php-integrator-autocomplete-plus-has-additional-icons' + if member.isDeprecated then ' php-integrator-autocomplete-plus-strike' else '' 184 | 185 | processList(classInfo.methods, 'method') 186 | processList(classInfo.constants, 'constant') 187 | processList(classInfo.properties, 'property') 188 | 189 | return suggestions 190 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.5.0 2 | * Fix incorrect service version. 3 | * PHPUnit tags will now be autocompleted in docblocks (thanks to [msdm](https://github.com/msdm)). 4 | * you can disable this via the settings. 5 | 6 | ## 1.4.1 7 | * Fix parameter default values that were "falsy" in JavaScript not showing up (such as `0`). 8 | 9 | ## 1.4.0 10 | * Add `finally` to keyword suggestions. 11 | * Add PHP 7 keywords to keyword suggestions. 12 | * Remove `resource`, `object`, `mixed` and `numeric` from keyword suggestions. 13 | * These are soft reserved in PHP, but aren't actual keywords so they aren't useful to the developer as suggestions. 14 | * Give keywords a higher priority (https://github.com/php-integrator/atom-autocompletion/issues/96). 15 | * As these are lowercase and class names should start with an uppercase character, these will seldom interfere with the completion of class names. Previously, on the other hand, when you wanted keywords, you would constantly get spammed with the list of classes. 16 | 17 | ## 1.3.0 18 | * Use the new shield icon to denote protected members (feels more appropriate than a key). 19 | * Constants are no longer listed after the arrow operator `->`. 20 | ** Doing this results in an `Undefined property` notice. 21 | ** This also fixes the issue where any valid property named `class` would be overridden by the `class` constant introduced in PHP 5.5. 22 | 23 | ## 1.2.0 (base 2.0.0) 24 | * The functionality for importing use statements has been moved to the base package. This also caused the setting `insertNewlinesForUseStatements` to be moved to the base package. 25 | * If you had this enabled, you will need to reenable it in the base package after upgrading. 26 | * If you had a shortcut attached to sorting menu items, you will need to point it to `php-integrator-base` instead of `php-integrator-autocomplete-plus` after upgrading. 27 | * Actual namespaces will now be suggested after the namespace keyword instead of a list of classes. 28 | * Fix use statements unnecessarily being added for classes in the same namespace when the cursor was outside a class. 29 | * Member autocompletion suggestions will now show an icon indicating the access modifier of the method (public, private or protected). 30 | * Fix use statements being added for non-compound classnames in anonymous namespaces or files without a namespace. This would result in PHP warnings on execution. 31 | * Member autocompletion suggestions will no longer be filtered out based on their accessibility from the current scope. 32 | * It wasn't transparant that this check was even happening. Instead, all suggestions are always available and if something is not accessible, the linter will show it [in the future](https://github.com/php-integrator/core/issues/20) instead. 33 | * [Version 3.0 of the `autocomplete-plus` provider specification](https://github.com/atom/autocomplete-plus/issues/776) is now used. 34 | * Fix the right label not always showing precisely where the method is defined. Instead, it was sometimes showing the overriden method's location, sometimes an implemented interface method location and sometimes the actual location of the method. 35 | 36 | ## 1.1.5 37 | ### Bugs fixed 38 | * Rename the package and repository. 39 | 40 | ## 1.1.4 41 | ### Bugs fixed 42 | * Fix not being able to navigate to the PHP documentation for built-in classes with longer FQCN's, such as classes from MongoDB. 43 | * Fix built-in classes sometimes navigating to the wrong page, e.g. `DateTime` was navigating to the overview page instead of the class documentation page. 44 | 45 | ## 1.1.3 46 | ### Bugs fixed 47 | * Fix the parameter list itself also being inserted for class methods calls. 48 | 49 | ## 1.1.2 50 | ### Bugs fixed 51 | * Fix incorrect styling on the parameter list for functions and methods. Unfortunately this meant removing the styling for the parameter list, see also [this ticket](https://github.com/atom/autocomplete-plus/issues/764). 52 | 53 | ## 1.1.1 54 | ### Bugs fixed 55 | * Fix constants being added to the suggestions twice, resulting in some information such as deprecation information getting lost. 56 | 57 | ## 1.1.0 58 | ### Features and improvements 59 | * Make the timeout before refreshing global data adjustable via the settings panel. 60 | 61 | ### Bugs fixed 62 | * Trait suggestions will now be shown after the `use` keyword occurs in a class. 63 | 64 | ## 1.0.4 65 | ### Bugs fixed 66 | * Fixed `@inheritDoc` being missing from docblock tag suggestions (thanks to [squio](https://github.com/squio)). 67 | 68 | ## 1.0.3 69 | ### Bugs fixed 70 | * Fixed error with member autocompletion when there was no current class. 71 | 72 | ## 1.0.2 73 | ### Bugs fixed 74 | * Global function names with upper cased letters were not being autocompleted. 75 | 76 | ## 1.0.1 77 | ### Bugs fixed 78 | * Add a sanity check for providers on package deactivation. 79 | 80 | ## 1.0.0 (base 1.0.0) 81 | ### Features and improvements 82 | * Only interfaces will now be listed after the `implements` keyword. 83 | * Only appropriate classes or interfaces will now be listed after the `extends` keyword. 84 | * The default value for function and method parameters that have one will now be shown during autocompletion. 85 | * "Branched" member autocompletion is now supported. This means that if a structural element returns multiple types, all of their members will be listed, for example: 86 | 87 | ```php 88 | /** 89 | * @return \IteratorAggregate|\Countable 90 | */ 91 | public function foo() 92 | { 93 | $this->foo()-> // Will list members for both Countable and IteratorAggregate. 94 | } 95 | ``` 96 | 97 | ### Bugs fixed 98 | * Global constants will now also show their type in the left column. 99 | * Global constants will now properly show their short description instead of just "Global PHP constant". 100 | * Fix the FQCN not properly being completed in some cases when starting with a leading slash (only the last part was completed). 101 | * The ellipsis for variadic parameters is now shown at the front instead of the back of the parameter, consistent with PHP's syntax. 102 | * Global constants will no longer show after a backslash. It would always result in a syntax error and puts the class list up front, which is more likely to be what you're looking for. 103 | * Class suggestions will now be shown without having to type the first letter. An exception is the class list that is shown without any keyword (such as `new`, `extends`, ...), because it constantly pops up when writing regular code, accidentally triggering the completion whenever you want to move to the next line. 104 | 105 | ## 0.8.1 106 | ### Bugs fixed 107 | * Fixed the `class` suggestion being shown for classlikes that didn't exist. 108 | 109 | ## 0.8.0 (base 0.9.0) 110 | ### Features and improvements 111 | * Added a setting that allows disabling the automatic adding of use statements. 112 | * Added a new docblock annotation provider that will list classes that are usable as annotation (i.e. have the `@Annotation` tag in their docblock) after the `@` sign in docblocks (used by e.g. Doctrine and Symfony). 113 | 114 | ### Bugs fixed 115 | * Fix traits not having a different icon in the class suggestion list. 116 | 117 | ## 0.7.0 (base 0.8.0) 118 | ### Features and enhancements 119 | * Also show `$argv` and `$argv` in the autocompletion suggestions. 120 | * Also show new variable suggestions without first typing the dollar sign for fluency. 121 | * Tweaked the ordering of suggestions, which seems to improve overall relevancy of suggestions. 122 | * Fetching class members is now even more asynchronous, improving responsiveness of autocompletion. 123 | * Fetching class list, global function and global constant suggestions is now cached. This should further improve responsiveness of autocompletion. 124 | * The suggestions change fairly rarely and fetching them is expensive because PHP processes are spawned constantly due to the changing contents of the buffer (the base service only caches the results until the next reindex, which happens when the buffer stops changing). Instead, these three lists are refreshed after a couple of seconds after the last successful reindex, (i.e. a couple of seconds after the editor stops changing instead of a couple hundred milliseconds, assuming the code in the editor is valid). 125 | 126 | ### Bugs fixed 127 | * Fixed no local variables being suggested after keywords suchas `return`. 128 | * Fixed new variable names were being suggested after keywords such as `return`. 129 | 130 | ## 0.6.0 (base 0.7.0) 131 | ### Features and enhancements 132 | * Magic constants will now also be suggested. 133 | * Superglobal names will now also be suggested. 134 | * Local variables will now be fetched asynchronously, improving responsiveness. 135 | * New variable names will now be suggested after a type hint, for example typing `FooBarInterface $` will suggest `$fooBar` and `$fooBarInterface`. 136 | * Due to changes to the way variables are fetched in the base service, you will notice some changes when local variable names are suggested: 137 | * Variables outside current the scope will no longer be suggested. This previously only applied to variables outside the active function scope. Now, after you exit a statement such as an if statement, the variables contained in it will no longer be suggested: 138 | ```php 139 | $a = 1; 140 | 141 | if (condition) { 142 | $a = 2; 143 | $b = 3; 144 | } else { 145 | $a = 4; 146 | $c = 5; 147 | } 148 | 149 | $ // Autocompletion will not list $b and $c, only $a. 150 | ``` 151 | Even though they technically remain available in PHP afterwards, their presence is not guaranteed and the absence of them during autocompletion will guard you for mistakes. 152 | * Variables in other statements will no longer incorrectly be listed (for example, in the example above, `$b` will no longer show up inside the else block). 153 | * `$this` will no longer be suggested in global functions and outside class, interface or trait scopes. It will still be suggested inside closures as they can have a `$this` context. 154 | 155 | ### Bugs fixed 156 | * Local variables will no longer be suggested after type hints. 157 | 158 | ## 0.5.3 159 | ### Bugs fixed 160 | * Use statements will no longer be added when typing a namespace name. (Existing items will still be suggested for convenience.) 161 | 162 | ## 0.5.2 (base 0.6.0) 163 | ### Bugs fixed 164 | * Use statements will no longer be added for classes in the same namespace. This was previously only done for the current class. This will also work for relative imports if the import is relative to the current namespace (i.e. use statements will not be added). 165 | 166 | ## 0.5.1 (base 0.5.0) 167 | ### Bugs fixed 168 | * Fixed a rare TypeError in the Utility file. 169 | * Fixed the `yield` keyword not being suggested. 170 | * Fixed the `class` keyword introduced in PHP 5.5 not being completed after two dots. 171 | 172 | ## 0.5.0 (base 0.5.0) 173 | ### Features and enhancements 174 | * Only (non-abstract) classes will be suggested after the new keyword. 175 | * A new keyword provider will also show autocompletion for PHP keywords. 176 | * The parameter list for methods will now be displayed in a different style for better visual recognition. 177 | * A new snippet provider will now suggest a few useful snippets such as for `isset`, `unset`, `catch`, and more. 178 | * When autocompleting use statements, the suggestions will now have a different icon to indicate that it is an import. 179 | * Traits will now receive the 'mixin' type (which by default has the same icon as a 'class') to allow for separate styling. 180 | * Parameters for functions and methods will still be shown during completion, but will no longer actually be completed anymore. Instead, your cursor will be put between the parentheses. As a replacement, please consider using the [php-integrator-call-tips](https://github.com/Gert-dev/php-integrator-call-tips) package (if you're not already), which can now provide call tips instead. Call tips are an improvement as there is no longer a need to remove parameters you don't want and jump around using tab. 181 | 182 | ### Bugs fixed 183 | * Fixed autocompletion not properly completing and working with static class property access, such as `static::$foo->bar()`. 184 | * Autocompletion will now also trigger on a backslash for class names (i.e. `My\Namespace\` didn't trigger any autocompletion before, whilst `My\Namespace\A` did). 185 | 186 | ## 0.4.0 (base 0.4.0) 187 | ### Features and enhancements 188 | * The placement of use statements has been improved. 189 | * Added a new autocompletion provider that will provide snippets for tags in docblocks. 190 | * The class provider will now show a documentation URL for built-in classes, interfaces and traits. 191 | * Added a new command that sorts use statements according to the same algorithm that manages adding them. 192 | * The class provider will now show the type of the structural element (trait, interface or class) in the left label. 193 | * We no longer depend on fuzzaldrin directly. Filtering suggestions is now handled by the base autocomplete-plus package, allowing your configurations there to also take effect in this package. 194 | * $this will now always list private and protected members, which allows files that are being require-d inside classes to define a type override annotation for $this and still be able to access private and protected members there. 195 | * The order in which providers are registered has slightly changed; this results in things that are more interesting being shown first, such as members, class names and variable names. Global constants and functions are 'less important' and are shown further down the list. Note that this only applies if, after filtering (fuzzy matching), there are suggestions present from multiple providers. 196 | 197 | ### Bugs fixed 198 | * Fixed use statements ending up at an incorrect location in some situations. 199 | * Fixed a use statement still being added when starting a class name with a leading slash. 200 | * Fixed duplicate use statements being added in some cases with certain (unformatted) sorting combinations. 201 | * Fixed an unnecessary use statement being added when selecting the current class name during autocompletion. 202 | * When the base package service is not available, autocompletion will silently fail instead of spawning errors. 203 | 204 | ## 0.3.0 205 | ### Features and enhancements 206 | * Documentation for classes will now be shown during autocompletion. 207 | 208 | ### Bugs fixed 209 | * Fixed variables containing numbers not being suggested. 210 | * Use statements were still added if one was already present with a leading slash. 211 | * An error would sometimes be shown when trying to autocomplete a class that did not exist. 212 | * The partial name of the variable being autocompleted is now no longer included in the suggestions. 213 | 214 | ## 0.2.1 215 | ### Bugs fixed 216 | * Global functions inside namespaces were not being autocompleted properly. 217 | * Class names that start with a lower case letter will now also be autocompleted. 218 | * Class names were not being completed in some locations such as inside if statements. 219 | * Added a new configuration option that disables the built-in PHP autocompletion provider (enabled by default). `[1]` 220 | 221 | `[1]` This will solve the problem where built-in functions such as `array_walk` were showing up twice, once from this package and once from Atom's PHP support itself. 222 | 223 | ## 0.2.0 224 | ### Features and enhancements 225 | * Autocompletion now works inside double quoted strings `[1]`. 226 | * When autocompleting variable names, their type is now displayed, if possible. 227 | * Where possible, autocompletion is now asynchronous (using promises), improving performance. 228 | * The right label of class members in the autocompletion window will now display the originating structure (i.e. class, interface or trait). 229 | * The way use statements are added and class names are completed was drastically improved. 230 | * Multiple cursors are now properly supported. 231 | * The appropriate namespace will now be added if you are trying to do a relative import: 232 | 233 | ```php 234 | \w])(\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?)$/ 13 | 14 | ###* 15 | # Regular expression matching class names after the new keyword. 16 | ### 17 | newRegex: /new\s+(\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?)?$/ 18 | 19 | ###* 20 | # Regular expression matching import names after the use keyword. 21 | ### 22 | importUseRegex: /use\s+(\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?)?$/ 23 | 24 | ###* 25 | # Regular expression matching trait names after the use keyword. 26 | ### 27 | traitUseRegex: /use\s+(?:\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?,\s*)*(\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?)?$/ 28 | 29 | ###* 30 | # Regular expression that extracts the classlike keyword and the class being extended from after the extends 31 | # keyword. 32 | ### 33 | extendsRegex: /([A-Za-z]+)\s+[a-zA-Z_0-9]+\s+extends\s+(\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*)?$/ 34 | 35 | ###* 36 | # Regular expression matching (only the last) interface name after the implements keyword. 37 | ### 38 | implementsRegex: /implements\s+(?:\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?,\s*)*(\\?[a-zA-Z_][a-zA-Z0-9_]*(?:\\[a-zA-Z_][a-zA-Z0-9_]*)*\\?)?$/ 39 | 40 | ###* 41 | * @inheritdoc 42 | ### 43 | disableForScopeSelector: '.source.php .comment.line.double-slash, .source.php .comment.block:not(.phpdoc), .source.php .string' 44 | 45 | ###* 46 | # Cache object to help improve responsiveness of autocompletion. 47 | ### 48 | listCache: null 49 | 50 | ###* 51 | # A list of disposables to dispose on deactivation. 52 | ### 53 | disposables: null 54 | 55 | ###* 56 | # Keeps track of a currently pending promise to ensure only one is active at any given time. 57 | ### 58 | pendingPromise: null 59 | 60 | ###* 61 | # Keeps track of a currently pending timeout to ensure only one is active at any given time.. 62 | ### 63 | timeoutHandle: null 64 | 65 | ###* 66 | * @inheritdoc 67 | ### 68 | activate: (@service) -> 69 | {CompositeDisposable} = require 'atom' 70 | 71 | @disposables = new CompositeDisposable() 72 | 73 | @disposables.add(@service.onDidFinishIndexing(@onDidFinishIndexing.bind(this))) 74 | 75 | ###* 76 | * @inheritdoc 77 | ### 78 | deactivate: () -> 79 | if @disposables? 80 | @disposables.dispose() 81 | @disposables = null 82 | 83 | ###* 84 | * Called when reindexing successfully finishes. 85 | * 86 | * @param {Object} info 87 | ### 88 | onDidFinishIndexing: (info) -> 89 | # Only reindex a couple of seconds after the last reindex. This prevents constant refreshes being scheduled 90 | # while the user is still modifying the file. This is acceptable as this provider's data rarely changes and 91 | # it is fairly expensive to refresh the cache. 92 | if @timeoutHandle? 93 | clearTimeout(@timeoutHandle) 94 | @timeoutHandle = null 95 | 96 | @timeoutHandle = setTimeout ( => 97 | @timeoutHandle = null 98 | @refreshCache() 99 | ), @config.get('largeListRefreshTimeout') 100 | 101 | ###* 102 | * Refreshes the internal cache. Returns a promise that resolves with the cache once it has been refreshed. 103 | * 104 | * @return {Promise} 105 | ### 106 | refreshCache: () -> 107 | successHandler = (classes) => 108 | return @handleSuccessfulCacheRefresh(classes) 109 | 110 | failureHandler = () => 111 | return @handleFailedCacheRefresh() 112 | 113 | if not @pendingPromise? 114 | @pendingPromise = @service.getClassList().then(successHandler, failureHandler) 115 | 116 | return @pendingPromise 117 | 118 | ###* 119 | * @param {Object} classes 120 | * 121 | * @return {Object} 122 | ### 123 | handleSuccessfulCacheRefresh: (classes) -> 124 | @pendingPromise = null 125 | 126 | return unless classes 127 | 128 | @listCache = classes 129 | 130 | return @listCache 131 | 132 | ###* 133 | * @return {Object} 134 | ### 135 | handleFailedCacheRefresh: () -> 136 | @pendingPromise = null 137 | 138 | return [] 139 | 140 | ###* 141 | * Fetches a list of results that can be fed to the getSuggestions method. 142 | * 143 | * @return {Promise} 144 | ### 145 | fetchResults: () -> 146 | return new Promise (resolve, reject) => 147 | if @listCache? 148 | resolve(@listCache) 149 | return 150 | 151 | return @refreshCache() 152 | 153 | ###* 154 | * @inheritdoc 155 | ### 156 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 157 | return [] if not @service 158 | 159 | failureHandler = () => 160 | # Just return no results. 161 | return [] 162 | 163 | determineCurrentClassNameSuccessHandler = (currentClassName) => 164 | matches = null 165 | methodToUse = null 166 | 167 | dynamicKey = 'getImportUseSuggestions' 168 | dynamicRegex = @importUseRegex 169 | 170 | if currentClassName? 171 | dynamicKey = 'getTraitUseSuggestions' 172 | dynamicRegex = @traitUseRegex 173 | 174 | regexesToTry = [ 175 | ['getExtendsSuggestions', @extendsRegex] 176 | ['getImplementsSuggestions', @implementsRegex] 177 | [dynamicKey, dynamicRegex] 178 | ['getNewSuggestions', @newRegex] 179 | ['getClassSuggestions', @regex] 180 | ] 181 | 182 | for item in regexesToTry 183 | method = item[0] 184 | regex = item[1] 185 | 186 | matches = @getPrefixMatchesByRegex(editor, bufferPosition, regex) 187 | 188 | if matches? 189 | methodToUse = method 190 | break 191 | 192 | return [] if not methodToUse? 193 | 194 | successHandler = (classes) => 195 | return [] unless classes 196 | 197 | return this[methodToUse].apply(this, [classes, matches]) 198 | 199 | return @fetchResults().then(successHandler, failureHandler) 200 | 201 | @service.determineCurrentClassName(editor, bufferPosition).then(determineCurrentClassNameSuccessHandler, failureHandler) 202 | 203 | ###* 204 | * Retrieves suggestions after the extends keyword. 205 | * 206 | * @param {Array} classes 207 | * @param {Array} matches 208 | * 209 | * @return {Array} 210 | ### 211 | getExtendsSuggestions: (classes, matches) -> 212 | prefix = if matches[2]? then matches[2] else '' 213 | 214 | suggestions = [] 215 | 216 | if matches[1] == 'trait' 217 | return suggestions 218 | 219 | for name, element of classes 220 | if element.type != matches[1] or element.isFinal 221 | continue # Interfaces can only extend interfaces and classes can only extend classes. 222 | 223 | suggestion = @getSuggestionForData(element) 224 | suggestion.replacementPrefix = prefix 225 | 226 | @applyAutomaticImportData(suggestion, prefix) 227 | 228 | suggestions.push(suggestion) 229 | 230 | return suggestions 231 | 232 | ###* 233 | * Retrieves suggestions after the implements keyword. 234 | * 235 | * @param {Array} classes 236 | * @param {Array} matches 237 | * 238 | * @return {Array} 239 | ### 240 | getImplementsSuggestions: (classes, matches) -> 241 | prefix = if matches[1]? then matches[1] else '' 242 | 243 | suggestions = [] 244 | 245 | for name, element of classes 246 | if element.type != 'interface' 247 | continue 248 | 249 | suggestion = @getSuggestionForData(element) 250 | suggestion.replacementPrefix = prefix 251 | 252 | @applyAutomaticImportData(suggestion, prefix) 253 | 254 | suggestions.push(suggestion) 255 | 256 | return suggestions 257 | 258 | ###* 259 | * Retrieves suggestions for use statements. 260 | * 261 | * @param {Array} classes 262 | * @param {Array} matches 263 | * 264 | * @return {Array} 265 | ### 266 | getImportUseSuggestions: (classes, matches) -> 267 | prefix = if matches[1]? then matches[1] else '' 268 | 269 | suggestions = [] 270 | 271 | for name, element of classes 272 | suggestion = @getSuggestionForData(element) 273 | suggestion.type = 'import' 274 | suggestion.replacementPrefix = prefix 275 | 276 | suggestions.push(suggestion) 277 | 278 | return suggestions 279 | 280 | ###* 281 | * Retrieves suggestions for use statements. 282 | * 283 | * @param {Array} classes 284 | * @param {Array} matches 285 | * 286 | * @return {Array} 287 | ### 288 | getTraitUseSuggestions: (classes, matches) -> 289 | prefix = if matches[1]? then matches[1] else '' 290 | 291 | suggestions = [] 292 | 293 | for name, element of classes 294 | if element.type != 'trait' 295 | continue 296 | 297 | suggestion = @getSuggestionForData(element) 298 | suggestion.replacementPrefix = prefix 299 | 300 | @applyAutomaticImportData(suggestion, prefix) 301 | 302 | suggestions.push(suggestion) 303 | 304 | return suggestions 305 | 306 | ###* 307 | * Retrieves suggestions for class names after the new keyword. 308 | * 309 | * @param {Array} classes 310 | * @param {Array} matches 311 | * 312 | * @return {Array} 313 | ### 314 | getNewSuggestions: (classes, matches) -> 315 | prefix = if matches[1]? then matches[1] else '' 316 | 317 | suggestions = [] 318 | 319 | for name, element of classes 320 | if element.type != 'class' or element.isAbstract 321 | continue # Not possible to instantiate these. 322 | 323 | suggestion = @getSuggestionForData(element) 324 | suggestion.replacementPrefix = prefix 325 | 326 | @applyAutomaticImportData(suggestion, prefix) 327 | 328 | suggestions.push(suggestion) 329 | 330 | return suggestions 331 | 332 | ###* 333 | * Retrieves suggestions for classlike names. 334 | * 335 | * @param {Array} classes 336 | * @param {Array} matches 337 | * 338 | * @return {Array} 339 | ### 340 | getClassSuggestions: (classes, matches) -> 341 | prefix = if matches[1]? then matches[1] else '' 342 | 343 | suggestions = [] 344 | 345 | for name, element of classes 346 | suggestion = @getSuggestionForData(element) 347 | suggestion.replacementPrefix = prefix 348 | 349 | @applyAutomaticImportData(suggestion, prefix) 350 | 351 | suggestions.push(suggestion) 352 | 353 | return suggestions 354 | 355 | ###* 356 | * @param {Object} data 357 | * 358 | * @return {Array} 359 | ### 360 | getSuggestionForData: (data) -> 361 | fqcnWithoutLeadingSlash = data.name 362 | 363 | if fqcnWithoutLeadingSlash[0] == '\\' 364 | fqcnWithoutLeadingSlash = fqcnWithoutLeadingSlash.substring(1) 365 | 366 | suggestionData = 367 | text : fqcnWithoutLeadingSlash 368 | type : if data.type == 'trait' then 'mixin' else 'class' 369 | description : if data.isBuiltin then 'Built-in PHP structural data.' else data.shortDescription 370 | leftLabel : data.type 371 | descriptionMoreURL : if data.isBuiltin then @config.get('php_documentation_base_urls').classes + @getNormalizeFqcnDocumentationUrl(data.name) else null 372 | className : if data.isDeprecated then 'php-integrator-autocomplete-plus-strike' else '' 373 | displayText : fqcnWithoutLeadingSlash 374 | data : {} 375 | 376 | ###* 377 | * @param {String} name 378 | * 379 | * @return {String} 380 | ### 381 | getNormalizeFqcnDocumentationUrl: (name) -> 382 | return name.replace(/\\/g, '-').substr(1).toLowerCase() 383 | 384 | ###* 385 | * @param {Object} suggestion 386 | * @param {String} prefix 387 | ### 388 | applyAutomaticImportData: (suggestion, prefix) -> 389 | hasLeadingSlash = false 390 | 391 | if prefix.length > 0 and prefix[0] == '\\' 392 | hasLeadingSlash = true 393 | prefix = prefix.substring(1, prefix.length) 394 | 395 | prefixParts = prefix.split('\\') 396 | partsToSlice = (prefixParts.length - 1) 397 | 398 | fqcn = suggestion.text 399 | 400 | # We try to add an import that has only as many parts of the namespace as needed, for example, if the user 401 | # types 'Foo\Class' and confirms the suggestion 'My\Foo\Class', we add an import for 'My\Foo' and leave the 402 | # user's code at 'Foo\Class' as a relative import. We only add the full 'My\Foo\Class' if the user were to 403 | # type just 'Class' and then select 'My\Foo\Class' (i.e. we remove as many segments from the suggestion 404 | # as the user already has in his code). 405 | suggestion.data.nameToImport = null 406 | 407 | if hasLeadingSlash 408 | suggestion.text = '\\' + fqcn 409 | 410 | else 411 | # Don't try to add use statements for class names that the user wants to make absolute by adding a 412 | # leading slash. 413 | suggestion.text = @getNameToInsert(fqcn, partsToSlice) 414 | suggestion.data.nameToImport = @getNameToImport(fqcn, partsToSlice) 415 | 416 | ###* 417 | * Returns the name to insert into the buffer. 418 | * 419 | * @param {String} fqcn The FQCN of the class that needs to be imported. 420 | * @param {Number} partsToShiftOff The amount of parts to leave extra for the class name. For example, a value of 1 421 | * will return B\C instead of A\B\C. A value of 0 will return just C. 422 | * 423 | * @return {String|null} 424 | ### 425 | getNameToInsert: (fqcn, extraPartsToMaintain) -> 426 | if fqcn[0] == '\\' 427 | fqcn = fqcn.substring(1) 428 | 429 | fqcnParts = fqcn.split('\\') 430 | 431 | if true 432 | nameToUseParts = fqcnParts.slice(-extraPartsToMaintain - 1) 433 | 434 | return nameToUseParts.join('\\') 435 | 436 | ###* 437 | * Returns the name to import via a use statement. 438 | * 439 | * @param {String} fqcn The FQCN of the class that needs to be imported. 440 | * @param {Number} partsToPopOff The amount of parts to leave off of the end of the class name. For example, a 441 | * value of 1 will return A\B instead of A\B\C. 442 | * 443 | * @return {String|null} 444 | ### 445 | getNameToImport: (fqcn, partsToPopOff) -> 446 | if fqcn[0] == '\\' 447 | fqcn = fqcn.substring(1) 448 | 449 | fqcnParts = fqcn.split('\\') 450 | 451 | if partsToPopOff > 0 452 | fqcnParts = fqcnParts.slice(0, -partsToPopOff) 453 | 454 | return fqcnParts.join('\\') 455 | 456 | ###* 457 | * Called when the user confirms an autocompletion suggestion. 458 | * 459 | * @param {TextEditor} editor 460 | * @param {Position} triggerPosition 461 | * @param {Object} suggestion 462 | ### 463 | onDidInsertSuggestion: ({editor, triggerPosition, suggestion}) -> 464 | return unless suggestion.data?.nameToImport 465 | return unless @config.get('automaticallyAddUseStatements') 466 | 467 | successHandler = (currentNamespaceName) => 468 | if not currentNamespaceName? 469 | currentNamespaceName = '' 470 | 471 | else if currentNamespaceName[0] == '\\' 472 | currentNamespaceName = currentNamespaceName.substring(1) 473 | 474 | # When we have no namespace or are in an anonymous namespace, adding use statements for "non-compound" 475 | # namespaces, such as "DateTime" will generate a warning. 476 | if currentNamespaceName.length == 0 477 | return if suggestion.displayText.split('\\').length == 1 478 | 479 | else if suggestion.data.nameToImport.indexOf(currentNamespaceName) == 0 480 | nameToImportRelativeToNamespace = suggestion.displayText.substr(currentNamespaceName.length + 1) 481 | 482 | # If a user is in A\B and wants to import A\B\C\D, we don't need to add a use statement if he is typing 483 | # C\D, as it will be relative, but we will need to add one when he typed just D as it won't be 484 | # relative. 485 | return if nameToImportRelativeToNamespace.split('\\').length == suggestion.text.split('\\').length 486 | 487 | editor.transact () => 488 | linesAdded = @service.getUseStatementHelper().addUseClass( 489 | editor, 490 | suggestion.data.nameToImport, 491 | @config.get('insertNewlinesForUseStatements') 492 | ) 493 | 494 | failureHandler = () -> 495 | # Do nothing. 496 | 497 | @service.determineCurrentNamespaceName(editor, triggerPosition).then(successHandler, failureHandler) 498 | --------------------------------------------------------------------------------