├── .github
└── FUNDING.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── META-INF
└── plugin.xml
├── README.md
├── idea-php-advanced-autocomplete.iml
├── resources
└── php-advanced-meta
│ └── .phpstorm.meta.php
└── src
└── net
└── king2500
└── plugins
└── PhpAdvancedAutoComplete
├── GenericFileLookupElement.java
├── GenericFileReference.java
├── PhpAutoPopupSpaceTypedHandler.java
├── PhpAutoPopupTypedHandler.java
├── PhpCompletionTokens.java
├── PhpFileReferenceContributor.java
├── PhpFormatDocumentationProvider.java
├── PhpFunctionCompletionContributor.java
├── PhpHeaderDocumentationProvider.java
├── PhpInjectedFileReferenceContributor.java
├── PhpParameterStringCompletionConfidence.java
├── index
├── PhpInjectDirectoryReference.java
├── PhpInjectFileReference.java
├── PhpInjectFileReferenceCollector.java
└── PhpInjectFileReferenceIndex.java
├── reference
├── PhpHighlightPackParametersUsagesHandler.java
├── PhpHighlightPackParametersUsagesHandlerFactory.java
└── PhpPackFormatSpecificationParser.java
└── utils
├── DatabaseUtil.java
├── DateTimeUtil.java
├── FileUtil.java
├── PhpElementsUtil.java
├── PhpMetaUtil.java
├── StringUtil.java
└── XmlUtil.java
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [King2500]
2 | custom: https://www.paypal.me/ThomasSchulz83
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### JetBrains
2 |
3 | ## Directory-based project format:
4 | .idea/
5 |
6 |
7 | ## Plugin-specific files:
8 |
9 | # IntelliJ
10 | /out/
11 |
12 |
13 | ### NetBeans
14 | nbproject/private/
15 | build/
16 | nbbuild/
17 | dist/
18 | nbdist/
19 | nbactions.xml
20 | nb-configuration.xml
21 | .nb-gradle/
22 |
23 |
24 | ### Eclipse
25 | *.pydevproject
26 | .metadata
27 | .gradle
28 | bin/
29 | tmp/
30 | *.tmp
31 | *.bak
32 | *.swp
33 | *~.nib
34 | local.properties
35 | .settings/
36 | .loadpath
37 |
38 | # Eclipse Core
39 | .project
40 |
41 | # External tool builders
42 | .externalToolBuilders/
43 |
44 | # Locally stored "Eclipse launch configurations"
45 | *.launch
46 |
47 | # JDT-specific (Eclipse Java Development Tools)
48 | .classpath
49 |
50 |
51 | ### TortoiseGit
52 | # Project-level settings
53 | /.tgitconfig
54 |
55 |
56 | ### Windows
57 | # Windows image file caches
58 | Thumbs.db
59 | ehthumbs.db
60 |
61 | # Folder config file
62 | Desktop.ini
63 |
64 | # Recycle Bin used on file shares
65 | $RECYCLE.BIN/
66 |
67 | # Windows Installer files
68 | *.cab
69 | *.msi
70 | *.msm
71 | *.msp
72 |
73 | # Windows shortcuts
74 | *.lnk
75 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | ### 1.1.0
5 | * Added completion support for DateTime constructor / DateTime::modify / strtotime DateTime notations
6 | * Added completion for environment variables in getenv() ([#11](https://github.com/King2500/idea-php-advanced-autocomplete/issues/11))
7 | * Added support for DateTimeImmutable (DateTime formats and notations) ([#12](https://github.com/King2500/idea-php-advanced-autocomplete/issues/12))
8 | * AutoPopup completion implemented
9 | * Improved file reference contributor
10 | * Updated HTTP header completion tokens
11 | * Changed HTTP header field completion to insert space char
12 | * Fixed HTTP header field completions after colon ([#5](https://github.com/King2500/idea-php-advanced-autocomplete/issues/5))
13 | * Fixed full header completion in header_remove() function
14 | * Fixed prefixed completion for DateTime notations (also [#5](https://github.com/King2500/idea-php-advanced-autocomplete/issues/5))
15 | * Fixed completion auto-popup for '-' char in date formats
16 | * Fixed date format infos for ISO-8601 weeks
17 | * Fixed duplicate completion entries when PHP Toolbox plugin is installed ([#15](https://github.com/King2500/idea-php-advanced-autocomplete/issues/15))
18 | * Changes to compile against JDK 1.8 / PhpStorm 2019.1
19 | * Minimum IDEA version changed to 2018.2+
20 |
21 | ### 1.0.4
22 | * Changed date format info for 'W' ([#1](https://github.com/King2500/idea-php-advanced-autocomplete/pull/1))
23 | * Fixed StringIndexOutOfBoundsException: String index out of range ([#2](https://github.com/King2500/idea-php-advanced-autocomplete/issues/2))
24 |
25 | ### 1.0.3
26 | * Added support for date and time format characters for date_format, DateTime::format, DateTime::createFromFormat,
27 | date_create_from_format, strftime, gmstrftime and strptime
28 |
29 | ### 1.0.2
30 | * Added support for completion and file reference (Ctrl+Click, Rename..) for various file and folder related functions and methods
31 | * Added file mode completion support for SplFileInfo::openFile
32 | * Added basic file url completion support for header('Location: ... and header('Content-Location: ...
33 |
34 | ### 1.0.1
35 | * Added infos for date format characters
36 | * Added infos for fopen/popen file modes
37 | * Added completion support for mysql/mysqli/PDO connect functions, listing hostnames, database names and usernames from data sources defined in project
38 | * Added completion support for MySQL charsets in mysql_set_charset, mysqli_set_charset, mysqli::set_charset
39 | * Disabled case sensitivity for charsets/encodings (HTML, mbstring, header)
40 |
41 | ### 1.0.0
42 | * Initial release
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Thomas Schulz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | net.king2500.plugins.PhpAdvancedAutoComplete
3 | PHP Advanced AutoComplete
4 | 1.1.0
5 | Thomas Schulz
6 |
7 | Adds auto-completion support for various built-in PHP functions and methods, where parameter is a string literal.
9 | The following functions are currently supported:
10 |
11 | - header/header_remove
12 | HTTP response headers, status codes, charsets, mime-types, locations, and much more
13 | - File and folder related functions and methods (fopen, file_get_contents, dir...)
14 | Files and/or folders paths relative to the current file (completion and reference)
15 | - date / strftime / DateTime::format etc
16 | Format characters and common format strings
17 | - strtotime / DateTime constructor / DateTime::modify
18 | DateTime notations
19 | - htmlentities/htmlspecialchars
20 | Supported charsets
21 | - mb_string functions
22 | Charset, where required; types for mb_get_info and supported languages for mb_language
23 | - extension_loaded
24 | Known PHP extensions
25 | - fopen/popen/SplFileInfo::openFile
26 | File modes
27 | - fsockopen / stream_socket_client
28 | Socket transports (tcp://, udp://, etc)
29 | - mysql_connect/mysqli_connect/mysqli/PDO
30 | Hostnames, database names and usernames from data sources defined in project
31 | - mysql_select_db/mysqli_select_db/mysqli::select_db
32 | Database names from data sources defined in project
33 | - mysqli_change_user/mysqli::change_user
34 | Usernames and database names from data sources defined in project
35 | - mysql_set_charset/mysqli_set_charset/mysqli::set_charset
36 | Supported charsets for MySQL
37 | - getenv
38 | Common environment variables (like in $_SERVER array keys)
39 |
40 |
41 | If you have further suggestions/ideas, just send me an e-mail.
42 | Commend and rate this plugin here
43 | ]]>
44 |
45 | 1.1.0
48 |
49 | - Added completion support for DateTime constructor / DateTime::modify / strtotime DateTime notations
50 | - Added completion for environment variables in getenv() (#11)
51 | - Added support for DateTimeImmutable (DateTime formats and notations) (#12)
52 | - AutoPopup completion implemented
53 | - Improved file reference contributor
54 | - Updated HTTP header completion tokens
55 | - Changed HTTP header field completion to insert space char
56 | - Fixed HTTP header field completions after colon (#5)
57 | - Fixed full header completion in header_remove() function
58 | - Fixed prefixed completion for DateTime notations (also #5)
59 | - Fixed completion auto-popup for '-' char in date formats
60 | - Fixed date format infos for ISO-8601 weeks
61 | - Fixed duplicate completion entries when PHP Toolbox plugin is installed (#15)
62 | - Changes to compile against JDK 1.8 / PhpStorm 2019.1
63 | - Minimum IDEA version changed to 2018.2+
64 |
65 |
66 | 1.0.4
67 |
68 | - Changed date format info for 'W' (#1)
69 | - Fixed StringIndexOutOfBoundsException: String index out of range (#2)
70 |
71 |
72 | 1.0.3
73 |
74 | - Added support for date and time format characters for date_format, DateTime::format, DateTime::createFromFormat,
75 | date_create_from_format, strftime, gmstrftime and strptime
76 |
77 |
78 | 1.0.2
79 |
80 | - Added support for completion and file reference (Ctrl+Click, Rename..) for various file and folder related functions and methods
81 | - Added file mode completion support for SplFileInfo::openFile
82 | - Added basic file url completion support for header('Location: ... and header('Content-Location: ...
83 |
84 |
85 | 1.0.1
86 |
87 | - Added infos for date format characters
88 | - Added infos for fopen/popen file modes
89 | - Added completion support for mysql/mysqli/PDO connect functions, listing hostnames, database names and usernames from data sources defined in project
90 | - Added completion support for MySQL charsets in mysql_set_charset, mysqli_set_charset, mysqli::set_charset
91 | - Disabled case sensitivity for charsets/encodings (HTML, mbstring, header)
92 |
93 |
94 |
95 | Full changelog
96 |
97 | ]]>
98 |
99 |
100 |
101 |
102 |
103 |
105 |
108 | com.jetbrains.php
109 | com.intellij.modules.platform
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PhpStorm IDE Plugin:
PHP Advanced AutoComplete
2 | [](https://plugins.jetbrains.com/plugin/7276)
3 | [](https://plugins.jetbrains.com/plugin/7276)
4 | [](https://plugins.jetbrains.com/plugin/7276)
5 |
6 | Plugin URL: https://plugins.jetbrains.com/plugin/7276
7 |
8 | Adds auto-completion support for various built-in PHP functions and methods, where parameter is a string literal.
9 |
10 |
11 | The following functions are currently supported:
12 |
13 | * header / header_remove
14 | HTTP response headers, status codes, charsets, mime-types, locations, and much more
15 |
16 | * File and folder related functions and methods (fopen, file_get_contents, dir...)
17 | Files and/or folders paths relative to the current file (completion and reference)
18 |
19 | * date / strftime / DateTime::format etc
20 | Format characters and common format strings
21 |
22 | * strtotime / DateTime constructor / DateTime::modify
23 | DateTime notations
24 |
25 | * htmlentities / htmlspecialchars
26 | Supported charsets
27 |
28 | * mb_string functions
29 | Charset, where required; types for mb_get_info and supported languages for mb_language
30 |
31 | * extension_loaded
32 | Known PHP extensions
33 |
34 | * fopen / popen / SplFileInfo::openFile
35 | File modes
36 |
37 | * fsockopen / stream_socket_client
38 | Socket transports (tcp://, udp://, etc)
39 |
40 | * mysql_connect/mysqli_connect/mysqli/PDO
41 | Hostnames, database names and usernames from data sources defined in project
42 |
43 | * mysql_select_db/mysqli_select_db/mysqli::select_db
44 | Database names from data sources defined in project
45 |
46 | * mysqli_change_user/mysqli::change_user
47 | Usernames and database names from data sources defined in project
48 |
49 | * mysql_set_charset/mysqli_set_charset/mysqli::set_charset
50 | Supported charsets for MySQL
51 |
52 | * getenv
53 | Common environment variables (like in $_SERVER array keys)
54 |
55 | If you have further suggestions/ideas, just send me an e-mail to phpstorm at king2500.net.
56 |
--------------------------------------------------------------------------------
/idea-php-advanced-autocomplete.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/resources/php-advanced-meta/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | public class GenericFileLookupElement extends LookupElement {
16 | private String fileName;
17 | private PsiFileSystemItem psiFile;
18 | private PsiElement psiElement = null;
19 |
20 | @Nullable
21 | private InsertHandler insertHandler = null;
22 |
23 | public GenericFileLookupElement(String fileName, PsiFileSystemItem psiFile) {
24 | this.fileName = fileName;
25 | this.psiFile = psiFile;
26 | }
27 |
28 | public GenericFileLookupElement(String fileName, PsiFileSystemItem psiFile, PsiElement psiElement, InsertHandler insertHandler) {
29 | this.fileName = fileName;
30 | this.psiFile = psiFile;
31 | this.insertHandler = insertHandler;
32 | this.psiElement = psiElement;
33 | }
34 |
35 | @NotNull
36 | @Override
37 | public String getLookupString() {
38 | return fileName;
39 | }
40 |
41 | @NotNull
42 | public Object getObject() {
43 | return this.psiElement != null ? this.psiElement : super.getObject();
44 | }
45 |
46 | public void handleInsert(InsertionContext context) {
47 | if (this.insertHandler != null) {
48 | this.insertHandler.handleInsert(context, this);
49 | }
50 | }
51 |
52 | public void renderElement(LookupElementPresentation presentation) {
53 | presentation.setItemText(getLookupString());
54 | presentation.setIcon(psiFile.getIcon(0));
55 | //presentation.setTypeText(VfsUtil.getRelativePath(psiFile.getContainingDirectory().getVirtualFile(), psiFile.getProject().getBaseDir(), '/'));
56 | //presentation.setTypeGrayed(true);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/GenericFileReference.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.intellij.codeInsight.lookup.LookupElement;
4 | import com.intellij.psi.PsiElement;
5 | import com.intellij.psi.PsiFileSystemItem;
6 | import com.intellij.psi.PsiReference;
7 | import com.intellij.psi.PsiReferenceBase;
8 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
9 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.FileUtil;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | /**
18 | * @author Thomas Schulz
19 | */
20 | public class GenericFileReference extends PsiReferenceBase implements PsiReference {
21 |
22 | private String fileName;
23 | private int fileType;
24 |
25 | public GenericFileReference(@NotNull StringLiteralExpression element, int type) {
26 | super(element);
27 |
28 | fileName = element.getText().substring(
29 | element.getValueRange().getStartOffset(),
30 | element.getValueRange().getEndOffset()
31 | );
32 |
33 | fileType = type;
34 | }
35 |
36 | @Nullable
37 | @Override
38 | public PsiElement resolve() {
39 | Map filesByName = FileUtil.getRelativeFilesByName(getElement().getContainingFile(), fileType);
40 |
41 | return filesByName.get(fileName);
42 | }
43 |
44 | @NotNull
45 | @Override
46 | public Object[] getVariants() {
47 | List results = new ArrayList();
48 |
49 | Map filesByName = FileUtil.getRelativeFilesByName(getElement().getContainingFile(), fileType);
50 | for (Map.Entry entry : filesByName.entrySet()) {
51 | results.add(
52 | new GenericFileLookupElement(entry.getKey(), entry.getValue())
53 | );
54 | }
55 |
56 | return results.toArray();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/PhpAutoPopupSpaceTypedHandler.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.intellij.codeInsight.CodeInsightSettings;
4 | import com.intellij.codeInsight.completion.CompletionPhase;
5 | import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
6 | import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
7 | import com.intellij.ide.PowerSaveMode;
8 | import com.intellij.openapi.application.ApplicationManager;
9 | import com.intellij.openapi.editor.Editor;
10 | import com.intellij.openapi.project.Project;
11 | import com.intellij.openapi.util.Condition;
12 | import com.intellij.patterns.PlatformPatterns;
13 | import com.intellij.psi.PsiElement;
14 | import com.intellij.psi.PsiFile;
15 | import com.jetbrains.php.lang.lexer.PhpTokenTypes;
16 | import com.jetbrains.php.lang.psi.PhpFile;
17 | import org.jetbrains.annotations.NotNull;
18 | import org.jetbrains.annotations.Nullable;
19 |
20 | /**
21 | * @author Thomas Schulz
22 | */
23 | public class PhpAutoPopupSpaceTypedHandler extends TypedHandlerDelegate {
24 | @Override
25 | public Result checkAutoPopup(char charTyped, Project project, Editor editor, PsiFile file) {
26 |
27 | if (!(file instanceof PhpFile)) {
28 | return TypedHandlerDelegate.Result.CONTINUE;
29 | }
30 |
31 | // scheduleAutoPopup(project, editor, null);
32 | //
33 | // if ((charTyped != ' ')) {
34 | // return TypedHandlerDelegate.Result.STOP;
35 | // }
36 |
37 | return TypedHandlerDelegate.Result.CONTINUE;
38 | }
39 |
40 | @Override
41 | public Result charTyped(char c, Project project, @NotNull Editor editor, @NotNull PsiFile file) {
42 |
43 | if (!(file instanceof PhpFile)) {
44 | return TypedHandlerDelegate.Result.CONTINUE;
45 | }
46 |
47 | if ((c != ' ')) {
48 | return TypedHandlerDelegate.Result.CONTINUE;
49 | }
50 |
51 | PsiElement psiElement = file.findElementAt(editor.getCaretModel().getOffset() - 2);
52 | if (psiElement == null || !(PlatformPatterns.psiElement(PhpTokenTypes.STRING_LITERAL).accepts(psiElement) || PlatformPatterns.psiElement(PhpTokenTypes.STRING_LITERAL_SINGLE_QUOTE).accepts(psiElement))) {
53 | return TypedHandlerDelegate.Result.CONTINUE;
54 | }
55 |
56 | scheduleAutoPopup(project, editor, null);
57 |
58 | return TypedHandlerDelegate.Result.CONTINUE;
59 | }
60 |
61 | /**
62 | * PhpTypedHandler.scheduleAutoPopup but use SMART since BASIC is blocked
63 | */
64 | public void scheduleAutoPopup(final Project project, final Editor editor, @Nullable final Condition condition) {
65 | if (ApplicationManager.getApplication().isUnitTestMode()/* && !CompletionAutoPopupHandler.ourTestingAutopopup*/) {
66 | return;
67 | }
68 |
69 | if (!CodeInsightSettings.getInstance().AUTO_POPUP_COMPLETION_LOOKUP) {
70 | return;
71 | }
72 | if (PowerSaveMode.isEnabled()) {
73 | return;
74 | }
75 |
76 | if (!CompletionServiceImpl.isPhase(CompletionPhase.CommittingDocuments.class, CompletionPhase.NoCompletion.getClass())) {
77 | return;
78 | }
79 | //
80 | // final CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
81 | // if (currentCompletion != null) {
82 | // currentCompletion.closeAndFinish(true);
83 | // }
84 | //
85 | // final CompletionPhase.CommittingDocuments phase = new CompletionPhase.CommittingDocuments(null, editor);
86 | // CompletionServiceImpl.setCompletionPhase(phase);
87 | //
88 | // CompletionAutoPopupHandler.runLaterWithCommitted(project, editor.getDocument(), new Runnable() {
89 | // @Override
90 | // public void run() {
91 | // CompletionAutoPopupHandler.invokeCompletion(CompletionType.BASIC, true, project, editor, 0, false);
92 | // }
93 | // });
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/PhpAutoPopupTypedHandler.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
4 | import com.intellij.openapi.editor.Editor;
5 | import com.intellij.openapi.project.Project;
6 | import com.intellij.psi.PsiElement;
7 | import com.intellij.psi.PsiFile;
8 | import com.intellij.util.ObjectUtils;
9 | import com.jetbrains.php.completion.PhpCompletionUtil;
10 | import com.jetbrains.php.lang.psi.PhpFile;
11 | import com.jetbrains.php.lang.psi.PhpPsiUtil;
12 | import com.jetbrains.php.lang.psi.elements.FunctionReference;
13 | import com.jetbrains.php.lang.psi.elements.ParameterList;
14 | import com.jetbrains.php.lang.psi.elements.Statement;
15 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.PhpElementsUtil;
16 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.StringUtil;
17 | import org.jetbrains.annotations.NotNull;
18 |
19 | /**
20 | * @author Thomas Schulz
21 | */
22 | public class PhpAutoPopupTypedHandler extends TypedHandlerDelegate {
23 | @NotNull
24 | @Override
25 | public Result checkAutoPopup(char charTyped, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
26 |
27 | if (!(file instanceof PhpFile)) {
28 | return Result.CONTINUE;
29 | }
30 |
31 | if (charTyped != '%') {
32 | return Result.CONTINUE;
33 | }
34 |
35 | int offset = editor.getCaretModel().getOffset();
36 | PsiElement psiElement = file.findElementAt(offset);
37 |
38 | ParameterList parameterList = PhpPsiUtil.getParentByCondition(psiElement, true, ParameterList.INSTANCEOF, Statement.INSTANCEOF);
39 | if (parameterList != null) {
40 | FunctionReference functionCall = ObjectUtils.tryCast(parameterList.getParent(), FunctionReference.class);
41 | String fqn = PhpElementsUtil.resolveFqn(functionCall);
42 |
43 | if (/*charTyped == '%' &&*/ PhpElementsUtil.isFormatFunction(fqn)) {
44 | if (StringUtil.getPrecedingCharNum(editor.getDocument().getCharsSequence(), offset, '%') % 2 == 0) {
45 | PhpCompletionUtil.showCompletion(editor);
46 | }
47 | }
48 | }
49 |
50 | return Result.CONTINUE;
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/PhpFileReferenceContributor.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.intellij.patterns.PlatformPatterns;
4 | import com.intellij.psi.*;
5 | import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet;
6 | import com.intellij.util.ProcessingContext;
7 | import com.jetbrains.php.lang.PhpLanguage;
8 | import com.jetbrains.php.lang.psi.elements.FunctionReference;
9 | import com.jetbrains.php.lang.psi.elements.ParameterList;
10 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
11 | import com.jetbrains.php.lang.psi.elements.impl.NewExpressionImpl;
12 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.PhpElementsUtil;
13 | import org.jetbrains.annotations.NotNull;
14 |
15 | /**
16 | * @author Thomas Schulz
17 | */
18 | public class PhpFileReferenceContributor extends PsiReferenceContributor {
19 | @Override
20 | public void registerReferenceProviders(PsiReferenceRegistrar psiReferenceRegistrar) {
21 | psiReferenceRegistrar.registerReferenceProvider(
22 | PlatformPatterns
23 | .psiElement(StringLiteralExpression.class)
24 | .withParent(ParameterList.class)
25 | .withLanguage(PhpLanguage.INSTANCE),
26 | new PsiReferenceProvider() {
27 | @NotNull
28 | @Override
29 | public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
30 | if (!(psiElement.getContext() instanceof ParameterList)) {
31 | return new PsiReference[0];
32 | }
33 |
34 | ParameterList parameterList = (ParameterList) psiElement.getContext();
35 |
36 | if (!(parameterList.getContext() instanceof FunctionReference || parameterList.getContext() instanceof NewExpressionImpl)) {
37 | return new PsiReference[0];
38 | }
39 |
40 | String funcName = PhpElementsUtil.getCanonicalFuncName(psiElement.getParent().getParent());
41 | int paramIndex = PhpElementsUtil.getParameterIndex(psiElement);
42 |
43 | if (funcName == null) {
44 | return new PsiReference[0];
45 | }
46 |
47 | String text = ((StringLiteralExpression) psiElement).getContents();
48 | String prefix1 = "Location: /";
49 | String prefix2 = "Content-Location: /";
50 |
51 | if (funcName.equals("header")) {
52 | if (text.startsWith(prefix1)) {
53 | return getFileReferenceAfterPrefix(psiElement, prefix1);
54 | }
55 | if (text.startsWith(prefix2)) {
56 | return getFileReferenceAfterPrefix(psiElement, prefix2);
57 | }
58 | }
59 |
60 | return new PsiReference[0];
61 | }
62 |
63 | @NotNull
64 | private PsiReference[] getFileReferenceAfterPrefix(@NotNull PsiElement psiElement, String prefix) {
65 | FileReferenceSet referenceSet = new FileReferenceSet(((StringLiteralExpression) psiElement).getContents(), psiElement, prefix.length() + 1, null, false) {
66 | @Override
67 | protected boolean isUrlEncoded() {
68 | return true;
69 | }
70 |
71 | @Override
72 | public boolean isEndingSlashNotAllowed() {
73 | return false;
74 | }
75 | };
76 | referenceSet.addCustomization(FileReferenceSet.DEFAULT_PATH_EVALUATOR_OPTION, FileReferenceSet.ABSOLUTE_TOP_LEVEL);
77 | return referenceSet.getAllReferences();
78 | }
79 | }
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/PhpFormatDocumentationProvider.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.intellij.lang.Language;
4 | import com.intellij.lang.documentation.DocumentationProviderEx;
5 | import com.intellij.psi.PsiElement;
6 | import com.intellij.psi.PsiManager;
7 | import com.intellij.psi.impl.light.LightElement;
8 | import com.jetbrains.php.lang.psi.PhpPsiUtil;
9 | import com.jetbrains.php.lang.psi.elements.FunctionReference;
10 | import com.jetbrains.php.lang.psi.elements.Statement;
11 | import io.netty.util.internal.StringUtil;
12 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.PhpElementsUtil;
13 | import org.apache.commons.lang.StringUtils;
14 | import org.jetbrains.annotations.NotNull;
15 | import org.jetbrains.annotations.Nullable;
16 |
17 | import java.util.Arrays;
18 | import java.util.Collections;
19 | import java.util.List;
20 |
21 | /**
22 | * @author Thomas Schulz
23 | */
24 | public class PhpFormatDocumentationProvider extends DocumentationProviderEx {
25 |
26 | private static final String PHP_FORMAT_URL = "https://www.php.net/manual/en/function.{functionName}.php";
27 |
28 | @Override
29 | public @Nullable List getUrlFor(PsiElement element, PsiElement originalElement) {
30 | if (!(element instanceof FormatTokenDocElement)) {
31 | return null;
32 | }
33 | String functionName = ((FormatTokenDocElement)element).getFunctionName();
34 | return Collections.singletonList(PHP_FORMAT_URL.replace("{functionName}", functionName));
35 | }
36 |
37 | @Override
38 | public @Nullable String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
39 | if (!(element instanceof FormatTokenDocElement)) {
40 | return null;
41 | }
42 |
43 | String tokenText = ((FormatTokenDocElement)element).getTokenText();
44 | String functionName = ((FormatTokenDocElement)element).getFunctionName();
45 |
46 | if (Arrays.asList(PhpCompletionTokens.scanFormatFuncs).contains(functionName + ":1")) {
47 | return PhpCompletionTokens.scanFormatTokensDoc.getOrDefault(tokenText, "");
48 | }
49 | else {
50 | return PhpCompletionTokens.formatTokensDoc.getOrDefault(tokenText, "");
51 | }
52 | }
53 |
54 | @Override
55 | public @Nullable PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement psiElement) {
56 | if (!(object instanceof String)) {
57 | return null;
58 | }
59 |
60 | String fqn = getCallToFormatFunc(psiElement);
61 | if (StringUtil.isNullOrEmpty(fqn)) {
62 | return null;
63 | }
64 |
65 | String tokenText = (String)object;
66 |
67 | if ("%%".equals(tokenText)) {
68 | tokenText = "%";
69 | }
70 | else if (!"%".equals(tokenText)) {
71 | tokenText = StringUtils.strip((String)object, "%");
72 | }
73 | String functionName = StringUtils.strip(fqn, "\\");
74 | return new FormatTokenDocElement(psiManager, psiElement.getLanguage(), tokenText, functionName);
75 | }
76 |
77 | private String getCallToFormatFunc(PsiElement psiElement) {
78 | FunctionReference function = PhpPsiUtil.getParentByCondition(psiElement, true, FunctionReference.INSTANCEOF, Statement.INSTANCEOF);
79 | if (function == null) {
80 | return null;
81 | }
82 | return PhpElementsUtil.resolveFqn(function);
83 | }
84 |
85 | private static class FormatTokenDocElement extends LightElement {
86 |
87 | private final String tokenText;
88 | private final String functionName;
89 |
90 | protected FormatTokenDocElement(@NotNull final PsiManager manager, @NotNull final Language language, @NotNull final String tokenText, @NotNull final String functionName) {
91 | super(manager, language);
92 | this.tokenText = tokenText;
93 | this.functionName = functionName;
94 | }
95 |
96 | public String getTokenText() {
97 | return tokenText;
98 | }
99 |
100 | @Override
101 | public String getText() {
102 | return functionName;
103 | }
104 |
105 | public String getFunctionName() {
106 | return functionName;
107 | }
108 |
109 | @Override
110 | public String toString() {
111 | return "FormatTokenDocElement for " + tokenText;
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/PhpFunctionCompletionContributor.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.intellij.codeInsight.completion.*;
4 | import com.intellij.codeInsight.lookup.LookupElement;
5 | import com.intellij.codeInsight.lookup.LookupElementBuilder;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.patterns.PlatformPatterns;
8 | import com.intellij.patterns.PsiElementPattern;
9 | import com.intellij.psi.PsiElement;
10 | import com.intellij.psi.util.PsiTreeUtil;
11 | import com.intellij.util.ProcessingContext;
12 | import com.jetbrains.php.completion.PhpCompletionUtil;
13 | import com.jetbrains.php.lang.PhpLanguage;
14 | import com.jetbrains.php.lang.lexer.PhpTokenTypes;
15 | import com.jetbrains.php.lang.parser.PhpElementTypes;
16 | import com.jetbrains.php.lang.psi.elements.FunctionReference;
17 | import com.jetbrains.php.lang.psi.elements.ParameterList;
18 | import com.jetbrains.php.lang.psi.elements.PhpNamespace;
19 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
20 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.*;
21 | import org.jetbrains.annotations.NotNull;
22 |
23 | import java.util.*;
24 | import java.util.function.Predicate;
25 | import java.util.regex.Pattern;
26 | import java.util.stream.Collectors;
27 |
28 | /**
29 | * @author Thomas Schulz
30 | */
31 | public class PhpFunctionCompletionContributor extends CompletionContributor {
32 |
33 | public PhpFunctionCompletionContributor() {
34 | //noinspection unchecked
35 | PsiElementPattern.Capture stringInFuncCall = PlatformPatterns.psiElement(PhpElementTypes.STRING)
36 | .withParent(PlatformPatterns.psiElement(PhpElementTypes.PARAMETER_LIST)
37 | .withParent(
38 | PlatformPatterns.or(
39 | PlatformPatterns.psiElement(PhpElementTypes.FUNCTION_CALL),
40 | PlatformPatterns.psiElement(PhpElementTypes.METHOD_REFERENCE),
41 | PlatformPatterns.psiElement(PhpElementTypes.NEW_EXPRESSION)
42 | )
43 | )
44 | );
45 | //noinspection unchecked
46 | extend(CompletionType.BASIC,
47 | PlatformPatterns.or(
48 | PlatformPatterns
49 | .psiElement(PhpTokenTypes.STRING_LITERAL)
50 | .withParent(stringInFuncCall)
51 | .withLanguage(PhpLanguage.INSTANCE),
52 | PlatformPatterns
53 | .psiElement(PhpTokenTypes.STRING_LITERAL_SINGLE_QUOTE)
54 | .withParent(stringInFuncCall)
55 | .withLanguage(PhpLanguage.INSTANCE)
56 | ),
57 | new CompletionProvider() {
58 | final Pattern DATETIME_NUMBER_ONE = Pattern.compile("^[-+]?1$");
59 | final Pattern DATETIME_NUMBERS = Pattern.compile("^[-+]?\\d+$");
60 | final Pattern DATETIME_PREFIX_NUMBER_ONE = Pattern.compile("^[-+]?1 ");
61 | final Pattern DATETIME_PREFIX_NUMBERS = Pattern.compile("^[-+]?\\d+ ");
62 | final Pattern DATETIME_PREFIX_NUMBERS_UNITS = Pattern.compile("[^-+]?\\d+[ ]?(sec(ond)?|min(ute)?|hour|day|forth?night|month|year|week(day)?)s? ");
63 |
64 | public void addCompletions(@NotNull CompletionParameters parameters,
65 | ProcessingContext context,
66 | @NotNull CompletionResultSet result) {
67 |
68 | String funcName = PhpElementsUtil.getCanonicalFuncName(parameters.getPosition().getParent().getParent().getParent());
69 | Project project = parameters.getPosition().getProject();
70 | String[] resultElements = {};
71 | String[] resultInfos = {};
72 | String[] resultParams = {};
73 | String resultPrefix = "";
74 | String resultPostfix = "";
75 | String resultPostfixAlt = "";
76 | String resultTailText = null;
77 | String[] resultPostfixExceptions = {};
78 | boolean resultBold = false;
79 | boolean resultCaseSensitivity = true;
80 | String[] deprecatedElements = {};
81 | boolean allowMultiple = false;
82 | boolean allowRepeat = false;
83 | String splitter = ",";
84 | String splitterSpace = " ";
85 | boolean overwriteExistingCompletions = false;
86 | InsertHandler insertHandler = null;
87 |
88 | int paramIndex = PhpElementsUtil.getParameterIndex(parameters.getPosition().getParent());
89 |
90 | if (funcName == null) {
91 | return;
92 | }
93 |
94 | String stringLiteral = parameters.getPosition().getText();
95 | String stringPrefix = "";
96 |
97 | if (stringLiteral.contains(CompletionUtil.DUMMY_IDENTIFIER)) {
98 | stringPrefix = stringLiteral.substring(0, stringLiteral.indexOf(CompletionUtil.DUMMY_IDENTIFIER));
99 | }
100 |
101 | if (methodMatchesAt(funcName, paramIndex, PhpCompletionTokens.dbConnectFuncs, 0)) {
102 | if (funcName.startsWith("PDO::")) {
103 | resultElements = DatabaseUtil.getPdoDSNs(project, "mysql://");
104 |
105 | if (resultElements == null) {
106 | resultElements = prefixArray("mysql:dbname=;host=", PhpCompletionTokens.dbConnectElements);
107 | }
108 |
109 | resultElements = concatArrays(resultElements, prefixArray("mysql:dbname=;host=", PhpCompletionTokens.dbConnectElements));
110 | }
111 | else {
112 | resultElements = DatabaseUtil.getDbHostnames(project, "mysql://");
113 | if (resultElements == null) {
114 | resultElements = PhpCompletionTokens.dbConnectElements;
115 | }
116 |
117 | resultElements = concatArrays(resultElements, PhpCompletionTokens.dbConnectElements);
118 | }
119 | }
120 |
121 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.dbConnectUserFuncs)) {
122 | resultElements = DatabaseUtil.getDbUsers(project, "mysql://");
123 | }
124 |
125 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.dbCharSetFuncs)) {
126 | resultElements = PhpCompletionTokens.dbCharSets;
127 | resultInfos = PhpCompletionTokens.dbCharSetsInfos;
128 | }
129 |
130 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.dbSelectDbFuncs)) {
131 | resultElements = DatabaseUtil.getDbNames(project, "mysql://");
132 | }
133 |
134 | if (methodMatchesAt(funcName, paramIndex, PhpCompletionTokens.phpExtensionFuncs, 0)) {
135 | resultElements = PhpCompletionTokens.phpExtensionElements;
136 | }
137 |
138 | // if(methodMatches(funcName, paramIndex, PhpCompletionTokens.fileFuncs)) {
139 | // resultElements = FileUtil.getRelativeFiles(parameters.getPosition().getContainingFile().getOriginalFile());
140 | // }
141 |
142 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.fileModeFuncs)) {
143 | resultElements = PhpCompletionTokens.fileModeElements;
144 | resultInfos = PhpCompletionTokens.fileModeInfos;
145 | resultBold = true;
146 | overwriteExistingCompletions = true;
147 | }
148 |
149 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.dateFormatFuncs)) {
150 | resultElements = PhpCompletionTokens.dateFormatTokens;
151 | resultInfos = PhpCompletionTokens.dateFormatInfos;
152 | resultBold = true;
153 | resultParams = new String[resultElements.length];
154 |
155 | for (int i = 0; i < PhpCompletionTokens.dateFormatTokens.length; i++) {
156 | String format = PhpCompletionTokens.dateFormatTokens[i];
157 | String dateTimeText = DateTimeUtil.formatPhpDateTime(format, Locale.ENGLISH);
158 | resultParams[i] = !dateTimeText.isEmpty() ? (" = " + dateTimeText) : "";
159 | }
160 | }
161 |
162 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.timeFormatFuncs)) {
163 | resultElements = PhpCompletionTokens.timeFormatTokens;
164 | resultInfos = PhpCompletionTokens.timeFormatInfos;
165 | resultBold = true;
166 | }
167 |
168 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.dateTimeParserFuncs)) {
169 | stringPrefix = stringPrefix.toLowerCase();
170 | resultCaseSensitivity = false;
171 | overwriteExistingCompletions = true;
172 |
173 | // "1" without trailing space
174 | if (patternMatches(DATETIME_NUMBER_ONE, stringPrefix)) {
175 | resultElements = PhpCompletionTokens.dateTimeUnits;
176 | }
177 | // numbers like 2, 17 without trailing space
178 | else if (patternMatches(DATETIME_NUMBERS, stringPrefix)) {
179 | resultElements = PhpCompletionTokens.dateTimeUnits2;
180 | }
181 | else if (stringPrefix.contains(" ")) {
182 | result = result.withPrefixMatcher(stringPrefix.substring(stringPrefix.lastIndexOf(" ") + 1));
183 | String[] prefixParts = stringPrefix.split(" ");
184 | int numParts = prefixParts.length;
185 |
186 | // yesterday, today, tomorrow
187 | if (numParts >= 1 && numParts <= 2 && Arrays.asList(PhpCompletionTokens.dateTimeDayRelTexts).contains(prefixParts[0])) {
188 | resultElements = concatArrays(PhpCompletionTokens.dateTimeDaytimeTexts, PhpCompletionTokens.dateTimeHourTexts);
189 | }
190 | // back of, front of
191 | else if (numParts <= 3 && (stringPrefix.startsWith("back of ") || stringPrefix.startsWith("front of "))) {
192 | resultElements = PhpCompletionTokens.dateTimeHourTexts;
193 | }
194 | // first day of, last day of
195 | else if (numParts <= 5 && (stringPrefix.startsWith("first day of ") || stringPrefix.startsWith("last day of "))) {
196 | result = result.withPrefixMatcher(stringPrefix.substring(stringPrefix.lastIndexOf("day of ") + 7));
197 | resultElements = concatArrays(PhpCompletionTokens.dateTimeMonthNames, PhpCompletionTokens.dateTimeMonthRelTexts);
198 | }
199 | // first mon of, last friday of, ...
200 | else if (numParts <= 5 && stringPrefix.contains("of ") && Arrays.asList(PhpCompletionTokens.dateTimeDayOfTexts).contains(stringPrefix.substring(0, stringPrefix.indexOf("of ") + 3))) {
201 | result = result.withPrefixMatcher(stringPrefix.substring(stringPrefix.lastIndexOf("of ") + 3));
202 | resultElements = concatArrays(PhpCompletionTokens.dateTimeMonthNames, PhpCompletionTokens.dateTimeMonthRelTexts);
203 | }
204 | // first, last, next, this
205 | else if ((numParts == 1 || (numParts == 2 && !stringPrefix.endsWith(" "))) && Arrays.asList(PhpCompletionTokens.dateTimeOrdinal).contains(prefixParts[0])) {
206 | resultElements = concatArrays(PhpCompletionTokens.dateTimeDayNames, PhpCompletionTokens.dateTimeUnits);
207 | }
208 | // back, front, first day, last day, first monday, ...
209 | else if (numParts >= 1 && Arrays.asList(PhpCompletionTokens.dateTimeOfPrefixes).contains(prefixParts[0] + " ")
210 | || numParts >= 2 && Arrays.asList(PhpCompletionTokens.dateTimeOfPrefixes).contains(prefixParts[0] + " " + prefixParts[1] + " ")) {
211 | resultElements = new String[]{"of "};
212 | }
213 | // monday, friday, ...
214 | else if (numParts >= 1 && Arrays.asList(PhpCompletionTokens.dateTimeDayNames).contains(prefixParts[0])) {
215 | result = result.withPrefixMatcher(stringPrefix.substring(stringPrefix.indexOf(" ") + 1));
216 | resultElements = PhpCompletionTokens.dateTimeRelWeek;
217 | }
218 | // -1, +1
219 | else if ((numParts == 1 || (numParts == 2 && !stringPrefix.endsWith(" "))) && patternMatches(DATETIME_PREFIX_NUMBER_ONE, stringPrefix)) {
220 | resultElements = PhpCompletionTokens.dateTimeUnits;
221 | }
222 | // -2, +7
223 | else if ((numParts == 1 || (numParts == 2 && !stringPrefix.endsWith(" "))) && patternMatches(DATETIME_PREFIX_NUMBERS, stringPrefix)) {
224 | resultElements = PhpCompletionTokens.dateTimeUnits2;
225 | }
226 | // 5 days, 3 weeks
227 | else if (numParts <= 3 && patternMatches(DATETIME_PREFIX_NUMBERS_UNITS, stringPrefix) && !stringPrefix.contains(" ago ")) {
228 | resultElements = PhpCompletionTokens.dateTimeAgo;
229 | }
230 | }
231 | else {
232 | resultElements = concatArrays(PhpCompletionTokens.dateTimeRelativeFormats, PhpCompletionTokens.dateTimeDayNames);
233 | }
234 | }
235 |
236 | if (methodMatchesAt(funcName, paramIndex, PhpCompletionTokens.htmlCharSetFuncs, 2)) {
237 | resultElements = PhpCompletionTokens.htmlCharSets;
238 | resultCaseSensitivity = false;
239 | }
240 |
241 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.mbStringEncodingFuncs)) {
242 | resultElements = PhpCompletionTokens.mbStringEncodingElements;
243 | resultCaseSensitivity = false;
244 | }
245 |
246 | if (methodMatchesAt(funcName, paramIndex, PhpCompletionTokens.mbStringInfoFuncs, 0)) {
247 | resultElements = PhpCompletionTokens.mbStringInfoTypes;
248 | }
249 |
250 | if (methodMatchesAt(funcName, paramIndex, PhpCompletionTokens.mbStringLanguageFuncs, 0)) {
251 | resultElements = PhpCompletionTokens.mbStringLanguageElements;
252 | }
253 |
254 | if (methodMatchesAt(funcName, paramIndex, PhpCompletionTokens.obHandlerFuncs, 0)) {
255 | resultElements = PhpCompletionTokens.obHandlerElements;
256 | }
257 |
258 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.socketFuncs)) {
259 | resultElements = PhpCompletionTokens.socketTransports;
260 | resultParams = new String[resultElements.length];
261 | Arrays.fill(resultParams, funcName.equals("stream_socket_client") ? ":" : "");
262 | }
263 |
264 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.envFuncs)) {
265 | resultElements = PhpCompletionTokens.envNames;
266 | }
267 |
268 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.packFuncs) && !stringPrefix.contains("*")) {
269 | resultElements = PhpCompletionTokens.packCodes;
270 | resultInfos = PhpCompletionTokens.packCodesInfos;
271 | allowMultiple = true;
272 | allowRepeat = true;
273 | splitter = "";
274 | }
275 |
276 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.unpackFuncs) && !stringPrefix.contains("*")) {
277 | resultElements = PhpCompletionTokens.packCodes;
278 | resultInfos = PhpCompletionTokens.packCodesInfos;
279 | allowMultiple = true;
280 | allowRepeat = true;
281 | splitter = "/";
282 | splitterSpace = "";
283 | }
284 |
285 | if (methodMatches(funcName, paramIndex, PhpCompletionTokens.formatFuncs) || methodMatches(funcName, paramIndex, PhpCompletionTokens.scanFormatFuncs)) {
286 |
287 | boolean isScanFormat = methodMatches(funcName, paramIndex, PhpCompletionTokens.scanFormatFuncs);
288 | FormatSpecification formatSpec = getFormatSpecification(stringPrefix);
289 | boolean insideFormat = formatSpec != null;
290 |
291 | if (insideFormat || parameters.getInvocationCount() > 0) {
292 | if (!insideFormat) {
293 | resultPrefix = "%";
294 | }
295 | Collection specifiers = new ArrayList<>();
296 | Collection specifiersInfos = new ArrayList<>();
297 | String[] formatTokens = isScanFormat ? PhpCompletionTokens.scanFormatTokens : PhpCompletionTokens.formatTokens;
298 | String[] formatInfos = isScanFormat ? PhpCompletionTokens.scanFormatInfos : PhpCompletionTokens.formatInfos;
299 |
300 | for (int i = 0; i < formatTokens.length; i++) {
301 | if (formatSpec != null) {
302 | if (formatTokens[i].equals("%") && !"%".equals(formatSpec.getText())) {
303 | continue;
304 | }
305 |
306 | if (formatSpec.hasPrecision() && formatInfos[i].equals("integer")) {
307 | continue;
308 | }
309 | }
310 |
311 | specifiers.add(formatTokens[i]);
312 | specifiersInfos.add(formatInfos[i]);
313 | }
314 |
315 | if (formatSpec != null && formatSpec.isInsideFlags()) {
316 | for (String flag : PhpCompletionTokens.formatFlags) {
317 | if (formatSpec.getText().contains(flag)) {
318 | continue;
319 | }
320 |
321 | specifiers.add(flag);
322 | specifiersInfos.add("");
323 | }
324 | }
325 |
326 | resultElements = specifiers.toArray(new String[0]);
327 | resultInfos = specifiersInfos.toArray(new String[0]);
328 | allowMultiple = true;
329 | allowRepeat = true;
330 | splitter = "";
331 | }
332 | }
333 |
334 | if (methodMatchesAt(funcName, paramIndex, PhpCompletionTokens.httpHeaderResponseFuncs, 0)) {
335 | boolean isFullHeader = funcName.equals("header");
336 | if (!stringPrefix.contains(":") && !stringPrefix.startsWith("HTTP/1.0") && !stringPrefix.startsWith("HTTP/1.1")) {
337 | resultElements = PhpCompletionTokens.httpHeaderResponseFields;
338 | resultPostfixAlt = " ";
339 | resultPostfixExceptions = new String[]{"HTTP/1.0", "HTTP/1.1"};
340 | deprecatedElements = PhpCompletionTokens.httpHeaderDeprecatedFields;
341 | overwriteExistingCompletions = true;
342 | if (isFullHeader) {
343 | resultBold = true;
344 | resultPostfix = ": ";
345 | resultParams = new String[resultElements.length];
346 |
347 | for (int i = 0; i < resultElements.length; i++) {
348 | String header = resultElements[i];
349 | resultParams[i] = "";
350 | if (PhpCompletionTokens.httpHeaderResponseFieldsSyntax.containsKey(header)) {
351 | resultParams[i] = PhpCompletionTokens.httpHeaderResponseFieldsSyntax.get(header);
352 | }
353 | }
354 | }
355 | }
356 | else if (isFullHeader) {
357 | result = result.withPrefixMatcher(stringPrefix.substring(stringPrefix.contains(": ") ? stringPrefix.indexOf(": ") + 2 : stringPrefix.indexOf(":") + 1));
358 |
359 | if (stringPrefix.startsWith("Allow: ") || stringPrefix.startsWith("Access-Control-Allow-Methods: ")) {
360 | allowMultiple = true;
361 | resultElements = PhpCompletionTokens.httpMethods;
362 | }
363 | else if (stringPrefix.startsWith("Accept-CH: ")) {
364 | allowMultiple = true;
365 | resultElements = PhpCompletionTokens.httpClientHintDirectives;
366 | }
367 | else if (stringPrefix.startsWith("Accept-Ranges: ")) {
368 | resultElements = PhpCompletionTokens.httpRangeTypes;
369 | }
370 | else if (stringPrefix.startsWith("Access-Control-Allow-Credentials: ")) {
371 | resultElements = PhpCompletionTokens.httpACAllowCredentials;
372 | }
373 | else if (stringPrefix.startsWith("Access-Control-Allow-Headers: ")) {
374 | allowMultiple = true;
375 | List alwaysAllowed = Arrays.asList(PhpCompletionTokens.httpACAllowHeadersAlways);
376 | List filteredHeaderFields = Arrays.stream(PhpCompletionTokens.httpHeaderRequestFields).filter(((Predicate)alwaysAllowed::contains).negate()).collect(Collectors.toList());
377 | resultElements = new String[filteredHeaderFields.size()];
378 | resultElements = filteredHeaderFields.toArray(resultElements);
379 | }
380 | else if (stringPrefix.startsWith("Access-Control-Allow-Origin: ")) {
381 | resultElements = PhpCompletionTokens.httpACAllowOrigin;
382 | }
383 | else if (stringPrefix.startsWith("Access-Control-Expose-Headers: ")) {
384 | allowMultiple = true;
385 | resultElements = PhpCompletionTokens.httpHeaderResponseFields;
386 | }
387 | else if (stringPrefix.startsWith("Cache-Control: ")) {
388 | allowMultiple = true;
389 | resultElements = PhpCompletionTokens.httpCacheControlDirectives;
390 | }
391 | else if (stringPrefix.startsWith("Clear-Site-Data: ")) {
392 | allowMultiple = true;
393 | resultElements = PhpCompletionTokens.httpClearSiteDataDirectives;
394 | }
395 | else if (stringPrefix.startsWith("Connection: ")) {
396 | resultElements = PhpCompletionTokens.httpConnectionOptions;
397 | }
398 | else if (stringPrefix.startsWith("Content-Disposition: ")) {
399 | resultElements = PhpCompletionTokens.httpContentDispositionTokens;
400 | }
401 | else if (stringPrefix.startsWith("Content-Encoding: ")) {
402 | allowMultiple = true;
403 | resultElements = PhpCompletionTokens.httpEncodingTokens;
404 | resultCaseSensitivity = false;
405 | }
406 | else if (stringPrefix.startsWith("Content-Language: ")) {
407 | allowMultiple = true;
408 | resultElements = PhpCompletionTokens.isoLanguageCodes;
409 | }
410 | else if (stringPrefix.startsWith("Content-Location: /") || stringPrefix.startsWith("Location: /")) {
411 | overwriteExistingCompletions = true;
412 | resultElements = prefixArray("/", FileUtil.getProjectFiles(project));
413 | resultElements = concatArrays(new String[]{"/"}, resultElements);
414 | }
415 | else if ((stringPrefix.equals("Content-Location: ") || stringPrefix.equals("Location: ")
416 | || stringPrefix.startsWith("Content-Location: h") || stringPrefix.startsWith("Location: h"))
417 | && !stringPrefix.contains("://")) {
418 | resultElements = PhpCompletionTokens.httpLocationBaseUrls;
419 | insertHandler = InvokeCompletionInsertHandler.getInstance();
420 | resultTailText = "...";
421 | }
422 | else if (stringPrefix.startsWith("Content-Range: ")) {
423 | resultElements = new String[]{PhpCompletionTokens.httpRangeTypes[0]};
424 | resultPostfix = " ";
425 | }
426 | else if (stringPrefix.startsWith("Content-Security-Policy: ") || stringPrefix.startsWith("X-Content-Security-Policy: ") || stringPrefix.startsWith("X-WebKit-CSP: ") || stringPrefix.startsWith("Content-Security-Policy-Report-Only: ")) {
427 | allowMultiple = true;
428 | resultElements = PhpCompletionTokens.httpCSP;
429 | splitter = ";";
430 | }
431 | else if (stringPrefix.startsWith("Content-Type: ")) {
432 | if (!stringPrefix.contains(";")) {
433 | resultElements = PhpCompletionTokens.mimeTypes;
434 | }
435 | else if (!stringPrefix.contains("charset=")) {
436 | result = result.withPrefixMatcher(stringPrefix.substring(stringPrefix.contains("; ") ? stringPrefix.lastIndexOf("; ") + 2 : stringPrefix.lastIndexOf(";") + 1));
437 | resultElements = prefixArray("charset=", PhpCompletionTokens.httpCharSets);
438 | }
439 | else /*if(stringPrefix.contains("charset="))*/ {
440 | result = result.withPrefixMatcher(stringPrefix.substring(stringPrefix.lastIndexOf("charset=") + 8));
441 | resultElements = PhpCompletionTokens.httpCharSets;
442 | }
443 | }
444 | else if (stringPrefix.startsWith("Cross-Origin-Resource-Policy: ")) {
445 | resultElements = PhpCompletionTokens.httpCORS;
446 | }
447 | else if (stringPrefix.startsWith("Expect-CT: ")) {
448 | allowMultiple = true;
449 | resultElements = PhpCompletionTokens.httpExpectCT;
450 | }
451 | else if (stringPrefix.startsWith("Feature-Policy: ")) {
452 | allowMultiple = true;
453 | resultElements = PhpCompletionTokens.httpFeaturePolicyDirectives;
454 | splitter = ";";
455 | }
456 | else if (stringPrefix.startsWith("Keep-Alive: ")) {
457 | allowMultiple = true;
458 | resultElements = PhpCompletionTokens.httpKeepAliveDirectives;
459 | }
460 | else if (stringPrefix.startsWith("Pragma: ")) {
461 | resultElements = PhpCompletionTokens.httpPragmaDirectives;
462 | }
463 | else if (stringPrefix.startsWith("Proxy-Authenticate: ") || stringPrefix.startsWith("WWW-Authenticate: ")) {
464 | resultElements = PhpCompletionTokens.httpAuthenticationTypes;
465 | }
466 | else if (stringPrefix.startsWith("Public-Key-Pins: ") || stringPrefix.startsWith("Public-Key-Pins-Report-Only: ")) {
467 | allowMultiple = true;
468 | resultElements = PhpCompletionTokens.httpPublicKeyPinsDirectives;
469 | splitter = ";";
470 | }
471 | else if (stringPrefix.startsWith("Referrer-Policy: ")) {
472 | resultElements = PhpCompletionTokens.httpReferrerPolicyDirectives;
473 | }
474 | else if (stringPrefix.startsWith("Set-Cookie: ") && stringPrefix.contains(";")) {
475 | allowMultiple = true;
476 | resultElements = PhpCompletionTokens.httpSetCookieDirectives;
477 | splitter = ";";
478 | }
479 | else if (stringPrefix.startsWith("Strict-Transport-Security: ")) {
480 | allowMultiple = true;
481 | resultElements = PhpCompletionTokens.httpStrictTransportSecurityDirectives;
482 | splitter = ";";
483 | }
484 | else if (stringPrefix.startsWith("Status: ") || stringPrefix.startsWith("HTTP/1.0 ") || stringPrefix.startsWith("HTTP/1.1 ")) {
485 | resultElements = PhpCompletionTokens.httpStatusCodes;
486 |
487 | if (stringPrefix.startsWith("HTTP/1.1 ")) {
488 | resultElements = concatArrays(resultElements, PhpCompletionTokens.httpStatusCodes11);
489 | }
490 | }
491 | else if (stringPrefix.startsWith("Trailer: ")) {
492 | allowMultiple = true;
493 | List notAllowed = Arrays.asList(PhpCompletionTokens.httpHeaderResponseFieldsNotInTrailer);
494 | List filteredHeaderFields = Arrays.stream(PhpCompletionTokens.httpHeaderResponseFields).filter(((Predicate)notAllowed::contains).negate()).collect(Collectors.toList());
495 | resultElements = new String[filteredHeaderFields.size()];
496 | resultElements = filteredHeaderFields.toArray(resultElements);
497 | }
498 | else if (stringPrefix.startsWith("Transfer-Encoding: ")) {
499 | allowMultiple = true;
500 | resultElements = PhpCompletionTokens.httpTransferEncodingValues;
501 | }
502 | else if (stringPrefix.startsWith("Vary: ")) {
503 | allowMultiple = true;
504 | resultElements = concatArrays(new String[]{"*"}, PhpCompletionTokens.httpHeaderRequestFields);
505 | }
506 | else if (stringPrefix.startsWith("Via: ")) {
507 | allowMultiple = true;
508 | resultElements = PhpCompletionTokens.httpViaProtocols;
509 | resultPostfix = " ";
510 | }
511 | else if (stringPrefix.startsWith("Warning: ")) {
512 | resultElements = PhpCompletionTokens.httpWarningCodes;
513 | resultInfos = PhpCompletionTokens.httpWarningTexts;
514 | resultPostfix = " ";
515 | }
516 | else if (stringPrefix.startsWith("X-Content-Type-Options: ")) {
517 | resultElements = PhpCompletionTokens.httpXContentTypeOptions;
518 | }
519 | else if (stringPrefix.startsWith("X-DNS-Prefetch-Control: ")) {
520 | resultElements = PhpCompletionTokens.httpXDnsPrefetchControlDirectives;
521 | }
522 | else if (stringPrefix.startsWith("X-Frame-Options: ")) {
523 | resultElements = PhpCompletionTokens.httpXFrameOptions;
524 | }
525 | else if (stringPrefix.startsWith("X-UA-Compatible: ")) {
526 | allowMultiple = true;
527 | resultElements = PhpCompletionTokens.httpXUACompatibleValues;
528 | splitter = ";";
529 | }
530 | else if (stringPrefix.startsWith("X-Robots-Tag: ")) {
531 | allowMultiple = true;
532 | resultElements = PhpCompletionTokens.httpXRobotsTagDirectives;
533 | }
534 | else if (stringPrefix.startsWith("X-XSS-Protection: ")) {
535 | resultElements = PhpCompletionTokens.httpXXssProtectionValues;
536 | }
537 | }
538 | }
539 |
540 | if (funcName.equals("argumentsSet")) {
541 | resultElements = collectMetaArgumentsSets(parameters.getPosition());
542 | }
543 |
544 | if (resultElements == null) {
545 | return;
546 | }
547 |
548 | // ", "
549 | String split = splitter + splitterSpace;
550 | if (allowMultiple && !splitter.isEmpty() && stringPrefix.contains(split)) {
551 | result = result.withPrefixMatcher(stringPrefix.substring(stringPrefix.lastIndexOf(split) + split.length()));
552 | }
553 | else if (allowMultiple && splitter.isEmpty()) {
554 | result = result.withPrefixMatcher("");
555 | }
556 |
557 | if (overwriteExistingCompletions) {
558 | result.stopHere();
559 | }
560 |
561 | for (int i = 0; i < resultElements.length; i++) {
562 |
563 | if (allowMultiple && !allowRepeat && stringPrefix.contains(resultElements[i] + splitter)) {
564 | continue;
565 | }
566 | String postfix = Arrays.asList(resultPostfixExceptions).contains(resultElements[i]) ? resultPostfixAlt : resultPostfix;
567 | LookupElementBuilder builder = LookupElementBuilder.create(resultPrefix + resultElements[i] + postfix)
568 | .withCaseSensitivity(resultCaseSensitivity)
569 | .withPresentableText(resultPrefix + resultElements[i])
570 | // .withTailText(resultPostfix, true)
571 | .withBoldness(resultBold)
572 | .withLookupString(resultPrefix + resultElements[i].toLowerCase() + postfix);
573 |
574 | if (Arrays.asList(deprecatedElements).contains(resultElements[i])) {
575 | builder = builder.withStrikeoutness(true);
576 | }
577 |
578 | if (i < resultParams.length) {
579 | builder = builder.withTailText(resultParams[i], true);
580 | }
581 | else if (resultTailText != null) {
582 | builder = builder.withTailText(resultTailText, true);
583 | }
584 | if (i < resultInfos.length) {
585 | builder = builder.withTypeText(resultInfos[i]);
586 | }
587 |
588 | InsertHandler handler = insertHandler;
589 |
590 | if (handler == null && (resultElements[i] + postfix).endsWith(" ")) {
591 | handler = InvokeCompletionInsertHandler.getInstance();
592 | }
593 |
594 | if (handler != null) {
595 | builder = builder.withInsertHandler(handler);
596 | }
597 |
598 | //LookupElement element = builder.withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE);
599 | //result.addElement(element);
600 | result.addElement(builder);
601 | }
602 | }
603 |
604 | private String[] concatArrays(String[] A, String[] B) {
605 | int aLen = A.length;
606 | int bLen = B.length;
607 | String[] C = new String[aLen + bLen];
608 | System.arraycopy(A, 0, C, 0, aLen);
609 | System.arraycopy(B, 0, C, aLen, bLen);
610 | return C;
611 | }
612 |
613 | private String[] prefixArray(String prefix, String[] array) {
614 | String[] B = new String[array.length];
615 | for (int i = 0; i < B.length; i++) {
616 | B[i] = prefix + array[i];
617 | }
618 | return B;
619 | }
620 |
621 | private boolean methodMatches(String methodName, int paramIndex, String[] tokens) {
622 | return Arrays.asList(tokens).contains(methodName + ":" + paramIndex);
623 | }
624 |
625 | private boolean methodMatchesAt(String methodName, int paramIndex, String[] tokens, int expectedParamIndex) {
626 | return Arrays.asList(tokens).contains(methodName) && paramIndex == expectedParamIndex;
627 | }
628 |
629 | private boolean patternMatches(Pattern pattern, @NotNull String string) {
630 | return pattern.matcher(string).lookingAt();
631 | }
632 | }
633 | );
634 | }
635 |
636 | private FormatSpecification getFormatSpecification(String stringPrefix) {
637 | if (stringPrefix.isEmpty()) {
638 | return null;
639 | }
640 | FormatSpecification spec = new FormatSpecification();
641 | char lastChar = stringPrefix.charAt(stringPrefix.length() - 1);
642 |
643 | if (lastChar == '%' && StringUtil.getPrecedingCharNum(stringPrefix, stringPrefix.length() - 1, '%') % 2 == 0) {
644 | spec.setText("%");
645 | spec.setInsideFlags(true);
646 | return spec;
647 | }
648 |
649 | if (lastChar == '$' || Arrays.asList(PhpCompletionTokens.formatFlags).contains(Character.toString(lastChar))) {
650 | spec.setInsideFlags(true);
651 | }
652 |
653 | // Syntax: %[argnum$][flags][width][.precision]specifier
654 |
655 | char[] chars = stringPrefix.toCharArray();
656 |
657 | for (int i = chars.length - 1; i >= 0; i--) {
658 | char c = chars[i];
659 |
660 | // width or precision
661 | if (Character.isDigit(c)) {
662 | continue;
663 | }
664 | // .precision
665 | if (c == '.' && i < chars.length - 1 && Character.isDigit(chars[i + 1])) {
666 | spec.setPrecision();
667 | continue;
668 | }
669 | // '-' flag requires width
670 | if (c == '-' && i < chars.length - 1 && Character.isDigit(chars[i + 1])) {
671 | continue;
672 | }
673 | // '(char) flag requires at least one char after it
674 | if (c == '\'' && i < chars.length - 1) {
675 | continue;
676 | }
677 | // other flags
678 | if (c == '+' || c == ' ' || c == '0') {
679 | continue;
680 | }
681 | // $ for argnum
682 | if (c == '$' && i > 0 && Character.isDigit(chars[i - 1])) {
683 | continue;
684 | }
685 |
686 | if (c == '%') {
687 | if (StringUtil.getPrecedingCharNum(stringPrefix, i, '%') % 2 == 0) {
688 | spec.setText(stringPrefix.substring(i));
689 | return spec;
690 | }
691 | }
692 |
693 | break;
694 | }
695 |
696 | return null;
697 | }
698 |
699 | private static class FormatSpecification {
700 | private String text;
701 | private boolean precision;
702 | private boolean insideFlags;
703 |
704 | FormatSpecification() {
705 | }
706 | FormatSpecification(String text) {
707 | this.text = text;
708 | }
709 |
710 | public String getText() {
711 | return text;
712 | }
713 |
714 | void setText(String text) {
715 | this.text = text;
716 | }
717 |
718 | public boolean hasPrecision() {
719 | return precision;
720 | }
721 |
722 | void setPrecision() {
723 | this.precision = true;
724 | }
725 |
726 | public boolean isInsideFlags() {
727 | return insideFlags;
728 | }
729 |
730 | public void setInsideFlags(boolean insideFlags) {
731 | this.insideFlags = insideFlags;
732 | }
733 | }
734 |
735 | private String[] collectMetaArgumentsSets(PsiElement position) {
736 | Collection argumentsSets = new ArrayList<>();
737 |
738 | PhpNamespace root = PsiTreeUtil.getParentOfType(position, PhpNamespace.class);
739 | if (root == null || !"PHPSTORM_META".equals(root.getName())) {
740 | return new String[0];
741 | }
742 |
743 | Collection arguments = PsiTreeUtil.findChildrenOfType(root, ParameterList.class);
744 | for (ParameterList args : arguments) {
745 | PsiElement parent = args.getParent();
746 | if (!(parent instanceof FunctionReference) || !"registerArgumentsSet".equals(((FunctionReference)parent).getName())) {
747 | continue;
748 | }
749 |
750 | StringLiteralExpression arg0 = PsiTreeUtil.findChildOfType(args, StringLiteralExpression.class);
751 | if (arg0 == null) {
752 | continue;
753 | }
754 |
755 | argumentsSets.add(arg0.getContents());
756 | }
757 |
758 | return argumentsSets.toArray(new String[0]);
759 | }
760 |
761 | @Override
762 | public boolean invokeAutoPopup(@NotNull PsiElement position, char typeChar) {
763 | if (typeChar == ' ' || typeChar == '-') {
764 | return true;
765 | }
766 |
767 | return super.invokeAutoPopup(position, typeChar);
768 | }
769 |
770 | static class InvokeCompletionInsertHandler implements InsertHandler {
771 |
772 | private static final InvokeCompletionInsertHandler instance = new InvokeCompletionInsertHandler();
773 |
774 | @Override
775 | public void handleInsert(InsertionContext context, LookupElement lookupElement) {
776 | PhpCompletionUtil.showCompletion(context);
777 | }
778 |
779 | public static InvokeCompletionInsertHandler getInstance() {
780 | return instance;
781 | }
782 | }
783 | }
784 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/PhpHeaderDocumentationProvider.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.google.gson.JsonArray;
4 | import com.google.gson.JsonElement;
5 | import com.google.gson.JsonObject;
6 | import com.google.gson.JsonParser;
7 | import com.intellij.lang.Language;
8 | import com.intellij.lang.documentation.DocumentationProviderEx;
9 | import com.intellij.openapi.editor.Editor;
10 | import com.intellij.openapi.util.io.FileUtil;
11 | import com.intellij.openapi.util.text.StringUtil;
12 | import com.intellij.psi.PsiElement;
13 | import com.intellij.psi.PsiFile;
14 | import com.intellij.psi.PsiManager;
15 | import com.intellij.psi.impl.light.LightElement;
16 | import com.intellij.util.containers.ContainerUtil;
17 | import com.jetbrains.php.lang.psi.PhpPsiUtil;
18 | import com.jetbrains.php.lang.psi.elements.FunctionReference;
19 | import com.jetbrains.php.lang.psi.elements.Statement;
20 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
21 | import org.apache.commons.lang.StringEscapeUtils;
22 | import org.apache.commons.lang.StringUtils;
23 | import org.jetbrains.annotations.NotNull;
24 | import org.jetbrains.annotations.Nullable;
25 |
26 | import java.io.IOException;
27 | import java.io.InputStream;
28 | import java.util.Arrays;
29 | import java.util.Collections;
30 | import java.util.List;
31 | import java.util.Map;
32 |
33 | /**
34 | * @author Thomas Schulz
35 | */
36 | public class PhpHeaderDocumentationProvider extends DocumentationProviderEx {
37 |
38 | private static final String MDN_URL_PREFIX = "https://developer.mozilla.org/docs/Web/HTTP/Headers/";
39 | private static final String HEADERS_DOC_JSON = "/com/intellij/ws/rest/client/headers/headers-doc.json";
40 |
41 | private static Map httpHeaderDescriptions = null;
42 |
43 | @Override
44 | public @Nullable List getUrlFor(PsiElement element, PsiElement originalElement) {
45 | if (!(element instanceof HeaderDocElement)) {
46 | return null;
47 | }
48 | String headerName = ((HeaderDocElement)element).getHeaderName();
49 | return Collections.singletonList(MDN_URL_PREFIX + headerName);
50 | }
51 |
52 | @Override
53 | public @Nullable String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
54 | if (!(element instanceof HeaderDocElement)) {
55 | return null;
56 | }
57 |
58 | String headerName = ((HeaderDocElement)element).getHeaderName();
59 | StringBuilder buffer = new StringBuilder();
60 |
61 | buffer.append("").append(headerName).append("");
62 | String syntax = PhpCompletionTokens.httpHeaderResponseFieldsSyntax.getOrDefault(headerName, ": ");
63 | buffer.append(StringEscapeUtils.escapeHtml(syntax));
64 | buffer.append("
");
65 |
66 | buffer.append("");
67 | addHeaderDescription(buffer, headerName);
68 | buffer.append("
");
69 |
70 | return buffer.toString();
71 | }
72 |
73 | private void addHeaderDescription(StringBuilder buffer, String headerName) {
74 | String description = getHeaders().get(headerName);
75 | if (description == null) {
76 | return;
77 | }
78 | buffer.append(description);
79 | }
80 |
81 | @Override
82 | public @Nullable PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement psiElement) {
83 | if (!(object instanceof String)) {
84 | return null;
85 | }
86 |
87 | if (!isCallToHeaderFunc(psiElement)) {
88 | return null;
89 | }
90 |
91 | String headerName = StringUtils.strip((String)object, ": ");
92 | if (!isHeaderName(headerName)) {
93 | return null;
94 | }
95 | return new HeaderDocElement(psiManager, psiElement.getLanguage(), headerName);
96 | }
97 |
98 | private boolean isCallToHeaderFunc(PsiElement psiElement) {
99 | FunctionReference function = PhpPsiUtil.getParentByCondition(psiElement, true, FunctionReference.INSTANCEOF, Statement.INSTANCEOF);
100 | if (function == null) {
101 | return false;
102 | }
103 | return "header".equals(function.getName());
104 | }
105 |
106 | @Override
107 | public @Nullable PsiElement getCustomDocumentationElement(@NotNull Editor editor, @NotNull PsiFile file, @Nullable PsiElement contextElement) {
108 | if (!isCallToHeaderFunc(contextElement)) {
109 | return null;
110 | }
111 |
112 | if (!(contextElement instanceof StringLiteralExpression)) {
113 | contextElement = PhpPsiUtil.getParentByCondition(contextElement, true, StringLiteralExpression.INSTANCEOF);
114 | }
115 |
116 | if (contextElement instanceof StringLiteralExpression) {
117 | String contents = ((StringLiteralExpression)contextElement).getContents();
118 | if (!contents.contains(":")) {
119 | return null;
120 | }
121 | String headerName = StringUtils.substringBefore(contents, ":");
122 | if (headerName.isEmpty() || !isHeaderName(headerName)) {
123 | return null;
124 | }
125 | return new HeaderDocElement(contextElement.getManager(), contextElement.getLanguage(), headerName);
126 | }
127 | return null;
128 | }
129 |
130 | private static boolean isHeaderName(String headerName) {
131 | return Arrays.asList(PhpCompletionTokens.httpHeaderResponseFields).contains(headerName)
132 | || Arrays.asList(PhpCompletionTokens.httpHeaderRequestFields).contains(headerName);
133 | }
134 |
135 | @NotNull
136 | private static synchronized Map getHeaders() {
137 | if (httpHeaderDescriptions == null) {
138 | httpHeaderDescriptions = readHeaderDescriptions();
139 | }
140 |
141 | return httpHeaderDescriptions;
142 | }
143 |
144 | @NotNull
145 | private static Map readHeaderDescriptions() {
146 | Map result = ContainerUtil.newHashMap();
147 |
148 | // Re-use docs from JB HTTP Client plugin
149 | InputStream stream = PhpHeaderDocumentationProvider.class.getResourceAsStream(HEADERS_DOC_JSON);
150 |
151 | try {
152 | String file = stream != null ? FileUtil.loadTextAndClose(stream) : "";
153 | if (StringUtil.isNotEmpty(file)) {
154 | JsonElement root = (new JsonParser()).parse(file);
155 | if (root.isJsonArray()) {
156 | JsonArray array = root.getAsJsonArray();
157 |
158 | for (JsonElement element : array) {
159 | if (element.isJsonObject()) {
160 | JsonObject obj = element.getAsJsonObject();
161 | String name = getAsString(obj, "name");
162 | if (!StringUtil.isNotEmpty(name)) {
163 | continue;
164 | }
165 | String description = getAsString(obj, "descr");
166 | if (!StringUtil.isNotEmpty(description)) {
167 | continue;
168 | }
169 | result.put(name, description);
170 | }
171 | }
172 | }
173 | }
174 | } catch (IOException ignored) {
175 | }
176 |
177 | return result;
178 | }
179 |
180 | @NotNull
181 | private static String getAsString(@NotNull JsonObject obj, @NotNull String name) {
182 | JsonElement element = obj.get(name);
183 | return element != null && element.isJsonPrimitive() ? element.getAsString() : "";
184 | }
185 |
186 | private class HeaderDocElement extends LightElement {
187 |
188 | private final String headerName;
189 |
190 | protected HeaderDocElement(@NotNull final PsiManager manager, @NotNull final Language language, @NotNull final String headerName) {
191 | super(manager, language);
192 | this.headerName = headerName;
193 | }
194 |
195 | public String getHeaderName() {
196 | return headerName;
197 | }
198 |
199 | @Override
200 | public String getText() {
201 | return headerName;
202 | }
203 |
204 | @Override
205 | public String toString() {
206 | return "HeaderDocElement for " + headerName;
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/PhpInjectedFileReferenceContributor.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.intellij.openapi.util.Condition;
4 | import com.intellij.patterns.PlatformPatterns;
5 | import com.intellij.psi.*;
6 | import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet;
7 | import com.intellij.util.ProcessingContext;
8 | import com.jetbrains.php.lang.PhpLanguage;
9 | import com.jetbrains.php.lang.psi.elements.Function;
10 | import com.jetbrains.php.lang.psi.elements.FunctionReference;
11 | import com.jetbrains.php.lang.psi.elements.ParameterList;
12 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
13 | import com.jetbrains.php.lang.psi.elements.impl.NewExpressionImpl;
14 | import net.king2500.plugins.PhpAdvancedAutoComplete.index.PhpInjectDirectoryReference;
15 | import net.king2500.plugins.PhpAdvancedAutoComplete.index.PhpInjectFileReference;
16 | import net.king2500.plugins.PhpAdvancedAutoComplete.index.PhpInjectFileReferenceIndex;
17 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.PhpElementsUtil;
18 | import org.jetbrains.annotations.NotNull;
19 |
20 | /**
21 | * @author Thomas Schulz
22 | */
23 | public class PhpInjectedFileReferenceContributor extends PsiReferenceContributor {
24 | @Override
25 | public void registerReferenceProviders(PsiReferenceRegistrar psiReferenceRegistrar) {
26 | psiReferenceRegistrar.registerReferenceProvider(
27 | PlatformPatterns
28 | .psiElement(StringLiteralExpression.class)
29 | .withParent(ParameterList.class)
30 | .withLanguage(PhpLanguage.INSTANCE),
31 | new PsiReferenceProvider() {
32 | @NotNull
33 | @Override
34 | public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
35 | if (!(psiElement.getContext() instanceof ParameterList)) {
36 | return PsiReference.EMPTY_ARRAY;
37 | }
38 |
39 | ParameterList parameterList = (ParameterList)psiElement.getContext();
40 | if (!(parameterList.getContext() instanceof FunctionReference || parameterList.getContext() instanceof NewExpressionImpl)) {
41 | return PsiReference.EMPTY_ARRAY;
42 | }
43 |
44 | int argumentIndex = PhpElementsUtil.getParameterIndex(psiElement);
45 | if (argumentIndex < 0) {
46 | return PsiReference.EMPTY_ARRAY;
47 | }
48 |
49 | Function function = PhpElementsUtil.getFunction(psiElement);
50 | if (function == null) {
51 | return PsiReference.EMPTY_ARRAY;
52 | }
53 |
54 | PhpInjectFileReference injectFileReference = PhpInjectFileReferenceIndex.getInjectFileReference(psiElement.getProject(), function, argumentIndex);
55 | if (injectFileReference == null) {
56 | return PsiReference.EMPTY_ARRAY;
57 | }
58 |
59 | if (injectFileReference.getArgumentIndex() == argumentIndex) {
60 | boolean isAbsolute = injectFileReference.getRelativeMode() == PhpInjectFileReference.RelativeMode.TOP_LEVEL;
61 | boolean isDir = injectFileReference instanceof PhpInjectDirectoryReference;
62 |
63 | FileReferenceSet referenceSet = new FileReferenceSet(psiElement) {
64 | @Override
65 | public boolean isEndingSlashNotAllowed() {
66 | return false;
67 | }
68 |
69 | @Override
70 | public boolean isAbsolutePathReference() {
71 | return isAbsolute || super.isAbsolutePathReference();
72 | }
73 |
74 | @Override
75 | public boolean absoluteUrlNeedsStartSlash() {
76 | return !isAbsolute;
77 | }
78 |
79 | @Override
80 | protected Condition getReferenceCompletionFilter() {
81 | return isDir ? FileReferenceSet.DIRECTORY_FILTER : super.getReferenceCompletionFilter();
82 | }
83 | };
84 |
85 | if (isAbsolute) {
86 | referenceSet.addCustomization(FileReferenceSet.DEFAULT_PATH_EVALUATOR_OPTION, FileReferenceSet.ABSOLUTE_TOP_LEVEL);
87 | }
88 | return referenceSet.getAllReferences();
89 | }
90 |
91 | /* String funcName = PhpElementsUtil.getCanonicalFuncName(psiElement.getParent().getParent());
92 | int paramIndex = PhpElementsUtil.getParameterIndex(psiElement);
93 |
94 | if (funcName == null) {
95 | return new PsiReference[0];
96 | }
97 |
98 | if (Arrays.asList(PhpCompletionTokens.fileFuncs).contains(funcName + ":" + paramIndex + ":d")) {
99 | return (new FileReferenceSet(psiElement) {
100 | @Override
101 | protected Condition getReferenceCompletionFilter() {
102 | return FileReferenceSet.DIRECTORY_FILTER;
103 | }
104 | }).getAllReferences();
105 | }
106 | else if (Arrays.asList(PhpCompletionTokens.fileFuncs).contains(funcName + ":" + paramIndex)) {
107 | return (new FileReferenceSet(psiElement)).getAllReferences();
108 | }
109 |
110 | String text = ((StringLiteralExpression) psiElement).getContents();
111 | String prefix1 = "Location: /";
112 | String prefix2 = "Content-Location: /";
113 |
114 | if (funcName.equals("header")) {
115 | if (text.startsWith(prefix1)) {
116 | return getFileReferenceAfterPrefix(psiElement, prefix1);
117 | }
118 | if (text.startsWith(prefix2)) {
119 | return getFileReferenceAfterPrefix(psiElement, prefix2);
120 | }
121 | }*/
122 |
123 | return PsiReference.EMPTY_ARRAY;
124 | }
125 | }
126 | );
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/PhpParameterStringCompletionConfidence.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete;
2 |
3 | import com.intellij.codeInsight.completion.CompletionConfidence;
4 | import com.intellij.psi.PsiElement;
5 | import com.intellij.psi.PsiFile;
6 | import com.intellij.util.ThreeState;
7 | import com.jetbrains.php.lang.psi.PhpFile;
8 | import com.jetbrains.php.lang.psi.elements.ParameterList;
9 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | /**
13 | * @author Thomas Schulz
14 | */
15 | public class PhpParameterStringCompletionConfidence extends CompletionConfidence {
16 |
17 | @NotNull
18 | @Override
19 | public ThreeState shouldSkipAutopopup(@NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) {
20 |
21 | if (!(psiFile instanceof PhpFile)) {
22 | return ThreeState.UNSURE;
23 | }
24 |
25 | PsiElement context = contextElement.getContext();
26 | if (!(context instanceof StringLiteralExpression)) {
27 | return ThreeState.UNSURE;
28 | }
29 |
30 | // // $test == "";
31 | // if(context.getParent() instanceof BinaryExpression) {
32 | // return ThreeState.NO;
33 | // }
34 |
35 | // $object->method("");
36 | PsiElement stringContext = context.getContext();
37 | if (stringContext instanceof ParameterList) {
38 | return ThreeState.NO;
39 | }
40 |
41 | // // $object->method(... array('foo'); array('bar' => 'foo') ...);
42 | // ArrayCreationExpression arrayCreationExpression = PhpElementsUtil.getCompletableArrayCreationElement(context);
43 | // if(arrayCreationExpression != null && arrayCreationExpression.getContext() instanceof ParameterList) {
44 | // return ThreeState.NO;
45 | // }
46 |
47 | // // $array['value']
48 | // if(PlatformPatterns.psiElement().withSuperParent(2, ArrayIndex.class).accepts(contextElement)) {
49 | // return ThreeState.NO;
50 | // }
51 |
52 | return ThreeState.UNSURE;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/index/PhpInjectDirectoryReference.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.index;
2 |
3 | /**
4 | * @author Thomas Schulz
5 | */
6 | public class PhpInjectDirectoryReference extends PhpInjectFileReference {
7 | PhpInjectDirectoryReference(int argumentIndex, RelativeMode relativeMode, String prefix) {
8 | super(argumentIndex, relativeMode, prefix);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/index/PhpInjectFileReference.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.index;
2 |
3 | /**
4 | * @author Thomas Schulz
5 | */
6 | public class PhpInjectFileReference {
7 | private final int argumentIndex;
8 | private final RelativeMode relativeMode;
9 | private final String prefix;
10 |
11 | PhpInjectFileReference(int argumentIndex, RelativeMode relativeMode, String prefix) {
12 | this.argumentIndex = argumentIndex;
13 | this.relativeMode = relativeMode;
14 | this.prefix = prefix;
15 | }
16 |
17 | public int getArgumentIndex() {
18 | return argumentIndex;
19 | }
20 |
21 | public RelativeMode getRelativeMode() {
22 | return relativeMode;
23 | }
24 |
25 | String getPrefix() {
26 | return prefix;
27 | }
28 |
29 | public enum RelativeMode {
30 | AUTO,
31 | TOP_LEVEL,
32 | CURRENT_FILE
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/index/PhpInjectFileReferenceCollector.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.index;
2 |
3 | import com.intellij.psi.PsiElement;
4 | import com.intellij.util.ObjectUtils;
5 | import com.jetbrains.php.codeInsight.PhpCodeInsightUtil;
6 | import com.jetbrains.php.codeInsight.controlFlow.PhpControlFlowUtil;
7 | import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpCallInstruction;
8 | import com.jetbrains.php.lang.psi.elements.*;
9 | import net.king2500.plugins.PhpAdvancedAutoComplete.index.PhpInjectFileReference.RelativeMode;
10 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.PhpMetaUtil;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.util.Arrays;
14 | import java.util.Map;
15 |
16 | /**
17 | * @author Thomas Schulz
18 | */
19 | public class PhpInjectFileReferenceCollector extends PhpControlFlowUtil.PhpRecursiveInstructionProcessor {
20 | private static final String INJECT_FILE_REFERENCE_NAME = "xAdvancedInjectFileReference";
21 | private static final String INJECT_DIR_REFERENCE_NAME = "xAdvancedInjectDirectoryReference";
22 | private final Map map;
23 |
24 | PhpInjectFileReferenceCollector(Map map) {
25 | this.map = map;
26 | }
27 |
28 | @Override
29 | public boolean processPhpCallInstruction(PhpCallInstruction instruction) {
30 | boolean isDir = false;
31 | FunctionReference targetFunctionReference = PhpMetaUtil.getMetaFunctionReferenceWithName(instruction, INJECT_FILE_REFERENCE_NAME);
32 | if (targetFunctionReference == null) {
33 | targetFunctionReference = PhpMetaUtil.getMetaFunctionReferenceWithName(instruction, INJECT_DIR_REFERENCE_NAME);
34 | isDir = true;
35 | }
36 | if (targetFunctionReference == null) {
37 | return true;
38 | }
39 |
40 | PsiElement[] parameters = instruction.getFunctionReference().getParameters();
41 | if (parameters.length < 2) {
42 | return true;
43 | }
44 |
45 | String fqn = getFQN(targetFunctionReference);
46 | if (fqn != null) {
47 | int argumentIndex = getArgumentIndexValue(parameters[1]);
48 | PhpInjectFileReference injectFileReference = this.getInjectFileReference(Arrays.copyOfRange(parameters, 2, parameters.length), argumentIndex, isDir);
49 |
50 | if (injectFileReference != null) {
51 | this.map.putIfAbsent(fqn + ":" + argumentIndex, injectFileReference);
52 | }
53 | }
54 |
55 | return super.processPhpCallInstruction(instruction);
56 | }
57 |
58 | private PhpInjectFileReference getInjectFileReference(PsiElement[] parameters, int argumentIndex, boolean isDir) {
59 | if (argumentIndex < 0) {
60 | return null;
61 | }
62 |
63 | RelativeMode mode = RelativeMode.AUTO;
64 | String prefixString = "";
65 |
66 | if (parameters.length > 0) {
67 | if (parameters[0] instanceof StringLiteralExpression) {
68 | String relativeString = ((StringLiteralExpression)parameters[0]).getContents();
69 | if ("/".equals(relativeString)) {
70 | mode = RelativeMode.TOP_LEVEL;
71 | }
72 | else if (".".equals(relativeString)) {
73 | mode = RelativeMode.CURRENT_FILE;
74 | }
75 | }
76 | else if (parameters[0] instanceof ConstantReference) {
77 | String fqn = ((ConstantReference)parameters[0]).getFQN();
78 | if (PhpMetaUtil.getMemberFQN("RELATIVE_TOP_LEVEL").equals(fqn)) {
79 | mode = RelativeMode.TOP_LEVEL;
80 | }
81 | else if (PhpMetaUtil.getMemberFQN("RELATIVE_CURRENT_FILE").equals(fqn)) {
82 | mode = RelativeMode.CURRENT_FILE;
83 | }
84 | }
85 | }
86 |
87 | if (parameters.length > 1) {
88 | if (parameters[1] instanceof StringLiteralExpression) {
89 | prefixString = ((StringLiteralExpression)parameters[1]).getContents();
90 | }
91 | }
92 |
93 | return isDir
94 | ? new PhpInjectDirectoryReference(argumentIndex, mode, prefixString)
95 | : new PhpInjectFileReference(argumentIndex, mode, prefixString);
96 | }
97 |
98 | @Nullable
99 | private static String getFQN(FunctionReference targetFunctionReference) {
100 | if (targetFunctionReference instanceof MethodReference) {
101 | PhpReference classReference = ObjectUtils.tryCast(((MethodReference)targetFunctionReference).getClassReference(), PhpReference.class);
102 | if (classReference != null) {
103 | return PhpCodeInsightUtil.getImmediateFQN(classReference) + "." + targetFunctionReference.getName();
104 | }
105 | }
106 |
107 | return PhpCodeInsightUtil.getImmediateFQN(targetFunctionReference);
108 | }
109 |
110 | private static int getArgumentIndexValue(PsiElement argumentIndex) {
111 | try {
112 | return Integer.parseInt(argumentIndex.getText());
113 | } catch (NumberFormatException var2) {
114 | return -1;
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/index/PhpInjectFileReferenceIndex.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.index;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import com.intellij.openapi.util.Ref;
5 | import com.intellij.openapi.util.text.StringUtil;
6 | import com.intellij.openapi.vfs.VirtualFile;
7 | import com.intellij.psi.PsiFile;
8 | import com.intellij.psi.search.GlobalSearchScope;
9 | import com.intellij.util.containers.ContainerUtil;
10 | import com.intellij.util.indexing.*;
11 | import com.intellij.util.indexing.FileBasedIndex.InputFilter;
12 | import com.intellij.util.io.DataExternalizer;
13 | import com.intellij.util.io.EnumeratorStringDescriptor;
14 | import com.intellij.util.io.KeyDescriptor;
15 | import com.jetbrains.php.PhpClassHierarchyUtils;
16 | import com.jetbrains.php.codeInsight.controlFlow.PhpControlFlowUtil;
17 | import com.jetbrains.php.lang.PhpFileType;
18 | import com.jetbrains.php.lang.psi.PhpFile;
19 | import com.jetbrains.php.lang.psi.elements.Function;
20 | import com.jetbrains.php.lang.psi.elements.PhpClassMember;
21 | import gnu.trove.THashMap;
22 | import net.king2500.plugins.PhpAdvancedAutoComplete.utils.PhpMetaUtil;
23 | import org.jetbrains.annotations.NonNls;
24 | import org.jetbrains.annotations.NotNull;
25 |
26 | import java.io.DataInput;
27 | import java.io.DataOutput;
28 | import java.io.IOException;
29 | import java.util.List;
30 | import java.util.Map;
31 |
32 | /**
33 | * @author Thomas Schulz
34 | */
35 | public class PhpInjectFileReferenceIndex extends FileBasedIndexExtension {
36 | @NonNls
37 | public static final ID KEY = ID.create("php.advanced.inject.file.reference");
38 | public static final InputFilter INPUT_FILTER;
39 | public static final DataExternalizer VALUE_EXTERNALIZER;
40 |
41 | public static PhpInjectFileReference getInjectFileReference(@NotNull Project project, @NotNull Function function, int argumentIndex) {
42 | FileBasedIndex index = FileBasedIndex.getInstance();
43 | GlobalSearchScope scope = GlobalSearchScope.allScope(project);
44 | Ref> result = new Ref<>(ContainerUtil.emptyList());
45 | result.set(index.getValues(KEY, function.getFQN() + ":" + argumentIndex, scope));
46 |
47 | if (result.get().isEmpty() && function instanceof PhpClassMember) {
48 | PhpClassHierarchyUtils.processSuperMembers((PhpClassMember)function, (classMember, subClass, baseClass) -> {
49 | List values = index.getValues(KEY, classMember.getFQN() + ":" + argumentIndex, scope);
50 | if (values.isEmpty()) {
51 | return true;
52 | } else {
53 | result.set(values);
54 | return false;
55 | }
56 | });
57 | }
58 |
59 | if (result.get().isEmpty()) {
60 | return null;
61 | }
62 | return result.get().get(0);
63 | }
64 |
65 | public PhpInjectFileReferenceIndex() {
66 | }
67 |
68 | @NotNull
69 | public ID getName() {
70 | return KEY;
71 | }
72 |
73 | @NotNull
74 | public DataIndexer getIndexer() {
75 | return inputData -> {
76 | Map map = new THashMap<>();
77 | PsiFile file = inputData.getPsiFile();
78 | if (file instanceof PhpFile) {
79 | PhpControlFlowUtil.processFile((PhpFile)file, new PhpInjectFileReferenceCollector(map));
80 | }
81 | return map;
82 | };
83 | }
84 |
85 | @NotNull
86 | public DataExternalizer getValueExternalizer() {
87 | return VALUE_EXTERNALIZER;
88 | }
89 |
90 | @NotNull
91 | public KeyDescriptor getKeyDescriptor() {
92 | return EnumeratorStringDescriptor.INSTANCE;
93 | }
94 |
95 | public int getVersion() {
96 | return 1;
97 | }
98 |
99 | @NotNull
100 | public InputFilter getInputFilter() {
101 | return INPUT_FILTER;
102 | }
103 |
104 | public boolean dependsOnFileContent() {
105 | return true;
106 | }
107 |
108 | static {
109 | INPUT_FILTER = new DefaultFileTypeSpecificInputFilter(PhpFileType.INSTANCE) {
110 | public boolean acceptInput(@NotNull VirtualFile file) {
111 | return PhpMetaUtil.isMetaFilename(file.getNameSequence()) && super.acceptInput(file);
112 | }
113 | };
114 | VALUE_EXTERNALIZER = new DataExternalizer() {
115 | @Override
116 | public void save(@NotNull DataOutput out, PhpInjectFileReference value) throws IOException {
117 | PhpInjectFileReferenceIndex.write(out, value);
118 | }
119 |
120 | @Override
121 | public PhpInjectFileReference read(@NotNull DataInput in) throws IOException {
122 | return PhpInjectFileReferenceIndex.read(in);
123 | }
124 | };
125 | }
126 |
127 | private static PhpInjectFileReference read(DataInput in) throws IOException {
128 | boolean isDir = in.readBoolean();
129 | int argumentIndex = in.readInt();
130 | PhpInjectFileReference.RelativeMode relativeMode = PhpInjectFileReference.RelativeMode.values()[in.readInt()];
131 | String prefix = StringUtil.nullize(in.readUTF());
132 | return isDir
133 | ? new PhpInjectDirectoryReference(argumentIndex, relativeMode, prefix)
134 | : new PhpInjectFileReference(argumentIndex, relativeMode, prefix);
135 | }
136 |
137 | private static void write(DataOutput out, PhpInjectFileReference fileReference) throws IOException {
138 | out.writeBoolean(fileReference instanceof PhpInjectDirectoryReference);
139 | out.writeInt(fileReference.getArgumentIndex());
140 | out.writeInt(fileReference.getRelativeMode().ordinal());
141 | out.writeUTF(StringUtil.notNullize(fileReference.getPrefix()));
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/reference/PhpHighlightPackParametersUsagesHandler.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.reference;
2 |
3 | import com.intellij.codeInsight.highlighting.HighlightUsagesHandlerBase;
4 | import com.intellij.lang.injection.InjectedLanguageManager;
5 | import com.intellij.openapi.editor.Editor;
6 | import com.intellij.openapi.util.TextRange;
7 | import com.intellij.psi.PsiElement;
8 | import com.intellij.psi.PsiFile;
9 | import com.intellij.util.Consumer;
10 | import com.intellij.util.containers.ContainerUtil;
11 | import com.intellij.util.containers.hash.HashMap;
12 | import com.jetbrains.php.lang.intentions.stringDoc.PhpHeredocToStringIntention;
13 | import com.jetbrains.php.lang.intentions.strings.converters.PhpConcatenationStringRepresentationConverter;
14 | import com.jetbrains.php.lang.intentions.strings.converters.PhpInterpolationStringRepresentationConverter;
15 | import com.jetbrains.php.lang.intentions.strings.converters.PhpStringPartDescriptor;
16 | import com.jetbrains.php.lang.psi.PhpPsiUtil;
17 | import com.jetbrains.php.lang.psi.elements.BinaryExpression;
18 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
19 | import gnu.trove.THashMap;
20 | import one.util.streamex.StreamEx;
21 | import org.jetbrains.annotations.NotNull;
22 |
23 | import java.util.Collections;
24 | import java.util.Iterator;
25 | import java.util.List;
26 | import java.util.Map;
27 | import java.util.stream.Collectors;
28 |
29 | /**
30 | * @author Thomas Schulz
31 | */
32 | public class PhpHighlightPackParametersUsagesHandler extends HighlightUsagesHandlerBase {
33 | private final int myFormatExpressionIndex;
34 | private final int mySelectedParameterIndex;
35 | private final PsiElement[] myParameters;
36 |
37 | PhpHighlightPackParametersUsagesHandler(@NotNull Editor editor, @NotNull PsiFile file, int formatExpressionIndex, int selectedParameterIndex, PsiElement[] parameters) {
38 | super(editor, file);
39 | this.myFormatExpressionIndex = formatExpressionIndex;
40 | this.mySelectedParameterIndex = selectedParameterIndex;
41 | this.myParameters = parameters;
42 | }
43 |
44 | @Override
45 | public List getTargets() {
46 | PsiElement parameter = this.myParameters[this.myFormatExpressionIndex];
47 | if (parameter instanceof StringLiteralExpression) {
48 | return Collections.singletonList((StringLiteralExpression)parameter);
49 | }
50 | else {
51 | return PhpConcatenationStringRepresentationConverter.isConcatenation(parameter) ? getLiteralExpressions((BinaryExpression)parameter, this.myEditor) : Collections.emptyList();
52 | }
53 | }
54 |
55 | @NotNull
56 | private static List getLiteralExpressions(BinaryExpression parameter, Editor editor) {
57 | List parts = ContainerUtil.map(PhpConcatenationStringRepresentationConverter.INSTANCE.getStringParts(parameter, editor), PhpStringPartDescriptor::getElement);
58 | List expressions = StreamEx.of(parts).select(StringLiteralExpression.class).collect(Collectors.toList());
59 | boolean allExpressionHaveSameQuotes = ((StreamEx)StreamEx.of(expressions).distinct(StringLiteralExpression::isSingleQuote)).limit(2L).count() <= 1L;
60 |
61 | return expressions.size() == parts.size() && allExpressionHaveSameQuotes ? expressions : Collections.emptyList();
62 | }
63 |
64 | @Override
65 | protected void selectTargets(List targets, Consumer> selectionConsumer) {
66 | selectionConsumer.consume(targets);
67 | }
68 |
69 | @Override
70 | public void computeUsages(List targets) {
71 | StringLiteralExpression formatExpression = ContainerUtil.getFirstItem(targets);
72 | if (formatExpression == null) {
73 | return;
74 | }
75 |
76 | String contents = PhpInterpolationStringRepresentationConverter.createExpressionContent(targets);
77 | HashMap specificationsWithIndices = PhpPackFormatSpecificationParser.parseFormat(contents, formatExpression.isSingleQuote() || PhpHeredocToStringIntention.isNowdoc(formatExpression), this.myParameters.length);
78 | HashMap relativeSpecificationRanges = getRelativeSpecificationRanges(specificationsWithIndices, targets);
79 |
80 | int specificationIndex = this.mySelectedParameterIndex == this.myFormatExpressionIndex
81 | ? resolveSpecificationIndexFromCaret(relativeSpecificationRanges)
82 | : resolveSpecificationIndexFromParameter(specificationsWithIndices);
83 |
84 | RelativeRange pair = relativeSpecificationRanges.get(specificationIndex);
85 | if (pair == null) {
86 | return;
87 | }
88 |
89 | this.myReadUsages.add(getRangeInsideDocument(pair.getContainingExpression(), pair.getRangeInsideExpression()));
90 | int offset = 1;
91 |
92 | for (Map.Entry entry : specificationsWithIndices.entrySet()) {
93 | PhpPackFormatSpecificationParser.PackSpecification specification = entry.getValue();
94 | if (entry.getKey() == specificationIndex) {
95 | for (int r = 0; r < specification.getRepeater(); r++) {
96 | int parameterIndex = offset + r >= 0 ? this.myFormatExpressionIndex + offset + r : offset + r;
97 | if (parameterIndex >= 0 && parameterIndex < this.myParameters.length) {
98 | this.addOccurrence(this.myParameters[parameterIndex]);
99 | }
100 | }
101 | break;
102 | }
103 | offset += specification.getRepeater();
104 | }
105 | }
106 |
107 | @NotNull
108 | private static HashMap getRelativeSpecificationRanges(HashMap specifications, List targets) {
109 | Map rangesInsideResultingFormatString = getRangesWithExpressionInsideResultingFormatString(targets);
110 | HashMap result = new HashMap<>();
111 |
112 | for (Map.Entry entry : specifications.entrySet()) {
113 | PhpPackFormatSpecificationParser.PackSpecification specification = entry.getValue();
114 | for (Map.Entry e : rangesInsideResultingFormatString.entrySet()) {
115 | TextRange expressionRangeInsideFormatString = e.getKey();
116 | TextRange specificationRangeInsideFormatString = expressionRangeInsideFormatString.intersection(specification.getRangeInElement());
117 | if (specificationRangeInsideFormatString != null && !specificationRangeInsideFormatString.isEmpty()) {
118 | result.put(entry.getKey(), new RelativeRange(e.getValue(), specificationRangeInsideFormatString.shiftLeft(expressionRangeInsideFormatString.getStartOffset())));
119 | }
120 | }
121 | }
122 |
123 | return result;
124 | }
125 |
126 | @NotNull
127 | private static Map getRangesWithExpressionInsideResultingFormatString(List targets) {
128 | int lastOffset = 0;
129 | Map result = new THashMap<>();
130 |
131 | TextRange range;
132 | for (Iterator var3 = targets.iterator(); var3.hasNext(); lastOffset = range.getEndOffset()) {
133 | StringLiteralExpression target = (StringLiteralExpression)var3.next();
134 | int length = target.getContents().length();
135 | range = TextRange.create(lastOffset, lastOffset + length);
136 | result.put(range, target);
137 | }
138 |
139 | return result;
140 | }
141 |
142 | @NotNull
143 | private static TextRange getRangeInsideDocument(@NotNull StringLiteralExpression formatExpression, @NotNull TextRange rangeInsideExpression) {
144 | return InjectedLanguageManager.getInstance(formatExpression.getProject())
145 | .injectedToHost(formatExpression, rangeInsideExpression)
146 | .shiftRight(formatExpression.getTextOffset())
147 | .shiftRight(formatExpression.getValueRange().getStartOffset());
148 | }
149 |
150 | private int resolveSpecificationIndexFromParameter(HashMap specifications) {
151 | int offset = 1;
152 |
153 | for (Map.Entry entry : specifications.entrySet()) {
154 | PhpPackFormatSpecificationParser.PackSpecification specification = entry.getValue();
155 | for (int r = 0; r < specification.getRepeater(); r++) {
156 | int parameterIndex = offset + r >= 0 ? this.myFormatExpressionIndex + offset + r : offset + r;
157 | if (parameterIndex == this.mySelectedParameterIndex) {
158 | return entry.getKey();
159 | }
160 | }
161 | offset += specification.getRepeater();
162 | }
163 |
164 | return -1;
165 | }
166 |
167 | private int resolveSpecificationIndexFromCaret(@NotNull HashMap specificationRelativeRanges) {
168 | int caretOffset = this.myEditor.getCaretModel().getOffset();
169 | StringLiteralExpression selectedLiteralExpression = PhpPsiUtil.getParentByCondition(this.myFile.findElementAt(caretOffset), false, StringLiteralExpression.INSTANCEOF);
170 | return StreamEx.of(specificationRelativeRanges.entrySet())
171 | .findFirst((e) -> specificationAtCaretOffsetExists(caretOffset, selectedLiteralExpression, e.getValue()))
172 | .map(Map.Entry::getKey).orElse(-1);
173 | }
174 |
175 | private static boolean specificationAtCaretOffsetExists(int caretOffset, StringLiteralExpression formatExpression, RelativeRange specification) {
176 | return specification.getContainingExpression() == formatExpression
177 | && getRangeInsideDocument(formatExpression, specification.getRangeInsideExpression()).containsOffset(caretOffset);
178 | }
179 |
180 | @Override
181 | public boolean highlightReferences() {
182 | return true;
183 | }
184 |
185 | public static class RelativeRange {
186 | @NotNull
187 | private final StringLiteralExpression containingExpression;
188 | @NotNull
189 | private final TextRange rangeInsideExpression;
190 |
191 | private RelativeRange(@NotNull StringLiteralExpression containingExpression, @NotNull TextRange rangeInsideExpression) {
192 | this.containingExpression = containingExpression;
193 | this.rangeInsideExpression = rangeInsideExpression;
194 | }
195 |
196 | @NotNull
197 | public StringLiteralExpression getContainingExpression() {
198 | return this.containingExpression;
199 | }
200 |
201 | @NotNull
202 | public TextRange getRangeInsideExpression() {
203 | return this.rangeInsideExpression;
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/reference/PhpHighlightPackParametersUsagesHandlerFactory.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.reference;
2 |
3 | import com.intellij.codeInsight.highlighting.HighlightUsagesHandlerBase;
4 | import com.intellij.codeInsight.highlighting.HighlightUsagesHandlerFactoryBase;
5 | import com.intellij.openapi.editor.Editor;
6 | import com.intellij.psi.PsiElement;
7 | import com.intellij.psi.PsiFile;
8 | import com.intellij.util.ObjectUtils;
9 | import com.jetbrains.php.codeInsight.PhpCodeInsightUtil;
10 | import com.jetbrains.php.lang.psi.PhpPsiUtil;
11 | import com.jetbrains.php.lang.psi.elements.Function;
12 | import com.jetbrains.php.lang.psi.elements.FunctionReference;
13 | import com.jetbrains.php.lang.psi.elements.ParameterList;
14 | import com.jetbrains.php.lang.psi.elements.Statement;
15 | import one.util.streamex.StreamEx;
16 | import org.jetbrains.annotations.NotNull;
17 | import org.jetbrains.annotations.Nullable;
18 |
19 | /**
20 | * @author Thomas Schulz
21 | */
22 | public class PhpHighlightPackParametersUsagesHandlerFactory extends HighlightUsagesHandlerFactoryBase {
23 | @Override
24 | public @Nullable HighlightUsagesHandlerBase createHighlightUsagesHandler(@NotNull Editor editor, @NotNull PsiFile file, @NotNull PsiElement target) {
25 |
26 | ParameterList parameterList = PhpPsiUtil.getParentByCondition(target, true, ParameterList.INSTANCEOF, Statement.INSTANCEOF);
27 | if (parameterList == null) {
28 | return null;
29 | }
30 |
31 | FunctionReference functionCall = ObjectUtils.tryCast(parameterList.getParent(), FunctionReference.class);
32 | String fqn = resolveFqn(functionCall);
33 | if (!"\\pack".equals(fqn)) {
34 | return null;
35 | }
36 |
37 | PsiElement[] parameters = parameterList.getParameters();
38 | PsiElement selectedParameter = StreamEx.of(parameters).findFirst((p) -> p.getTextRange().containsOffset(editor.getCaretModel().getOffset())).orElse(null);
39 | if (selectedParameter == null) {
40 | return null;
41 | }
42 |
43 | int selectedIndex = PhpCodeInsightUtil.getParameterIndex(selectedParameter);
44 | if (selectedIndex < 0 || selectedIndex >= parameters.length) {
45 | return null;
46 | }
47 | return new PhpHighlightPackParametersUsagesHandler(editor, file, 0, selectedIndex, parameters);
48 | }
49 |
50 | @Nullable
51 | private static String resolveFqn(@Nullable FunctionReference reference) {
52 | Function function = reference != null ? ObjectUtils.tryCast(reference.resolve(), Function.class) : null;
53 | return function != null ? function.getFQN() : null;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/reference/PhpPackFormatSpecificationParser.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.reference;
2 |
3 | import com.intellij.openapi.util.TextRange;
4 | import com.intellij.util.containers.hash.HashMap;
5 | import net.king2500.plugins.PhpAdvancedAutoComplete.PhpCompletionTokens;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | import java.util.Arrays;
10 | import java.util.List;
11 |
12 | /**
13 | * @author Thomas Schulz
14 | */
15 | class PhpPackFormatSpecificationParser {
16 | private PhpPackFormatSpecificationParser() {
17 | }
18 |
19 | static HashMap parseFormat(@NotNull String expression, boolean singleQuote, int parametersCount) {
20 | int index = 0;
21 | int num = 0;
22 | HashMap result = new HashMap<>();
23 | List codesList = Arrays.asList(PhpCompletionTokens.packCodes);
24 |
25 | while (index < expression.length()) {
26 | char c = expression.charAt(index);
27 |
28 | // Cancel when we hit variables inside string
29 | if (!singleQuote && charAtEqualsToAny(expression, index, '$')) {
30 | return result;
31 | }
32 |
33 | if (!codesList.contains(Character.toString(c)) || charAtEqualsToAny(expression, index, 'x', 'X')) {
34 | ++index;
35 | continue;
36 | }
37 |
38 | int endIndex = index;
39 | int repeater = 1;
40 |
41 | TextRange width = parseUnsignedInt(expression, index+1);
42 | if (width != null && !charAtEqualsToAny(expression, index, 'a', 'A', 'h', 'H')) {
43 | endIndex = width.getEndOffset() - 1;
44 | repeater = Integer.parseUnsignedInt(width.substring(expression));
45 | }
46 |
47 | boolean repeatToEnd = charAtEqualsToAny(expression, index + 1, '*');
48 | if (repeatToEnd) {
49 | ++endIndex;
50 | repeater = parametersCount - num;
51 | }
52 |
53 | TextRange range = TextRange.create(index, endIndex + 1);
54 | result.put(num, new PackSpecification(range, repeater));
55 |
56 | if (repeatToEnd) {
57 | return result;
58 | }
59 | ++num;
60 | ++index;
61 | }
62 | return result;
63 | }
64 |
65 | private static boolean charAtEqualsToAny(@NotNull String s, int index, char... chars) {
66 | if (index >= 0 && index < s.length()) {
67 | for (char c : chars) {
68 | if (c == s.charAt(index)) {
69 | return true;
70 | }
71 | }
72 | }
73 |
74 | return false;
75 | }
76 |
77 | @Nullable
78 | private static TextRange parseUnsignedInt(@NotNull String expression, int from) {
79 | if (!isDigit(expression, from)) {
80 | return null;
81 | } else {
82 | int start;
83 | start = from;
84 | while (isDigit(expression, from)) {
85 | ++from;
86 | }
87 |
88 | return TextRange.create(start, from);
89 | }
90 | }
91 |
92 | private static boolean isDigit(@NotNull String expression, int index) {
93 | return index >= 0 && index < expression.length() && Character.isDigit(expression.charAt(index));
94 | }
95 |
96 |
97 | public static class PackSpecification {
98 | @NotNull
99 | private final TextRange myRange;
100 | private final int myRepeater;
101 |
102 | private PackSpecification(@NotNull TextRange range, int repeater) {
103 | this.myRange = range;
104 | this.myRepeater = repeater;
105 | }
106 |
107 | public TextRange getRangeInElement() {
108 | return this.myRange;
109 | }
110 |
111 | public int getRepeater() {
112 | return this.myRepeater;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/utils/DatabaseUtil.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.utils;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import org.w3c.dom.Element;
5 | import org.w3c.dom.NodeList;
6 |
7 | import java.io.File;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * @author Thomas Schulz
13 | */
14 | public class DatabaseUtil {
15 | public static String[] getDbHostnames(Project project, String jdbcPrefix) {
16 | List hostnames = new ArrayList();
17 |
18 | if (project == null) {
19 | return null;
20 | }
21 |
22 | String dataSourcesPath = getDataSourcesXmlPath(project);
23 | File dataSourcesFile = new File(dataSourcesPath);
24 |
25 | if (!jdbcPrefix.startsWith("jdbc:")) {
26 | jdbcPrefix = "jdbc:" + jdbcPrefix;
27 | }
28 |
29 | String jdbcPrefixQuoted = '"' + jdbcPrefix + '"';
30 |
31 | NodeList nodes = XmlUtil.getNodesByXPath(dataSourcesFile, "/project/component/data-source/jdbc-url/text()[starts-with(., " + jdbcPrefixQuoted + ")]");
32 |
33 | if (nodes == null) {
34 | return null;
35 | }
36 |
37 | for (int i = 0; i < nodes.getLength(); i++) {
38 | String jdbcUrl = nodes.item(i).getNodeValue().trim();
39 | String jdbcPath = jdbcUrl.replace(jdbcPrefix, "");
40 |
41 | if (jdbcPath.contains("/")) {
42 | hostnames.add(jdbcPath.substring(0, jdbcPath.indexOf('/')));
43 | }
44 | else {
45 | hostnames.add(jdbcPath);
46 | }
47 | }
48 |
49 | return hostnames.toArray(new String[hostnames.size()]);
50 | }
51 |
52 | public static String[] getDbUsers(Project project, String jdbcPrefix) {
53 | List users = new ArrayList();
54 |
55 | if (project == null) {
56 | return null;
57 | }
58 |
59 | String dataSourcesPath = getDataSourcesXmlPath(project);
60 | File dataSourcesFile = new File(dataSourcesPath);
61 |
62 | if (!jdbcPrefix.startsWith("jdbc:")) {
63 | jdbcPrefix = "jdbc:" + jdbcPrefix;
64 | }
65 |
66 | String jdbcPrefixQuoted = '"' + jdbcPrefix + '"';
67 |
68 | NodeList nodes = XmlUtil.getNodesByXPath(dataSourcesFile, "/project/component/data-source[jdbc-url/text()[starts-with(., " + jdbcPrefixQuoted + ")]]/user-name/text()");
69 |
70 | if (nodes == null) {
71 | return null;
72 | }
73 |
74 | for (int i = 0; i < nodes.getLength(); i++) {
75 | String username = nodes.item(i).getNodeValue().trim();
76 | users.add(username);
77 | }
78 |
79 | return users.toArray(new String[users.size()]);
80 | }
81 |
82 | public static String[] getDbNames(Project project, String jdbcPrefix) {
83 | List dbNames = new ArrayList();
84 |
85 | if (project == null) {
86 | return null;
87 | }
88 |
89 | String dataSourcesPath = getDataSourcesXmlPath(project);
90 | File dataSourcesFile = new File(dataSourcesPath);
91 |
92 | if (!jdbcPrefix.startsWith("jdbc:")) {
93 | jdbcPrefix = "jdbc:" + jdbcPrefix;
94 | }
95 |
96 | String jdbcPrefixQuoted = '"' + jdbcPrefix + '"';
97 |
98 | // Search in JDBC URL
99 | NodeList nodes1 = XmlUtil.getNodesByXPath(dataSourcesFile, "/project/component/data-source/jdbc-url/text()[starts-with(., " + jdbcPrefixQuoted + ")]");
100 |
101 | if (nodes1 == null) {
102 | return null;
103 | }
104 |
105 | for (int i = 0; i < nodes1.getLength(); i++) {
106 | String jdbcUrl = nodes1.item(i).getNodeValue().trim();
107 | String jdbcPath = jdbcUrl.replace(jdbcPrefix, "");
108 |
109 | if (!jdbcPath.contains("/")) {
110 | continue;
111 | }
112 |
113 | if (jdbcPath.contains("?")) {
114 | dbNames.add(jdbcPath.substring(jdbcPath.indexOf('/') + 1, jdbcPath.indexOf('?')));
115 | }
116 | else {
117 | dbNames.add(jdbcPath.substring(jdbcPath.indexOf('/') + 1));
118 | }
119 | }
120 |
121 | // Search in selected schemas
122 | NodeList nodes2 = XmlUtil.getNodesByXPath(dataSourcesFile, "/project/component/data-source[jdbc-url/text()[starts-with(., " + jdbcPrefixQuoted + ")]]/*[self::schema-pattern or self::default-schemas]/text()");
123 |
124 | if (nodes2 == null) {
125 | return null;
126 | }
127 |
128 | for (int i = 0; i < nodes2.getLength(); i++) {
129 | String schemaPattern = nodes2.item(i).getNodeValue().trim();
130 |
131 | // we can only handle schemas listed in xml file. '*' is not yet supported
132 | String[] schemas = schemaPattern.split(" ");
133 |
134 | for (String schema : schemas) {
135 | if (!schema.contains(".")) {
136 | continue;
137 | }
138 |
139 | String schemaName = schema.substring(0, schema.indexOf('.'));
140 | dbNames.add(schemaName);
141 | }
142 | }
143 |
144 | return dbNames.toArray(new String[dbNames.size()]);
145 | }
146 |
147 | public static String[] getPdoDSNs(Project project, String jdbcPrefix) {
148 | List dsns = new ArrayList();
149 |
150 | if (project == null) {
151 | return null;
152 | }
153 |
154 | String dataSourcesPath = getDataSourcesXmlPath(project);
155 | File dataSourcesFile = new File(dataSourcesPath);
156 |
157 | if (!jdbcPrefix.startsWith("jdbc:")) {
158 | jdbcPrefix = "jdbc:" + jdbcPrefix;
159 | }
160 |
161 | String jdbcPrefixQuoted = '"' + jdbcPrefix + '"';
162 |
163 | NodeList nodes = XmlUtil.getNodesByXPath(dataSourcesFile, "/project/component/data-source[jdbc-url/text()[starts-with(., " + jdbcPrefixQuoted + ")]]");
164 |
165 | if (nodes == null) {
166 | return null;
167 | }
168 |
169 | for (int i = 0; i < nodes.getLength(); i++) {
170 | Element element = (Element)nodes.item(i);
171 |
172 | String jdbcUrl = element.getElementsByTagName("jdbc-url").item(0).getTextContent().trim();
173 | String jdbcPath = jdbcUrl.replace(jdbcPrefix, "");
174 | String dbHost;
175 |
176 | if (jdbcPath.contains("/")) {
177 | dbHost = jdbcPath.substring(0, jdbcPath.indexOf('/'));
178 | String dbName = jdbcPath.substring(jdbcPath.indexOf('/') + 1);
179 |
180 | if (dbName.contains("?")) {
181 | dbName = dbName.substring(0, dbName.indexOf('?'));
182 | }
183 |
184 | dsns.add("mysql:dbname=" + dbName + ";host=" + dbHost);
185 | }
186 | else {
187 | dbHost = jdbcPath;
188 | }
189 |
190 | NodeList schemaNodes = element.getElementsByTagName("schema-pattern");
191 |
192 | if (schemaNodes.getLength() > 0) {
193 | String schemaPattern = schemaNodes.item(0).getTextContent().trim();
194 |
195 | // we can only handle schemas listed in xml file. '*' is not yet supported
196 | String[] schemas = schemaPattern.split(" ");
197 |
198 | for (String schema : schemas) {
199 | if (!schema.contains(".")) {
200 | continue;
201 | }
202 |
203 | String schemaName = schema.substring(0, schema.indexOf('.'));
204 | dsns.add("mysql:dbname=" + schemaName + ";host=" + dbHost);
205 | }
206 | }
207 |
208 | NodeList schemaDefaultNodes = element.getElementsByTagName("default-schemas");
209 |
210 | if (schemaNodes.getLength() > 0) {
211 | String schemaPattern = schemaDefaultNodes.item(0).getTextContent().trim();
212 |
213 | // we can only handle schemas listed in xml file. '*' is not yet supported
214 | String[] schemas = schemaPattern.split(" ");
215 |
216 | for (String schema : schemas) {
217 | if (!schema.contains(".")) {
218 | continue;
219 | }
220 |
221 | String schemaName = schema.substring(0, schema.indexOf('.'));
222 | dsns.add("mysql:dbname=" + schemaName + ";host=" + dbHost);
223 | }
224 | }
225 | }
226 |
227 | return dsns.toArray(new String[dsns.size()]);
228 | }
229 |
230 | private static String getDataSourcesXmlPath(Project project) {
231 | return project.getBasePath() + File.separator + ".idea" + File.separator + "dataSources.xml";
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/utils/DateTimeUtil.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.utils;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.*;
5 |
6 | /**
7 | * @author Thomas Schulz
8 | */
9 | public class DateTimeUtil {
10 |
11 | private final static HashMap dateTimePhpToJavaPattern = new HashMap() {{
12 | put("d", "dd");
13 | put("D", "EEE");
14 | put("j", "d");
15 | put("l", "EEEE");
16 | put("N", "u");
17 | put("z", "D"); // zero-based 0..365 ?
18 | put("W", "w"); // ISO-8601 ?
19 | put("F", "MMMM");
20 | put("m", "MM");
21 | put("M", "MMM");
22 | put("n", "M");
23 | put("o", "Y");
24 | put("Y", "yyyy");
25 | put("y", "yy");
26 | put("A", "a");
27 | put("g", "h");
28 | put("G", "H");
29 | put("h", "hh");
30 | put("H", "HH");
31 | put("i", "mm");
32 | put("s", "ss");
33 | put("v", "SSS");
34 | put("e", "zzzz");
35 | put("O", "Z");
36 | put("P", "XXX");
37 | put("T", "zzz");
38 | put("c", "yyyy-MM-dd'T'HH:mm:ssXXX");
39 | put("r", "EEE, d MMM yyyy HH:mm:ss Z");
40 | }};
41 |
42 | public static String formatPhpDateTime(String phpFormat, Locale locale) {
43 | StringBuilder str = new StringBuilder();
44 | for (char phpChar : phpFormat.toCharArray()) {
45 | String phpCharStr = Character.toString(phpChar);
46 | if (!dateTimePhpToJavaPattern.containsKey(phpCharStr)) {
47 | switch (phpChar) {
48 | case 'S':
49 | // S -> st, nd, rd, th
50 | String day = formatDateTime("d", locale);
51 | switch (day.charAt(day.length() - 1)) {
52 | case '1':
53 | str.append("st");
54 | continue;
55 |
56 | case '2':
57 | str.append("nd");
58 | continue;
59 |
60 | case '3':
61 | str.append("rd");
62 | continue;
63 |
64 | default:
65 | str.append("th");
66 | continue;
67 | }
68 |
69 | case 'w':
70 | // w -> 0 (Sun) ... 6 (Sat)
71 | String weekday = formatDateTime("u", locale);
72 | if (weekday.equals("7")) {
73 | weekday = "0";
74 | }
75 | str.append(weekday);
76 | continue;
77 |
78 | case 't':
79 | // t -> Number of days in month
80 | String month = formatDateTime("M", locale);
81 | String year = formatDateTime("y", locale);
82 | byte numDays = 31;
83 | if ("4".equals(month) || "6".equals(month) || "9".equals(month) || "11".equals(month)) {
84 | numDays = 30;
85 | }
86 | if ("2".equals(month)) {
87 | numDays = (byte)(isLeapYear(Integer.parseInt(year)) ? 29 : 28);
88 | }
89 | str.append(numDays);
90 | continue;
91 |
92 | case 'L':
93 | // L -> Leap year = 0 or 1
94 | String y = formatDateTime("y", locale);
95 | str.append(isLeapYear(Integer.parseInt(y)) ? '1' : '0');
96 | continue;
97 |
98 | case 'a':
99 | // a -> am/pm in lowercase
100 | String dayTime = formatDateTime("a", locale);
101 | str.append(dayTime.toLowerCase());
102 | continue;
103 |
104 | case 'I':
105 | // I -> DST = 0 or 1
106 | String dst = TimeZone.getDefault().inDaylightTime(new Date()) ? "1" : "0";
107 | str.append(dst);
108 | continue;
109 |
110 | case 'B':
111 | // B -> Swatch 000 .. 999
112 | str.append(getCurrentTimeInBeats());
113 | continue;
114 |
115 | case 'u':
116 | // u -> Milliseconds 000000
117 | long millis = System.currentTimeMillis();
118 | str.append(millis - ((int)(millis / 1000L)) * 1000L);
119 | continue;
120 |
121 | case 'Z':
122 | // Z -> timezone offset in seconds
123 | int tzOffset = TimeZone.getDefault().getOffset(new Date().getTime()) / 1000;
124 | str.append(tzOffset);
125 | continue;
126 |
127 | case 'U':
128 | // U -> unix timestamp (seconds)
129 | str.append(System.currentTimeMillis() / 1000L);
130 | continue;
131 | }
132 | // unsupported?
133 | if (Character.isLetter(phpChar)) {
134 | return "";
135 | }
136 | str.append(phpChar);
137 | continue;
138 | }
139 | String javaPattern = dateTimePhpToJavaPattern.get(phpCharStr);
140 | str.append(formatDateTime(javaPattern, locale));
141 | }
142 | return str.toString();
143 | }
144 |
145 | private static String formatDateTime(String pattern, Locale locale) {
146 | SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, locale);
147 | return dateFormat.format(new Date());
148 | }
149 |
150 | private static boolean isLeapYear(int year) {
151 | Calendar cal = Calendar.getInstance();
152 | cal.set(Calendar.YEAR, year);
153 | return cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;
154 | }
155 |
156 | private static int getCurrentTimeInBeats() {
157 | Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+01:00"));
158 | //noinspection UnnecessaryLocalVariable
159 | int beats = (int)((cal.get(Calendar.SECOND) + (cal.get(Calendar.MINUTE) * 60) + (cal.get(Calendar.HOUR_OF_DAY) * 3600)) / 86.4);
160 | return beats;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/utils/FileUtil.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.utils;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import com.intellij.openapi.roots.ContentIterator;
5 | import com.intellij.openapi.roots.ProjectFileIndex;
6 | import com.intellij.openapi.vfs.VirtualFile;
7 | import com.intellij.psi.PsiDirectory;
8 | import com.intellij.psi.PsiFile;
9 | import com.intellij.psi.PsiFileSystemItem;
10 | import com.intellij.psi.PsiManager;
11 |
12 | import java.util.ArrayList;
13 | import java.util.HashMap;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | /**
18 | * @author Thomas Schulz
19 | */
20 | public class FileUtil {
21 | static final public int TYPE_ALL = 0;
22 | static final public int TYPE_FILE = 1;
23 | static final public int TYPE_DIR = 2;
24 |
25 | public static String[] getProjectFiles(Project project)
26 | {
27 | final List files = new ArrayList();
28 | final VirtualFile projectDirectory = project.getBaseDir();
29 |
30 | ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(project);
31 |
32 | fileIndex.iterateContentUnderDirectory(projectDirectory, file -> {
33 | String relativePath = file.getPath().replace(projectDirectory.getPath() + "/", "");
34 |
35 | //files.add(file.getName());
36 | if(!file.isDirectory() && !relativePath.startsWith(".idea/")) {
37 | files.add(relativePath);
38 | }
39 |
40 | return true;
41 | });
42 |
43 | return files.toArray(new String[files.size()]);
44 | }
45 |
46 | public static String[] getRelativeFiles(PsiFile baseFile)
47 | {
48 | final List files = new ArrayList();
49 | Project project = baseFile.getProject();
50 | final VirtualFile projectDirectory = project.getBaseDir();
51 | PsiDirectory directory = baseFile.getContainingDirectory();
52 |
53 | if(directory == null)
54 | return null;
55 |
56 | final VirtualFile originDirectory = directory.getVirtualFile();
57 |
58 | ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(project);
59 |
60 | /*
61 | for(VirtualFile file : originDirectory.getChildren()) {
62 | files.add(file.getPath().replace(projectDirectory.getPath() + "/", ""));
63 | }
64 | */
65 |
66 | fileIndex.iterateContentUnderDirectory(originDirectory, new ContentIterator() {
67 | @Override
68 | public boolean processFile(VirtualFile file) {
69 | //files.add(file.getName());
70 | if(!file.isDirectory()) {
71 | files.add(file.getPath().replace(originDirectory.getPath() + "/", ""));
72 | }
73 |
74 | return true;
75 | }
76 | }
77 | );
78 |
79 | return files.toArray(new String[files.size()]);
80 | }
81 |
82 | public static Map getRelativeFilesByName(PsiFile baseFile, final int fileType) {
83 | final Map files = new HashMap();
84 | final Project project = baseFile.getProject();
85 |
86 | final VirtualFile projectDirectory = project.getBaseDir();
87 |
88 | PsiDirectory directory = baseFile.getContainingDirectory();
89 |
90 | if(directory == null)
91 | directory = baseFile.getOriginalFile().getContainingDirectory();
92 |
93 | if(directory == null)
94 | return files;
95 |
96 | final VirtualFile originDirectory = directory.getVirtualFile();
97 |
98 | ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(project);
99 |
100 | if(projectDirectory != null) {
101 |
102 | if(fileType == TYPE_DIR) {
103 | PsiDirectory psiDir = PsiManager.getInstance(project).findDirectory(originDirectory);
104 |
105 | if (psiDir != null) {
106 | files.put(".", psiDir);
107 | }
108 | }
109 |
110 | fileIndex.iterateContentUnderDirectory(originDirectory, new ContentIterator() {
111 | @Override
112 | public boolean processFile(VirtualFile virtualFile) {
113 | String relativePath = virtualFile.getPath().replace(originDirectory.getPath() + "/", "");
114 |
115 | if((fileType == TYPE_ALL || fileType == TYPE_FILE) && !virtualFile.isDirectory() && !relativePath.startsWith(".idea/")) {
116 | PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
117 |
118 | if (psiFile != null) {
119 | files.put(relativePath, psiFile);
120 | }
121 | }
122 |
123 | if((fileType == TYPE_ALL || fileType == TYPE_DIR) && virtualFile.isDirectory() && !relativePath.startsWith(".idea") && !virtualFile.getPath().equals(originDirectory.getPath())) {
124 | PsiDirectory psiDir = PsiManager.getInstance(project).findDirectory(virtualFile);
125 |
126 | if (psiDir != null) {
127 | files.put(relativePath, psiDir);
128 | }
129 | }
130 |
131 | return true;
132 | }
133 | }
134 | );
135 | }
136 |
137 | return files;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/utils/PhpElementsUtil.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.utils;
2 |
3 | import com.intellij.psi.PsiElement;
4 | import com.intellij.psi.PsiReference;
5 | import com.intellij.psi.ResolveResult;
6 | import com.intellij.util.ObjectUtils;
7 | import com.jetbrains.php.lang.psi.PhpPsiUtil;
8 | import com.jetbrains.php.lang.psi.elements.*;
9 | import net.king2500.plugins.PhpAdvancedAutoComplete.PhpCompletionTokens;
10 | import one.util.streamex.StreamEx;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | /**
14 | * @author Thomas Schulz
15 | */
16 | public class PhpElementsUtil {
17 |
18 | public static int getParameterIndex(PsiElement paramElement) {
19 | int index = 0;
20 | PsiElement element = paramElement;
21 |
22 | while (element != null && element.getPrevSibling() != null) {
23 | String elementClass = element.getPrevSibling().getClass().getSimpleName();
24 |
25 | if (elementClass.equals("LeafPsiElement")) {
26 | index++;
27 | }
28 |
29 | element = element.getPrevSibling();
30 | }
31 |
32 | return index;
33 | }
34 |
35 | public static String getCanonicalFuncName(PsiElement caller) {
36 | if (caller.getReference() instanceof MethodReference) {
37 | return getMethodName(caller);
38 | }
39 | else if (caller.getReference() instanceof FunctionReference) {
40 | return getFuncName(caller);
41 | }
42 | else if (caller instanceof NewExpression) {
43 | return getClassConstructName(caller);
44 | }
45 |
46 | return null;
47 | }
48 |
49 | private static String getFuncName(PsiElement caller) {
50 |
51 | PsiReference psiReference = caller.getReference();
52 |
53 | if (psiReference == null) {
54 | return null;
55 | }
56 |
57 | PsiElement resolvedReference = psiReference.resolve();
58 |
59 | if (!(resolvedReference instanceof Function)) {
60 | return null;
61 | }
62 |
63 | Function function = (Function)resolvedReference;
64 |
65 | return function.getName();
66 | }
67 |
68 | private static String getMethodName(PsiElement caller) {
69 |
70 | PsiReference psiReference = caller.getReference();
71 |
72 | if (psiReference == null) {
73 | return null;
74 | }
75 |
76 | PsiElement resolvedReference = psiReference.resolve();
77 |
78 | if (!(resolvedReference instanceof Method)) {
79 | return null;
80 | }
81 |
82 | Method method = (Method)resolvedReference;
83 | PhpClass methodClass = method.getContainingClass();
84 |
85 | if (methodClass == null) {
86 | return null;
87 | }
88 |
89 | String className = methodClass.getName();
90 | String methodName = method.getName();
91 |
92 | return className + "::" + methodName;
93 | }
94 |
95 | private static String getClassConstructName(PsiElement caller) {
96 |
97 | PsiElement[] children = caller.getChildren();
98 |
99 | if (children.length == 0) {
100 | return null;
101 | }
102 |
103 | PsiReference psiReference = children[0].getReference();
104 |
105 | if (!(psiReference instanceof ClassReference)) {
106 | return null;
107 | }
108 |
109 | PsiElement resolvedReference = psiReference.resolve();
110 |
111 | if (!(resolvedReference instanceof Method)) {
112 | return null;
113 | }
114 |
115 | Method method = (Method)resolvedReference;
116 |
117 | if (!method.getName().equals("__construct")) {
118 | return null;
119 | }
120 |
121 | PhpClass methodClass = method.getContainingClass();
122 |
123 | if (methodClass == null) {
124 | return null;
125 | }
126 |
127 | String className = methodClass.getName();
128 | return className + "::__construct";
129 | }
130 |
131 |
132 | public static boolean isFormatFunction(String fqn) {
133 | return StreamEx.of(PhpCompletionTokens.formatFuncFqns).has(fqn);
134 | }
135 |
136 | @Nullable
137 | public static String resolveFqn(@Nullable FunctionReference reference) {
138 | Function function = reference != null ? ObjectUtils.tryCast(reference.resolve(), Function.class) : null;
139 | return function != null ? function.getFQN() : null;
140 | }
141 |
142 | @Nullable
143 | public static Function getFunction(PsiElement position) {
144 | FunctionReference functionReference = PhpPsiUtil.getParentByCondition(position, true, FunctionReference.INSTANCEOF, Statement.INSTANCEOF);
145 | if (functionReference != null) {
146 | return getFunction(functionReference);
147 | } else {
148 | NewExpression newExpression = PhpPsiUtil.getParentByCondition(position, true, NewExpression.INSTANCEOF, Statement.INSTANCEOF);
149 | if (newExpression != null) {
150 | ClassReference classReference = newExpression.getClassReference();
151 | if (classReference != null) {
152 | return getFunction(classReference);
153 | }
154 | }
155 |
156 | return null;
157 | }
158 | }
159 |
160 | @Nullable
161 | private static Function getFunction(PhpReference reference) {
162 | return StreamEx.of(reference.multiResolve(false)).map(ResolveResult::getElement).select(Function.class).findFirst().orElse(null);
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/utils/PhpMetaUtil.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.utils;
2 |
3 | import com.intellij.openapi.util.text.StringUtil;
4 | import com.intellij.psi.PsiElement;
5 | import com.intellij.util.ObjectUtils;
6 | import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpCallInstruction;
7 | import com.jetbrains.php.lang.PhpLangUtil;
8 | import com.jetbrains.php.lang.psi.elements.FunctionReference;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | /**
12 | * @author Thomas Schulz
13 | */
14 | public class PhpMetaUtil {
15 | private static final String META_FILENAME = ".phpstorm.meta.php";
16 | private static final String META_NAMESPACE_PREFIX = "\\PHPSTORM_META\\";
17 |
18 | public static boolean isMetaFilename(CharSequence fileName) {
19 | return StringUtil.equals(fileName, META_FILENAME);
20 | }
21 |
22 | public static @NotNull String getMemberFQN(@NotNull String memberName) {
23 | return META_NAMESPACE_PREFIX + memberName;
24 | }
25 |
26 | public static FunctionReference getMetaFunctionReferenceWithName(PhpCallInstruction instruction, String name) {
27 | FunctionReference reference = instruction.getFunctionReference();
28 | if (!metaFunctionWithName(reference, name)) {
29 | return null;
30 | } else {
31 | PsiElement[] parameters = reference.getParameters();
32 | return parameters.length < 1 ? null : ObjectUtils.tryCast(parameters[0], FunctionReference.class);
33 | }
34 | }
35 |
36 | private static boolean metaFunctionWithName(FunctionReference reference, String name) {
37 | return PhpLangUtil.equalsClassNames(reference.getName(), name) && META_NAMESPACE_PREFIX.equals(reference.getNamespaceName());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/utils/StringUtil.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.utils;
2 |
3 | /**
4 | * @author Thomas Schulz
5 | */
6 | public class StringUtil {
7 |
8 | public static int getPrecedingCharNum(CharSequence charSequence, int offset, char character) {
9 | int num = 0;
10 | while (offset >= 1 && charSequence.charAt(offset - 1) == character) {
11 | offset--;
12 | num++;
13 | }
14 |
15 | return num;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/net/king2500/plugins/PhpAdvancedAutoComplete/utils/XmlUtil.java:
--------------------------------------------------------------------------------
1 | package net.king2500.plugins.PhpAdvancedAutoComplete.utils;
2 |
3 | import org.w3c.dom.Document;
4 | import org.w3c.dom.NodeList;
5 | import org.xml.sax.SAXException;
6 |
7 | import javax.xml.parsers.DocumentBuilder;
8 | import javax.xml.parsers.DocumentBuilderFactory;
9 | import javax.xml.parsers.ParserConfigurationException;
10 | import javax.xml.xpath.*;
11 | import java.io.File;
12 | import java.io.IOException;
13 |
14 | /**
15 | * @author Thomas Schulz
16 | */
17 | public class XmlUtil {
18 | public static NodeList getNodesByXPath(File file, String xpath) {
19 | if (!file.isFile() || !file.canRead()) {
20 | return null;
21 | }
22 |
23 | Document document;
24 | DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
25 |
26 | try {
27 | DocumentBuilder documentBuilder = dbFactory.newDocumentBuilder();
28 | document = documentBuilder.parse(file);
29 | } catch (ParserConfigurationException e) {
30 | return null;
31 | } catch (SAXException e) {
32 | return null;
33 | } catch (IOException e) {
34 | return null;
35 | }
36 |
37 | if (document == null) {
38 | return null;
39 | }
40 |
41 | Object result;
42 | try {
43 | XPath xpathCompiler = XPathFactory.newInstance().newXPath();
44 | XPathExpression xPathExpr = xpathCompiler.compile(xpath);
45 | result = xPathExpr.evaluate(document, XPathConstants.NODESET);
46 | } catch (XPathExpressionException e) {
47 | return null;
48 | }
49 |
50 | return (NodeList)result;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------