Skip to content

Commit b1d912a

Browse files
Limit completions to top 40 (#1218)
* Limit completions to top 20 We are overwhelming the LSP client by sending 100s of completions after the first character. Instead, let's send 20 at a time and refresh for more when the user types another word * Simplify (thanks Neil!) * Magic constant explained and increased to 40 * Add test * Turn maxCompletions into config * Fix some inaccuracies in tests * document haskell.maxCompletions * Fix another test Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 3278d53 commit b1d912a

File tree

6 files changed

+38
-22
lines changed

6 files changed

+38
-22
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ Here is a list of the additional settings currently supported by `haskell-langua
298298
- Completion snippets (`haskell.completionSnippetsOn`, default true): whether to support completion snippets
299299
- Liquid Haskell (`haskell.liquidOn`, default false): whether to enable Liquid Haskell support (currently unused until the Liquid Haskell support is functional again)
300300
- Hlint (`haskell.hlintOn`, default true): whether to enable Hlint support
301+
- Max completions (`haskell.maxCompletions`, default 40): maximum number of completions sent to the LSP client.
301302

302303
Settings like this are typically provided by the language-specific LSP client support for your editor, for example in Emacs by `lsp-haskell`.
303304

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import Development.IDE.GHC.Util
3030
import Development.IDE.LSP.Server
3131
import TcRnDriver (tcRnImportDecls)
3232
import Data.Maybe
33-
import Ide.Plugin.Config (Config(completionSnippetsOn))
33+
import Ide.Plugin.Config (Config (completionSnippetsOn, maxCompletions))
3434
import Ide.PluginUtils (getClientConfig)
3535

3636
#if defined(GHC_LIB)
@@ -115,7 +115,6 @@ data NonLocalCompletions = NonLocalCompletions
115115
instance Hashable NonLocalCompletions
116116
instance NFData NonLocalCompletions
117117
instance Binary NonLocalCompletions
118-
119118
-- | Generate code actions.
120119
getCompletionsLSP
121120
:: LSP.LspFuncs Config
@@ -144,12 +143,14 @@ getCompletionsLSP lsp ide
144143
-> return (Completions $ List [])
145144
(Just pfix', _) -> do
146145
let clientCaps = clientCapabilities $ shakeExtras ide
147-
snippets <- WithSnippets . completionSnippetsOn <$> getClientConfig lsp
148-
Completions . List <$> getCompletions ideOpts cci' parsedMod bindMap pfix' clientCaps snippets
146+
config <- getClientConfig lsp
147+
let snippets = WithSnippets . completionSnippetsOn $ config
148+
allCompletions <- getCompletions ideOpts cci' parsedMod bindMap pfix' clientCaps snippets
149+
let (topCompletions, rest) = splitAt (maxCompletions config) allCompletions
150+
pure $ CompletionList (CompletionListType (null rest) (List topCompletions))
149151
_ -> return (Completions $ List [])
150152
_ -> return (Completions $ List [])
151153
_ -> return (Completions $ List [])
152-
153154
setHandlersCompletion :: PartialHandlers Config
154155
setHandlersCompletion = PartialHandlers $ \WithMessage{..} x -> return x{
155156
LSP.completionHandler = withResponse RspCompletion getCompletionsLSP

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,9 +565,11 @@ getCompletions ideOpts CC { allModNamesAsNS, unqualCompls, qualCompls, importabl
565565
= filtPragmaCompls (pragmaSuffix fullLine)
566566
| otherwise
567567
= let uniqueFiltCompls = nubOrdOn insertText filtCompls
568-
in filtModNameCompls ++ map (toggleSnippets caps withSnippets
569-
. mkCompl ideOpts . stripAutoGenerated) uniqueFiltCompls
570-
++ filtKeywordCompls
568+
in filtModNameCompls
569+
++ filtKeywordCompls
570+
++ map ( toggleSnippets caps withSnippets
571+
. mkCompl ideOpts . stripAutoGenerated
572+
) uniqueFiltCompls
571573
return result
572574

573575

ghcide/test/exe/Main.hs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3262,7 +3262,17 @@ otherCompletionTests = [
32623262
-- This should be sufficient to detect that we are in a
32633263
-- type context and only show the completion to the type.
32643264
(Position 3 11)
3265-
[("Integer", CiStruct, "Integer ", True, True, Nothing)]
3265+
[("Integer", CiStruct, "Integer ", True, True, Nothing)],
3266+
3267+
testSessionWait "maxCompletions" $ do
3268+
doc <- createDoc "A.hs" "haskell" $ T.unlines
3269+
[ "{-# OPTIONS_GHC -Wunused-binds #-}",
3270+
"module A () where",
3271+
"a = Prelude."
3272+
]
3273+
_ <- waitForDiagnostics
3274+
compls <- getCompletions doc (Position 3 13)
3275+
liftIO $ length compls @?= maxCompletions def
32663276
]
32673277

32683278
highlightTests :: TestTree

hls-plugin-api/src/Ide/Plugin/Config.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ data Config =
6969
, completionSnippetsOn :: !Bool
7070
, formatOnImportOn :: !Bool
7171
, formattingProvider :: !T.Text
72+
, maxCompletions :: !Int
7273
, plugins :: !(Map.Map T.Text PluginConfig)
7374
} deriving (Show,Eq)
7475

@@ -87,6 +88,7 @@ instance Default Config where
8788
, formattingProvider = "ormolu"
8889
-- , formattingProvider = "floskell"
8990
-- , formattingProvider = "stylish-haskell"
91+
, maxCompletions = 40
9092
, plugins = Map.empty
9193
}
9294

@@ -107,6 +109,7 @@ instance A.FromJSON Config where
107109
<*> o .:? "completionSnippetsOn" .!= completionSnippetsOn def
108110
<*> o .:? "formatOnImportOn" .!= formatOnImportOn def
109111
<*> o .:? "formattingProvider" .!= formattingProvider def
112+
<*> o .:? "maxCompletions" .!= maxCompletions def
110113
<*> o .:? "plugin" .!= plugins def
111114

112115
-- 2017-10-09 23:22:00.710515298 [ThreadId 11] - ---> {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"haskell":{"maxNumberOfProblems":100,"hlintOn":true}}}}

test/functional/Completion.hs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import Test.Tasty
1313
import Test.Tasty.ExpectedFailure (ignoreTestBecause)
1414
import Test.Tasty.HUnit
1515
import qualified Data.Text as T
16-
import System.Time.Extra (sleep)
1716

1817
tests :: TestTree
1918
tests = testGroup "completions" [
@@ -54,12 +53,12 @@ tests = testGroup "completions" [
5453
, testCase "completes imports" $ runSession hlsCommand fullCaps "test/testdata/completion" $ do
5554
doc <- openDoc "Completion.hs" "haskell"
5655

57-
liftIO $ sleep 4
56+
_ <- waitForDiagnostics
5857

5958
let te = TextEdit (Range (Position 1 17) (Position 1 26)) "Data.M"
6059
_ <- applyEdit doc te
6160

62-
compls <- getCompletions doc (Position 1 22)
61+
compls <- getCompletions doc (Position 1 23)
6362
let item = head $ filter ((== "Maybe") . (^. label)) compls
6463
liftIO $ do
6564
item ^. label @?= "Maybe"
@@ -69,22 +68,22 @@ tests = testGroup "completions" [
6968
, testCase "completes qualified imports" $ runSession hlsCommand fullCaps "test/testdata/completion" $ do
7069
doc <- openDoc "Completion.hs" "haskell"
7170

72-
liftIO $ sleep 4
71+
_ <- waitForDiagnostics
7372

74-
let te = TextEdit (Range (Position 2 17) (Position 1 25)) "Dat"
73+
let te = TextEdit (Range (Position 2 17) (Position 2 25)) "Data.L"
7574
_ <- applyEdit doc te
7675

77-
compls <- getCompletions doc (Position 1 19)
78-
let item = head $ filter ((== "Data.List") . (^. label)) compls
76+
compls <- getCompletions doc (Position 2 24)
77+
let item = head $ filter ((== "List") . (^. label)) compls
7978
liftIO $ do
80-
item ^. label @?= "Data.List"
79+
item ^. label @?= "List"
8180
item ^. detail @?= Just "Data.List"
8281
item ^. kind @?= Just CiModule
8382

8483
, testCase "completes language extensions" $ runSession hlsCommand fullCaps "test/testdata/completion" $ do
8584
doc <- openDoc "Completion.hs" "haskell"
8685

87-
liftIO $ sleep 4
86+
_ <- waitForDiagnostics
8887

8988
let te = TextEdit (Range (Position 0 24) (Position 0 31)) ""
9089
_ <- applyEdit doc te
@@ -98,7 +97,7 @@ tests = testGroup "completions" [
9897
, testCase "completes pragmas" $ runSession hlsCommand fullCaps "test/testdata/completion" $ do
9998
doc <- openDoc "Completion.hs" "haskell"
10099

101-
liftIO $ sleep 4
100+
_ <- waitForDiagnostics
102101

103102
let te = TextEdit (Range (Position 0 4) (Position 0 34)) ""
104103
_ <- applyEdit doc te
@@ -128,7 +127,7 @@ tests = testGroup "completions" [
128127
, testCase "completes options pragma" $ runSession hlsCommand fullCaps "test/testdata/completion" $ do
129128
doc <- openDoc "Completion.hs" "haskell"
130129

131-
liftIO $ sleep 4
130+
_ <- waitForDiagnostics
132131

133132
let te = TextEdit (Range (Position 0 4) (Position 0 34)) "OPTIONS"
134133
_ <- applyEdit doc te
@@ -159,7 +158,7 @@ tests = testGroup "completions" [
159158
doc <- openDoc "Completion.hs" "haskell"
160159

161160
compls <- getCompletions doc (Position 5 7)
162-
liftIO $ any ((== "!!") . (^. label)) compls @? ""
161+
liftIO $ assertBool "Expected completions" $ not $ null compls
163162

164163
-- See https://github.com/haskell/haskell-ide-engine/issues/903
165164
, testCase "strips compiler generated stuff from completions" $ runSession hlsCommand fullCaps "test/testdata/completion" $ do
@@ -349,7 +348,7 @@ contextTests = testGroup "contexts" [
349348
, testCase "only provides value suggestions" $ runSession hlsCommand fullCaps "test/testdata/completion" $ do
350349
doc <- openDoc "Context.hs" "haskell"
351350

352-
compls <- getCompletions doc (Position 3 9)
351+
compls <- getCompletions doc (Position 3 10)
353352
liftIO $ do
354353
compls `shouldContainCompl` "abs"
355354
compls `shouldNotContainCompl` "Applicative"

0 commit comments

Comments
 (0)