├── .gitignore
├── .editorconfig
├── LICENSE
├── package.json
├── styles
└── main.less
├── README.md
├── LICENSE-atom-autocomplete-php
├── lib
├── GlobalVariableProvider.coffee
├── DocblockAnnotationProvider.coffee
├── MagicConstantProvider.coffee
├── AtomConfig.coffee
├── SnippetProvider.coffee
├── Config.coffee
├── TypeHintNewVariableNameProvider.coffee
├── VariableProvider.coffee
├── KeywordProvider.coffee
├── NamespaceProvider.coffee
├── GlobalConstantProvider.coffee
├── DocblockTagProvider.coffee
├── GlobalFunctionProvider.coffee
├── Main.coffee
├── AbstractProvider.coffee
├── PHPUnitTagProvider.coffee
├── MemberProvider.coffee
└── ClassProvider.coffee
└── 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",
3 | "main": "./lib/Main",
4 | "version": "1.6.2",
5 | "description": "Provides autocompletion for your PHP source code.",
6 | "repository": "php-integrator/atom-autocompletion",
7 | "license": "GPL-3.0",
8 | "engines": {
9 | "atom": ">=1.19.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 | "^3.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # php-integrator/atom-autocompletion
2 | ## Legacy
3 | Autocompletion is included in the [base package](https://github.com/php-integrator/atom-base) since version 3.2.0. This package has become redundant as a result. It will likely cease to stop working in the future as new versions of the base package are released.
4 |
5 | ## About
6 | This package provides autocompletion for your PHP source code using [PHP Integrator](https://github.com/php-integrator/atom-base) as well as Atom's [autocomplete-plus](https://github.com/atom/autocomplete-plus).
7 |
8 | **Note that the [php-integrator-base](https://github.com/php-integrator/atom-base) package is required and needs to be set up correctly for this package to function correctly.**
9 |
10 | What is included?
11 | * Autocompletion for local variable names.
12 | * Autocompletion for global functions and constants.
13 | * Autocompletion (snippets) for tag names in docblocks.
14 | * Autocompletion for class, interface and trait members.
15 | * Autocompletion for class, interface and trait names as well as their constructors.
16 | * Automatic adding of use statements when class names are autocompleted (with a somewhat intelligent positioning).
17 | * Included is a command to sort the current use statements.
18 |
19 | 
20 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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('automaticallyAddUseStatements', atom.config.get("#{@packageName}.automaticallyAddUseStatements"))
30 | @set('largeListRefreshTimeout', atom.config.get("#{@packageName}.largeListRefreshTimeout"))
31 | @set('largeListRefreshTimeoutJitter', atom.config.get("#{@packageName}.largeListRefreshTimeoutJitter"))
32 |
33 | ###*
34 | * Attaches listeners to listen to Atom configuration changes.
35 | ###
36 | attachListeners: () ->
37 | atom.config.onDidChange "#{@packageName}.disableBuiltinAutocompletion", () =>
38 | @set('disableBuiltinAutocompletion', atom.config.get("#{@packageName}.disableBuiltinAutocompletion"))
39 |
40 | atom.config.onDidChange "#{@packageName}.insertNewlinesForUseStatements", () =>
41 | @set('insertNewlinesForUseStatements', atom.config.get("#{@packageName}.insertNewlinesForUseStatements"))
42 |
43 | atom.config.onDidChange "#{@packageName}.enablePhpunitAnnotationTags", () =>
44 | @set('enablePhpunitAnnotationTags', atom.config.get("#{@packageName}.enablePhpunitAnnotationTags"))
45 |
46 | atom.config.onDidChange "#{@packageName}.automaticallyAddUseStatements", () =>
47 | @set('automaticallyAddUseStatements', atom.config.get("#{@packageName}.automaticallyAddUseStatements"))
48 |
49 | atom.config.onDidChange "#{@packageName}.largeListRefreshTimeout", () =>
50 | @set('largeListRefreshTimeout', atom.config.get("#{@packageName}.largeListRefreshTimeout"))
51 |
52 | atom.config.onDidChange "#{@packageName}.largeListRefreshTimeoutJitter", () =>
53 | @set('largeListRefreshTimeoutJitter', atom.config.get("#{@packageName}.largeListRefreshTimeoutJitter"))
54 |
--------------------------------------------------------------------------------
/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/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 | largeListRefreshTimeoutJitter : 3000
31 |
32 | # See also http://www.phpdoc.org/docs/latest/index.html .
33 | phpdoc_base_url : {
34 | prefix: 'http://www.phpdoc.org/docs/latest/references/phpdoc/tags/'
35 | suffix: '.html'
36 | }
37 |
38 | # See also https://phpunit.de/manual/current/en/index.html .
39 | phpunit_annotations_base_url : {
40 | prefix: 'https://phpunit.de/manual/current/en/appendixes.annotations.html'
41 | id_prefix: '#appendixes.annotations.'
42 | }
43 |
44 | # See also https://secure.php.net/urlhowto.php .
45 | php_documentation_base_urls : {
46 | classes : 'https://secure.php.net/class.'
47 | functions : 'https://secure.php.net/function.'
48 | keywords : 'https://secure.php.net/manual/en/reserved.php'
49 | }
50 |
51 | @load()
52 |
53 | ###*
54 | * Loads the configuration.
55 | ###
56 | load: () ->
57 | throw new Error("This method is abstract and must be implemented!")
58 |
59 | ###*
60 | * Registers a listener that is invoked when the specified property is changed.
61 | ###
62 | onDidChange: (name, callback) ->
63 | if name not of @listeners
64 | @listeners[name] = []
65 |
66 | @listeners[name].push(callback)
67 |
68 | ###*
69 | * Retrieves the config setting with the specified name.
70 | *
71 | * @return {mixed}
72 | ###
73 | get: (name) ->
74 | return @data[name]
75 |
76 | ###*
77 | * Retrieves the config setting with the specified name.
78 | *
79 | * @param {string} name
80 | * @param {mixed} value
81 | ###
82 | set: (name, value) ->
83 | @data[name] = value
84 |
85 | if name of @listeners
86 | for listener in @listeners[name]
87 | listener(value, name)
88 |
--------------------------------------------------------------------------------
/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('.meta.function.parameters') == -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/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(editor.getPath(), 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/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 | timeoutTime = @config.get('largeListRefreshTimeout')
66 | timeoutTime += Math.random() * @config.get('largeListRefreshTimeoutJitter')
67 |
68 | @timeoutHandle = setTimeout ( =>
69 | @timeoutHandle = null
70 | @refreshCache()
71 | ), timeoutTime
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 = (namespaces) =>
80 | @pendingPromise = null
81 |
82 | return unless namespaces
83 |
84 | @listCache = namespaces
85 |
86 | return @listCache
87 |
88 | failureHandler = () =>
89 | @pendingPromise = null
90 |
91 | return []
92 |
93 | if not @pendingPromise?
94 | @pendingPromise = @service.getNamespaceList().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 = (namespaces) =>
121 | return [] unless namespaces
122 |
123 | return @addSuggestions(namespaces, prefix.trim())
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 | *
136 | * @return {array}
137 | ###
138 | addSuggestions: (namespaces, prefix) ->
139 | suggestions = []
140 |
141 | for id, namespace of namespaces
142 |
143 | continue if namespace.name == null # No point in showing anonymous namespaces.
144 |
145 | fqcnWithoutLeadingSlash = namespace.name
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 | return suggestions
156 |
--------------------------------------------------------------------------------
/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 | timeoutTime = @config.get('largeListRefreshTimeout')
70 | timeoutTime += Math.random() * @config.get('largeListRefreshTimeoutJitter')
71 |
72 | @timeoutHandle = setTimeout ( =>
73 | @timeoutHandle = null
74 | @refreshCache()
75 | ), timeoutTime
76 |
77 | ###*
78 | * Refreshes the internal cache. Returns a promise that resolves with the cache once it has been refreshed.
79 | *
80 | * @return {Promise}
81 | ###
82 | refreshCache: () ->
83 | successHandler = (constants) =>
84 | @pendingPromise = null
85 |
86 | return unless constants
87 |
88 | @listCache = constants
89 |
90 | return @listCache
91 |
92 | failureHandler = () =>
93 | @pendingPromise = null
94 |
95 | return []
96 |
97 | if not @pendingPromise?
98 | @pendingPromise = @service.getGlobalConstants().then(successHandler, failureHandler)
99 |
100 | return @pendingPromise
101 |
102 | ###*
103 | * Fetches a list of results that can be fed to the getSuggestions method.
104 | *
105 | * @return {Promise}
106 | ###
107 | fetchResults: () ->
108 | return new Promise (resolve, reject) =>
109 | if @listCache?
110 | resolve(@listCache)
111 | return
112 |
113 | return @refreshCache()
114 |
115 | ###*
116 | * @inheritdoc
117 | ###
118 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
119 | return [] if not @service
120 |
121 | tmpPrefix = @getPrefix(editor, bufferPosition)
122 | return [] unless tmpPrefix != null
123 |
124 | successHandler = (constants) =>
125 | return [] unless constants
126 |
127 | return @addSuggestions(constants, prefix.trim())
128 |
129 | failureHandler = () =>
130 | return []
131 |
132 | return @fetchResults().then(successHandler, failureHandler)
133 |
134 | ###*
135 | * Returns available suggestions.
136 | *
137 | * @param {array} constants
138 | * @param {string} prefix
139 | *
140 | * @return {array}
141 | ###
142 | addSuggestions: (constants, prefix) ->
143 | suggestions = []
144 |
145 | for fqcn, constant of constants
146 | suggestions.push
147 | text : constant.name
148 | type : 'constant'
149 | displayText : constant.name
150 | replacementPrefix : prefix
151 | leftLabel : @getTypeSpecificationFromTypeArray(constant.types)
152 | description : constant.shortDescription
153 | className : 'php-integrator-autocomplete-plus-suggestion' + if constant.isDeprecated then ' php-integrator-autocomplete-plus-strike' else ''
154 |
155 | return suggestions
156 |
--------------------------------------------------------------------------------
/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/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 | timeoutTime = @config.get('largeListRefreshTimeout')
69 | timeoutTime += Math.random() * @config.get('largeListRefreshTimeoutJitter')
70 |
71 | @timeoutHandle = setTimeout ( =>
72 | @timeoutHandle = null
73 | @refreshCache()
74 | ), timeoutTime
75 |
76 | ###*
77 | * Refreshes the internal cache. Returns a promise that resolves with the cache once it has been refreshed.
78 | *
79 | * @return {Promise}
80 | ###
81 | refreshCache: () ->
82 | successHandler = (functions) =>
83 | @pendingPromise = null
84 |
85 | return unless functions
86 |
87 | @listCache = functions
88 |
89 | return @listCache
90 |
91 | failureHandler = () =>
92 | @pendingPromise = null
93 |
94 | return []
95 |
96 | if not @pendingPromise?
97 | @pendingPromise = @service.getGlobalFunctions().then(successHandler, failureHandler)
98 |
99 | return @pendingPromise
100 |
101 | ###*
102 | * Fetches a list of results that can be fed to the addSuggestions method.
103 | *
104 | * @return {Promise}
105 | ###
106 | fetchResults: () ->
107 | return new Promise (resolve, reject) =>
108 | if @listCache?
109 | resolve(@listCache)
110 | return
111 |
112 | return @refreshCache()
113 |
114 | ###*
115 | * @inheritdoc
116 | ###
117 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
118 | return [] if not @service
119 |
120 | prefix = @getPrefix(editor, bufferPosition)
121 | return [] unless prefix != null
122 |
123 | successHandler = (functions) =>
124 | return [] unless functions
125 |
126 | characterAfterPrefix = editor.getTextInRange([bufferPosition, [bufferPosition.row, bufferPosition.column + 1]])
127 | insertParameterList = if characterAfterPrefix == '(' then false else true
128 |
129 | return @addSuggestions(functions, prefix.trim(), insertParameterList)
130 |
131 | failureHandler = () =>
132 | return []
133 |
134 | return @fetchResults().then(successHandler, failureHandler)
135 |
136 | ###*
137 | * Returns available suggestions.
138 | *
139 | * @param {array} functions
140 | * @param {string} prefix
141 | * @param {bool} insertParameterList Whether to insert a list of parameters or not.
142 | *
143 | * @return {array}
144 | ###
145 | addSuggestions: (functions, prefix, insertParameterList = true) ->
146 | suggestions = []
147 |
148 | for fqcn, func of functions
149 | shortDescription = ''
150 |
151 | if func.shortDescription? and func.shortDescription.length > 0
152 | shortDescription = func.shortDescription
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 : null
165 | className : 'php-integrator-autocomplete-plus-suggestion' + if func.isDeprecated then ' php-integrator-autocomplete-plus-strike' else ''
166 |
167 | return suggestions
168 |
--------------------------------------------------------------------------------
/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 : 'Fetching these large lists from the core is almost costless since core 3.0, but due to the
34 | sheer amount of data being sent over the socket, there may still be noticeable delays if this
35 | setting is set too low. Also, the contents of these lists change relatively rarely during
36 | editing. This amount of time (in milliseconds) will pass after successful reindexing before
37 | the lists are refetched.'
38 | type : 'integer'
39 | default : 5000
40 | order : 4
41 |
42 | largeListRefreshTimeoutJitter:
43 | title : 'Jitter for timeout for refreshing large data'
44 | description : 'Adds a random amount of jitter (between 0 and this number) to the large list refresh timeout
45 | above to ensure the large lists don\'t all get fetched at once. As sockets naturally queue,
46 | this adds a more even distribution of load on the socket and ensures other requests don\'t
47 | hang up too long.'
48 | type : 'integer'
49 | default : 3000
50 | order : 5
51 |
52 | ###*
53 | * The name of the package.
54 | ###
55 | packageName: 'php-integrator-autocomplete-plus'
56 |
57 | ###*
58 | * The configuration object.
59 | ###
60 | configuration: null
61 |
62 | ###*
63 | * List of tooltip providers.
64 | ###
65 | providers: []
66 |
67 | ###*
68 | * Activates the package.
69 | ###
70 | activate: ->
71 | AtomConfig = require './AtomConfig'
72 | MemberProvider = require './MemberProvider'
73 | SnippetProvider = require './SnippetProvider'
74 | KeywordProvider = require './KeywordProvider'
75 | NamespaceProvider = require './NamespaceProvider'
76 | ClassProvider = require './ClassProvider'
77 | GlobalConstantProvider = require './GlobalConstantProvider'
78 | VariableProvider = require './VariableProvider'
79 | GlobalVariableProvider = require './GlobalVariableProvider'
80 | TypeHintNewVariableNameProvider = require './TypeHintNewVariableNameProvider'
81 | MagicConstantProvider = require './MagicConstantProvider'
82 | GlobalFunctionProvider = require './GlobalFunctionProvider'
83 | DocblockAnnotationProvider = require './DocblockAnnotationProvider'
84 | DocblockTagProvider = require './DocblockTagProvider'
85 | PHPUnitTagProvider = require './PHPUnitTagProvider'
86 |
87 | @configuration = new AtomConfig(@packageName)
88 |
89 | @providers.push(new SnippetProvider(@configuration))
90 | @providers.push(new KeywordProvider(@configuration))
91 | @providers.push(new MemberProvider(@configuration))
92 | @providers.push(new TypeHintNewVariableNameProvider(@configuration))
93 | @providers.push(new VariableProvider(@configuration))
94 | @providers.push(new GlobalFunctionProvider(@configuration))
95 | @providers.push(new GlobalConstantProvider(@configuration))
96 | @providers.push(new NamespaceProvider(@configuration))
97 | @providers.push(new ClassProvider(@configuration))
98 | @providers.push(new GlobalVariableProvider(@configuration))
99 | @providers.push(new MagicConstantProvider(@configuration))
100 | @providers.push(new DocblockAnnotationProvider(@configuration))
101 | @providers.push(new DocblockTagProvider(@configuration))
102 | @providers.push(new PHPUnitTagProvider(@configuration))
103 |
104 | ###*
105 | * Deactivates the package.
106 | ###
107 | deactivate: ->
108 | @deactivateProviders()
109 |
110 | ###*
111 | * Activates the providers using the specified service.
112 | ###
113 | activateProviders: (service) ->
114 | for provider in @providers
115 | provider.activate(service)
116 |
117 | ###*
118 | * Deactivates any active providers.
119 | ###
120 | deactivateProviders: () ->
121 | for provider in @providers
122 | provider.deactivate()
123 |
124 | @providers = []
125 |
126 | ###*
127 | * Sets the php-integrator service.
128 | *
129 | * @param {mixed} service
130 | *
131 | * @return {Disposable}
132 | ###
133 | setService: (service) ->
134 | @activateProviders(service)
135 |
136 | {Disposable} = require 'atom'
137 |
138 | return new Disposable => @deactivateProviders()
139 |
140 | ###*
141 | * Retrieves a list of supported autocompletion providers.
142 | *
143 | * @return {array}
144 | ###
145 | getProviders: ->
146 | return @providers
147 |
--------------------------------------------------------------------------------
/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.6.2
2 | * [Fix namespace suggestions no longer showing](https://github.com/php-integrator/atom-autocompletion/pull/108)(thanks to [msdm](https://github.com/msdm))
3 |
4 | ## 1.6.1
5 | * Fix parameter type hint variable name suggestions no longer working due to CSS changes in Atom 1.19
6 | * This also means at least Atom 1.19 is now required for this package.
7 |
8 | ## 1.6.0 (base 3.0.0)
9 | * Fix not being able to disable automatic adding of use statements.
10 | * Fetching large lists from the core is now much less expensive, as the core maintains an internal registry instead of recalculating the list completely every time.
11 | * Calculating the data is now almost instant, but there may still be hangs in Atom if you set the large list timeout too low due to the sheer amount of data that is sent back over the socket. (This will be addressed at a later date, after which the large list timeout setting will be removed.)
12 | * An additional (configurable) random jitter is now also added to the large list timeout, to prevent all of them from being fetched at the same time and cooperating in hogging up the socket for other requests.
13 |
14 | ## 1.5.0
15 | * Fix incorrect service version.
16 | * PHPUnit tags will now be autocompleted in docblocks (thanks to [msdm](https://github.com/msdm)).
17 | * you can disable this via the settings.
18 |
19 | ## 1.4.1
20 | * Fix parameter default values that were "falsy" in JavaScript not showing up (such as `0`).
21 |
22 | ## 1.4.0
23 | * Add `finally` to keyword suggestions.
24 | * Add PHP 7 keywords to keyword suggestions.
25 | * Remove `resource`, `object`, `mixed` and `numeric` from keyword suggestions.
26 | * These are soft reserved in PHP, but aren't actual keywords so they aren't useful to the developer as suggestions.
27 | * Give keywords a higher priority (https://github.com/php-integrator/atom-autocompletion/issues/96).
28 | * 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.
29 |
30 | ## 1.3.0
31 | * Use the new shield icon to denote protected members (feels more appropriate than a key).
32 | * Constants are no longer listed after the arrow operator `->`.
33 | ** Doing this results in an `Undefined property` notice.
34 | ** This also fixes the issue where any valid property named `class` would be overridden by the `class` constant introduced in PHP 5.5.
35 |
36 | ## 1.2.0 (base 2.0.0)
37 | * 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.
38 | * If you had this enabled, you will need to reenable it in the base package after upgrading.
39 | * 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.
40 | * Actual namespaces will now be suggested after the namespace keyword instead of a list of classes.
41 | * Fix use statements unnecessarily being added for classes in the same namespace when the cursor was outside a class.
42 | * Member autocompletion suggestions will now show an icon indicating the access modifier of the method (public, private or protected).
43 | * 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.
44 | * Member autocompletion suggestions will no longer be filtered out based on their accessibility from the current scope.
45 | * 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.
46 | * [Version 3.0 of the `autocomplete-plus` provider specification](https://github.com/atom/autocomplete-plus/issues/776) is now used.
47 | * 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.
48 |
49 | ## 1.1.5
50 | ### Bugs fixed
51 | * Rename the package and repository.
52 |
53 | ## 1.1.4
54 | ### Bugs fixed
55 | * Fix not being able to navigate to the PHP documentation for built-in classes with longer FQCN's, such as classes from MongoDB.
56 | * 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.
57 |
58 | ## 1.1.3
59 | ### Bugs fixed
60 | * Fix the parameter list itself also being inserted for class methods calls.
61 |
62 | ## 1.1.2
63 | ### Bugs fixed
64 | * 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).
65 |
66 | ## 1.1.1
67 | ### Bugs fixed
68 | * Fix constants being added to the suggestions twice, resulting in some information such as deprecation information getting lost.
69 |
70 | ## 1.1.0
71 | ### Features and improvements
72 | * Make the timeout before refreshing global data adjustable via the settings panel.
73 |
74 | ### Bugs fixed
75 | * Trait suggestions will now be shown after the `use` keyword occurs in a class.
76 |
77 | ## 1.0.4
78 | ### Bugs fixed
79 | * Fixed `@inheritDoc` being missing from docblock tag suggestions (thanks to [squio](https://github.com/squio)).
80 |
81 | ## 1.0.3
82 | ### Bugs fixed
83 | * Fixed error with member autocompletion when there was no current class.
84 |
85 | ## 1.0.2
86 | ### Bugs fixed
87 | * Global function names with upper cased letters were not being autocompleted.
88 |
89 | ## 1.0.1
90 | ### Bugs fixed
91 | * Add a sanity check for providers on package deactivation.
92 |
93 | ## 1.0.0 (base 1.0.0)
94 | ### Features and improvements
95 | * Only interfaces will now be listed after the `implements` keyword.
96 | * Only appropriate classes or interfaces will now be listed after the `extends` keyword.
97 | * The default value for function and method parameters that have one will now be shown during autocompletion.
98 | * "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:
99 |
100 | ```php
101 | /**
102 | * @return \IteratorAggregate|\Countable
103 | */
104 | public function foo()
105 | {
106 | $this->foo()-> // Will list members for both Countable and IteratorAggregate.
107 | }
108 | ```
109 |
110 | ### Bugs fixed
111 | * Global constants will now also show their type in the left column.
112 | * Global constants will now properly show their short description instead of just "Global PHP constant".
113 | * Fix the FQCN not properly being completed in some cases when starting with a leading slash (only the last part was completed).
114 | * The ellipsis for variadic parameters is now shown at the front instead of the back of the parameter, consistent with PHP's syntax.
115 | * 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.
116 | * 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.
117 |
118 | ## 0.8.1
119 | ### Bugs fixed
120 | * Fixed the `class` suggestion being shown for classlikes that didn't exist.
121 |
122 | ## 0.8.0 (base 0.9.0)
123 | ### Features and improvements
124 | * Added a setting that allows disabling the automatic adding of use statements.
125 | * 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).
126 |
127 | ### Bugs fixed
128 | * Fix traits not having a different icon in the class suggestion list.
129 |
130 | ## 0.7.0 (base 0.8.0)
131 | ### Features and enhancements
132 | * Also show `$argv` and `$argv` in the autocompletion suggestions.
133 | * Also show new variable suggestions without first typing the dollar sign for fluency.
134 | * Tweaked the ordering of suggestions, which seems to improve overall relevancy of suggestions.
135 | * Fetching class members is now even more asynchronous, improving responsiveness of autocompletion.
136 | * Fetching class list, global function and global constant suggestions is now cached. This should further improve responsiveness of autocompletion.
137 | * 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).
138 |
139 | ### Bugs fixed
140 | * Fixed no local variables being suggested after keywords suchas `return`.
141 | * Fixed new variable names were being suggested after keywords such as `return`.
142 |
143 | ## 0.6.0 (base 0.7.0)
144 | ### Features and enhancements
145 | * Magic constants will now also be suggested.
146 | * Superglobal names will now also be suggested.
147 | * Local variables will now be fetched asynchronously, improving responsiveness.
148 | * New variable names will now be suggested after a type hint, for example typing `FooBarInterface $` will suggest `$fooBar` and `$fooBarInterface`.
149 | * Due to changes to the way variables are fetched in the base service, you will notice some changes when local variable names are suggested:
150 | * 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:
151 | ```php
152 | $a = 1;
153 |
154 | if (condition) {
155 | $a = 2;
156 | $b = 3;
157 | } else {
158 | $a = 4;
159 | $c = 5;
160 | }
161 |
162 | $ // Autocompletion will not list $b and $c, only $a.
163 | ```
164 | 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.
165 | * 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).
166 | * `$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.
167 |
168 | ### Bugs fixed
169 | * Local variables will no longer be suggested after type hints.
170 |
171 | ## 0.5.3
172 | ### Bugs fixed
173 | * Use statements will no longer be added when typing a namespace name. (Existing items will still be suggested for convenience.)
174 |
175 | ## 0.5.2 (base 0.6.0)
176 | ### Bugs fixed
177 | * 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).
178 |
179 | ## 0.5.1 (base 0.5.0)
180 | ### Bugs fixed
181 | * Fixed a rare TypeError in the Utility file.
182 | * Fixed the `yield` keyword not being suggested.
183 | * Fixed the `class` keyword introduced in PHP 5.5 not being completed after two dots.
184 |
185 | ## 0.5.0 (base 0.5.0)
186 | ### Features and enhancements
187 | * Only (non-abstract) classes will be suggested after the new keyword.
188 | * A new keyword provider will also show autocompletion for PHP keywords.
189 | * The parameter list for methods will now be displayed in a different style for better visual recognition.
190 | * A new snippet provider will now suggest a few useful snippets such as for `isset`, `unset`, `catch`, and more.
191 | * When autocompleting use statements, the suggestions will now have a different icon to indicate that it is an import.
192 | * Traits will now receive the 'mixin' type (which by default has the same icon as a 'class') to allow for separate styling.
193 | * 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.
194 |
195 | ### Bugs fixed
196 | * Fixed autocompletion not properly completing and working with static class property access, such as `static::$foo->bar()`.
197 | * 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).
198 |
199 | ## 0.4.0 (base 0.4.0)
200 | ### Features and enhancements
201 | * The placement of use statements has been improved.
202 | * Added a new autocompletion provider that will provide snippets for tags in docblocks.
203 | * The class provider will now show a documentation URL for built-in classes, interfaces and traits.
204 | * Added a new command that sorts use statements according to the same algorithm that manages adding them.
205 | * The class provider will now show the type of the structural element (trait, interface or class) in the left label.
206 | * 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.
207 | * $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.
208 | * 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.
209 |
210 | ### Bugs fixed
211 | * Fixed use statements ending up at an incorrect location in some situations.
212 | * Fixed a use statement still being added when starting a class name with a leading slash.
213 | * Fixed duplicate use statements being added in some cases with certain (unformatted) sorting combinations.
214 | * Fixed an unnecessary use statement being added when selecting the current class name during autocompletion.
215 | * When the base package service is not available, autocompletion will silently fail instead of spawning errors.
216 |
217 | ## 0.3.0
218 | ### Features and enhancements
219 | * Documentation for classes will now be shown during autocompletion.
220 |
221 | ### Bugs fixed
222 | * Fixed variables containing numbers not being suggested.
223 | * Use statements were still added if one was already present with a leading slash.
224 | * An error would sometimes be shown when trying to autocomplete a class that did not exist.
225 | * The partial name of the variable being autocompleted is now no longer included in the suggestions.
226 |
227 | ## 0.2.1
228 | ### Bugs fixed
229 | * Global functions inside namespaces were not being autocompleted properly.
230 | * Class names that start with a lower case letter will now also be autocompleted.
231 | * Class names were not being completed in some locations such as inside if statements.
232 | * Added a new configuration option that disables the built-in PHP autocompletion provider (enabled by default). `[1]`
233 |
234 | `[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.
235 |
236 | ## 0.2.0
237 | ### Features and enhancements
238 | * Autocompletion now works inside double quoted strings `[1]`.
239 | * When autocompleting variable names, their type is now displayed, if possible.
240 | * Where possible, autocompletion is now asynchronous (using promises), improving performance.
241 | * The right label of class members in the autocompletion window will now display the originating structure (i.e. class, interface or trait).
242 | * The way use statements are added and class names are completed was drastically improved.
243 | * Multiple cursors are now properly supported.
244 | * The appropriate namespace will now be added if you are trying to do a relative import:
245 |
246 | ```php
247 | \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 | timeoutTime = @config.get('largeListRefreshTimeout')
97 | timeoutTime += Math.random() * @config.get('largeListRefreshTimeoutJitter')
98 |
99 | @timeoutHandle = setTimeout ( =>
100 | @timeoutHandle = null
101 | @refreshCache()
102 | ), timeoutTime
103 |
104 | ###*
105 | * Refreshes the internal cache. Returns a promise that resolves with the cache once it has been refreshed.
106 | *
107 | * @return {Promise}
108 | ###
109 | refreshCache: () ->
110 | successHandler = (classes) =>
111 | return @handleSuccessfulCacheRefresh(classes)
112 |
113 | failureHandler = () =>
114 | return @handleFailedCacheRefresh()
115 |
116 | if not @pendingPromise?
117 | @pendingPromise = @service.getClassList().then(successHandler, failureHandler)
118 |
119 | return @pendingPromise
120 |
121 | ###*
122 | * @param {Object} classes
123 | *
124 | * @return {Object}
125 | ###
126 | handleSuccessfulCacheRefresh: (classes) ->
127 | @pendingPromise = null
128 |
129 | return unless classes
130 |
131 | @listCache = classes
132 |
133 | return @listCache
134 |
135 | ###*
136 | * @return {Object}
137 | ###
138 | handleFailedCacheRefresh: () ->
139 | @pendingPromise = null
140 |
141 | return []
142 |
143 | ###*
144 | * Fetches a list of results that can be fed to the getSuggestions method.
145 | *
146 | * @return {Promise}
147 | ###
148 | fetchResults: () ->
149 | return new Promise (resolve, reject) =>
150 | if @listCache?
151 | resolve(@listCache)
152 | return
153 |
154 | return @refreshCache()
155 |
156 | ###*
157 | * @inheritdoc
158 | ###
159 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
160 | return [] if not @service
161 |
162 | failureHandler = () =>
163 | # Just return no results.
164 | return []
165 |
166 | determineCurrentClassNameSuccessHandler = (currentClassName) =>
167 | matches = null
168 | methodToUse = null
169 |
170 | dynamicKey = 'getImportUseSuggestions'
171 | dynamicRegex = @importUseRegex
172 |
173 | if currentClassName?
174 | dynamicKey = 'getTraitUseSuggestions'
175 | dynamicRegex = @traitUseRegex
176 |
177 | regexesToTry = [
178 | ['getExtendsSuggestions', @extendsRegex]
179 | ['getImplementsSuggestions', @implementsRegex]
180 | [dynamicKey, dynamicRegex]
181 | ['getNewSuggestions', @newRegex]
182 | ['getClassSuggestions', @regex]
183 | ]
184 |
185 | for item in regexesToTry
186 | method = item[0]
187 | regex = item[1]
188 |
189 | matches = @getPrefixMatchesByRegex(editor, bufferPosition, regex)
190 |
191 | if matches?
192 | methodToUse = method
193 | break
194 |
195 | return [] if not methodToUse?
196 |
197 | successHandler = (classes) =>
198 | return [] unless classes
199 |
200 | return this[methodToUse].apply(this, [classes, matches])
201 |
202 | return @fetchResults().then(successHandler, failureHandler)
203 |
204 | @service.determineCurrentClassName(editor, bufferPosition).then(determineCurrentClassNameSuccessHandler, failureHandler)
205 |
206 | ###*
207 | * Retrieves suggestions after the extends keyword.
208 | *
209 | * @param {Array} classes
210 | * @param {Array} matches
211 | *
212 | * @return {Array}
213 | ###
214 | getExtendsSuggestions: (classes, matches) ->
215 | prefix = if matches[2]? then matches[2] else ''
216 |
217 | suggestions = []
218 |
219 | if matches[1] == 'trait'
220 | return suggestions
221 |
222 | for name, element of classes
223 | if element.type != matches[1] or element.isFinal
224 | continue # Interfaces can only extend interfaces and classes can only extend classes.
225 |
226 | suggestion = @getSuggestionForData(element)
227 | suggestion.replacementPrefix = prefix
228 |
229 | @applyAutomaticImportData(suggestion, prefix)
230 |
231 | suggestions.push(suggestion)
232 |
233 | return suggestions
234 |
235 | ###*
236 | * Retrieves suggestions after the implements keyword.
237 | *
238 | * @param {Array} classes
239 | * @param {Array} matches
240 | *
241 | * @return {Array}
242 | ###
243 | getImplementsSuggestions: (classes, matches) ->
244 | prefix = if matches[1]? then matches[1] else ''
245 |
246 | suggestions = []
247 |
248 | for name, element of classes
249 | if element.type != 'interface'
250 | continue
251 |
252 | suggestion = @getSuggestionForData(element)
253 | suggestion.replacementPrefix = prefix
254 |
255 | @applyAutomaticImportData(suggestion, prefix)
256 |
257 | suggestions.push(suggestion)
258 |
259 | return suggestions
260 |
261 | ###*
262 | * Retrieves suggestions for use statements.
263 | *
264 | * @param {Array} classes
265 | * @param {Array} matches
266 | *
267 | * @return {Array}
268 | ###
269 | getImportUseSuggestions: (classes, matches) ->
270 | prefix = if matches[1]? then matches[1] else ''
271 |
272 | suggestions = []
273 |
274 | for name, element of classes
275 | suggestion = @getSuggestionForData(element)
276 | suggestion.type = 'import'
277 | suggestion.replacementPrefix = prefix
278 |
279 | suggestions.push(suggestion)
280 |
281 | return suggestions
282 |
283 | ###*
284 | * Retrieves suggestions for use statements.
285 | *
286 | * @param {Array} classes
287 | * @param {Array} matches
288 | *
289 | * @return {Array}
290 | ###
291 | getTraitUseSuggestions: (classes, matches) ->
292 | prefix = if matches[1]? then matches[1] else ''
293 |
294 | suggestions = []
295 |
296 | for name, element of classes
297 | if element.type != 'trait'
298 | continue
299 |
300 | suggestion = @getSuggestionForData(element)
301 | suggestion.replacementPrefix = prefix
302 |
303 | @applyAutomaticImportData(suggestion, prefix)
304 |
305 | suggestions.push(suggestion)
306 |
307 | return suggestions
308 |
309 | ###*
310 | * Retrieves suggestions for class names after the new keyword.
311 | *
312 | * @param {Array} classes
313 | * @param {Array} matches
314 | *
315 | * @return {Array}
316 | ###
317 | getNewSuggestions: (classes, matches) ->
318 | prefix = if matches[1]? then matches[1] else ''
319 |
320 | suggestions = []
321 |
322 | for name, element of classes
323 | if element.type != 'class' or element.isAbstract
324 | continue # Not possible to instantiate these.
325 |
326 | suggestion = @getSuggestionForData(element)
327 | suggestion.replacementPrefix = prefix
328 |
329 | @applyAutomaticImportData(suggestion, prefix)
330 |
331 | suggestions.push(suggestion)
332 |
333 | return suggestions
334 |
335 | ###*
336 | * Retrieves suggestions for classlike names.
337 | *
338 | * @param {Array} classes
339 | * @param {Array} matches
340 | *
341 | * @return {Array}
342 | ###
343 | getClassSuggestions: (classes, matches) ->
344 | prefix = if matches[1]? then matches[1] else ''
345 |
346 | suggestions = []
347 |
348 | for name, element of classes
349 | suggestion = @getSuggestionForData(element)
350 | suggestion.replacementPrefix = prefix
351 |
352 | @applyAutomaticImportData(suggestion, prefix)
353 |
354 | suggestions.push(suggestion)
355 |
356 | return suggestions
357 |
358 | ###*
359 | * @param {Object} data
360 | *
361 | * @return {Array}
362 | ###
363 | getSuggestionForData: (data) ->
364 | fqcnWithoutLeadingSlash = data.fqcn
365 |
366 | if fqcnWithoutLeadingSlash[0] == '\\'
367 | fqcnWithoutLeadingSlash = fqcnWithoutLeadingSlash.substring(1)
368 |
369 | suggestionData =
370 | text : fqcnWithoutLeadingSlash
371 | type : if data.type == 'trait' then 'mixin' else 'class'
372 | description : data.shortDescription
373 | leftLabel : data.type
374 | descriptionMoreURL : null
375 | className : if data.isDeprecated then 'php-integrator-autocomplete-plus-strike' else ''
376 | displayText : fqcnWithoutLeadingSlash
377 | data : {}
378 |
379 | ###*
380 | * @param {String} name
381 | *
382 | * @return {String}
383 | ###
384 | getNormalizeFqcnDocumentationUrl: (name) ->
385 | return name.replace(/\\/g, '-').substr(1).toLowerCase()
386 |
387 | ###*
388 | * @param {Object} suggestion
389 | * @param {String} prefix
390 | ###
391 | applyAutomaticImportData: (suggestion, prefix) ->
392 | hasLeadingSlash = false
393 |
394 | if prefix.length > 0 and prefix[0] == '\\'
395 | hasLeadingSlash = true
396 | prefix = prefix.substring(1, prefix.length)
397 |
398 | prefixParts = prefix.split('\\')
399 | partsToSlice = (prefixParts.length - 1)
400 |
401 | fqcn = suggestion.text
402 |
403 | # We try to add an import that has only as many parts of the namespace as needed, for example, if the user
404 | # types 'Foo\Class' and confirms the suggestion 'My\Foo\Class', we add an import for 'My\Foo' and leave the
405 | # user's code at 'Foo\Class' as a relative import. We only add the full 'My\Foo\Class' if the user were to
406 | # type just 'Class' and then select 'My\Foo\Class' (i.e. we remove as many segments from the suggestion
407 | # as the user already has in his code).
408 | suggestion.data.nameToImport = null
409 |
410 | if hasLeadingSlash
411 | suggestion.text = '\\' + fqcn
412 |
413 | else
414 | # Don't try to add use statements for class names that the user wants to make absolute by adding a
415 | # leading slash.
416 | suggestion.text = @getNameToInsert(fqcn, partsToSlice)
417 | suggestion.data.nameToImport = @getNameToImport(fqcn, partsToSlice)
418 |
419 | ###*
420 | * Returns the name to insert into the buffer.
421 | *
422 | * @param {String} fqcn The FQCN of the class that needs to be imported.
423 | * @param {Number} partsToShiftOff The amount of parts to leave extra for the class name. For example, a value of 1
424 | * will return B\C instead of A\B\C. A value of 0 will return just C.
425 | *
426 | * @return {String|null}
427 | ###
428 | getNameToInsert: (fqcn, extraPartsToMaintain) ->
429 | if fqcn[0] == '\\'
430 | fqcn = fqcn.substring(1)
431 |
432 | fqcnParts = fqcn.split('\\')
433 |
434 | if true
435 | nameToUseParts = fqcnParts.slice(-extraPartsToMaintain - 1)
436 |
437 | return nameToUseParts.join('\\')
438 |
439 | ###*
440 | * Returns the name to import via a use statement.
441 | *
442 | * @param {String} fqcn The FQCN of the class that needs to be imported.
443 | * @param {Number} partsToPopOff The amount of parts to leave off of the end of the class name. For example, a
444 | * value of 1 will return A\B instead of A\B\C.
445 | *
446 | * @return {String|null}
447 | ###
448 | getNameToImport: (fqcn, partsToPopOff) ->
449 | if fqcn[0] == '\\'
450 | fqcn = fqcn.substring(1)
451 |
452 | fqcnParts = fqcn.split('\\')
453 |
454 | if partsToPopOff > 0
455 | fqcnParts = fqcnParts.slice(0, -partsToPopOff)
456 |
457 | return fqcnParts.join('\\')
458 |
459 | ###*
460 | * Called when the user confirms an autocompletion suggestion.
461 | *
462 | * @param {TextEditor} editor
463 | * @param {Position} triggerPosition
464 | * @param {Object} suggestion
465 | ###
466 | onDidInsertSuggestion: ({editor, triggerPosition, suggestion}) ->
467 | return unless suggestion.data?.nameToImport
468 | return unless @config.get('automaticallyAddUseStatements')
469 |
470 | successHandler = (currentNamespaceName) =>
471 | if not currentNamespaceName?
472 | currentNamespaceName = ''
473 |
474 | else if currentNamespaceName[0] == '\\'
475 | currentNamespaceName = currentNamespaceName.substring(1)
476 |
477 | # When we have no namespace or are in an anonymous namespace, adding use statements for "non-compound"
478 | # namespaces, such as "DateTime" will generate a warning.
479 | if currentNamespaceName.length == 0
480 | return if suggestion.displayText.split('\\').length == 1
481 |
482 | else if suggestion.data.nameToImport.indexOf(currentNamespaceName) == 0
483 | nameToImportRelativeToNamespace = suggestion.displayText.substr(currentNamespaceName.length + 1)
484 |
485 | # 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
486 | # C\D, as it will be relative, but we will need to add one when he typed just D as it won't be
487 | # relative.
488 | return if nameToImportRelativeToNamespace.split('\\').length == suggestion.text.split('\\').length
489 |
490 | editor.transact () =>
491 | linesAdded = @service.getUseStatementHelper().addUseClass(
492 | editor,
493 | suggestion.data.nameToImport,
494 | @config.get('insertNewlinesForUseStatements')
495 | )
496 |
497 | failureHandler = () ->
498 | # Do nothing.
499 |
500 | @service.determineCurrentNamespaceName(editor, triggerPosition).then(successHandler, failureHandler)
501 |
--------------------------------------------------------------------------------