Skip to content

Commit 7727c92

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

File tree

1 file changed

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

1 file changed

+43
-16
lines changed

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

Lines changed: 43 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,63 @@ 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+
[ s | s <- 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 = fromIntegral (max (T.length q) (T.length m)) :: Double
1532+
minLength = fromIntegral (min (T.length q) (T.length m)) :: Double
1533+
lengthPenaltyFactor = 100 * minLength / maxLength
1534+
in max 0 (floor (similarityScore * lengthPenaltyFactor))
1535+
| otherwise
1536+
= 0
1537+
where
1538+
unpackMatchScore pScore
1539+
| Just score <- pScore = score
1540+
| otherwise = 0
15251541
m = moduleNameText identInfo
15261542

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

0 commit comments

Comments
 (0)