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

1.0.4

67 | 71 | 72 |

1.0.3

73 | 77 | 78 |

1.0.2

79 | 84 | 85 |

1.0.1

86 | 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 | [![Version](http://phpstorm.espend.de/badge/7276/version)](https://plugins.jetbrains.com/plugin/7276) 3 | [![Downloads](http://phpstorm.espend.de/badge/7276/downloads)](https://plugins.jetbrains.com/plugin/7276) 4 | [![Downloads last month](http://phpstorm.espend.de/badge/7276/last-month)](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 | --------------------------------------------------------------------------------