Skip to content

Commit e176eca

Browse files
Use an importance score to order the suggested import code action (#3234) (#3271)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 21c8e9b commit e176eca

File tree

1 file changed

+45
-16
lines changed
  • plugins/hls-refactor-plugin/src/Development/IDE/Plugin

1 file changed

+45
-16
lines changed

plugins/hls-refactor-plugin/src/Development/IDE/Plugin/CodeAction.hs

+45-16
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import Data.Ord (comparing)
3838
import qualified Data.Set as S
3939
import qualified Data.Text as T
4040
import qualified Data.Text.Utf16.Rope as Rope
41-
import Data.Tuple.Extra (fst3)
4241
import Development.IDE.Core.Rules
4342
import Development.IDE.Core.RuleTypes
4443
import Development.IDE.Core.Service
@@ -87,6 +86,7 @@ import Language.LSP.Types (ApplyWorkspa
8786
uriToFilePath)
8887
import Language.LSP.VFS (VirtualFile,
8988
_file_text)
89+
import qualified Text.Fuzzy.Parallel as TFP
9090
import Text.Regex.TDFA (mrAfter,
9191
(=~), (=~~))
9292
#if MIN_VERSION_ghc(9,2,0)
@@ -99,7 +99,6 @@ import GHC (AddEpAnn (Ad
9999
EpaLocation (..),
100100
LEpaComment,
101101
LocatedA)
102-
103102
#else
104103
import Language.Haskell.GHC.ExactPrint.Types (Annotation (annsDP),
105104
DeltaPos,
@@ -1521,35 +1520,65 @@ suggestNewImport packageExportsMap ps fileContents Diagnostic{_message}
15211520
, Just (range, indent) <- newImportInsertRange ps fileContents
15221521
, extendImportSuggestions <- matchRegexUnifySpaces msg
15231522
"Perhaps you want to add ‘[^’]*’ to the import list in the import of ‘([^’]*)’"
1524-
= sortOn fst3 [(imp, kind, TextEdit range (imp <> "\n" <> T.replicate indent " "))
1525-
| (kind, unNewImport -> imp) <- constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions
1526-
]
1523+
= let suggestions = nubSort
1524+
(constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions) in
1525+
map (\(ImportSuggestion _ kind (unNewImport -> imp)) -> (imp, kind, TextEdit range (imp <> "\n" <> T.replicate indent " "))) suggestions
15271526
where
15281527
L _ HsModule {..} = astA ps
15291528
suggestNewImport _ _ _ _ = []
15301529

15311530
constructNewImportSuggestions
1532-
:: ExportsMap -> (Maybe T.Text, NotInScope) -> Maybe [T.Text] -> [(CodeActionKind, NewImport)]
1533-
constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules = nubOrdOn snd
1531+
:: ExportsMap -> (Maybe T.Text, NotInScope) -> Maybe [T.Text] -> [ImportSuggestion]
1532+
constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules = nubOrd
15341533
[ suggestion
1535-
| Just name <- [T.stripPrefix (maybe "" (<> ".") qual) $ notInScope thingMissing]
1536-
, identInfo <- maybe [] Set.toList $ Map.lookup name (getExportsMap exportsMap)
1537-
, canUseIdent thingMissing identInfo
1538-
, moduleNameText identInfo `notElem` fromMaybe [] notTheseModules
1539-
, suggestion <- renderNewImport identInfo
1534+
| Just name <- [T.stripPrefix (maybe "" (<> ".") qual) $ notInScope thingMissing] -- strip away qualified module names from the unknown name
1535+
, identInfo <- maybe [] Set.toList $ Map.lookup name (getExportsMap exportsMap) -- look up the modified unknown name in the export map
1536+
, canUseIdent thingMissing identInfo -- check if the identifier information retrieved can be used
1537+
, moduleNameText identInfo `notElem` fromMaybe [] notTheseModules -- check if the module of the identifier is allowed
1538+
, suggestion <- renderNewImport identInfo -- creates a list of import suggestions for the retrieved identifier information
15401539
]
15411540
where
1542-
renderNewImport :: IdentInfo -> [(CodeActionKind, NewImport)]
1541+
renderNewImport :: IdentInfo -> [ImportSuggestion]
15431542
renderNewImport identInfo
15441543
| Just q <- qual
1545-
= [(quickFixImportKind "new.qualified", newQualImport m q)]
1544+
= [ImportSuggestion importanceScore (quickFixImportKind "new.qualified") (newQualImport m q)]
15461545
| otherwise
1547-
= [(quickFixImportKind' "new" importStyle, newUnqualImport m (renderImportStyle importStyle) False)
1546+
= [ImportSuggestion importanceScore (quickFixImportKind' "new" importStyle) (newUnqualImport m (renderImportStyle importStyle) False)
15481547
| importStyle <- NE.toList $ importStyles identInfo] ++
1549-
[(quickFixImportKind "new.all", newImportAll m)]
1548+
[ImportSuggestion importanceScore (quickFixImportKind "new.all") (newImportAll m)]
15501549
where
1550+
-- The importance score takes 2 metrics into account. The first being the similarity using
1551+
-- the Text.Fuzzy.Parallel.match function. The second is a factor of the relation between
1552+
-- the modules prefix import suggestion and the unknown identifier names.
1553+
importanceScore
1554+
| Just q <- qual
1555+
= let
1556+
similarityScore = fromIntegral $ unpackMatchScore (TFP.match (T.toLower q) (T.toLower m)) :: Double
1557+
(maxLength, minLength) = case (T.length q, T.length m) of
1558+
(la, lb)
1559+
| la >= lb -> (fromIntegral la, fromIntegral lb)
1560+
| otherwise -> (fromIntegral lb, fromIntegral la)
1561+
lengthPenaltyFactor = 100 * minLength / maxLength
1562+
in max 0 (floor (similarityScore * lengthPenaltyFactor))
1563+
| otherwise
1564+
= 0
1565+
where
1566+
unpackMatchScore pScore
1567+
| Just score <- pScore = score
1568+
| otherwise = 0
15511569
m = moduleNameText identInfo
15521570

1571+
-- | Implements a lexicographic order for import suggestions.
1572+
-- First compares the importance score in DESCENDING order.
1573+
-- If the scores are equal it compares the import names alphabetical order.
1574+
data ImportSuggestion = ImportSuggestion !Int !CodeActionKind !NewImport
1575+
deriving ( Eq )
1576+
1577+
instance Ord ImportSuggestion where
1578+
compare (ImportSuggestion s1 _ i1) (ImportSuggestion s2 _ i2)
1579+
| s1 == s2 = compare i1 i2
1580+
| otherwise = flip compare s1 s2
1581+
15531582
newtype NewImport = NewImport {unNewImport :: T.Text}
15541583
deriving (Show, Eq, Ord)
15551584

0 commit comments

Comments
 (0)