/** * Copyright (c) 2012 by JP Moresmau * This code is made available under the terms of the Eclipse Public License, * version 1.0 (EPL). See http://www.eclipse.org/legal/epl-v10.html */ package net.sf.eclipsefp.haskell.ui.internal.resolve; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Locale; 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.DeclarationId; import net.sf.eclipsefp.haskell.buildwrapper.BuildWrapperPlugin; import net.sf.eclipsefp.haskell.buildwrapper.types.CabalMessages; import net.sf.eclipsefp.haskell.buildwrapper.types.GhcMessages; import net.sf.eclipsefp.haskell.buildwrapper.types.SearchResultLocation; import net.sf.eclipsefp.haskell.buildwrapper.types.UsageResults; import net.sf.eclipsefp.haskell.buildwrapper.usage.UsageQueryFlags; import net.sf.eclipsefp.haskell.core.HaskellCorePlugin; import net.sf.eclipsefp.haskell.core.cabalmodel.CabalSyntax; import net.sf.eclipsefp.haskell.core.compiler.CompilerManager; import net.sf.eclipsefp.haskell.core.util.ResourceUtil; import net.sf.eclipsefp.haskell.hlint.HLintFixer; import net.sf.eclipsefp.haskell.hlint.Suggestion; import net.sf.eclipsefp.haskell.ui.HaskellUIPlugin; import net.sf.eclipsefp.haskell.ui.internal.backend.BackendManager; import net.sf.eclipsefp.haskell.util.HaskellText; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.ui.IMarkerResolution; import org.eclipse.ui.IMarkerResolutionGenerator; /** * <p>Provides resolutions for markers</p> * * @author JP Moresmau */ public class BuildMarkerResolutionGenerator implements IMarkerResolutionGenerator { @Override public IMarkerResolution[] getResolutions( final IMarker marker ) { if (marker==null || !marker.exists()){ return new IMarkerResolution[0]; } List<IMarkerResolution> res=new ArrayList<>(); IMarkerResolution hlr=getHLintResolution( marker ); if (hlr!=null){ res.add( hlr ); } else { if (marker.getAttribute( IMarker.SEVERITY , IMarker.SEVERITY_ERROR)==IMarker.SEVERITY_WARNING || (marker.getAttribute( IMarker.SEVERITY , IMarker.SEVERITY_ERROR)==IMarker.SEVERITY_ERROR)){ String msg=marker.getAttribute(IMarker.MESSAGE,""); //$NON-NLS-1$ if (msg!=null){ String msgL=msg.toLowerCase(Locale.ENGLISH); int ix=-1; // Type signature not found if (msgL.indexOf( GhcMessages.WARNING_NOTYPE_CONTAINS )>-1){ res.add(new MissingTypeWarningResolution(GhcMessages.WARNING_INFERREDTYPE_START)); } if (msgL.indexOf( GhcMessages.WARNING_NOTYPE_TOPLEVEL_CONTAINS )>-1){ // type is given on next line res.add(new MissingTypeWarningResolution(GhcMessages.WARNING_NOTYPE_TOPLEVEL_CONTAINS)); } // Useless import if (msgL.indexOf( GhcMessages.WARNING_IMPORT_USELESS_CONTAINS )>-1){ res.add(new RemoveImportResolution()); int ix2=msgL.indexOf( GhcMessages.WARNING_IMPORT_USELESS_START ); if (ix2>-1){ String newImport=msg.substring( ix2+GhcMessages.WARNING_IMPORT_USELESS_START.length() ).trim(); res.add( new ReplaceImportResolution( newImport ) ); } } if (msgL.indexOf( GhcMessages.WARNING_IMPORT_USELESS_CONTAINS2 )>-1){ int ixe2=-1; if ((ixe2=msgL.indexOf( GhcMessages.WARNING_IMPORT_USELESS_ELEMENT2 )) > -1) { // Redundant element // 1. Find redundant element int start=msgL.indexOf( GhcMessages.WARNING_IMPORT_USELESS_CONTAINS_START ); if (start>-1){ String redundantElement=msg.substring( start+GhcMessages.WARNING_IMPORT_USELESS_CONTAINS_START.length(), ixe2 ).trim(); if (redundantElement.startsWith( "`" ) || redundantElement.startsWith( "‘" )){ redundantElement=redundantElement.substring( 1,redundantElement.length()-1); } //int backQuote1 = msg.indexOf( '`' ); //int endQuote1 = msg.indexOf( '\'',backQuote1 ); //String redundantElement = msg.substring( backQuote1 + 1, endQuote1 ); /*String rest = msg.substring( endQuote1 + 1 ); int backQuote2 = rest.indexOf( '`' ); int endQuote2 = rest.indexOf( '\'' ); String inImport = rest.substring( backQuote2 + 1, endQuote2 );*/ res.add( new RemoveRedundantElementInImportResolution( redundantElement ) ); } } else { // Redundant entire import res.add(new RemoveImportResolution()); int ix2=msgL.indexOf( GhcMessages.WARNING_IMPORT_USELESS_START2 ); if (ix2>-1){ String newImport=msg.substring( ix2+GhcMessages.WARNING_IMPORT_USELESS_START2.length() ).trim(); res.add( new ReplaceImportResolution( newImport ) ); } } } // if (addFlagPragma(res,msg,msgL, GhcMessages.WARNING_USEFLAG_CONTAINS // ,GhcMessages.WARNING_USEFLAG_CONTAINS2,GhcMessages.WARNING_USEFLAG_CONTAINS3 // ,GhcMessages.WARNING_USEFLAG_CONTAINS4,GhcMessages.WARNING_USEFLAG_CONTAINS5 // ,GhcMessages.WARNING_USEFLAG_CONTAINS6)){ // // // } // if ((ix=msgL.indexOf( GhcMessages.WARNING_SUPPRESS_CONTAINS ))>-1){ // int end=ix-2; // int ix2=msg.lastIndexOf( ' ',end); // if (ix2>-1){ // String flag=msg.substring( ix2+1,end+1 ).trim(); // addPragma(res,flag); // } // } // if ((ix=msgL.indexOf( GhcMessages.NOT_ENABLED ))>1){ // String flag=msg.substring( 0,ix ).trim(); // res.add( new AddLanguagePragmaResolution( flag ) ); // } // if ((ix=msgL.indexOf( GhcMessages.PERMITS_THIS ))>1){ // int ix2=msg.substring(0,ix).lastIndexOf("(-X"); // String flag=msg.substring( ix2+1,ix ).trim(); // addPragma(res,flag); // } // if ((ix=msgL.indexOf( GhcMessages.TRY ))>1){ // int ix2=msg.indexOf(" ",ix+GhcMessages.TRY.length()); // if (ix2>-1){ // String flag=msg.substring( ix+GhcMessages.TRY.length()-2,ix2).trim(); // addPragma(res,flag); // } // } // if ((ix=msgL.indexOf( GhcMessages.YOU_NEED ))>1){ // int ix2=msg.indexOf(" ",ix+GhcMessages.YOU_NEED.length()); // if (ix2>-1){ // String flag=msg.substring( ix+GhcMessages.YOU_NEED.length()-2,ix2).trim(); // addPragma(res,flag); // } // } // if ((ix=msgL.indexOf( GhcMessages.DID_YOU_MEAN ))>1){ // int ix2=msg.indexOf("?",ix+GhcMessages.DID_YOU_MEAN.length()); // if (ix2>-1){ // String flag=msg.substring( ix+GhcMessages.DID_YOU_MEAN.length(),ix2).trim(); // addPragma(res,flag); // } // } // if ((ix=msgL.indexOf( GhcMessages.DID_YOU_MEAN_NO_X ))>1){ // int ix2=msg.indexOf("?",ix+GhcMessages.DID_YOU_MEAN_NO_X.length()); // if (ix2>-1){ // String flag=msg.substring( ix+GhcMessages.DID_YOU_MEAN_NO_X.length(),ix2).trim(); // addPragma(res,flag); // } // } if ((ix=msgL.indexOf( GhcMessages.CAST_FROM_CHAR ))>1){ addPragma( res, "-XOverloadedStrings" ); } if ((ix=msgL.indexOf( GhcMessages.CAST_FROM_CHAR_SHORT ))>1){ addPragma( res, "-XOverloadedStrings" ); } if ((ix=msgL.indexOf( GhcMessages.CAST_FROM_CHAR_7_8 ))>1){ addPragma( res, "-XOverloadedStrings" ); } if ((ix=msgL.indexOf( GhcMessages.CAST_FROM_CHAR_SHORT_7_8 ))>1){ addPragma( res, "-XOverloadedStrings" ); } // Import a package if (msgL.indexOf(GhcMessages.MISSING_MODULE)>-1){ int start=GhcMessages.MISSING_MODULE.length(); int length=GhcMessages.MISSING_MODULE_ADD_START.length(); ix=msgL.indexOf( GhcMessages.MISSING_MODULE_ADD_START,start ); if (ix==-1){ ix=msgL.indexOf( GhcMessages.MISSING_MODULE_ADD_START_7_8,ix); length=GhcMessages.MISSING_MODULE_ADD_START_7_8.length(); } if (ix>-1){ Set<String> pkgs=new HashSet<>(); while (ix>-1){ int ix2=msgL.indexOf( GhcMessages.MISSING_MODULE_ADD_END,ix); if (ix2==-1){ ix2=msgL.indexOf( GhcMessages.MISSING_MODULE_ADD_END_7_8,ix); } if (ix2>-1){ String pkg=msg.substring( ix+length,ix2 ); // if the dependency can be found in several versions, we'll get several messages if (pkgs.add( pkg )){ res.add(new AddPackageDependency( pkg )); } ix=ix2; } int st=ix+1; length=GhcMessages.MISSING_MODULE_ADD_START.length(); ix=msgL.indexOf( GhcMessages.MISSING_MODULE_ADD_START,st ); if (ix==-1){ ix=msgL.indexOf( GhcMessages.MISSING_MODULE_ADD_START_7_8,st); length=GhcMessages.MISSING_MODULE_ADD_START_7_8.length(); } } } int l=msgL.indexOf( "\n",start+1 ); if (l>-1){ String sug=msg.substring( l ); List<String> suggestions=ReplaceTextResolution.getSuggestionsFromGHCMessage( sug,msgL.substring( l ) ); msgL=msgL.substring( 0,l ); int end = msgL.lastIndexOf( GhcMessages.NOT_IN_SCOPE_END ); if (end==-1){ end = msgL.lastIndexOf( GhcMessages.NOT_IN_SCOPE_END_7_8 ); } String notInScope = msg.substring( start + 2, end ); for (String suggestion:suggestions){ res.add( new ReplaceTextResolution( notInScope, suggestion ) ); } } } // Not in scope if ((ix=msgL.indexOf( GhcMessages.NOT_IN_SCOPE_START) )>-1){ ResolutionSuggestion s=new ResolutionSuggestion( msg, ix,msgL ); if (s.getSuggestions()!=null){ for (String suggestion:s.getSuggestions()){ res.add( new ReplaceTextResolution( s.getOutOfScope(), suggestion ) ); } } addBrowserSuggestions( marker, s.getOutOfScopeName(), s.getOutOfScopeQualifier(), res); addUsageSuggestions( marker, s.getOutOfScopeName() , s.getOutOfScopeQualifier(), res); } if (msgL.indexOf( GhcMessages.IS_A_DATA_CONSTRUCTOR )>-1){ int btix=msg.indexOf('`'); int sqix=msg.indexOf('\'',btix); //String module=msg.substring(btix+1,sqix); btix=msg.indexOf('`',sqix); sqix=msg.indexOf('\'',btix); String constructor=msg.substring(btix+1,sqix); btix=msg.indexOf('`',sqix); sqix=msg.indexOf('\'',btix); String type=msg.substring(btix+1,sqix); res.add( new ReplaceImportElement( constructor, type+"("+constructor+")" ) ); res.add( new ReplaceImportElement( constructor, type+"(..)" ) ); /* btix=msg.indexOf('`',btix+1); sqix=msg.indexOf('\'',btix); String import1=msg.substring(btix+1,sqix); btix=msg.indexOf('`',sqix); sqix=msg.indexOf('\'',btix); String import2=msg.substring(btix+1,sqix); res.add(new ReplaceImportResolution( import1 )); res.add(new ReplaceImportResolution( import2 ));*/ /* Description Resource Path Location Type ID In module `System.Exit': `ExitFailure' is a data constructor of `ExitCode' To import it use `import System.Exit (ExitCode (ExitFailure))' or `import System.Exit (ExitCode (..))' Main.hs /nxt/test line 16 Haskell Problem 39935*/ } if (msgL.indexOf( GhcMessages.DO_DISCARDED_START )>-1){ res.add(new AddGhcPragmaResolution( "-fno-warn-unused-do-bind" )); res.add(new AddGHCOptionResolution( "-fno-warn-unused-do-bind" )); // int fixIx=msgL.indexOf( GhcMessages.DO_DISCARDED_FIX ); // if (fixIx>-1){ // // } } if ((ix=msgL.indexOf( CabalMessages.DEPENDENCIES_MISSING ))>-1){ // sandbox does the download for us, so if we're missing a dependency and sandbox, // either it's badly spelt or we don't have internet connnection... if (!BackendManager.getCabalImplDetails().isSandboxed()){ int nlid=msg.indexOf( "\n",ix ); Set<String> all=new HashSet<>(); for (String s:msg.substring( nlid ).split( "\\n" )){ s=s.trim(); if (s.length()>0){ for (String p:s.split( "," )){ p=p.trim(); if (p.endsWith( CabalMessages.ANY)){ p=p.substring( 0,p.length()-CabalMessages.ANY.length() ).trim(); } all.add(p); res.add(new InstallMissingPackage( Collections.singleton( p ) )); } } } if (all.size()>1){ res.add(new InstallMissingPackage( all )); } // ... well it can happen that the configure has failed, so still give the option } else { res.add( new InstallDeps() ); } } if (msgL.indexOf( GhcMessages.NAKED )>-1){ res.add( new AddLanguagePragmaResolution( "TemplateHaskell" ) ); } if (msgL.indexOf( GhcMessages.INPUT_CASE )>-1){ res.add( new AddLanguagePragmaResolution( "LambdaCase" ) ); } if ((ix=msgL.indexOf( CabalMessages.CABAL_VERSION ))>-1){ ix+=CabalMessages.CABAL_VERSION.length(); int ix2=msgL.indexOf( '\'', ix ); if (ix2>-1){ String value=msg.substring( ix,ix2 ).trim(); if (value.length()>0){ res.add(new CabalFieldSetter( CabalSyntax.FIELD_CABAL_VERSION, value )); } } } // Language pragma needed for (String ext:CompilerManager.getExtensions()){ if (msg.indexOf( ext)>-1){ res.add( new AddLanguagePragmaResolution( ext ) ); } } } } } return res.toArray( new IMarkerResolution[res.size()] ); } /** * add suggestions from Browser * @param marker the current marked to fix * @param name the name * @param qualified the qualifier if any * @param res the suggestions */ private void addBrowserSuggestions(final IMarker marker,String name,final String qualified,final List<IMarkerResolution> res){ try { if (BrowserPlugin.getSharedInstance().isAnyDatabaseLoaded() && !BrowserPlugin.getSharedInstance().isRunning()) { // symbols are wrapped in (), so we want to make sure this is true if (name!=null && name.length()>0){ char ch=name.charAt(0); if (!HaskellText.isHaskellIdentifierPart(ch) && ch!='('){ name="("+name+")"; } } DeclarationId[] availableMods = BrowserPlugin.getSharedInstance().findModulesForDeclaration(Database.ALL, name ); /** * get the packages we know, we'll show declarations from these first */ final Set<String> refs=getReferencedPackages(marker); Arrays.sort(availableMods,new Comparator<DeclarationId>() { /* (non-Javadoc) * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ @Override public int compare( final DeclarationId o1, final DeclarationId o2 ) { String m1=o1.getModule().getName(); String m2=o2.getModule().getName(); // known packages go first boolean isPkg1=refs.contains( o1.getPackageName() ); boolean isPkg2=refs.contains( o2.getPackageName() ); if (isPkg1){ if (!isPkg2){ return -1; } } else if (isPkg2){ return 1; } int c=m1.compareToIgnoreCase( m2 ); if (c==0){ c=o1.getName().compareTo( o2.getName() ); } return c; } }); Set<String> modules=new HashSet<>(); for (DeclarationId place : availableMods) { String module=place.getModule().getName(); if (!modules.contains(module )){ modules.add(module); if (place.getName().length()>0){ res.add( new AddImportResolution( place.getName()+"(..)", module, qualified ) ); res.add( new AddImportResolution( place.getName()+"("+name+")", module, qualified ) ); } else { res.add( new AddImportResolution( name, module, qualified ) ); } res.add( new AddImportResolution( null, module, qualified ) ); } } } } catch (Exception e) { // Do nothing } } /** * add suggestions from the Usage db * @param marker the current marked to fix * @param name the name * @param qualified the qualifier if any * @param res the suggestions */ private void addUsageSuggestions(final IMarker marker,final String name,final String qualified,final List<IMarkerResolution> res){ if (name.length()>0 ){ if (Character.isLowerCase( name.charAt( 0 ))){ UsageResults ur=BuildWrapperPlugin.getDefault().getUsageAPI().likeSearch( null, name, null,UsageQueryFlags.TYPE_VAR, UsageQueryFlags.SCOPE_DEFINITIONS); for (IProject p:ur.listProjects()){ for (IFile f:ur.getUsageInProject( p ).keySet()){ String module=ResourceUtil.getModuleName( f ); if (module!=null){ res.add( new AddImportResolution( name, module, qualified ) ); res.add( new AddImportResolution( null, module, qualified ) ); } } } } else { UsageResults ur=BuildWrapperPlugin.getDefault().getUsageAPI().likeSearch( null, name, null, UsageQueryFlags.TYPE_TYPE, UsageQueryFlags.SCOPE_DEFINITIONS); boolean found=false; for (IProject p:ur.listProjects()){ for (IFile f:ur.getUsageInProject( p ).keySet()){ String module=ResourceUtil.getModuleName( f ); if (module!=null){ res.add( new AddImportResolution( name, module, qualified ) ); res.add( new AddImportResolution( name+"(..)", module, qualified ) ); res.add( new AddImportResolution( null, module, qualified ) ); found=true; } } } if (!found){ ur=BuildWrapperPlugin.getDefault().getUsageAPI().likeSearch( null, name, null, UsageQueryFlags.TYPE_CONSTRUCTOR, UsageQueryFlags.SCOPE_DEFINITIONS); for (IProject p:ur.listProjects()){ Map<IFile,Map<String,Collection<SearchResultLocation>>> m=ur.getUsageInProject( p ); for (IFile f:m.keySet()){ String module=ResourceUtil.getModuleName( f ); if (module!=null){ for (String s:m.get(f).keySet()){ res.add( new AddImportResolution( s+"("+name+")", module, qualified ) ); res.add( new AddImportResolution( s+"(..)", module, qualified ) ); res.add( new AddImportResolution( null, module, qualified ) ); found=true; } } } } } } } } private IMarkerResolution getHLintResolution(final IMarker marker) { try { if (marker.getType().equals(HaskellCorePlugin.ID_HLINT_MARKER)){ Suggestion s=new Suggestion(); s.fromString( marker.getAttribute( HaskellCorePlugin.ATT_HLINT_SUGGESTION,"" )); if (HLintFixer.canFix( s )){ return new HLintResolution(); } } }catch (CoreException ce){ HaskellUIPlugin.log( ce ); } return null; } /** * add a pragma * @param res * @param msg * @param msgL * @param toSearch * @return true if a pragma was found */ private boolean addFlagPragma(final List<IMarkerResolution> res,final String msg,final String msgL,final String... toSearch){ int ix=-1; for (String s:toSearch){ if ((ix=msgL.indexOf( s ))>-1){ // start getting the pragma name from after the -x int ix2=ix+1+s.length(); // we actually rely on -X to begin the pragma name later on if (s.endsWith( " -x" )){ s=s.substring( s.length()-3 ); } else { ix2+=3; } // we'll cut from start, but we'll look at the name from ix2... int start=ix+1+s.length(); for (;ix2<msg.length();ix2++){ // flags are letters and sometimes numbers (Rank2Types) if (!Character.isLetterOrDigit( msg.charAt( ix2 ) )){ break; } } boolean ret=false; if (ix2<msg.length()){ String flag=msg.substring( start,ix2 ).trim(); ret=addPragma(res,flag); } else { String flag=msg.substring( start).trim(); ret=addPragma(res,flag); } // return if we find something, otherwise look for another string if (ret){ return true; } } } return false; } /** * * @param res * @param flag * @return true if the pragma was valid, false otherwise */ private boolean addPragma(final List<IMarkerResolution> res,final String flag){ if (flag!=null){ if (flag.length()>2 && flag.startsWith( "-X" )){ //$NON-NLS-1$ res.add( new AddLanguagePragmaResolution( flag.substring( 2 ) ) ); return true; } else if (flag.length()>0 && Character.isUpperCase( flag.charAt( 0 ))){ res.add( new AddLanguagePragmaResolution( flag ) ); return true; } } return false; } private static Set<String> getReferencedPackages(final IMarker marker){ IResource res=marker.getResource(); Set<String> ret=new HashSet<>(); if (res instanceof IFile){ return ResourceUtil.getImportPackages( new IFile[]{(IFile)res} ); } return ret; } }