diff --git a/common/src/com/intellij/plugins/haxe/compilation/HaxeCompilerError.java b/common/src/com/intellij/plugins/haxe/compilation/HaxeCompilerError.java index 810519455..938b87864 100644 --- a/common/src/com/intellij/plugins/haxe/compilation/HaxeCompilerError.java +++ b/common/src/com/intellij/plugins/haxe/compilation/HaxeCompilerError.java @@ -67,6 +67,18 @@ public int getColumn() { return column; } + public boolean isInformationalMessage() { + return CompilerMessageCategory.INFORMATION.equals(category); + } + + public boolean isErrorMessage() { + return CompilerMessageCategory.ERROR.equals(category); + } + + public boolean isWarningMessage() { + return CompilerMessageCategory.WARNING.equals(category); + } + @Nullable public static HaxeCompilerError create(@NotNull String rootPath, final String message) { return create(rootPath, message, true); @@ -116,7 +128,7 @@ else if ((m = pBareError.matcher(message)).matches()) { // match the pattern. else if ((m = pGenericError.matcher(message)).matches()) { String error = m.group(1).trim(); - if (isInformationalMessage(error)) { + if (matchesInformationalPattern(error)) { return new HaxeCompilerError(CompilerMessageCategory.INFORMATION, message.trim(), "", -1, -1); } @@ -188,7 +200,7 @@ private static String buildGenericErrorMessage(String error, String reason) { return msg.toString(); } - private static boolean isInformationalMessage(String message) { + private static boolean matchesInformationalPattern(String message) { Boolean isStatusMessage = pGeneratingStatusMessage.matcher(message).matches(); Boolean isInfoMessages = mInformationalMessages.contains(message); Boolean isCompilingStatusMessage = pCompilingStatusMessage.matcher(message).matches(); diff --git a/src/META-INF/plugin.xml b/src/META-INF/plugin.xml index e56d98e81..044ee463d 100644 --- a/src/META-INF/plugin.xml +++ b/src/META-INF/plugin.xml @@ -37,6 +37,10 @@

Documentation: http://github.com/HaxeFoundation/intellij-haxe/README.md.

Support: http://github.com/HaxeFoundation/intellij-haxe/issues.

+

Unreleased:

+

0.10.1.1: (community release)

  • Update compatibility for 2016.3.2
  • @@ -690,11 +694,14 @@ - + + diff --git a/src/common/com/intellij/plugins/haxe/ide/HaxeCompilerCompletionContributor.java b/src/common/com/intellij/plugins/haxe/ide/HaxeCompilerCompletionContributor.java index c4a7b3114..8b6b6f3d5 100644 --- a/src/common/com/intellij/plugins/haxe/ide/HaxeCompilerCompletionContributor.java +++ b/src/common/com/intellij/plugins/haxe/ide/HaxeCompilerCompletionContributor.java @@ -2,6 +2,7 @@ * Copyright 2000-2013 JetBrains s.r.o. * Copyright 2014-2014 AS3Boyan * Copyright 2014-2014 Elias Ku + * Copyright 2017 Eric Bishton * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,19 +21,23 @@ import com.google.common.base.Joiner; import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.codeStyle.CodeStyleFacade; import com.intellij.compiler.ant.BuildProperties; import com.intellij.ide.highlighter.XmlFileType; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; +import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.impl.status.StatusBarUtil; import com.intellij.patterns.PlatformPatterns; +import com.intellij.plugins.haxe.compilation.HaxeCompilerError; import com.intellij.plugins.haxe.haxelib.HaxelibCommandUtils; import com.intellij.plugins.haxe.ide.module.HaxeModuleSettings; import com.intellij.plugins.haxe.ide.module.HaxeModuleType; @@ -41,24 +46,30 @@ import com.intellij.plugins.haxe.lang.psi.HaxeReferenceExpression; import com.intellij.plugins.haxe.module.impl.HaxeModuleSettingsBaseImpl; import com.intellij.plugins.haxe.util.HaxeDebugLogger; +import com.intellij.plugins.haxe.util.HaxeDebugTimeLog; import com.intellij.plugins.haxe.util.HaxeHelpUtil; import com.intellij.plugins.haxe.util.HaxeSdkUtilBase; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; +import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlDocument; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.LineSeparator; import com.intellij.util.ProcessingContext; +import com.intellij.util.text.StringTokenizer; import icons.HaxeIcons; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.io.LocalFileFinder; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -73,6 +84,9 @@ public class HaxeCompilerCompletionContributor extends CompletionContributor { LOG.setLevel(org.apache.log4j.Level.DEBUG); } + String myErrorMessage = null; + Project myProject; + // Cache to keep the openFL display args (read from the project.xml). We only keep a single // file so that we don't get into cache coherency issues. The worst we will get is if somebody // edits the project file outside of IDEA between edits of a file inside of IDEA. @@ -125,25 +139,6 @@ public List get(Module module, String target) { static final Pattern EMPTY_LINE_REGEX = Pattern.compile( "^\\s+$" ); - // Because FileDocumentManager.getLineSeparator() can assert and force the failure - // of completion, we make sure that there is *always* a line separator available - // on the file, so that we don't fail the completion. - @NotNull - private static void ensureLineSeparatorIsSet(@NotNull Project project, @NotNull Document document, VirtualFile file) { - // FileDocumentManagerImpl.LINE_SEPARATOR_KEY is private. So we have - // to use the deprecated hack to find it. - Key key = (Key)Key.findKeyByName("LINE_SEPARATOR_KEY"); - String lineSeparator = document.getUserData(key); - - if (null == lineSeparator) { - CodeStyleFacade settingsManager = project == null - ? CodeStyleFacade.getInstance() - : CodeStyleFacade.getInstance(project); - lineSeparator = settingsManager.getLineSeparator(); - document.putUserData(key, lineSeparator); - } - } - public HaxeCompilerCompletionContributor() { //Trigger completion only on HaxeReferenceExpressions extend(CompletionType.BASIC, PlatformPatterns.psiElement(HaxeTokenTypes.ID) @@ -155,138 +150,179 @@ public HaxeCompilerCompletionContributor() { protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { - ArrayList commandLineArguments = new ArrayList(); - - PsiFile file = parameters.getOriginalFile(); - - PsiElement position = parameters.getPosition(); - int parent = position.getTextOffset(); - int offset = parent; - - Editor editor = parameters.getEditor(); - Document document = editor.getDocument(); - VirtualFile file2 = file.getVirtualFile(); - Project project = file.getProject(); - ensureLineSeparatorIsSet(project, document, file2); - String separator = FileDocumentManagerImpl.getLineSeparator(document, file2); - - //IntelliJ IDEA normalizes file line endings, so if file line endings is CRLF - then we have to shift an offset so Haxe compiler could get proper offset - if (LineSeparator.CRLF.getSeparatorString().equals(separator)) { - int lineNumber = - com.intellij.openapi.util.text.StringUtil.offsetToLineNumber(parameters.getEditor().getDocument().getText(), offset); - offset += lineNumber; - } + HaxeDebugTimeLog timeLog = HaxeDebugTimeLog.startNew("HaxeCompilerCompletionContributor", + HaxeDebugTimeLog.Since.StartAndPrevious); + + try { + myErrorMessage = null; + PsiFile file = parameters.getOriginalFile(); + myProject = file.getProject(); + Module moduleForFile = ModuleUtil.findModuleForFile(file.getVirtualFile(), myProject); + + ArrayList commandLineArguments = new ArrayList(); - Module moduleForFile = ModuleUtil.findModuleForFile(file.getVirtualFile(), project); - if (moduleForFile != null) { //Make sure module is Haxe Module - if (ModuleUtil.getModuleType(moduleForFile).equals(HaxeModuleType.getInstance())) { + if (moduleForFile != null + && ModuleUtil.getModuleType(moduleForFile).equals(HaxeModuleType.getInstance())) { //Get module settings HaxeModuleSettings moduleSettings = HaxeModuleSettings.getInstance(moduleForFile); int buildConfig = moduleSettings.getBuildConfig(); + VirtualFile projectFile = null; switch (buildConfig) { case HaxeModuleSettings.USE_HXML: - String hxmlPath = moduleSettings.getHxmlPath(); - if (hxmlPath != null) { - VirtualFile file1 = LocalFileFinder.findFile(hxmlPath); - if (file1 != null) { - commandLineArguments.add(HaxeHelpUtil.getHaxePath(moduleForFile)); - commandLineArguments.add(file1.getPath()); - commandLineArguments.add("--display"); - commandLineArguments.add(file.getVirtualFile().getPath() + "@" + Integer.toString(offset)); - - List stderr = - HaxelibCommandUtils.getProcessStderr(commandLineArguments, BuildProperties.getProjectBaseDir(project), - HaxeSdkUtilBase.getSdkData(moduleForFile)); - - getCompletionFromXml(result, project, stderr); - } + projectFile = verifyProjectFile(moduleForFile, "HXML", moduleSettings.getHxmlPath()); + if (null == projectFile) { + break; } + + commandLineArguments.add(HaxeHelpUtil.getHaxePath(moduleForFile)); + commandLineArguments.add(projectFile.getPath()); + + collectCompletionsFromCompiler(parameters, result, commandLineArguments, timeLog); break; case HaxeModuleSettings.USE_NMML: - //Not sure if NME has display command - //Export/flash/haxe contains build.hxml which gets generated after build + projectFile = verifyProjectFile(moduleForFile, "NMML", moduleSettings.getNmmlPath()); + if (null == projectFile) { + break; + } + formatAndAddCompilerArguments(commandLineArguments, moduleSettings.getNmeFlags()); + collectCompletionsFromNME(parameters, result, commandLineArguments, timeLog); break; case HaxeModuleSettingsBaseImpl.USE_OPENFL: - String targetFlag = moduleSettings.getOpenFLTarget().getTargetFlag(); + projectFile = verifyProjectFile(moduleForFile, "OpenFL", moduleSettings.getOpenFLPath()); + if (null == projectFile) { + break; + } + String targetFlag = moduleSettings.getOpenFLTarget().getTargetFlag(); // Load the project defines, etc, from the project.xml file. Cache them. - List compilerArgsFromProjectFile = openFLDisplayArguments.get(moduleForFile, targetFlag); + List compilerArgsFromProjectFile = null; //openFLDisplayArguments.get(moduleForFile, targetFlag); if (compilerArgsFromProjectFile == null) { + ArrayList limeArguments = new ArrayList(); - commandLineArguments.add(HaxelibCommandUtils.getHaxelibPath(moduleForFile)); - commandLineArguments.add("run"); - commandLineArguments.add("lime"); - commandLineArguments.add("display"); + limeArguments.add(HaxelibCommandUtils.getHaxelibPath(moduleForFile)); + limeArguments.add("run"); + limeArguments.add("lime"); + limeArguments.add("display"); //flash, html5, linux, etc + limeArguments.add(targetFlag); - commandLineArguments.add(targetFlag); + // Add arguments from the settings panel. They get echoed out via display, + // if appropriate. + formatAndAddCompilerArguments(limeArguments, moduleSettings.getOpenFLFlags()); - List stdout = HaxelibCommandUtils.getProcessStdout(commandLineArguments, - BuildProperties.getProjectBaseDir(project), - HaxeSdkUtilBase.getSdkData(moduleForFile)); + timeLog.stamp("Get display vars from lime."); + List stdout = HaxelibCommandUtils.getProcessStdout(limeArguments, + BuildProperties.getProjectBaseDir(myProject), + HaxeSdkUtilBase.getSdkData(moduleForFile)); // Need to filter out empty/blank lines. They cause an empty argument to // haxelib, which errors out and breaks completion. - compilerArgsFromProjectFile = new ArrayList(stdout.size()); - for (String l : stdout) { - if ( ! (l.isEmpty() || EMPTY_LINE_REGEX.matcher(l).matches())) { - compilerArgsFromProjectFile.add(l); - } - } - + compilerArgsFromProjectFile = filterEmptyLines(stdout); openFLDisplayArguments.put(moduleForFile, targetFlag, compilerArgsFromProjectFile); } - commandLineArguments.clear(); - commandLineArguments.add(HaxeHelpUtil.getHaxePath(moduleForFile)); - formatAndAddCompilerArguments(commandLineArguments, compilerArgsFromProjectFile); - // Tell the compiler we want field completion, adding the type (var or method) - commandLineArguments.add("-D"); - commandLineArguments.add("display-details"); - commandLineArguments.add("--display"); - - commandLineArguments.add(file.getVirtualFile().getPath() + "@" + Integer.toString(offset)); - - List stderr = - HaxelibCommandUtils.getProcessStderr(commandLineArguments, - BuildProperties.getProjectBaseDir(project), - HaxeSdkUtilBase.getSdkData(moduleForFile)); - try { - getCompletionFromXml(result, project, stderr); - } catch (ProcessCanceledException e) { - LOG.debug("Haxe compiler completion canceled.", e); - result.addLookupAdvertisement("Haxe compiler completion canceled."); - throw e; - } + collectCompletionsFromCompiler(parameters, result, commandLineArguments, timeLog); + break; case HaxeModuleSettingsBaseImpl.USE_PROPERTIES: - String arguments = moduleSettings.getArguments(); - if (!arguments.isEmpty()) { - commandLineArguments.add(HaxeHelpUtil.getHaxePath(moduleForFile)); - formatAndAddCompilerArguments(commandLineArguments, Arrays.asList(arguments.split("\n"))); - commandLineArguments.add("--display"); - commandLineArguments.add(file.getVirtualFile().getPath() + "@" + Integer.toString(offset)); - - List stderr1 = - HaxelibCommandUtils.getProcessStderr(commandLineArguments, - BuildProperties.getProjectBaseDir(project), - HaxeSdkUtilBase.getSdkData(moduleForFile)); - - getCompletionFromXml(result, project, stderr1); - } + commandLineArguments.add(HaxeHelpUtil.getHaxePath(moduleForFile)); + formatAndAddCompilerArguments(commandLineArguments, moduleSettings.getArguments()); + collectCompletionsFromCompiler(parameters, result, commandLineArguments, timeLog); break; } } } + finally { + timeLog.stamp("Finished"); + timeLog.print(); + } } }); } + @Override + public void beforeCompletion(@NotNull CompletionInitializationContext context) { + saveEditsToDisk(context.getFile().getVirtualFile()); + super.beforeCompletion(context); + } + + @Nullable + @Override + public String handleEmptyLookup(@NotNull CompletionParameters parameters, Editor editor) { + if (null != myErrorMessage) { + return myErrorMessage; + } + return super.handleEmptyLookup(parameters, editor); + } + + private List filterEmptyLines(List lines) { + List filtered = new ArrayList(lines.size()); + for (String l : lines) { + if ( ! (l.isEmpty() || EMPTY_LINE_REGEX.matcher(l).matches())) { + filtered.add(l); + } + } + return filtered; + } + + private void advertiseError(String message) { + myErrorMessage = message; + LOG.info(message); // XXX - May happen to often. + StatusBarUtil.setStatusBarInfo(myProject, message); + } + + /** + * Verify that the Haxe project file (.xml, .nmml, etc.; NOT an IDEA project file) + * specified in the build settings is available. Put up an error message if it's not. + */ + private VirtualFile verifyProjectFile(@NotNull Module module, @NotNull String type, @NotNull String path) { + if (path.isEmpty()) { + advertiseError("Completion error: No " + type + " project type is specified in project settings."); // TODO: Externalize string. + return null; + } + + // Look up the project file. + // XXX Might want to use CoreLocalFileSystem instead? + VirtualFile file = LocalFileFinder.findFile(path); + + // If we didn't find it, check the module content roots for it. + if (null == file) { + ModuleRootManager mgr = ModuleRootManager.getInstance(module); + for (VirtualFile contentRoot : mgr.getContentRoots()) { + file = contentRoot.findFileByRelativePath(path); + if (null != file) { + break; + } + } + } + + // If that didn't work, then try the directory the module file (.iml) is in. + if (null == file) { + VirtualFile moduleFile = module.getModuleFile(); + VirtualFile moduleDir = null != moduleFile ? moduleFile.getParent() : null; + file = null != moduleDir ? moduleDir.findFileByRelativePath(path) : null; + } + + + // Still no luck? Try the project root (actually, the directory where the .prj file is). + if (null == file) { + VirtualFile projectDirectory = myProject.getBaseDir(); + file = projectDirectory != null ? projectDirectory.findFileByRelativePath(path) : null; + } + + if (null == file) { + String message = "Completion error: " + type + " project file does not exist: " + path; // TODO: Externalize string. + advertiseError(message); + } + return file; + + } + private void formatAndAddCompilerArguments(ArrayList commandLineArguments, List stdout) { for (int i = 0; i < stdout.size(); i++) { String s = stdout.get(i).trim(); @@ -296,61 +332,235 @@ private void formatAndAddCompilerArguments(ArrayList commandLineArgument } } - private void getCompletionFromXml(CompletionResultSet result, Project project, List stderr) { - if (!stderr.isEmpty() && !stderr.get(0).contains("Error") && stderr.size() > 1) { - String s = Joiner.on("").join(stderr); - PsiFile fileFromText = PsiFileFactory.getInstance(project).createFileFromText("data.xml", XmlFileType.INSTANCE, s); - - XmlFile xmlFile = (XmlFile)fileFromText; - XmlDocument document = xmlFile.getDocument(); - - if (document != null) { - XmlTag rootTag = document.getRootTag(); - if (rootTag != null) { - XmlTag[] xmlTags = rootTag.findSubTags("i"); - for (XmlTag xmlTag : xmlTags) { - String n = xmlTag.getAttribute("n").getValue(); - String k = xmlTag.getAttribute("k").getValue(); - XmlTag t = xmlTag.findFirstSubTag("t"); - XmlTag d = xmlTag.findFirstSubTag("d"); - - LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(n); - if (k != null) { - lookupElementBuilder = lookupElementBuilder.withIcon( - k.equals("var") ? HaxeIcons.Field_Haxe : HaxeIcons.Method_Haxe); - } + private void formatAndAddCompilerArguments(ArrayList commandLineArguments, String flags) { + if (null != flags && !flags.isEmpty()) { + final StringTokenizer flagsTokenizer = new StringTokenizer(flags); + while (flagsTokenizer.hasMoreTokens()) { + String nextToken = flagsTokenizer.nextToken(); + if (!nextToken.isEmpty()) { + commandLineArguments.add(nextToken); + } + } + } + } - String formattedType = ""; - String formattedDescription = ""; + private int recalculateFileOffset(@NotNull CompletionParameters parameters) { + PsiElement position = parameters.getPosition(); + int offset = position.getTextOffset();; - if (t != null) { - formattedType = getFormattedText(t.getValue().getText()); - HaxeCompilerCompletionItem item = parseFunctionParams(formattedType); - String text = ""; + // Get the separator, checking the file if we don't know yet. May still return null. + String separator = LoadTextUtil.detectLineSeparator(parameters.getOriginalFile().getVirtualFile(), true); - if (item.parameters != null) { - String presentableText = n + "(" + Joiner.on(", ").join(item.parameters) + "):" + item.retType; - lookupElementBuilder = lookupElementBuilder.withPresentableText(presentableText); - } - else { - text = formattedType; - } + // IntelliJ IDEA normalizes file line endings, so if file line endings is + // CRLF - then we have to shift an offset so Haxe compiler could get proper offset + if (LineSeparator.CRLF.getSeparatorString().equals(separator)) { + int lineNumber = + com.intellij.openapi.util.text.StringUtil.offsetToLineNumber(parameters.getEditor().getDocument().getText(), offset); + offset += lineNumber; + } + return offset; + } - if (d != null) { - String text1 = d.getValue().getText(); - text1 = getFormattedText(text1); - formattedDescription = text1; - text += " " + formattedDescription; + /** + * Save the file contents to disk so that the compiler has the correct data to work with. + */ + private void saveEditsToDisk(VirtualFile file) { + // TODO: Don't save if we can use the compiler stdin or compiler completion isn't turned off. + // TODO: Add checkbox/setting to turn off compiler completion. + final Document doc = FileDocumentManager.getInstance().getDocument(file); + if (FileDocumentManager.getInstance().isDocumentUnsaved(doc)) { + final Application application = ApplicationManager.getApplication(); + if (application.isDispatchThread()) { + LOG.debug("Saving buffer to disk..."); + FileDocumentManager.getInstance().saveDocumentAsIs(doc); + } else { + LOG.debug("Not on dispatch thread and document is unsaved."); + } + } + } + + private void collectCompletionsFromCompiler(@NotNull CompletionParameters parameters, + @NotNull CompletionResultSet result, + ArrayList commandLineArguments, + HaxeDebugTimeLog timeLog) { + // There is a problem here in that the current buffer may not have been saved. + // If that is the case, then the position is also incorrect, and the compiler + // doesn't have access to the correct sources. If the haxe compiler is version 3.4 or + // later, then it has the -D display-stdin parameter available and we can pump the + // unsaved buffer through to the compiler. (Though that does nothing for completion + // from related but also unsaved buffers.) Doing so will also require the compiler + // server (if used) to be started with the "--wait stdin" parameter. + + PsiFile file = parameters.getOriginalFile(); + Project project = file.getProject(); + Module moduleForFile = ModuleUtil.findModuleForFile(file.getVirtualFile(), project); + int offset = recalculateFileOffset(parameters); + + // Tell the compiler we want field completion, adding the type (var or method) + commandLineArguments.add("-D"); + commandLineArguments.add("display-details"); + commandLineArguments.add("--display"); + + commandLineArguments.add(file.getVirtualFile().getPath() + "@" + Integer.toString(offset)); + + timeLog.stamp("Calling compiler"); + List stderr = + HaxelibCommandUtils.getProcessStderr(commandLineArguments, + BuildProperties.getProjectBaseDir(project), + HaxeSdkUtilBase.getSdkData(moduleForFile)); + timeLog.stamp("Compiler finished"); + parseCompletionFromXml(result, project, stderr); + } + + private void collectCompletionsFromNME(@NotNull CompletionParameters parameters, + @NotNull CompletionResultSet result, + ArrayList commandLineArguments, + HaxeDebugTimeLog timeLog) { + // See note on collectCompletionsFromCompiler. + + PsiFile file = parameters.getOriginalFile(); + Project project = file.getProject(); + Module moduleForFile = ModuleUtil.findModuleForFile(file.getVirtualFile(), project); + int offset = recalculateFileOffset(parameters); + + HaxeModuleSettings moduleSettings = HaxeModuleSettings.getInstance(moduleForFile); + String nmmlPath = moduleSettings.getNmmlPath(); + String nmeTarget = moduleSettings.getNmeTarget().getTargetFlag(); + + // To get completions out of the haxe compiler when NME calls it, we have to + // add nodes to the project.nmml. We'll do that by creating a + // temporary NMML file and including the real project file. + StringBuilder xml = new StringBuilder(); + xml.append(""); + xml.append(""); + + xml.append(""); + + xml.append(""); + + xml.append(""); + xml.append(""); + + File tempFile = null; + try { + tempFile = File.createTempFile("HaxeProjectWrapper", ".nmml"); + FileWriter writer = new FileWriter(tempFile); + writer.write(xml.toString()); + writer.close(); + + commandLineArguments.add(HaxelibCommandUtils.getHaxelibPath(moduleForFile)); + commandLineArguments.add("run"); + commandLineArguments.add("nme"); + commandLineArguments.add("build"); + commandLineArguments.add(tempFile.getPath()); + commandLineArguments.add(nmeTarget); + + timeLog.stamp("Calling NME"); + List stderr = + HaxelibCommandUtils.getProcessStderr(commandLineArguments, + BuildProperties.getProjectBaseDir(project), + HaxeSdkUtilBase.getSdkData(moduleForFile)); + timeLog.stamp("NME finished"); + parseCompletionFromXml(result, project, stderr); + } catch (IOException e) { + advertiseError("Completion failed: Could not create temporary file."); // TODO: Externalize string. + } finally { + if (tempFile != null) { + tempFile.delete(); + } + } + + + } + + private void parseCompletionFromXml(CompletionResultSet result, Project project, List stderr) { + try { + if (!stderr.isEmpty() && stderr.get(0).contains("") && stderr.size() > 1) { + String s = Joiner.on("").join(stderr); + PsiFile fileFromText = PsiFileFactory.getInstance(project).createFileFromText("data.xml", XmlFileType.INSTANCE, s); + + XmlFile xmlFile = (XmlFile)fileFromText; + XmlDocument document = xmlFile.getDocument(); + + if (document != null) { + XmlTag rootTag = document.getRootTag(); + if (rootTag != null) { + XmlTag[] xmlTags = rootTag.findSubTags("i"); + for (XmlTag xmlTag : xmlTags) { + XmlAttribute nAttr = xmlTag.getAttribute("n"); + XmlAttribute kAttr = xmlTag.getAttribute("k"); + String n = null == nAttr ? null : nAttr.getValue(); + String k = null == kAttr ? null : kAttr.getValue(); + XmlTag t = xmlTag.findFirstSubTag("t"); + XmlTag d = xmlTag.findFirstSubTag("d"); + + LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(n); + if (k != null) { + lookupElementBuilder = lookupElementBuilder.withIcon( + k.equals("var") ? HaxeIcons.Field_Haxe : HaxeIcons.Method_Haxe); } - lookupElementBuilder = lookupElementBuilder.withTailText(" " + text, true); + String formattedType = ""; + String formattedDescription = ""; + + if (t != null) { + formattedType = getFormattedText(t.getValue().getText()); + HaxeCompilerCompletionItem item = parseFunctionParams(formattedType); + String text = ""; + + if (item.parameters != null) { + String presentableText = n + "(" + Joiner.on(", ").join(item.parameters) + "):" + item.retType; + lookupElementBuilder = lookupElementBuilder.withPresentableText(presentableText); + } + else { + text = formattedType; + } + + if (d != null) { + String text1 = d.getValue().getText(); + text1 = getFormattedText(text1); + formattedDescription = text1; + text += " " + formattedDescription; + } + + lookupElementBuilder = lookupElementBuilder.withTailText(" " + text, true); + } + result.addElement(lookupElementBuilder); } - result.addElement(lookupElementBuilder); } } } - } else { - result.addLookupAdvertisement("Compiler completion error: " + stderr.get(0)); + else { + if (!stderr.isEmpty()) { + LOG.debug(stderr.toString()); + // Could be a syntax warning. + HaxeCompilerError compilerError = HaxeCompilerError.create( + project.getBaseDir().getPath(), + stderr.get(0)); + StringBuilder msg = new StringBuilder(); + msg.append("Compiler completion"); // TODO: Externalize string. + if (compilerError.isErrorMessage()) { + msg.append(" error"); // TODO: Externalize and don't build the string. + } + msg.append(": "); + msg.append(compilerError.getErrorMessage()); + + String smsg = msg.toString(); + result.addLookupAdvertisement(smsg); + advertiseError(smsg); + } + } + } + catch (ProcessCanceledException e) { + advertiseError("Haxe compiler completion canceled."); // TODO: Externalize string. + LOG.debug("Haxe compiler completion canceled.", e); + throw e; } }