diff --git a/CHANGES.md b/CHANGES.md index 8049d22..5266eef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,14 +1,16 @@ -**Added** -* - -**Changed** -* - -**Fixed** -* - -**Notes** -* ArmaAddonsManager needs to finish ArmaAddonsIndexingCallback.java utilization by incrementing an addon's current work progress and total work progress -* WE NEED TO DO THIS: https://github.com/kayler-renslow/arma-intellij-plugin/issues/45 -* What if we automatically mark any Addons in the current module that the user has (they are developing an addon) as a reference directory? - We could reuse code this way. People could also reference other projects if we had multiple reference directories without needing to copy and paste stuff everywhere +**Added** +* auto completion for literals (ctrl+space on disableAI will reveal things like "AUTOCOMBAT") + +**Changed** +* removed duplicate vars from auto completion +* prioritized auto completion such that literals are always first, config functions are second, vars are third, and commands are last. + +**Fixed** +* scenario where config functions couldn't be located when no directory was marked as sources root. + This was resolved by assuming the parent directory of the module .iml file was the src root. + +**Notes** +* ArmaAddonsManager needs to finish ArmaAddonsIndexingCallback.java utilization by incrementing an addon's current work progress and total work progress +* WE NEED TO DO THIS: https://github.com/kayler-renslow/arma-intellij-plugin/issues/45 +* What if we automatically mark any Addons in the current module that the user has (they are developing an addon) as a reference directory? + We could reuse code this way. People could also reference other projects if we had multiple reference directories without needing to copy and paste stuff everywhere diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml index 474a7db..4369de6 100644 --- a/META-INF/plugin.xml +++ b/META-INF/plugin.xml @@ -2,7 +2,7 @@ com.kaylerrenslow.plugin.armaplugin.id Arma IntelliJ Plugin - 2.0.1 + 2.0.2 Kayler Renslow diff --git a/VERSION changelog.md b/VERSION changelog.md index 22570a7..9918234 100644 --- a/VERSION changelog.md +++ b/VERSION changelog.md @@ -1,12 +1,17 @@ -**Version:** 2.0.1 -**Release Date:** February 13, 2018 +**Version:** 2.0.2 (NOTE TO SELF: update plugin.xml version) +**Release Date:** June 21, 2018 **Added** -* +* auto completion for literals (ctrl+space on disableAI will reveal things like "AUTOCOMBAT") **Changed** -* Fixed https://github.com/kayler-renslow/arma-intellij-plugin/issues/70 -* Updated command documentation and command syntaxes to 1.82 +* removed duplicate vars from auto completion +* prioritized auto completion such that literals are always first, config functions are second, vars are third, and commands are last. + +**Fixed** +* scenario where config functions couldn't be located when no directory was marked as sources root. + This was resolved by assuming the parent directory of the module .iml file was the src root. +* https://github.com/kayler-renslow/arma-intellij-plugin/issues/73 **Known Issues** * \ No newline at end of file diff --git a/resources/com/kaylerrenslow/armaplugin/SQFBundle.properties b/resources/com/kaylerrenslow/armaplugin/SQFBundle.properties index 1a3fd1f..2900b92 100644 --- a/resources/com/kaylerrenslow/armaplugin/SQFBundle.properties +++ b/resources/com/kaylerrenslow/armaplugin/SQFBundle.properties @@ -1,22 +1,24 @@ -#@formatter:off -# This bundle is for strings related to SQF code. Do not use for plugin code. -# This is shown at the top of every command/bis function documentation window -SQFStatic.external-wiki-link=Online Wiki link: %1$s

Green links are external links.

-FindUsagesProvider.HelpId.function=Function -FindUsagesProvider.HelpId.value_read=Value read -FindUsagesProvider.HelpId.string=String -FindUsagesProvider.Type.function=Function -FindUsagesProvider.Type.variable=Variable -FindUsagesProvider.Type.command=Command -FindUsagesProvider.Type.string=String -FindUsagesProvider.Type.unknown=Unknown Type - -Inspections.CommandCamelCase.display-name=Command Camel Case -Inspections.CommandCamelCase.annotator-problem-description=Command is not camel case (e.g. camelCase). -Inspections.CommandCamelCase.quickfix=Make the Command camel case. - -Inspections.SyntaxAndTypeCheck.display-name=SQF Syntax and Type Checking - -DocTagsAutoCompletion.trail_text.command = Documentation link for Commands -DocTagsAutoCompletion.trail_text.bis = Documentation link for BIS functions -DocTagsAutoCompletion.trail_text.fnc = Documentation link for description.ext/config.cpp config functions +#@formatter:off +# This bundle is for strings related to SQF code. Do not use for plugin code. +# This is shown at the top of every command/bis function documentation window +SQFStatic.external-wiki-link=Online Wiki link: %1$s

