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:
+
+ - Reworked code completion using the compiler.
+
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;
}
}