Skip to content

Commit 6ba5eef

Browse files
pepeiborrajneira
andauthored
Partial sort of fuzzy filtering results (#2240)
* partial sort of fuzzy filtering results * use slice instead of fromWithN for another 2% * fix perfect score computation * redundant import * bump ghcide version number * cleanup * fix a test Co-authored-by: Javier Neira <[email protected]>
1 parent 21c42d5 commit 6ba5eef

File tree

4 files changed

+53
-29
lines changed

4 files changed

+53
-29
lines changed

ghcide/ghcide.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cabal-version: 2.4
22
build-type: Simple
33
category: Development
44
name: ghcide
5-
version: 1.4.2.2
5+
version: 1.4.2.3
66
license: Apache-2.0
77
license-file: LICENSE
88
author: Digital Asset and Ghcide contributors

ghcide/src/Development/IDE/Plugin/Completions/Logic.hs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -544,9 +544,9 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
544544
filtModNameCompls =
545545
map mkModCompl
546546
$ mapMaybe (T.stripPrefix enteredQual)
547-
$ Fuzzy.simpleFilter chunkSize fullPrefix allModNamesAsNS
547+
$ Fuzzy.simpleFilter chunkSize maxC fullPrefix allModNamesAsNS
548548

549-
filtCompls = map Fuzzy.original $ Fuzzy.filter chunkSize prefixText ctxCompls "" "" label False
549+
filtCompls = map Fuzzy.original $ Fuzzy.filter chunkSize maxC prefixText ctxCompls "" "" label False
550550
where
551551

552552
mcc = case maybe_parsed of
@@ -593,7 +593,7 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
593593

594594
filtListWith f list =
595595
[ f label
596-
| label <- Fuzzy.simpleFilter chunkSize fullPrefix list
596+
| label <- Fuzzy.simpleFilter chunkSize maxC fullPrefix list
597597
, enteredQual `T.isPrefixOf` label
598598
]
599599

@@ -621,8 +621,7 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
621621
-> return []
622622
| otherwise -> do
623623
-- assumes that nubOrdBy is stable
624-
-- nubOrd is very slow - take 10x the maximum configured
625-
let uniqueFiltCompls = nubOrdBy uniqueCompl $ take (maxC*10) filtCompls
624+
let uniqueFiltCompls = nubOrdBy uniqueCompl filtCompls
626625
let compls = map (mkCompl plId ideOpts) uniqueFiltCompls
627626
return $ filtModNameCompls
628627
++ filtKeywordCompls

ghcide/src/Text/Fuzzy/Parallel.hs

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,33 @@ module Text.Fuzzy.Parallel
1010
import Control.Monad.ST (runST)
1111
import Control.Parallel.Strategies (Eval, Strategy, evalTraversable,
1212
parTraversable, rseq, using)
13-
import Data.Function (on)
1413
import Data.Monoid.Textual (TextualMonoid)
15-
import Data.Ord (Down (Down))
1614
import Data.Vector (Vector, (!))
1715
import qualified Data.Vector as V
1816
-- need to use a stable sort
1917
import Data.Bifunctor (second)
20-
import qualified Data.Vector.Algorithms.Tim as VA
18+
import Data.Maybe (fromJust)
2119
import Prelude hiding (filter)
2220
import Text.Fuzzy (Fuzzy (..), match)
2321

2422
-- | The function to filter a list of values by fuzzy search on the text extracted from them.
25-
--
26-
-- >>> length $ filter 1000 200 "ML" (concat $ replicate 10000 [("Standard ML", 1990),("OCaml",1996),("Scala",2003)]) "<" ">" fst False
27-
-- 200
2823
filter :: (TextualMonoid s)
2924
=> Int -- ^ Chunk size. 1000 works well.
25+
-> Int -- ^ Max. number of results wanted
3026
-> s -- ^ Pattern.
3127
-> [t] -- ^ The list of values containing the text to search in.
3228
-> s -- ^ The text to add before each match.
3329
-> s -- ^ The text to add after each match.
3430
-> (t -> s) -- ^ The function to extract the text from the container.
3531
-> Bool -- ^ Case sensitivity.
3632
-> [Fuzzy t s] -- ^ The list of results, sorted, highest score first.
37-
filter chunkSize pattern ts pre post extract caseSen = runST $ do
38-
let v = (V.mapMaybe id
33+
filter chunkSize maxRes pattern ts pre post extract caseSen = runST $ do
34+
let v = V.mapMaybe id
3935
(V.map (\t -> match pattern t pre post extract caseSen) (V.fromList ts)
4036
`using`
41-
parVectorChunk chunkSize (evalTraversable forceScore)))
42-
v' <- V.unsafeThaw v
43-
VA.sortBy (compare `on` (Down . score)) v'
44-
v'' <- V.unsafeFreeze v'
45-
return $ V.toList v''
37+
parVectorChunk chunkSize (evalTraversable forceScore))
38+
perfectScore = score $ fromJust $ match pattern pattern "" "" id False
39+
return $ partialSortByAscScore maxRes perfectScore v
4640