Green links are external links.

+FindUsagesProvider.HelpId.function=Function +FindUsagesProvider.HelpId.value_read=Value read +FindUsagesProvider.HelpId.string=String +FindUsagesProvider.Type.function=Function +FindUsagesProvider.Type.variable=Variable +FindUsagesProvider.Type.command=Command +FindUsagesProvider.Type.string=String +FindUsagesProvider.Type.unknown=Unknown Type + +Inspections.CommandCamelCase.display-name=Command Camel Case +Inspections.CommandCamelCase.annotator-problem-description=Command is not camel case (e.g. camelCase). +Inspections.CommandCamelCase.quickfix=Make the Command camel case. + +Inspections.SyntaxAndTypeCheck.display-name=SQF Syntax and Type Checking + +DocTagsAutoCompletion.trail_text.command = Documentation link for Commands +DocTagsAutoCompletion.trail_text.bis = Documentation link for BIS functions +DocTagsAutoCompletion.trail_text.fnc = Documentation link for description.ext/config.cpp config functions + +CompletionContributors.literal=Literal \ No newline at end of file diff --git a/src/com/kaylerrenslow/armaplugin/VirtualFileHeaderFileTextProvider.java b/src/com/kaylerrenslow/armaplugin/VirtualFileHeaderFileTextProvider.java index 50e7bef..da09211 100644 --- a/src/com/kaylerrenslow/armaplugin/VirtualFileHeaderFileTextProvider.java +++ b/src/com/kaylerrenslow/armaplugin/VirtualFileHeaderFileTextProvider.java @@ -1,129 +1,142 @@ -package com.kaylerrenslow.armaplugin; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.kaylerrenslow.armaDialogCreator.arma.header.HeaderFileTextProvider; -import com.kaylerrenslow.armaplugin.settings.ArmaPluginProjectSettings; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Scanner; - -/** - * A {@link HeaderFileTextProvider} implementation that accepts {@link VirtualFile} instances - * - * @author Kayler - * @since 12/09/2017 - */ -public class VirtualFileHeaderFileTextProvider implements HeaderFileTextProvider { - - @NotNull - private final VirtualFile virtualFile; - @NotNull - private final Project project; - - public VirtualFileHeaderFileTextProvider(@NotNull VirtualFile virtualFile, @NotNull Project project) { - this.virtualFile = virtualFile; - this.project = project; - } - - @Override - @NotNull - public Scanner newTextScanner() throws IOException { - PsiFile file = PsiManager.getInstance(project).findFile(virtualFile); - if (file == null) { - throw new FileNotFoundException("File " + virtualFile + " couldn't be found"); - - } - return new Scanner(file.getText()); - } - - @Override - @NotNull - public String getFileName() { - return virtualFile.getName(); - } - - @Override - @NotNull - public String getFilePath() { - return virtualFile.getPath(); - } - - @Override - public long getFileLength() { - return virtualFile.getLength(); - } - - @Override - @Nullable - public HeaderFileTextProvider resolvePath(@NotNull String path) { - VirtualFile resolvedFile = null; - path = path.replaceAll("\\\\", "/"); - if (!path.startsWith("/")) { - VirtualFile srcRoot = ProjectFileIndex.getInstance(project).getSourceRootForFile(this.virtualFile); - if (srcRoot == null) { - return null; - } - - resolvedFile = srcRoot.findFileByRelativePath(path); - if (resolvedFile == null) { - return null; - } - return new VirtualFileHeaderFileTextProvider(resolvedFile, project); - } - - path = path.substring(1); //remove \ - String addonPrefix = ArmaPluginProjectSettings.getInstance(this.project).getState().addonPrefixName; - if (addonPrefix != null) { - if (path.startsWith(addonPrefix + "/")) { - path = path.substring((addonPrefix + "/").length()); - VirtualFile srcRoot = ProjectFileIndex.getInstance(project).getSourceRootForFile(this.virtualFile); - if (srcRoot == null) { - return null; - } - - resolvedFile = srcRoot.findFileByRelativePath(path); - if (resolvedFile != null) { - return new VirtualFileHeaderFileTextProvider(resolvedFile, project); - } - } - } - - Path pathAsPathObj = null; - try { - pathAsPathObj = Paths.get(path); - } catch (InvalidPathException ignore) { - } - if (pathAsPathObj == null) { - return null; - } - List addons = ArmaAddonsManager.getAddons(); - for (ArmaAddon addon : addons) { - File parentFile = addon.getAddonDirectoryInReferenceDirectory().getParentFile(); - if (parentFile == null) { - continue; - } - Path resolved = parentFile.toPath().resolve(pathAsPathObj); - if (resolved == null) { - continue; - } - File file = resolved.toFile(); - if (!file.exists()) { - continue; - } - return new HeaderFileTextProvider.BasicFileInput(file); - } - return null; - } -} +package com.kaylerrenslow.armaplugin; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.kaylerrenslow.armaDialogCreator.arma.header.HeaderFileTextProvider; +import com.kaylerrenslow.armaplugin.settings.ArmaPluginProjectSettings; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Scanner; + +/** + * A {@link HeaderFileTextProvider} implementation that accepts {@link VirtualFile} instances + * + * @author Kayler + * @since 12/09/2017 + */ +public class VirtualFileHeaderFileTextProvider implements HeaderFileTextProvider { + + @NotNull + private final VirtualFile virtualFile; + @NotNull + private final Project project; + + public VirtualFileHeaderFileTextProvider(@NotNull VirtualFile virtualFile, @NotNull Project project) { + this.virtualFile = virtualFile; + this.project = project; + } + + @Override + @NotNull + public Scanner newTextScanner() throws IOException { + PsiFile file = PsiManager.getInstance(project).findFile(virtualFile); + if (file == null) { + throw new FileNotFoundException("File " + virtualFile + " couldn't be found"); + + } + return new Scanner(file.getText()); + } + + @Override + @NotNull + public String getFileName() { + return virtualFile.getName(); + } + + @Override + @NotNull + public String getFilePath() { + return virtualFile.getPath(); + } + + @Override + public long getFileLength() { + return virtualFile.getLength(); + } + + @Override + @Nullable + public HeaderFileTextProvider resolvePath(@NotNull String path) { + VirtualFile resolvedFile = null; + path = path.replaceAll("\\\\", "/"); + if (!path.startsWith("/")) { + VirtualFile srcRoot = ProjectFileIndex.getInstance(project).getSourceRootForFile(this.virtualFile); + if (srcRoot == null) { + Module module = ModuleUtil.findModuleForFile(this.virtualFile, project); + if (module == null) { + return null; + } + srcRoot = module.getModuleFile(); + if (srcRoot == null) { + return null; + } + srcRoot = srcRoot.getParent(); + if (srcRoot == null) { + return null; + } + } + + resolvedFile = srcRoot.findFileByRelativePath(path); + if (resolvedFile == null) { + return null; + } + return new VirtualFileHeaderFileTextProvider(resolvedFile, project); + } + + path = path.substring(1); //remove \ + String addonPrefix = ArmaPluginProjectSettings.getInstance(this.project).getState().addonPrefixName; + if (addonPrefix != null) { + if (path.startsWith(addonPrefix + "/")) { + path = path.substring((addonPrefix + "/").length()); + VirtualFile srcRoot = ProjectFileIndex.getInstance(project).getSourceRootForFile(this.virtualFile); + if (srcRoot == null) { + return null; + } + + resolvedFile = srcRoot.findFileByRelativePath(path); + if (resolvedFile != null) { + return new VirtualFileHeaderFileTextProvider(resolvedFile, project); + } + } + } + + Path pathAsPathObj = null; + try { + pathAsPathObj = Paths.get(path); + } catch (InvalidPathException ignore) { + } + if (pathAsPathObj == null) { + return null; + } + List addons = ArmaAddonsManager.getAddons(); + for (ArmaAddon addon : addons) { + File parentFile = addon.getAddonDirectoryInReferenceDirectory().getParentFile(); + if (parentFile == null) { + continue; + } + Path resolved = parentFile.toPath().resolve(pathAsPathObj); + if (resolved == null) { + continue; + } + File file = resolved.toFile(); + if (!file.exists()) { + continue; + } + return new HeaderFileTextProvider.BasicFileInput(file); + } + return null; + } +} diff --git a/src/com/kaylerrenslow/armaplugin/lang/sqf/SQFCompletionContributor.java b/src/com/kaylerrenslow/armaplugin/lang/sqf/SQFCompletionContributor.java index 3244c54..85d3ecc 100644 --- a/src/com/kaylerrenslow/armaplugin/lang/sqf/SQFCompletionContributor.java +++ b/src/com/kaylerrenslow/armaplugin/lang/sqf/SQFCompletionContributor.java @@ -1,64 +1,78 @@ -package com.kaylerrenslow.armaplugin.lang.sqf; - -import com.intellij.codeInsight.completion.CompletionContributor; -import com.intellij.codeInsight.completion.CompletionType; -import com.intellij.patterns.PlatformPatterns; -import com.kaylerrenslow.armaplugin.lang.sqf.completion.SQFDocTagsCompletionProvider; -import com.kaylerrenslow.armaplugin.lang.sqf.completion.SQFFunctionNameCompletionProvider; -import com.kaylerrenslow.armaplugin.lang.sqf.completion.SQFLocalizeCompletionProvider; -import com.kaylerrenslow.armaplugin.lang.sqf.completion.SQFVariableCompletionProvider; -import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFCommand; -import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFPsiCommandAfter; -import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFTypes; - -import static com.intellij.patterns.PlatformPatterns.*; - -/** - * @author Kayler - * @since 09/09/2017 - */ -public class SQFCompletionContributor extends CompletionContributor { - public SQFCompletionContributor() { - extend(CompletionType.BASIC, - PlatformPatterns.psiElement(SQFTypes.GLOBAL_VAR).withLanguage(SQFLanguage.INSTANCE), - new SQFVariableCompletionProvider(false) - ); - extend(CompletionType.BASIC, - PlatformPatterns.psiElement(SQFTypes.LOCAL_VAR).withLanguage(SQFLanguage.INSTANCE), - new SQFVariableCompletionProvider(true) - ); - - extend(CompletionType.BASIC, - PlatformPatterns.psiComment().withLanguage(SQFLanguage.INSTANCE), - new SQFDocTagsCompletionProvider() - ); - - extend( - CompletionType.BASIC, - PlatformPatterns.psiElement(SQFTypes.GLOBAL_VAR) - .withAncestor(4, - psiElement(SQFPsiCommandAfter.class).afterSibling( - psiElement(SQFCommand.class) - .withText( - or(object("call"), object("spawn")) - ) - ) - ).withLanguage(SQFLanguage.INSTANCE), - new SQFFunctionNameCompletionProvider() - ); - - extend( - CompletionType.BASIC, - PlatformPatterns.psiElement(SQFTypes.GLOBAL_VAR) - .withAncestor(4, - psiElement(SQFPsiCommandAfter.class).afterSibling( - psiElement(SQFCommand.class) - .withText( - object("localize") - ) - ) - ).withLanguage(SQFLanguage.INSTANCE), - new SQFLocalizeCompletionProvider() - ); - } -} +package com.kaylerrenslow.armaplugin.lang.sqf; + +import com.intellij.codeInsight.completion.*; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiElement; +import com.intellij.util.ProcessingContext; +import com.kaylerrenslow.armaplugin.lang.sqf.completion.*; +import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFCommand; +import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFPsiCommandAfter; +import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFTypes; +import org.jetbrains.annotations.NotNull; + +import static com.intellij.patterns.PlatformPatterns.*; + +/** + * @author Kayler + * @since 09/09/2017 + */ +public class SQFCompletionContributor extends CompletionContributor { + public SQFCompletionContributor() { + extend(CompletionType.BASIC, + PlatformPatterns.psiElement(SQFTypes.GLOBAL_VAR).withLanguage(SQFLanguage.INSTANCE), + new SQFVariableCompletionProvider(false) + ); + extend(CompletionType.BASIC, + PlatformPatterns.psiElement(SQFTypes.LOCAL_VAR).withLanguage(SQFLanguage.INSTANCE), + new SQFVariableCompletionProvider(true) + ); + extend(CompletionType.BASIC, + PlatformPatterns.psiElement(SQFTypes.STRING_LITERAL).withLanguage(SQFLanguage.INSTANCE), + new CompletionProvider() { + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext processingContext, @NotNull CompletionResultSet result) { + PsiElement cursor = parameters.getOriginalPosition(); //cursor is on a word + + boolean originalPositionNull = cursor == null; + if (originalPositionNull) { + cursor = parameters.getPosition(); //cursor is after a word + } + CompletionAdders.addLiterals(cursor, result, true); + } + } + ); + + extend(CompletionType.BASIC, + PlatformPatterns.psiComment().withLanguage(SQFLanguage.INSTANCE), + new SQFDocTagsCompletionProvider() + ); + + extend( + CompletionType.BASIC, + PlatformPatterns.psiElement(SQFTypes.GLOBAL_VAR) + .withAncestor(4, + psiElement(SQFPsiCommandAfter.class).afterSibling( + psiElement(SQFCommand.class) + .withText( + or(object("call"), object("spawn")) + ) + ) + ).withLanguage(SQFLanguage.INSTANCE), + new SQFFunctionNameCompletionProvider() + ); + + extend( + CompletionType.BASIC, + PlatformPatterns.psiElement(SQFTypes.GLOBAL_VAR) + .withAncestor(4, + psiElement(SQFPsiCommandAfter.class).afterSibling( + psiElement(SQFCommand.class) + .withText( + object("localize") + ) + ) + ).withLanguage(SQFLanguage.INSTANCE), + new SQFLocalizeCompletionProvider() + ); + } +} diff --git a/src/com/kaylerrenslow/armaplugin/lang/sqf/completion/CompletionAdders.java b/src/com/kaylerrenslow/armaplugin/lang/sqf/completion/CompletionAdders.java index f019a10..4e08ec6 100644 --- a/src/com/kaylerrenslow/armaplugin/lang/sqf/completion/CompletionAdders.java +++ b/src/com/kaylerrenslow/armaplugin/lang/sqf/completion/CompletionAdders.java @@ -1,72 +1,152 @@ -package com.kaylerrenslow.armaplugin.lang.sqf.completion; - -import com.intellij.codeInsight.completion.CompletionParameters; -import com.intellij.codeInsight.completion.CompletionResultSet; -import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.openapi.project.Project; -import com.kaylerrenslow.armaplugin.ArmaPluginIcons; -import com.kaylerrenslow.armaplugin.ArmaPluginUserData; -import com.kaylerrenslow.armaplugin.lang.PsiUtil; -import com.kaylerrenslow.armaplugin.lang.header.HeaderConfigFunction; -import com.kaylerrenslow.armaplugin.lang.sqf.SQFFileType; -import com.kaylerrenslow.armaplugin.lang.sqf.SQFStatic; -import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFCommand; -import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFVariable; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -/** - * @author Kayler - * @since 12/10/2017 - */ -public class CompletionAdders { - /** - * Adds all description.ext/config.cpp functions to the completion result - */ - static void addFunctions(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { - List allConfigFunctions = ArmaPluginUserData.getInstance().getAllConfigFunctions(parameters.getOriginalFile()); - if (allConfigFunctions == null) { - return; - } - for (HeaderConfigFunction function : allConfigFunctions) { - result.addElement(LookupElementBuilder.create(function) - .withIcon(HeaderConfigFunction.getIcon()) - .withPresentableText(function.getCallableName()) - ); - } - } - - /** - * Adds all SQF commands to the completion result - */ - static void addCommands(@NotNull Project project, @NotNull CompletionResultSet result) { - for (String command : SQFStatic.LIST_COMMANDS) { - SQFCommand cmd = PsiUtil.createElement(project, command, SQFFileType.INSTANCE, SQFCommand.class); - if (cmd == null) { - continue; - } - result.addElement(LookupElementBuilder.createWithSmartPointer(command, cmd) - .withIcon(ArmaPluginIcons.ICON_SQF_COMMAND) - .appendTailText(" (Command)", true) - ); - } - } - - /** - * Adds all SQF BIS functions to the result - */ - static void addBISFunctions(@NotNull Project project, @NotNull CompletionResultSet result) { - for (String functionName : SQFStatic.LIST_BIS_FUNCTIONS) { - SQFVariable fnc = PsiUtil.createElement(project, functionName, SQFFileType.INSTANCE, SQFVariable.class); - if (fnc == null) { - continue; - } - result.addElement(LookupElementBuilder.createWithSmartPointer(functionName, fnc) - .withIcon(ArmaPluginIcons.ICON_SQF_FUNCTION) - .appendTailText(" Bohemia Interactive Function", true) - ); - } - } -} - +package com.kaylerrenslow.armaplugin.lang.sqf.completion; + +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.completion.PrioritizedLookupElement; +import com.intellij.codeInsight.lookup.AutoCompletionPolicy; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.util.ProcessingContext; +import com.kaylerrenslow.armaplugin.ArmaPluginIcons; +import com.kaylerrenslow.armaplugin.ArmaPluginUserData; +import com.kaylerrenslow.armaplugin.lang.PsiUtil; +import com.kaylerrenslow.armaplugin.lang.header.HeaderConfigFunction; +import com.kaylerrenslow.armaplugin.lang.sqf.SQFFileType; +import com.kaylerrenslow.armaplugin.lang.sqf.SQFStatic; +import com.kaylerrenslow.armaplugin.lang.sqf.psi.*; +import com.kaylerrenslow.armaplugin.lang.sqf.syntax.CommandDescriptor; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.List; + +/** + * @author Kayler + * @since 12/10/2017 + */ +public class CompletionAdders { + private static int VAR_PRIORITY = 7000; + private static int FUNCTION_PRIORITY = 8000; + private static int LITERAL_PRIORITY = 9000; + + public static void addVariables(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result, + @NotNull PsiElement cursor, boolean forLocalVars) { + HashSet putVars = new HashSet<>(); + PsiUtil.traverseDepthFirstSearch(parameters.getOriginalFile().getNode(), astNode -> { + PsiElement nodeAsElement = astNode.getPsi(); + if (nodeAsElement == cursor) { + return false; + } + if (!(nodeAsElement instanceof SQFVariable)) { + return false; + } + SQFVariable var = (SQFVariable) nodeAsElement; + if (((var.isLocal() && forLocalVars) || (!var.isLocal() && !forLocalVars)) && !putVars.contains(var.getVarName().toLowerCase())) { + putVars.add(var.getVarName().toLowerCase()); + result.addElement(PrioritizedLookupElement.withPriority( + LookupElementBuilder.createWithSmartPointer(var.getVarName(), var) + .withTailText(var.isMagicVar() ? " (Magic Var)" : ( + forLocalVars ? " (Local Variable)" : " (Global Variable)" + ) + ) + .withIcon(var.isMagicVar() ? ArmaPluginIcons.ICON_SQF_MAGIC_VARIABLE : ArmaPluginIcons.ICON_SQF_VARIABLE), VAR_PRIORITY) + ); + } + return false; + }); + } + + /** + * Adds all literals to completion set for all parent commands expression at the cursor. + * + * @see CommandDescriptor#getAllLiterals() + */ + public static void addLiterals(@NotNull PsiElement cursor, @NotNull CompletionResultSet result, boolean trimQuotes) { + ASTNode ancestor = PsiUtil.getFirstAncestorOfType(cursor.getNode(), SQFTypes.EXPRESSION_STATEMENT, null); + if (ancestor == null) { + return; + } + + PsiUtil.traverseDepthFirstSearch(ancestor, astNode -> { + PsiElement nodeAsPsi = astNode.getPsi(); + if (nodeAsPsi == cursor) { + return true; + } + if (!(nodeAsPsi instanceof SQFCommandExpression)) { + return false; + } + SQFCommandExpression commandExpression = (SQFCommandExpression) nodeAsPsi; + SQFExpressionOperator operator = commandExpression.getExprOperator(); + + CommandDescriptor descriptor = SQFSyntaxHelper.getInstance().getDescriptor(operator.getText()); + if (descriptor == null) { + return false; + } + for (String s : descriptor.getAllLiterals()) { + result.addElement( + PrioritizedLookupElement.withPriority(LookupElementBuilder.create(trimQuotes ? s.substring(1, s.length() - 1) : s) + .bold() + .withTailText(" (" + SQFStatic.getSQFBundle().getString("CompletionContributors.literal") + ")") + .withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE), LITERAL_PRIORITY + ) + ); + } + + return false; + }); + } + + /** + * Adds all description.ext/config.cpp functions to the completion result + */ + public static void addFunctions(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { + List allConfigFunctions = ArmaPluginUserData.getInstance().getAllConfigFunctions(parameters.getOriginalFile()); + if (allConfigFunctions == null) { + return; + } + for (HeaderConfigFunction function : allConfigFunctions) { + result.addElement( + PrioritizedLookupElement.withPriority( + LookupElementBuilder.create(function) + .withIcon(HeaderConfigFunction.getIcon()) + .withPresentableText(function.getCallableName()), FUNCTION_PRIORITY + ) + ); + } + } + + /** + * Adds all SQF commands to the completion result + */ + public static void addCommands(@NotNull Project project, @NotNull CompletionResultSet result) { + for (String command : SQFStatic.LIST_COMMANDS) { + SQFCommand cmd = PsiUtil.createElement(project, command, SQFFileType.INSTANCE, SQFCommand.class); + if (cmd == null) { + continue; + } + result.addElement(LookupElementBuilder.createWithSmartPointer(command, cmd) + .withIcon(ArmaPluginIcons.ICON_SQF_COMMAND) + .appendTailText(" (Command)", true) + ); + } + } + + /** + * Adds all SQF BIS functions to the result + */ + public static void addBISFunctions(@NotNull Project project, @NotNull CompletionResultSet result) { + for (String functionName : SQFStatic.LIST_BIS_FUNCTIONS) { + SQFVariable fnc = PsiUtil.createElement(project, functionName, SQFFileType.INSTANCE, SQFVariable.class); + if (fnc == null) { + continue; + } + result.addElement(LookupElementBuilder.createWithSmartPointer(functionName, fnc) + .withIcon(ArmaPluginIcons.ICON_SQF_FUNCTION) + .appendTailText(" Bohemia Interactive Function", true) + ); + } + } +} + diff --git a/src/com/kaylerrenslow/armaplugin/lang/sqf/completion/SQFVariableCompletionProvider.java b/src/com/kaylerrenslow/armaplugin/lang/sqf/completion/SQFVariableCompletionProvider.java index 35cc1f4..57e8b03 100644 --- a/src/com/kaylerrenslow/armaplugin/lang/sqf/completion/SQFVariableCompletionProvider.java +++ b/src/com/kaylerrenslow/armaplugin/lang/sqf/completion/SQFVariableCompletionProvider.java @@ -1,78 +1,54 @@ -package com.kaylerrenslow.armaplugin.lang.sqf.completion; - -import com.intellij.codeInsight.completion.CompletionParameters; -import com.intellij.codeInsight.completion.CompletionProvider; -import com.intellij.codeInsight.completion.CompletionResultSet; -import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.util.ProcessingContext; -import com.kaylerrenslow.armaplugin.ArmaPluginIcons; -import com.kaylerrenslow.armaplugin.lang.PsiUtil; -import com.kaylerrenslow.armaplugin.lang.sqf.psi.SQFVariable; -import org.jetbrains.annotations.NotNull; - -/** - * Used for completing variables and commands - * - * @author Kayler - * @since 12/10/2017 - */ -public class SQFVariableCompletionProvider extends CompletionProvider { - private final boolean forLocalVars; - - /** - * @param forLocalVars true if this completion provider is meant for local variables. - * False if for commands and global variables. - */ - public SQFVariableCompletionProvider(boolean forLocalVars) { - this.forLocalVars = forLocalVars; - } - - @Override - protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { - PsiElement cursor = parameters.getOriginalPosition(); //cursor is on a word - Project project = parameters.getOriginalFile().getProject(); - - boolean originalPositionNull = cursor == null; - if (originalPositionNull) { - cursor = parameters.getPosition(); //cursor is after a word - } - if (forLocalVars) { - if (cursor.getText().startsWith("_fnc")) { - CompletionAdders.addFunctions(parameters, result); - } - addVariables(parameters, result, cursor); - } else { - if (cursor.getText().startsWith("BIS_")) { - CompletionAdders.addBISFunctions(project, result); - } else { - addVariables(parameters, result, cursor); - CompletionAdders.addCommands(project, result); - } - } - } - - private void addVariables(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result, @NotNull PsiElement cursor) { - PsiUtil.traverseDepthFirstSearch(parameters.getOriginalFile().getNode(), astNode -> { - PsiElement nodeAsElement = astNode.getPsi(); - if (nodeAsElement == cursor) { - return false; - } - if (!(nodeAsElement instanceof SQFVariable)) { - return false; - } - SQFVariable var = (SQFVariable) nodeAsElement; - if ((var.isLocal() && forLocalVars) || (!var.isLocal() && !forLocalVars)) { - result.addElement(LookupElementBuilder.createWithSmartPointer(var.getVarName(), var) - .withTailText(var.isMagicVar() ? " (Magic Var)" : ( - forLocalVars ? " (Local Variable)" : " (Global Variable)" - ) - ) - .withIcon(var.isMagicVar() ? ArmaPluginIcons.ICON_SQF_MAGIC_VARIABLE : ArmaPluginIcons.ICON_SQF_VARIABLE) - ); - } - return false; - }); - } -} +package com.kaylerrenslow.armaplugin.lang.sqf.completion; + +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; + +/** + * Used for completing variables and commands + * + * @author Kayler + * @since 12/10/2017 + */ +public class SQFVariableCompletionProvider extends CompletionProvider { + private final boolean forLocalVars; + + /** + * @param forLocalVars true if this completion provider is meant for local variables. + * False if for commands and global variables. + */ + public SQFVariableCompletionProvider(boolean forLocalVars) { + this.forLocalVars = forLocalVars; + } + + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { + PsiElement cursor = parameters.getOriginalPosition(); //cursor is on a word + Project project = parameters.getOriginalFile().getProject(); + + boolean originalPositionNull = cursor == null; + if (originalPositionNull) { + cursor = parameters.getPosition(); //cursor is after a word + } + if (forLocalVars) { + if (cursor.getText().startsWith("_fnc")) { + CompletionAdders.addFunctions(parameters, result); + } + CompletionAdders.addVariables(parameters, context, result, cursor, true); + } else { + if (cursor.getText().startsWith("BIS_")) { + CompletionAdders.addBISFunctions(project, result); + } else { + CompletionAdders.addLiterals(cursor, result, false); + CompletionAdders.addVariables(parameters, context, result, cursor, false); + CompletionAdders.addCommands(project, result); + } + } + } + + +} diff --git a/src/com/kaylerrenslow/armaplugin/lang/sqf/syntax/CommandDescriptor.java b/src/com/kaylerrenslow/armaplugin/lang/sqf/syntax/CommandDescriptor.java index dfb2501..11c5333 100644 --- a/src/com/kaylerrenslow/armaplugin/lang/sqf/syntax/CommandDescriptor.java +++ b/src/com/kaylerrenslow/armaplugin/lang/sqf/syntax/CommandDescriptor.java @@ -1,124 +1,140 @@ -package com.kaylerrenslow.armaplugin.lang.sqf.syntax; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; - -/** - * @author Kayler - * @since 06/11/2016. - */ -public class CommandDescriptor { - /** - * @see CommandXMLInputStream#CommandXMLInputStream(String) - */ - @Nullable - public static CommandDescriptor getDescriptorFromFile(@NotNull String commandName) { - try { - return SQFCommandSyntaxXMLLoader.importFromStream(new CommandXMLInputStream(commandName), false); - } catch (Exception e) { - if (e instanceof UnsupportedOperationException) { - //command doesn't have a syntax xml file - System.out.println(e.getMessage()); - return null; - } - e.printStackTrace(); - return null; - } - } - - private final List syntaxList; - private final String commandName; - private String gameVersion; - private final BIGame game; - - private boolean deprecated = false; - - private boolean uncertain = false; - - public CommandDescriptor(@NotNull String commandName) { - this.commandName = commandName; - syntaxList = Collections.emptyList(); - game = BIGame.UNKNOWN; - } - - public CommandDescriptor(@NotNull String commandName, - @NotNull List syntaxList, - @NotNull String gameVersion, - @NotNull BIGame game) { - this.syntaxList = syntaxList; - this.commandName = commandName; - this.gameVersion = gameVersion; - this.game = game; - } - - /** - * @return a list of {@link CommandSyntax} instances for this command - */ - @NotNull - public List getSyntaxList() { - return syntaxList; - } - - /** - * @return the command's case-sensitive name - */ - @NotNull - public String getCommandName() { - return commandName; - } - - /** - * @return {@link BIGame} that describes that this command was introduced in - */ - @NotNull - public BIGame getGameIntroducedIn() { - return game; - } - - /** - * @return game version of {@link #getGameIntroducedIn()} - */ - @NotNull - public String getGameVersion() { - return gameVersion; - } - - /** - * @return true if the command is deprecated, false if it isn't - */ - public boolean isDeprecated() { - return deprecated; - } - - void setDeprecated(boolean deprecated) { - this.deprecated = deprecated; - } - - /** - * @return true if the syntaxes for the command aren't exactly known and the current syntaxes are estimates - */ - public boolean isUncertain() { - return uncertain; - } - - void setUncertain(boolean uncertain) { - this.uncertain = uncertain; - } - - @Override - public int hashCode() { - return commandName.hashCode(); - } - - @Override - public String toString() { - return "CommandDescriptor{" + - "commandName='" + commandName + '\'' + - ", deprecated=" + deprecated + - ", uncertain=" + uncertain + - '}'; - } -} +package com.kaylerrenslow.armaplugin.lang.sqf.syntax; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Kayler + * @since 06/11/2016. + */ +public class CommandDescriptor { + /** + * @see CommandXMLInputStream#CommandXMLInputStream(String) + */ + @Nullable + public static CommandDescriptor getDescriptorFromFile(@NotNull String commandName) { + try { + return SQFCommandSyntaxXMLLoader.importFromStream(new CommandXMLInputStream(commandName), false); + } catch (Exception e) { + if (e instanceof UnsupportedOperationException) { + //command doesn't have a syntax xml file + System.out.println(e.getMessage()); + return null; + } + e.printStackTrace(); + return null; + } + } + + private final List syntaxList; + private final String commandName; + private String gameVersion; + private final BIGame game; + + private boolean deprecated = false; + + private boolean uncertain = false; + + public CommandDescriptor(@NotNull String commandName) { + this.commandName = commandName; + syntaxList = Collections.emptyList(); + game = BIGame.UNKNOWN; + } + + public CommandDescriptor(@NotNull String commandName, + @NotNull List syntaxList, + @NotNull String gameVersion, + @NotNull BIGame game) { + this.syntaxList = syntaxList; + this.commandName = commandName; + this.gameVersion = gameVersion; + this.game = game; + } + + /** + * @return a list of {@link CommandSyntax} instances for this command + */ + @NotNull + public List getSyntaxList() { + return syntaxList; + } + + /** + * @return the command's case-sensitive name + */ + @NotNull + public String getCommandName() { + return commandName; + } + + /** + * @return {@link BIGame} that describes that this command was introduced in + */ + @NotNull + public BIGame getGameIntroducedIn() { + return game; + } + + /** + * @return game version of {@link #getGameIntroducedIn()} + */ + @NotNull + public String getGameVersion() { + return gameVersion; + } + + /** + * @return true if the command is deprecated, false if it isn't + */ + public boolean isDeprecated() { + return deprecated; + } + + void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } + + /** + * @return true if the syntaxes for the command aren't exactly known and the current syntaxes are estimates + */ + public boolean isUncertain() { + return uncertain; + } + + void setUncertain(boolean uncertain) { + this.uncertain = uncertain; + } + + /** + * @return a list of all literals across all {@link Param}s + * @see Param#getLiterals() + */ + @NotNull + public Iterable getAllLiterals() { + List all = new ArrayList<>(); + for (CommandSyntax syntax : syntaxList) { + for (Param p : syntax.getAllParams()) { + all.addAll(p.getLiterals()); + } + } + return all; + } + + @Override + public int hashCode() { + return commandName.hashCode(); + } + + @Override + public String toString() { + return "CommandDescriptor{" + + "commandName='" + commandName + '\'' + + ", deprecated=" + deprecated + + ", uncertain=" + uncertain + + '}'; + } +}