package com.intellij.perlplugin.extensions; import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.lang.ASTNode; import com.intellij.lang.FileASTNode; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.patterns.PlatformPatterns; import com.intellij.perlplugin.ConfigurationHolder; import com.intellij.perlplugin.ModulesContainer; import com.intellij.perlplugin.Utils; import com.intellij.perlplugin.bo.ImportedSub; import com.intellij.perlplugin.bo.Package; import com.intellij.perlplugin.bo.Sub; import com.intellij.perlplugin.language.PerlIcons; import com.intellij.perlplugin.language.PerlLanguage; import com.intellij.perlplugin.psi.PerlElement; import com.intellij.perlplugin.psi.PerlTypes; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.tree.IElementType; import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.regex.Matcher; public class PerlCompletionContributor extends CompletionContributor { public static final int AUTO_POPUP_PACKAGE_ITEMS_LIMIT = 100; public static final int AUTO_POPUP_SUBS_ITEMS_LIMIT = 50; public static final int AUTO_POPUP_VARS_ITEMS_LIMIT = 200; private static HashMap<String, LookupElement> variablesCache = new HashMap<String, LookupElement>(); private static HashMap<Sub, LookupElement> subsCache = new HashMap<Sub, LookupElement>(); private static HashMap<Sub, LookupElement> subsCacheNoArgs = new HashMap<Sub, LookupElement>(); private static HashMap<Package, LookupElement> packagesCache = new HashMap<Package, LookupElement>(); private static boolean updateFlipper = false; /** * this will try to autocomplete subroutines from package in block like this: * use PACKAGE qw( * sub1 * sub2 * ... * ) * * returns true if contributed some results, false otherwise * @return */ public boolean tryAutocompletePackageSubImportBlock( PerlElement currentElement, @NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) { if (currentElement.isAny(PerlTypes.WHITESPACE, PerlTypes.BRACES, PerlTypes.PROPERTY)) { PerlElement maybeBrace = currentElement; if (!maybeBrace.is(PerlTypes.BRACES)) { //if we are not already on brace go back permitting only vars and whitespace maybeBrace = maybeBrace.previousIgnoring(PerlTypes.WHITESPACE, PerlTypes.VARIABLE, PerlTypes.PROPERTY); } if (maybeBrace.is(PerlTypes.BRACES)) { //after going back check once more if it is brace already PerlElement tmp = maybeBrace.previousIgnoringWhitespace(); if (tmp.is(PerlTypes.LANG_SYNTAX) && tmp.getText().equals("qw")) { PerlElement potentialPackage = tmp.previousIgnoringWhitespace(); if (potentialPackage.is(PerlTypes.PACKAGE)) { tmp = potentialPackage.previousIgnoringWhitespace(); if (tmp.is(PerlTypes.LANG_FUNCTION) && tmp.getText().equals("use")) { addAllSubsInPackage(resultSet, potentialPackage, false, parameters.isAutoPopup()); return true; } } } } } return false; } public PerlCompletionContributor() { CompletionProvider<CompletionParameters> handler = new CompletionProvider<CompletionParameters>() { public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet resultSet) { if (!ModulesContainer.isInitialized()) { Utils.alert("warning: perl parser was not initialized"); return; } Editor editor = parameters.getEditor(); VirtualFile virtualFile = parameters.getOriginalFile().getVirtualFile(); if (virtualFile == null) { return; } PerlElement currentElement = PerlElement.fromCompletionParameter(parameters); PerlElement prevElement = currentElement.previous(); //current psiElement based if (tryAutocompletePackageSubImportBlock(currentElement, parameters, resultSet)) { return; } if (currentElement.is(PerlTypes.PROPERTY)) { addAllPackages(resultSet, currentElement, parameters.isAutoPopup()); if (currentElement.getTextLength() >= 2) { addLanguageKeyword(resultSet, currentElement.getText()); } } else { if (currentElement.is(PerlTypes.WHITESPACE) && !prevElement.is(PerlTypes.POINTER) || currentElement.is(PerlTypes.BRACES)) { addAllSubsInFile(parameters, resultSet, parameters.isAutoPopup()); addAllVariablesInFile(parameters, resultSet, parameters.isAutoPopup()); } else if (currentElement.isAny(PerlTypes.VARIABLE, PerlTypes.VALUE, PerlTypes.PREDICATE, PerlTypes.BRACES, PerlTypes.LANG_SYNTAX)) { addAllVariablesInFile(parameters, resultSet, parameters.isAutoPopup()); } else if (currentElement.is(PerlTypes.PACKAGE)) { addAllPackages(resultSet, currentElement, parameters.isAutoPopup()); }/* else if (is(currentElement, PerlTypes.SUBROUTINE)) { ModulesContainer.updateFile(virtualFile.getPath(),editor.getDocument().getText()); }*/ } //prev element based if (prevElement.is(PerlTypes.POINTER)) { PerlElement prevPrevElement = prevElement.previous(); if (prevPrevElement.is(PerlTypes.PACKAGE)) { //get all subs of package if we are on a package's pointer addAllSubsInPackage(resultSet, prevPrevElement, true, parameters.isAutoPopup()); } else if (prevPrevElement.is(PerlTypes.VARIABLE)) { //get all subs of current package if we are on an variable pointer addAllSubsInFile(parameters, resultSet, parameters.isAutoPopup()); } } else if (prevElement.is(PerlTypes.WHITESPACE)) { addAllSubsInFile(parameters, resultSet, parameters.isAutoPopup()); } //ya, i know this is crappy - temporary fix if ((parameters.isAutoPopup() && updateFlipper) || parameters.getInvocationCount() == 1 || parameters.getInvocationCount() == 3) { ModulesContainer.updateFile(virtualFile.getPath(), editor.getDocument().getText()); updateFlipper = !updateFlipper; } } }; addCompleteHandler(PerlTypes.PROPERTY, handler); addCompleteHandler(PerlTypes.OPERATOR, handler); addCompleteHandler(PerlTypes.POINTER, handler); addCompleteHandler(PerlTypes.PACKAGE, handler); addCompleteHandler(PerlTypes.VARIABLE, handler); addCompleteHandler(PerlTypes.WHITESPACE, handler); addCompleteHandler(PerlTypes.VALUE, handler); addCompleteHandler(PerlTypes.PREDICATE, handler); addCompleteHandler(PerlTypes.LANG_SYNTAX, handler); addCompleteHandler(PerlTypes.BRACES, handler); addCompleteHandler(PerlTypes.SUBROUTINE, handler); } public static void initialize() { float start = 0; if (Utils.verbose) { start = System.currentTimeMillis(); } cacheAllPackages(); cacheAllSubsAndVarsOfOpenedFiles(); if (Utils.verbose) { float end = System.currentTimeMillis(); Utils.print("performance[intcch]: " + ((end - start) / 1000) + "sec"); } } private static void cacheAllPackages() { ArrayList<Package> packages = ModulesContainer.getAllPackages(); for (int i = 0; i < packages.size(); i++) { addCachedPackage(null, packages.get(i)); } } private static void cacheAllSubsAndVarsOfOpenedFiles() { Project[] projects = ProjectManager.getInstance().getOpenProjects(); for (int i = 0; i < projects.length; i++) { VirtualFile[] openFiles = FileEditorManager.getInstance(projects[i]).getOpenFiles(); for (int j = 0; j < openFiles.length; j++) { VirtualFile openFile = openFiles[j]; cacheSingleFile(projects[i], openFile); } } } public static void cacheSingleFile(Project project, VirtualFile openFile) { if(openFile.exists()) { //cache attributes PsiManager psiManager = PsiManager.getInstance(project); if(psiManager == null) return; PsiFile psiFile = psiManager.findFile(openFile); if(psiFile == null) return; FileASTNode astNode = psiFile.getNode(); if(astNode == null) return; HashSet<String> rs = findAllVariables(astNode.getChildren(null), PerlTypes.VARIABLE, false); if(rs == null) return; for (String str : rs) { addCachedVariables(null, str); } //cache subs ArrayList<Package> packages = ModulesContainer.getPackageListFromFile(openFile.getPath()); for (int i = 0; i < packages.size(); i++) { ArrayList<Sub> subs = packages.get(i).getAllSubs(); for (int j = 0; j < subs.size(); j++) { addCachedSub(null, subs.get(j)); } } } } private void addLanguageKeyword(CompletionResultSet resultSet, String text) { //TODO:: order most common used first String keywords = "|abs|accept|alarm|atan2|AUTOLOAD|BEGIN|bind|binmode|bless|break|caller|chdir|CHECK|chmod|chomp|chop|chown|chr|chroot|close|closedir|connect|cos|crypt|dbmclose|dbmopen|defined|delete|DESTROY|die|dump|each|END|endgrent|endhostent|endnetent|endprotoent|endpwent|endservent|eof|eval|exec|exists|exit|fcntl|fileno|flock|fork|format|formline|getc|getgrent|getgrgid|getgrnam|gethostbyaddr|gethostbyname|gethostent|getlogin|getnetbyaddr|getnetbyname|getnetent|getpeername|getpgrp|getppid|getpriority|getprotobyname|getprotobynumber|getprotoent|getpwent|getpwnam|getpwuid|getservbyname|getservbyport|getservent|getsockname|getsockopt|glob|gmtime|goto|grep|hex|index|INIT|int|ioctl|join|keys|kill|last|lc|each|lcfirst|setnetent|length|link|listen|local|localtime|log|lstat|map|mkdir|msgctl|msgget|msgrcv|msgsnd|next|not|oct|open|opendir|ord|our|pack|pipe|pop|pos|print|printf|prototype|push|quotemeta|rand|read|readdir|readline|readlink|readpipe|recv|redo|ref|rename|require|reset|return|reverse|rewinddir|rindex|rmdir|say|scalar|seek|seekdir|select|semctl|semget|semop|send|setgrent|sethostent|each|lcfirst|setnetent|setpgrp|setpriority|setprotoent|setpwent|setservent|setsockopt|shift|shmctl|shmget|shmread|shmwrite|shutdown|sin|sleep|socket|socketpair|sort|splice|split|sprintf|sqrt|srand|stat|state|study|substr|symlink|syscall|sysopen|sysread|sysseek|system|syswrite|tell|telldir|tie|tied|time|times|truncate|ucfirst|umask|undef|UNITCHECK|unlink|unpack|unshift|untie|use|utime|values|vec|wait|waitpid|wantarray|warn|write|each|lcfirst|setnetent|cmp|continue|CORE|else|elsif|exp|for|foreach|lock|package|unless|until|while|ARGV|ARGVOUT|STDERR|STDIN|STDOUT"; Matcher matcher = Utils.applyRegex("\\|(" + text + "[^\\|]+)", keywords); while (matcher.find()) { resultSet.addElement(LookupElementBuilder.create(matcher.group(1))); } } //================== //add cached methods //================== private void addAllPackages(CompletionResultSet resultSet, PerlElement element, boolean limitResults) { ArrayList<Package> packageList = ModulesContainer.searchPackageList(element.getText(), false); for (int i = 0; i < packageList.size(); i++) { addCachedPackage(resultSet, packageList.get(i)); } } private void addAllSubsInPackage(CompletionResultSet resultSet, PerlElement packageName, boolean withArguments, boolean limitResults) { ArrayList<Package> packageList = ModulesContainer.getPackageList(packageName.getText()); if (Utils.verbose) { Utils.print("Detected Package:" + packageName); } for (int i = 0; i < packageList.size(); i++) { Package packageObj = packageList.get(i); ArrayList<Sub> subs = packageObj.getAllSubs(); int amount = (false) ? Math.min(AUTO_POPUP_SUBS_ITEMS_LIMIT, subs.size()) : subs.size();//get all results only if users press ctrl+space for (int j = 0; j < amount; j++) { if (withArguments) { addCachedSub(resultSet, subs.get(j)); } else { addCachedSubNoArgs(resultSet, subs.get(j)); } } } } private void addAllSubsInFile(CompletionParameters parameters, CompletionResultSet resultSet, boolean limitResults) { ArrayList<Package> packageList = ModulesContainer.getPackageListFromFile(parameters.getOriginalFile().getVirtualFile().getCanonicalPath()); for (int i = 0; i < packageList.size(); i++) { ArrayList<Sub> subs = packageList.get(i).getAllSubs(); int amount = (false) ? Math.min(AUTO_POPUP_SUBS_ITEMS_LIMIT, subs.size()) : subs.size();//get all results only if users press ctrl+space for (int j = 0; j < amount; j++) { addCachedSub(resultSet, subs.get(j)); } ArrayList<ImportedSub> importedSubs = packageList.get(i).getImportedSubs(); amount = (false) ? Math.min(AUTO_POPUP_SUBS_ITEMS_LIMIT, importedSubs.size()) : importedSubs.size();//get all results only if users press ctrl+space for (int j = 0; j < amount; j++) { ArrayList<Package> packages = ModulesContainer.getPackageList(importedSubs.get(j).getContainingPackage());//TODO: handle more than 1 package if (packages.size() > 0) { Sub sub = packages.get(0).getSubByName(importedSubs.get(j).getImportSub()); if (sub != null) { addCachedSub(resultSet, sub); } } } } } private void addAllVariablesInFile(CompletionParameters parameters, CompletionResultSet resultSet, boolean limitResults) { HashSet<String> rs = findAllVariables(parameters.getOriginalFile().getNode().getChildren(null), PerlTypes.VARIABLE, false); for (String str : rs) { addCachedVariables(resultSet, str); } } private static void addCachedPackage(CompletionResultSet resultSet, Package packageObj) { if (packageObj != null && !packagesCache.containsKey(packageObj)) { if (Utils.verbose) { Utils.print("cache package: " + packageObj.getQualifiedName()); } packagesCache.put(packageObj, getPackageLookupElementBuilder(packageObj)); } if (resultSet != null) { resultSet.addElement(packagesCache.get(packageObj)); } } private static void addCachedSub(CompletionResultSet resultSet, Sub sub) { if (sub != null && !subsCache.containsKey(sub)) { if (Utils.verbose) { Utils.print("cache sub: " + sub.getName() + " , containingPackage:" + sub.getPackageObj().getQualifiedName()); } subsCache.put(sub, getSubLookupElementBuilder(sub, true)); } if (resultSet != null) { resultSet.addElement(subsCache.get(sub)); } } private static void addCachedSubNoArgs(CompletionResultSet resultSet, Sub sub) { if (!subsCacheNoArgs.containsKey(sub)) { if (Utils.verbose) { Utils.print("cache sub(no args): " + sub.getName() + " , containingPackage:" + sub.getPackageObj().getQualifiedName()); } subsCacheNoArgs.put(sub, getSubLookupElementBuilder(sub, false)); } if (resultSet != null) { resultSet.addElement(subsCacheNoArgs.get(sub)); } } private static void addCachedVariables(CompletionResultSet resultSet, String str) { if (str != null && !variablesCache.containsKey(str)) { if (Utils.verbose) { Utils.print("cache variable: " + str); } variablesCache.put(str, getVariableLookupElementBuilder(str)); } if (resultSet != null) { resultSet.addElement(variablesCache.get(str)); } } //get lookup elements methods private static LookupElement getPackageLookupElementBuilder(Package packageObj) { String text = packageObj.getQualifiedName(); return LookupElementBuilder.create(text).withIcon(PerlIcons.PACKAGE).withTypeText("Package", true); } private static LookupElement getSubLookupElementBuilder(Sub sub, boolean withArguments) { String text = (withArguments) ? sub.toString2(ConfigurationHolder.isHideFirstSelfArgument) : sub.getName(); String containingPackage = sub.getPackageObj().getQualifiedName() + "(" + sub.getPackageObj().getFileName() + ")"; return LookupElementBuilder.create(text).withIcon(PerlIcons.SUBROUTINE).withPresentableText(text).withTypeText(containingPackage, true); } private static LookupElement getVariableLookupElementBuilder(String text) { return LookupElementBuilder.create(text).withIcon(PerlIcons.VARIABLE).withTypeText("Variable", true); } private static HashSet<String> findAllVariables(ASTNode[] children, IElementType type, boolean limitResults) { HashSet<String> resultSet = new HashSet<String>(); return findAllVariables(children, resultSet, type, false); } private static HashSet<String> findAllVariables(ASTNode[] children, HashSet<String> resultSet, IElementType type, boolean limitResults) { int amount = (false) ? Math.min(AUTO_POPUP_VARS_ITEMS_LIMIT, children.length) : children.length;//get all results only if users press ctrl+space for (int i = 0; i < amount; i++) { ASTNode astNode = children[i].findChildByType(type); if (astNode != null) { resultSet.add(astNode.getText()); } else if (children[i].getChildren(null) != null) { findAllVariables(children[i].getChildren(null), resultSet, type, false); } } return resultSet; } private void addCompleteHandler(IElementType elementType, CompletionProvider<CompletionParameters> handler) { extend(CompletionType.BASIC, PlatformPatterns.psiElement(elementType).withLanguage(PerlLanguage.INSTANCE), handler); } public static void clear() { //variablesCache.clear();//temporarily commented to improve performance subsCache.clear(); subsCacheNoArgs.clear(); packagesCache.clear(); } }