4741
-- | Return all elements of the list that have a fuzzy
4842
-- match against the pattern. Runs with default settings where
@@ -53,11 +47,12 @@ filter chunkSize pattern ts pre post extract caseSen = runST $ do
5347
{-# INLINABLE simpleFilter #-}
5448
simpleFilter :: (TextualMonoid s)
5549
=> Int -- ^ Chunk size. 1000 works well.
50+
-> Int -- ^ Max. number of results wanted
5651
-> s -- ^ Pattern to look for.
5752
-> [s] -- ^ List of texts to check.
5853
-> [s] -- ^ The ones that match.
59-
simpleFilter chunk pattern xs =
60-
map original $ filter chunk pattern xs mempty mempty id False
54+
simpleFilter chunk maxRes pattern xs =
55+
map original $ filter chunk maxRes pattern xs mempty mempty id False
6156

6257
--------------------------------------------------------------------------------
6358

@@ -82,10 +77,8 @@ parVectorChunk chunkSize st v =
8277
-- [[0,1,2],[3,4,5],[6,7,8],[9,10,11],[12]]
8378
chunkVector :: Int -> Vector a -> [Vector a]
8479
chunkVector chunkSize v = do
85-
let indices = chunkIndices chunkSize (0,l)
86-
l = V.length v
87-
[V.fromListN (h-l+1) [v ! j | j <- [l .. h]]
88-
| (l,h) <- indices]
80+
let indices = chunkIndices chunkSize (0,V.length v)
81+
[V.slice l (h-l) v | (l,h) <- indices]
8982

9083
-- >>> chunkIndices 3 (0,9)
9184
-- >>> chunkIndices 3 (0,10)
@@ -103,3 +96,34 @@ pairwise :: [a] -> [(a,a)]
10396
pairwise [] = []
10497
pairwise [_] = []
10598
pairwise (x:y:xs) = (x,y) : pairwise (y:xs)
99+
100+
-- | A stable partial sort ascending by score. O(N) best case, O(wanted*N) worst case
101+
partialSortByAscScore :: TextualMonoid s
102+
=> Int -- ^ Number of items needed
103+
-> Int -- ^ Value of a perfect score
104+
-> Vector (Fuzzy t s)
105+
-> [Fuzzy t s]
106+
partialSortByAscScore wantedCount perfectScore v = loop 0 (SortState minBound perfectScore 0) [] where
107+
l = V.length v
108+
loop index st@SortState{..} acc
109+
| foundCount == wantedCount = reverse acc
110+
| index == l
111+
-- ProgressCancelledException
112+
= if bestScoreSeen < scoreWanted
113+
then loop 0 st{scoreWanted = bestScoreSeen, bestScoreSeen = minBound} acc
114+
else reverse acc
115+
| otherwise =
116+
case v!index of
117+
x | score x == scoreWanted
118+
-> loop (index+1) st{foundCount = foundCount+1} (x:acc)
119+
| score x < scoreWanted && score x > bestScoreSeen
120+
-> loop (index+1) st{bestScoreSeen = score x} acc
121+
| otherwise
122+
-> loop (index+1) st acc
123+
124+
data SortState a = SortState
125+
{ bestScoreSeen :: !Int
126+
, scoreWanted :: !Int
127+
, foundCount :: !Int
128+
}
129+
deriving Show

ghcide/test/exe/Main.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4508,7 +4508,9 @@ otherCompletionTests = [
45084508

45094509
packageCompletionTests :: [TestTree]
45104510
packageCompletionTests =
4511-
[ testSessionWait "fromList" $ do
4511+
[ testSession' "fromList" $ \dir -> do
4512+
liftIO $ writeFile (dir </> "hie.yaml")
4513+
"cradle: {direct: {arguments: [-hide-all-packages, -package, base, A]}}"
45124514
doc <- createDoc "A.hs" "haskell" $ T.unlines
45134515
[ "{-# OPTIONS_GHC -Wunused-binds #-}",
45144516
"module A () where",
@@ -4524,9 +4526,8 @@ packageCompletionTests =
45244526
]
45254527
liftIO $ take 3 (sort compls') @?=
45264528
map ("Defined in "<>)
4527-
[ "'Data.IntMap"
4528-
, "'Data.IntMap.Lazy"
4529-
, "'Data.IntMap.Strict"
4529+
[ "'Data.List.NonEmpty"
4530+
, "'GHC.Exts"
45304531
]
45314532

45324533
, testSessionWait "Map" $ do

0 commit comments

Comments
 (0)