/**
* (c) 2011, Alejandro Serrano
* Released under the terms of the EPL.
*/
package net.sf.eclipsefp.haskell.ui.internal.editors.haskell.imports;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.eclipsefp.haskell.browser.BrowserPlugin;
import net.sf.eclipsefp.haskell.browser.Database;
import net.sf.eclipsefp.haskell.browser.items.Constructor;
import net.sf.eclipsefp.haskell.browser.items.DataType;
import net.sf.eclipsefp.haskell.browser.items.Declaration;
import net.sf.eclipsefp.haskell.browser.items.Documented;
import net.sf.eclipsefp.haskell.browser.items.Function;
import net.sf.eclipsefp.haskell.browser.items.Gadt;
import net.sf.eclipsefp.haskell.browser.items.NewType;
import net.sf.eclipsefp.haskell.browser.items.PackageIdentifier;
import net.sf.eclipsefp.haskell.browser.items.Packaged;
import net.sf.eclipsefp.haskell.browser.items.TypeClass;
import net.sf.eclipsefp.haskell.browser.items.TypeSynonym;
import net.sf.eclipsefp.haskell.browser.items.Unknown;
import net.sf.eclipsefp.haskell.browser.util.ImageCache;
import net.sf.eclipsefp.haskell.buildwrapper.BWFacade;
import net.sf.eclipsefp.haskell.buildwrapper.BuildWrapperPlugin;
import net.sf.eclipsefp.haskell.buildwrapper.types.CabalPackage;
import net.sf.eclipsefp.haskell.buildwrapper.types.ExportDef;
import net.sf.eclipsefp.haskell.buildwrapper.types.ImportDef;
import net.sf.eclipsefp.haskell.buildwrapper.types.ImportExportType;
import net.sf.eclipsefp.haskell.buildwrapper.types.ImportSpecDef;
import net.sf.eclipsefp.haskell.buildwrapper.types.NameDef;
import net.sf.eclipsefp.haskell.buildwrapper.types.OutlineDef;
import net.sf.eclipsefp.haskell.buildwrapper.types.OutlineResult;
import net.sf.eclipsefp.haskell.core.util.ResourceUtil;
import net.sf.eclipsefp.haskell.ui.HaskellUIPlugin;
import net.sf.eclipsefp.haskell.ui.internal.editors.haskell.HaskellEditor;
import net.sf.eclipsefp.haskell.ui.internal.resolve.DiscreteCompletionProposal;
import net.sf.eclipsefp.haskell.util.LangUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
/**
* Represents information about an import: if it's qualified,
* the list of things that imports or hides...
* @author Alejandro Serrano
*
*/
public class AnImport {
// private final String name;
// private final String qualifiedName;
// private final IRegion location;
// private final boolean isComplete;
// private final boolean isHiding;
// private final boolean isQualified;
// private final String items;
private final ImportDef importDef;
private final boolean isMe;
// public AnImport( final String name, final IRegion location,
// final boolean isComplete, final boolean isHiding, final String items ) {
// this( name, location, isComplete, isHiding, false, null, items );
// }
//
// public AnImport( final String name, final IRegion location,
// final boolean isComplete, final boolean isHiding,
// final boolean isQualified, final String qualifiedName, final String items) {
// this( name, location, isComplete, isHiding, isQualified, qualifiedName, items, false );
// }
//
// public AnImport( final String name, final IRegion location,
// final boolean isComplete, final boolean isHiding,
// final boolean isQualified, final String qualifiedName, final String items,
// final boolean isMe) {
// this.name = name;
// this.location = location;
// this.isComplete = isComplete;
// this.isHiding = isHiding;
// this.isQualified = isQualified;
// this.qualifiedName = qualifiedName;
// this.items = items;
// this.isMe = isMe;
// }
public static AnImport createMe( final String name ) {
return new AnImport(new ImportDef(name, null, false, false, null), true);
}
//
// public String getItems() {
// return this.items;
// }
public AnImport( final ImportDef importDef, final boolean isMe ) {
super();
this.importDef = importDef;
this.isMe = isMe;
}
public ImportDef getImportDef() {
return importDef;
}
// public String[] getItemsList() {
// return this.items.trim().split( "[ ]*,[ ]*" );
// }
//
// public String getName() {
// return this.name;
// }
//
// public IRegion getLocation() {
// return this.location;
// }
//
// public boolean isComplete() {
// return this.isComplete;
// }
//
// public boolean isHiding() {
// return this.isHiding;
// }
//
// public boolean isQualified() {
// return this.isQualified;
// }
//
// public String getQualifiedName() {
// return this.qualifiedName;
// }
public Map<String, FileDocumented> getDeclarations( final IProject project,
final IFile file, final IDocument doc ) {
// ArrayList<String> items
// we will put names qualified with alias if it exist and with the full module name
String aliasName = importDef.getAlias() != null && importDef.getAlias().length()>0 ? importDef.getAlias() : null;
HashMap<String, FileDocumented> r = new HashMap<>();
try {
List<FileDocumented> decls;
Set<String> visited=new HashSet<>();
if (isMe) {
HaskellEditor ed=HaskellUIPlugin.getHaskellEditor( doc );
OutlineResult or=ed!=null?ed.getLastOutlineResult():null;
if (or!=null){
decls=getDeclarationsFromOutlineResult(file,or,visited);
} else {
decls = getDeclarationsFromFile( file,visited );
}
} else {
decls = getDeclarationsFromFile( importDef.getModule(), project,visited );
if (decls.size()==0){
BWFacade f=BuildWrapperPlugin.getFacade( file.getProject() );
if (f!=null && BrowserPlugin.getSharedInstance().isAnyDatabaseLoaded() && !BrowserPlugin.getSharedInstance().isRunning()){
// reducing the scope of the query
for (CabalPackage[] cps:f.getPackagesByDB().values()){
for (CabalPackage cp:cps){
if (cp.getModules().contains( importDef.getModule() )){
Database pkg=Database.Package( new PackageIdentifier( cp.getName(), cp.getVersion() ) );
Packaged<Declaration>[] browserDecls = BrowserPlugin.getSharedInstance().getDeclarations(pkg, importDef.getModule() );
if (browserDecls.length > 0) {
// If the browser found the module
decls = new ArrayList<>();
for (Packaged<Declaration> browserDecl : browserDecls) {
decls.add(new FileDocumented( browserDecl.getElement(),null) );
if (browserDecl.getElement() instanceof Gadt) {
Gadt g = (Gadt)browserDecl.getElement();
for (Constructor c : g.getConstructors()) {
decls.add(new FileDocumented( c, null) );
}
}
}
}
break;
}
}
}
}
}
}
if (importDef.getChildren()==null) {
// Add everything
for (FileDocumented decl : decls) {
addDeclaration( r, aliasName, decl, importDef.isQualified() );
addDeclaration( r, importDef.getModule(), decl, importDef.isQualified() );
}
} else {
//List<String> itemsExplode = Arrays.asList( this.items.split( "[ ]*,[ ]*" ) );
Set<String> itemsExplode=new HashSet<>();
for (ImportSpecDef isd:importDef.getChildren()){
itemsExplode.add(isd.getName());
}
for (FileDocumented decl : decls) {
boolean inList = itemsExplode.contains( decl.getDocumented().getName() );
boolean toAdd = (importDef.isHiding() && !inList) || (!importDef.isHiding() && inList);
if (toAdd) {
addDeclaration( r, aliasName, decl, importDef.isQualified() );
addDeclaration( r, importDef.getModule(), decl, importDef.isQualified() );
}
}
}
} catch( Exception e ) {
HaskellUIPlugin.log( e );
r.clear();
}
return r;
}
private void addDeclaration(final HashMap<String, FileDocumented> r, final String codeName,
final FileDocumented d, final boolean isQualified) {
if (codeName!=null){
String declName = d.getDocumented().getName();
declName = declName.startsWith( "(" ) ? declName.substring( 1, declName.length() - 1 ) : declName;
r.put( codeName + "." + declName, d );
if (!isQualified) {
r.put(declName, d);
}
}
}
private static List<FileDocumented> getDeclarationsFromFile( final String module, final IProject project,final Set<String> visited ) {
visited.add(module);
try {
IFile file = ResourceUtil.findFileFromModule( project, module );
// search in referenced projects
if (file==null){
for( IProject p: project.getReferencedProjects() ) {
if( ResourceUtil.hasHaskellNature( p ) ) {
file = ResourceUtil.findFileFromModule( p, module );
if (file!=null){
break;
}
}
}
}
if (file!=null){
return getDeclarationsFromFile( file, visited );
}
} catch (Exception e) {
HaskellUIPlugin.log( e );
}
return new ArrayList<>();
}
public static List<FileDocumented> getDeclarationsFromFile( final IFile file ) {
Set<String> visited = new HashSet<>();
return getDeclarationsFromFile( file, visited );
}
private static List<FileDocumented> getDeclarationsFromFile( final IFile file,final Set<String> visited ) {
try {
if (file!=null){
BWFacade f=BuildWrapperPlugin.getFacade( file.getProject() );
if (f!=null){
OutlineResult or=f.outline( file ,null);
return getDeclarationsFromOutlineResult( file, or,visited );
}
}
} catch (Exception e) {
HaskellUIPlugin.log( e );
}
return new ArrayList<>();
}
private static List<FileDocumented> getDeclarationsFromOutlineResult( final IFile file, final OutlineResult or,final Set<String> visited ) {
ArrayList<FileDocumented> decls = new ArrayList<>();
for (OutlineDef def : or.getOutlineDefs()) {
outlineToBrowser( def,null,file,decls );
}
for (ExportDef ed:or.getExportDefs()){
if (ed.getType().equals( ImportExportType.IEModule )
&& !visited.contains(ed.getName())) // avoid recursion if a module re-export itself
{
decls.addAll( getDeclarationsFromFile( ed.getName(), file.getProject(),visited ) );
} else {
decls.add( new FileDocumented( new Unknown( ed.getName() ), file ) );
}
}
return decls;
}
/**
* transform a NameDef into a Documented object
* @param def the name
* @return the Documented or null if not handled
*/
public static Documented nameToBrowser( final NameDef def) {
switch (def.getTypes().iterator().next()) {
case CLASS:
return new TypeClass("", new String[0], def.getName(), new String[0], new String[0] );
case DATA:
return new DataType( "", new String[0], def.getName(), new String[0], "", new Constructor[0] );
case TYPE:
return new NewType( "", new String[0], def.getName(), new String[0], "", new Constructor[0] );
case FUNCTION:
case FIELD:
return new Function( "", def.getName(), def.getTypeSignature() );
case SYN:
return new TypeSynonym( "", def.getName(), new String[0], "?" );
case CONSTRUCTOR:
return new Constructor( "", def.getName(), def.getTypeSignature() ,"");
default:
return null;
}
}
/**
* build a Documented structure from an outline definition
* @param def
* @return
*/
private static Documented outlineToBrowser( final OutlineDef def, final OutlineDef parent ) {
switch (def.getTypes().iterator().next()) {
case CLASS:
return new TypeClass( def.getComment(), new String[0], def.getName(), new String[0], new String[0] );
case DATA:
return new DataType( def.getComment(), new String[0], def.getName(), new String[0], "", new Constructor[0] );
case TYPE:
return new NewType( def.getComment(), new String[0], def.getName(), new String[0], "", new Constructor[0] );
case FUNCTION:
case FIELD:
return new Function( def.getComment(), def.getName(), def.getTypeSignature() );
case SYN:
return new TypeSynonym( def.getComment(), def.getName(), new String[0], "?" );
case CONSTRUCTOR:
return new Constructor( def.getComment(), def.getName(), def.getTypeSignature() ,parent.getName());
default:
return null;
}
}
public static void outlineToBrowser( final OutlineDef def ,final OutlineDef parent , final IFile file,final List<FileDocumented> ret) {
Documented d=outlineToBrowser( def,parent );
if (d!=null){
ret.add( new FileDocumented( d, file ) );
}
for (OutlineDef c:def.getChildren()){
outlineToBrowser(c,def,file,ret);
}
}
public ICompletionProposal addItem(final IDocument doc, final String item, final String label) {
try {
String ritem=item;
char c0=ritem.charAt( 0 );
// operators need to be surrounded by parens
if (!Character.isLetter(c0) && (c0!='(')){
ritem="("+ritem+")";
}
String contents = importDef.getLocation().getContents( doc );
// We had no items
int en=importDef.getLocation().getEndOffset( doc );
/*if (importDef.getChildren()==null) {
return new CompletionProposal( " ("+item+")", en, 0,
en + item.length(), ImageCache.MODULE, label, null, "" );
}*/
int pos = contents.indexOf( '(' );
if (pos==-1){
return new DiscreteCompletionProposal( " ("+ritem+")", en, 0,
ImageCache.MODULE, label, null, "" );
}
// We have some items
// Trim end the elements
//String toSearch = "(" + items.replaceAll("\\s+$", "");
//int pos = contents.indexOf( toSearch );
//int newPos = location.getOffset() + pos + toSearch.length();
int pos2=contents.lastIndexOf( ')' );
int insert=en-contents.length()+pos2;
String contentsToAdd = ritem;
if (importDef.getChildren()!=null && importDef.getChildren().size()>0){
contentsToAdd = ", " + ritem;
}
return new DiscreteCompletionProposal( contentsToAdd, insert, 0,
ImageCache.MODULE, label, null, "" );
} catch (Exception e) {
e.printStackTrace();
HaskellUIPlugin.log( e );
}
return null;
}
private static int[] trimRemovedImport(final String contents,final int ixParens,final int start,final int end){
int tstart=start;
int tend=end;
while (contents.charAt( tend )==' '){ // remove spaces after me
tend++;
}
if (contents.charAt( tend )==','){ // remove comma after me
tend++;
} else if (contents.charAt( tend )==')'){ // I'm at end
while (tstart>0 && contents.charAt( tstart )==' '){ // remove spaces before me
tstart--;
}
/*if (contents.charAt( tstart-1 )==','){ // remove comma before me
tstart--;
}*/
}
while (tstart>1 && contents.charAt( tstart-1 )==' '){ // remove spaces before me
tstart--;
}
if (tstart-1==ixParens){//at start: remove spaces after removed comma
while (contents.charAt( tend )==' '){
tend++;
}
}
return new int[]{tstart,tend};
}
public ICompletionProposal removeItem(final IDocument doc, final String item, final String label) {
// try {
// String contents = importDef.getLocation().getContents( doc );
// int ixP=contents.indexOf( "(" );
// int ix=contents.indexOf( item,ixP );
// if (ix>-1){
// int end=ix+item.length();
// int[] trimmed=trimRemovedImport( contents, ixP, ix, end );
// ix=trimmed[0];
// end=trimmed[1];
// if (contents.charAt( end )==')'){
// if (contents.charAt( ix-1 )=='('){
// if (ix-1>ixP){
// // we're in between (): remove them
// end++;
// ix--;
// trimmed=trimRemovedImport( contents, ixP, ix, end );
// ix=trimmed[0];
// end=trimmed[1];
// }
// } else {
// ix--; // remove preceding comma if we're at end
// }
// }
// int st=importDef.getLocation().getStartOffset( doc );
// String newContents=contents.substring( 0,ix )+contents.substring( end );
// return new CompletionProposal( newContents.toString(), st, contents.length(),
// 0, ImageCache.MODULE, label, null, "" );
// }
// } catch (Exception e) {
// HaskellUIPlugin.log( e );
// }
// return null;
return removeItem( doc, Collections.singleton( item ),label);
}
/**
* is a character a boundary before items?
* @param c
* @return
*/
private boolean isItemBoundary(final char c){
return Character.isWhitespace( c ) || c==',' || c=='(' || c==')';
}
public ICompletionProposal removeAll(final IDocument doc, final String label) {
try {
String contents = importDef.getLocation().getContents( doc );
String newContents=contents;
int ixP=contents.indexOf( "(" );
if (ixP>-1){
newContents=LangUtil.rtrim(contents.substring( 0,ixP ));
int st=importDef.getLocation().getStartOffset( doc );
return new DiscreteCompletionProposal( newContents.toString(), st, contents.length(),
ImageCache.MODULE, label, null, "" );
}
} catch (Exception e) {
HaskellUIPlugin.log( e );
}
return null;
}
public ICompletionProposal removeItem(final IDocument doc, final Collection<String> items, final String label) {
try {
String contents = importDef.getLocation().getContents( doc );
String newContents=contents;
int ixP=contents.indexOf( "(" );
for (String item:items){
int thisItemIx=ixP;
int ix=newContents.indexOf( item,thisItemIx );
while (ix>-1){
int end=ix+item.length();
char s=newContents.charAt( ix- 1);
char e=newContents.charAt( end );
if (isItemBoundary( s ) && isItemBoundary( e )){
int[] trimmed=trimRemovedImport( newContents, thisItemIx, ix, end );
ix=trimmed[0];
end=trimmed[1];
if (newContents.charAt( end )==')'){
if (newContents.charAt( ix-1 )=='('){
if (ix-1>thisItemIx){
// we're in between (): remove them
end++;
ix--;
trimmed=trimRemovedImport( newContents, thisItemIx, ix, end );
ix=trimmed[0];
end=trimmed[1];
}
} else {
ix--; // remove preceding comma if we're at end
}
} else if (newContents.charAt( end )=='('){ // remove constructor list
while (end<newContents.length() && newContents.charAt( end )!=')'){
end++;
}
if (end<newContents.length() && newContents.charAt( end )==')'){
end++;
}
trimmed=trimRemovedImport( newContents, thisItemIx, ix, end );
ix=trimmed[0];
end=trimmed[1];
}
newContents=newContents.substring( 0,ix )+newContents.substring( end );
}
thisItemIx=end;
ix=newContents.indexOf( item,thisItemIx );
}
}
int st=importDef.getLocation().getStartOffset( doc );
return new DiscreteCompletionProposal( newContents.toString(), st, contents.length(),
ImageCache.MODULE, label, null, "" );
} catch (Exception e) {
HaskellUIPlugin.log( e );
}
return null;
}
public ICompletionProposal replaceItem(final IDocument doc, final String item, final String newItem, final String label) {
try {
String contents = importDef.getLocation().getContents( doc );
int ix=contents.indexOf( item );
if (ix>-1){
int end=ix+item.length();
int st=importDef.getLocation().getStartOffset( doc );
String newContents=contents.substring( 0,ix )+newItem+contents.substring( end );
return new DiscreteCompletionProposal( newContents.toString(), st, contents.length(),
ImageCache.MODULE, label, null, "" );
}
} catch (Exception e) {
HaskellUIPlugin.log( e );
}
return null;
}
@Override
public String toString() {
return importDef.toString();
}
public static class FileDocumented {
private Documented documented;
private IFile file;
public FileDocumented( final Documented documented, final IFile file ) {
super();
this.documented = documented;
this.file = file;
}
public Documented getDocumented() {
return documented;
}
public void setDocumented( final Documented documented ) {
this.documented = documented;
}
public IFile getFile() {
return file;
}
public void setFile( final IFile file ) {
this.file = file;
}
}
}