├── .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 | 
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 |
--------------------------------------------------------------------------------