/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2014-2014 AS3Boyan
* Copyright 2014-2014 Elias Ku
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.plugins.haxe.ide;
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.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.plugins.haxe.haxelib.HaxelibCommandUtils;
import com.intellij.plugins.haxe.ide.module.HaxeModuleSettings;
import com.intellij.plugins.haxe.ide.module.HaxeModuleType;
import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes;
import com.intellij.plugins.haxe.lang.psi.HaxeIdentifier;
import com.intellij.plugins.haxe.lang.psi.HaxeReferenceExpression;
import com.intellij.plugins.haxe.module.impl.HaxeModuleSettingsBaseImpl;
import com.intellij.plugins.haxe.util.HaxeHelpUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.io.LocalFileFinder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* Created by as3boyan on 25.11.14.
*/
public class HaxeCompilerCompletionContributor extends CompletionContributor {
static HashMap<String, List<String>> openFLDisplayArguments = new HashMap<String, List<String>>();
// 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<String> key = (Key<String>)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)
.withParent(HaxeIdentifier.class)
.withSuperParent(2, HaxeReferenceExpression.class),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
ArrayList<String> commandLineArguments = new ArrayList<String>();
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;
}
Module moduleForFile = ModuleUtil.findModuleForFile(file.getVirtualFile(), project);
if (moduleForFile != null) {
//Make sure module is Haxe Module
if (ModuleUtil.getModuleType(moduleForFile).equals(HaxeModuleType.getInstance())) {
//Get module settings
HaxeModuleSettings moduleSettings = HaxeModuleSettings.getInstance(moduleForFile);
int buildConfig = moduleSettings.getBuildConfig();
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<String> stderr =
HaxelibCommandUtils.getProcessStderr(commandLineArguments, BuildProperties.getProjectBaseDir(project));
getCompletionFromXml(result, project, stderr);
}
}
break;
case HaxeModuleSettings.USE_NMML:
//Not sure if NME has display command
//Export/flash/haxe contains build.hxml which gets generated after build
break;
case HaxeModuleSettingsBaseImpl.USE_OPENFL:
String targetFlag = moduleSettings.getOpenFLTarget().getTargetFlag();
List<String> stdout = openFLDisplayArguments.get(moduleForFile.getModuleFilePath() + targetFlag);
if (stdout == null) {
commandLineArguments.add(HaxelibCommandUtils.getHaxelibPath(moduleForFile));
commandLineArguments.add("run");
commandLineArguments.add("lime");
commandLineArguments.add("display");
//flash, html5, linux, etc
commandLineArguments.add(targetFlag);
stdout = HaxelibCommandUtils.getProcessStdout(commandLineArguments,
BuildProperties.getProjectBaseDir(project));
openFLDisplayArguments.put(moduleForFile.getModuleFilePath() + targetFlag, stdout);
}
commandLineArguments.clear();
commandLineArguments.add(HaxeHelpUtil.getHaxePath(moduleForFile));
formatAndAddCompilerArguments(commandLineArguments, stdout);
commandLineArguments.add("--display");
commandLineArguments.add(file.getVirtualFile().getPath() + "@" + Integer.toString(offset));
List<String> stderr =
HaxelibCommandUtils.getProcessStderr(commandLineArguments, BuildProperties.getProjectBaseDir(project));
getCompletionFromXml(result, project, stderr);
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<String> stderr1 =
HaxelibCommandUtils.getProcessStderr(commandLineArguments, BuildProperties.getProjectBaseDir(project));
getCompletionFromXml(result, project, stderr1);
}
break;
}
}
}
}
});
}
private void formatAndAddCompilerArguments(ArrayList<String> commandLineArguments, List<String> stdout) {
for (int i = 0; i < stdout.size(); i++) {
String s = stdout.get(i).trim();
if (!s.startsWith("#")) {
commandLineArguments.addAll(Arrays.asList(s.split(" ")));
}
}
}
private void getCompletionFromXml(CompletionResultSet result, Project project, List<String> 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();
XmlTag t = xmlTag.findFirstSubTag("t");
XmlTag d = xmlTag.findFirstSubTag("d");
LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(n);
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);
}
}
}
}
}
private String getFormattedText(String text1) {
text1 = text1.replaceAll("\t", "");
text1 = text1.replaceAll("\n", "");
text1 = text1.replaceAll("<", "<");
text1 = text1.replaceAll(">", ">");
text1 = text1.trim();
return text1;
}
//Ported from HIDE
//https://github.com/HaxeIDE/HIDE/blob/master/src/core/FunctionParametersHelper.hx#L193
public HaxeCompilerCompletionItem parseFunctionParams(String type)
{
List<String> parameters = null;
String retType = null;
if (type != null && type.indexOf("->") != -1)
{
int openBracketsCount = 0;
List<Integer> startPositions = new ArrayList<Integer>();
List<Integer> endPositions = new ArrayList<Integer>();
int i = 0;
int lastPos = 0;
while (i < type.length())
{
switch (type.charAt(i))
{
case '-':
if (openBracketsCount == 0 && type.charAt(i + 1) == '>') {
startPositions.add(lastPos);
endPositions.add(i - 1);
i++;
i++;
lastPos = i;
}
case '(':
openBracketsCount++;
case ')':
openBracketsCount--;
default:
}
i++;
}
startPositions.add(lastPos);
endPositions.add(type.length());
parameters = new ArrayList<String>();
for (int j = 0; j < startPositions.size(); j++) {
String param = type.substring(startPositions.get(j), endPositions.get(j));
if (j < startPositions.size() - 1)
{
parameters.add(param);
}
else
{
retType = param;
}
}
if (parameters.size() == 1 && parameters.get(0) == "Void")
{
parameters.clear();
}
}
return new HaxeCompilerCompletionItem(parameters, retType);
}
}