Skip to content

Commit e30fdb8

Browse files
Use an importance score to order the suggested import code action (#3234)
1 parent d8e1e75 commit e30fdb8

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

Lines changed: 45 additions & 16 deletions
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
@@ -89,6 +88,7 @@ import Language.LSP.VFS (VirtualFile,
8988
_file_text)
9089
import Text.Regex.TDFA (mrAfter,
9190
(=~), (=~~))
91+
import qualified Text.Fuzzy.Parallel as TFP
9292
#if MIN_VERSION_ghc(9,2,0)
9393
import GHC (AddEpAnn (AddEpAnn),
9494
Anchor (anchor_op),
@@ -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,
@@ -1495,35 +1494,65 @@ suggestNewImport packageExportsMap ps fileContents Diagnostic{_message}
14951494
, Just (range, indent) <- newImportInsertRange ps fileContents
14961495
, extendImportSuggestions <- matchRegexUnifySpaces msg
14971496
"Perhaps you want to add ‘[^’]*’ to the import list in the import of ‘([^’]*)’"
1498-
= sortOn fst3 [(imp, kind, TextEdit range (imp <> "\n" <> T.replicate indent " "))
1499-
| (kind, unNewImport -> imp) <- constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions
1500-
]
1497+
= let suggestions = nubSort
1498+
(constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions) in
1499+
map (\(ImportSuggestion _ kind (unNewImport -> imp)) -> (imp, kind, TextEdit range (imp <> "\n" <> T.replicate indent " "))) suggestions
15011500
where
15021501
L _ HsModule {..} = astA ps
15031502
suggestNewImport _ _ _ _ = []
15041503

15051504
constructNewImportSuggestions
1506-
:: ExportsMap -> (Maybe T.Text, NotInScope) -> Maybe [T.Text] -> [(CodeActionKind, NewImport)]
1507-
constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules = nubOrdOn snd
1505+
:: ExportsMap -> (Maybe T.Text, NotInScope) -> Maybe [T.Text] -> [ImportSuggestion]
1506+
constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules = nubOrd
15081507
[ suggestion
1509-
| Just name <- [T.stripPrefix (maybe "" (<> ".") qual) $ notInScope thingMissing]
1510-
, identInfo <- maybe [] Set.toList $ Map.lookup name (getExportsMap exportsMap)
1511-
, canUseIdent thingMissing identInfo
1512-
, moduleNameText identInfo `notElem` fromMaybe [] notTheseModules
1513-
, suggestion <- renderNewImport identInfo
1508+
| Just name <- [T.stripPrefix (maybe "" (<> ".") qual) $ notInScope thingMissing] -- strip away qualified module names from the unknown name
1509+
, identInfo <- maybe [] Set.toList $ Map.lookup name (getExportsMap exportsMap) -- look up the modified unknown name in the export map
1510+
, canUseIdent thingMissing identInfo -- check if the identifier information retrieved can be used
1511+
, moduleNameText identInfo `notElem` fromMaybe [] notTheseModules -- check if the module of the identifier is allowed
1512+
, suggestion <- renderNewImport identInfo -- creates a list of import suggestions for the retrieved identifier information
15141513
]
15151514
where
1516-
renderNewImport :: IdentInfo -> [(CodeActionKind, NewImport)]
1515+
renderNewImport :: IdentInfo -> [ImportSuggestion]
15171516
renderNewImport identInfo
15181517
| Just q <- qual
1519-
= [(quickFixImportKind "new.qualified", newQualImport m q)]
1518+
= [ImportSuggestion importanceScore (quickFixImportKind "new.qualified") (newQualImport m q)]
15201519
| otherwise
1521-
= [(quickFixImportKind' "new" importStyle, newUnqualImport m (renderImportStyle importStyle) False)
1520+
= [ImportSuggestion importanceScore (quickFixImportKind' "new" importStyle) (newUnqualImport m (renderImportStyle importStyle) False)
15221521
| importStyle <- NE.toList $ importStyles identInfo] ++
1523-
[(quickFixImportKind "new.all", newImportAll m)]
1522+
[ImportSuggestion importanceScore (quickFixImportKind "new.all") (newImportAll m)]
15241523
where
1524+
-- The importance score takes 2 metrics into account. The first being the similarity using
1525+
-- the Text.Fuzzy.Parallel.match function. The second is a factor of the relation between
1526+
-- the modules prefix import suggestion and the unknown identifier names.
1527+
importanceScore
1528+
| Just q <- qual
1529+
= let
1530+
similarityScore = fromIntegral $ unpackMatchScore (TFP.match (T.toLower q) (T.toLower m)) :: Double
1531+
(maxLength, minLength) = case (T.length q, T.length m) of
1532+
(la, lb)
1533+
| la >= lb -> (fromIntegral la, fromIntegral lb)
1534+
| otherwise -> (fromIntegral lb, fromIntegral la)
1535+
lengthPenaltyFactor = 100 * minLength / maxLength
1536+
in max 0 (floor (similarityScore * lengthPenaltyFactor))
1537+
| otherwise
1538+
= 0
1539+
where
1540+
unpackMatchScore pScore
1541+
| Just score <- pScore = score
1542+
| otherwise = 0
15251543
m = moduleNameText identInfo
15261544

1545+
-- | Implements a lexicographic order for import suggestions.
1546+
-- First compares the importance score in DESCENDING order.
1547+
-- If the scores are equal it compares the import names alphabetical order.
1548+
data ImportSuggestion = ImportSuggestion !Int !CodeActionKind !NewImport
1549+
deriving ( Eq )
1550+
1551+
instance Ord ImportSuggestion where
1552+
compare (ImportSuggestion s1 _ i1) (ImportSuggestion s2 _ i2)
1553+
| s1 == s2 = compare i1 i2
1554+
| otherwise = flip compare s1 s2
1555+
15271556
newtype NewImport = NewImport {unNewImport :: T.Text}
15281557
deriving (Show, Eq, Ord)
15291558

0 commit comments

Comments
 (0)