Skip to content

Commit 1b21aad

Browse files
authored
Merge branch 'master' into mpj/dignostic-code-description
2 parents 0eab739 + e49d566 commit 1b21aad

File tree

23 files changed

+532
-39
lines changed

23 files changed

+532
-39
lines changed

cabal.project

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ packages:
88
./hls-test-utils
99

1010

11-
index-state: 2025-05-12T13:26:29Z
11+
index-state: 2025-06-07T14:57:40Z
1212

1313
tests: True
1414
test-show-details: direct

docs/support/plugin-support.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ For example, a plugin to provide a formatter which has itself been abandoned has
5555
| `hls-explicit-record-fields-plugin` | 2 | |
5656
| `hls-fourmolu-plugin` | 2 | |
5757
| `hls-gadt-plugin` | 2 | |
58-
| `hls-hlint-plugin` | 2 | 9.10.1 |
58+
| `hls-hlint-plugin` | 2 | |
5959
| `hls-module-name-plugin` | 2 | |
6060
| `hls-notes-plugin` | 2 | |
6161
| `hls-qualify-imported-names-plugin` | 2 | |

ghcide-test/exe/CompletionTests.hs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import Test.Hls.Util
3333
import Test.Tasty
3434
import Test.Tasty.HUnit
3535

36-
3736
tests :: TestTree
3837
tests
3938
= testGroup "completion"
@@ -61,6 +60,7 @@ completionTest :: HasCallStack => String -> [T.Text] -> Position -> [(T.Text, Co
6160
completionTest name src pos expected = testSessionSingleFile name "A.hs" (T.unlines src) $ do
6261
docId <- openDoc "A.hs" "haskell"
6362
_ <- waitForDiagnostics
63+
6464
compls <- getAndResolveCompletions docId pos
6565
let compls' = [ (_label, _kind, _insertText, _additionalTextEdits) | CompletionItem{..} <- compls]
6666
let emptyToMaybe x = if T.null x then Nothing else Just x
@@ -211,7 +211,38 @@ localCompletionTests = [
211211

212212
compls <- getCompletions doc (Position 0 15)
213213
liftIO $ filter ("AAA" `T.isPrefixOf`) (mapMaybe _insertText compls) @?= ["AAAAA"]
214-
pure ()
214+
pure (),
215+
completionTest
216+
"polymorphic record dot completion"
217+
[ "{-# LANGUAGE OverloadedRecordDot #-}"
218+
, "module A () where"
219+
, "data Record = Record"
220+
, " { field1 :: Int"
221+
, " , field2 :: Int"
222+
, " }"
223+
, -- Without the following, this file doesn't trigger any diagnostics, so completionTest waits forever
224+
"triggerDiag :: UnknownType"
225+
, "foo record = record.f"
226+
]
227+
(Position 7 21)
228+
[("field1", CompletionItemKind_Function, "field1", True, False, Nothing)
229+
,("field2", CompletionItemKind_Function, "field2", True, False, Nothing)
230+
],
231+
completionTest
232+
"qualified polymorphic record dot completion"
233+
[ "{-# LANGUAGE OverloadedRecordDot #-}"
234+
, "module A () where"
235+
, "data Record = Record"
236+
, " { field1 :: Int"
237+
, " , field2 :: Int"
238+
, " }"
239+
, "someValue = undefined"
240+
, "foo = A.someValue.f"
241+
]
242+
(Position 7 19)
243+
[("field1", CompletionItemKind_Function, "field1", True, False, Nothing)
244+
,("field2", CompletionItemKind_Function, "field2", True, False, Nothing)
245+
]
215246
]
216247

217248
nonLocalCompletionTests :: [TestTree]

ghcide-test/exe/Main.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ module Main (main) where
3333
import qualified HieDbRetry
3434
import Test.Tasty
3535
import Test.Tasty.Ingredients.Rerun
36+
import Test.Tasty.Runners
3637

3738
import AsyncTests
3839
import BootTests
@@ -70,7 +71,7 @@ import WatchedFileTests
7071
main :: IO ()
7172
main = do
7273
-- We mess with env vars so run single-threaded.
73-
defaultMainWithRerun $ testGroup "ghcide"
74+
defaultMainWithRerun $ PlusTestOptions mkSequential $ testGroup "ghcide"
7475
[ OpenCloseTest.tests
7576
, InitializeResponseTests.tests
7677
, CompletionTests.tests
@@ -104,3 +105,6 @@ main = do
104105
, HieDbRetry.tests
105106
, ExceptionTests.tests
106107
]
108+
where
109+
PlusTestOptions mkSequential _ =sequentialTestGroup "foo" AllFinish []
110+

ghcide/ghcide.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ library
5757
, deepseq
5858
, dependent-map
5959
, dependent-sum
60-
, Diff ^>=0.5
60+
, Diff ^>=0.5 || ^>=1.0.0
6161
, directory
6262
, dlist
6363
, enummapset

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,9 @@ getCompletionPrefixFromRope pos@(Position l c) ropetext =
878878
[] -> Nothing
879879
(x:xs) -> do
880880
let modParts = reverse $ filter (not .T.null) xs
881-
modName = T.intercalate "." modParts
881+
-- Must check the prefix is a valid module name, else record dot accesses treat
882+
-- the record name as a qualName for search and generated imports
883+
modName = if all (isUpper . T.head) modParts then T.intercalate "." modParts else ""
882884
return $ PosPrefixInfo { fullLine = curLine, prefixScope = modName, prefixText = x, cursorPos = pos }
883885

884886
completionPrefixPos :: PosPrefixInfo -> Position

haskell-language-server.cabal

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ library hls-eval-plugin
473473
, bytestring
474474
, containers
475475
, deepseq
476-
, Diff ^>=0.5
476+
, Diff ^>=0.5 || ^>=1.0.0
477477
, dlist
478478
, extra
479479
, filepath
@@ -703,14 +703,14 @@ flag hlint
703703
manual: True
704704

705705
common hlint
706-
if flag(hlint) && ((impl(ghc < 9.10) || impl(ghc > 9.11)) || flag(ignore-plugins-ghc-bounds))
706+
if flag(hlint)
707707
build-depends: haskell-language-server:hls-hlint-plugin
708708
cpp-options: -Dhls_hlint
709709

710710
library hls-hlint-plugin
711711
import: defaults, pedantic, warnings
712712
-- https://github.com/ndmitchell/hlint/pull/1594
713-
if !(flag(hlint)) || ((impl(ghc >= 9.10) && impl(ghc < 9.11)) && !flag(ignore-plugins-ghc-bounds))
713+
if !flag(hlint)
714714
buildable: False
715715
exposed-modules: Ide.Plugin.Hlint
716716
hs-source-dirs: plugins/hls-hlint-plugin/src
@@ -735,10 +735,14 @@ library hls-hlint-plugin
735735
, transformers
736736
, unordered-containers
737737
, ghc-lib-parser-ex
738-
, apply-refact
739-
--
740738
, lsp-types
741739

740+
-- apply-refact doesn't work on 9.10, or even have a buildable
741+
-- configuration
742+
if impl(ghc >= 9.11) || impl(ghc < 9.10)
743+
cpp-options: -DAPPLY_REFACT
744+
build-depends: apply-refact
745+
742746
if flag(ghc-lib)
743747
cpp-options: -DGHC_LIB
744748
build-depends:
@@ -753,14 +757,15 @@ library hls-hlint-plugin
753757

754758
test-suite hls-hlint-plugin-tests
755759
import: defaults, pedantic, test-defaults, warnings
756-
if !flag(hlint) || ((impl(ghc >= 9.10) && impl(ghc < 9.11)) && !flag(ignore-plugins-ghc-bounds))
760+
if !flag(hlint)
757761
buildable: False
758762
type: exitcode-stdio-1.0
759763
hs-source-dirs: plugins/hls-hlint-plugin/test
760764
main-is: Main.hs
761765
-- Work around https://gitlab.haskell.org/ghc/ghc/-/issues/24648
762766
if os(darwin)
763767
ghc-options: -optl-Wl,-ld_classic
768+
764769
build-depends:
765770
aeson
766771
, containers
@@ -801,7 +806,7 @@ library hls-stan-plugin
801806
, lsp-types
802807
, text
803808
, unordered-containers
804-
, stan >= 0.1.2.0
809+
, stan >= 0.2.1.0
805810
, trial
806811
, directory
807812

@@ -2110,7 +2115,7 @@ test-suite ghcide-tests
21102115
, sqlite-simple
21112116
, stm
21122117
, stm-containers
2113-
, tasty
2118+
, tasty >=1.5
21142119
, tasty-expected-failure
21152120
, tasty-hunit >=0.10
21162121
, tasty-quickcheck

hls-plugin-api/hls-plugin-api.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ library
6060
, data-default
6161
, dependent-map
6262
, dependent-sum >=0.7
63-
, Diff ^>=0.5
63+
, Diff ^>=0.5 || ^>=1.0.0
6464
, dlist
6565
, extra
6666
, filepath

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

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
module Ide.Plugin.ConfigUtils (
77
pluginsToDefaultConfig,
8-
pluginsToVSCodeExtensionSchema
8+
pluginsToVSCodeExtensionSchema,
9+
pluginsCustomConfigToMarkdownTables
910
) where
1011

1112
import Control.Lens (at, (&), (?~))
@@ -18,8 +19,15 @@ import qualified Data.Dependent.Sum as DSum
1819
import Data.List.Extra (nubOrd)
1920
import Data.String (IsString (fromString))
2021
import qualified Data.Text as T
22+
import GHC.TypeLits (symbolVal)
2123
import Ide.Plugin.Config
22-
import Ide.Plugin.Properties (toDefaultJSON,
24+
import Ide.Plugin.Properties (KeyNameProxy, MetaData (..),
25+
PluginCustomConfig (..),
26+
PluginCustomConfigParam (..),
27+
Properties (..),
28+
SPropertyKey (..),
29+
SomePropertyKeyWithMetaData (..),
30+
toDefaultJSON,
2331
toVSCodeExtensionSchema)
2432
import Ide.Types
2533
import Language.LSP.Protocol.Message
@@ -143,3 +151,92 @@ pluginsToVSCodeExtensionSchema IdePlugins {..} = A.object $ mconcat $ singlePlug
143151
]
144152
withIdPrefix x = "haskell.plugin." <> pId <> "." <> x
145153
toKey' = fromString . T.unpack . withIdPrefix
154+
155+
156+
-- | Generates markdown tables for custom config
157+
pluginsCustomConfigToMarkdownTables :: IdePlugins a -> T.Text
158+
pluginsCustomConfigToMarkdownTables IdePlugins {..} = T.unlines
159+
$ map renderCfg
160+
$ filter (\(PluginCustomConfig _ params) -> not $ null params)
161+
$ map toPluginCustomConfig ipMap
162+
where
163+
toPluginCustomConfig :: PluginDescriptor ideState -> PluginCustomConfig
164+
toPluginCustomConfig PluginDescriptor {pluginConfigDescriptor = ConfigDescriptor {configCustomConfig = c}, pluginId = PluginId pId} =
165+
PluginCustomConfig { pcc'Name = pId, pcc'Params = toPluginCustomConfigParams c}
166+
toPluginCustomConfigParams :: CustomConfig -> [PluginCustomConfigParam]
167+
toPluginCustomConfigParams (CustomConfig p) = toPluginCustomConfigParams' p
168+
toPluginCustomConfigParams' :: Properties r -> [PluginCustomConfigParam]
169+
toPluginCustomConfigParams' EmptyProperties = []
170+
toPluginCustomConfigParams' (ConsProperties (keyNameProxy :: KeyNameProxy s) (k :: SPropertyKey k) (m :: MetaData t) xs) =
171+
toEntry (SomePropertyKeyWithMetaData k m) : toPluginCustomConfigParams' xs
172+
where
173+
toEntry :: SomePropertyKeyWithMetaData -> PluginCustomConfigParam
174+
toEntry (SomePropertyKeyWithMetaData SNumber MetaData {..}) =
175+
PluginCustomConfigParam {
176+
pccp'Name = T.pack $ symbolVal keyNameProxy,
177+
pccp'Description = description,
178+
pccp'Default = T.pack $ show defaultValue,
179+
pccp'EnumValues = []
180+
}
181+
toEntry (SomePropertyKeyWithMetaData SInteger MetaData {..}) =
182+
PluginCustomConfigParam {
183+
pccp'Name = T.pack $ symbolVal keyNameProxy,
184+
pccp'Description = description,
185+
pccp'Default = T.pack $ show defaultValue,
186+
pccp'EnumValues = []
187+
}
188+
toEntry (SomePropertyKeyWithMetaData SString MetaData {..}) =
189+
PluginCustomConfigParam {
190+
pccp'Name = T.pack $ symbolVal keyNameProxy,
191+
pccp'Description = description,
192+
pccp'Default = T.pack $ show defaultValue,
193+
pccp'EnumValues = []
194+
}
195+
toEntry (SomePropertyKeyWithMetaData SBoolean MetaData {..}) =
196+
PluginCustomConfigParam {
197+
pccp'Name = T.pack $ symbolVal keyNameProxy,
198+
pccp'Description = description,
199+
pccp'Default = T.pack $ show defaultValue,
200+
pccp'EnumValues = []
201+
}
202+
toEntry (SomePropertyKeyWithMetaData (SObject _) MetaData {..}) =
203+
PluginCustomConfigParam {
204+
pccp'Name = T.pack $ symbolVal keyNameProxy,
205+
pccp'Description = description,
206+
pccp'Default = "TODO: nested object", -- T.pack $ show defaultValue,
207+
pccp'EnumValues = []
208+
}
209+
toEntry (SomePropertyKeyWithMetaData (SArray _) MetaData {..}) =
210+
PluginCustomConfigParam {
211+
pccp'Name = T.pack $ symbolVal keyNameProxy,
212+
pccp'Description = description,
213+
pccp'Default = "TODO: Array values", -- T.pack $ show defaultValue,
214+
pccp'EnumValues = []
215+
}
216+
toEntry (SomePropertyKeyWithMetaData (SEnum _) EnumMetaData {..}) =
217+
PluginCustomConfigParam {
218+
pccp'Name = T.pack $ symbolVal keyNameProxy,
219+
pccp'Description = description,
220+
pccp'Default = T.pack $ show defaultValue,
221+
pccp'EnumValues = map (T.pack . show) enumValues
222+
}
223+
toEntry (SomePropertyKeyWithMetaData SProperties PropertiesMetaData {..}) =
224+
PluginCustomConfigParam {
225+
pccp'Name = T.pack $ symbolVal keyNameProxy,
226+
pccp'Description = description,
227+
pccp'Default = T.pack $ show defaultValue,
228+
pccp'EnumValues = []
229+
}
230+
renderCfg :: PluginCustomConfig -> T.Text
231+
renderCfg (PluginCustomConfig pId pccParams) =
232+
T.unlines (pluginHeader : tableHeader : rows pccParams)
233+
where
234+
pluginHeader = "## " <> pId
235+
tableHeader =
236+
"| Property | Description | Default | Allowed values |" <> "\n" <>
237+
"| --- | --- | --- | --- |"
238+
rows = map renderRow
239+
renderRow PluginCustomConfigParam {..} =
240+
"| `" <> pccp'Name <> "` | " <> pccp'Description <> " | `" <> pccp'Default <> "` | " <> renderEnum pccp'EnumValues <> " |"
241+
renderEnum [] = " &nbsp; " -- Placeholder to prevent missing cells
242+
renderEnum vs = "<ul> " <> (T.intercalate " " $ map (\x -> "<li><code>" <> x <> "</code></li>") vs) <> " </ul>"

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ module Ide.Plugin.Properties
2121
MetaData (..),
2222
PropertyKey (..),
2323
SPropertyKey (..),
24+
SomePropertyKeyWithMetaData (..),
2425
KeyNameProxy (..),
2526
KeyNamePath (..),
26-
Properties,
27+
Properties(..),
2728
HasProperty,
2829
HasPropertyByPath,
2930
emptyProperties,
@@ -42,6 +43,8 @@ module Ide.Plugin.Properties
4243
usePropertyByPathEither,
4344
usePropertyByPath,
4445
(&),
46+
PluginCustomConfig(..),
47+
PluginCustomConfigParam(..),
4548
)
4649
where
4750

@@ -516,3 +519,15 @@ toVSCodeExtensionSchema' ps = case ps of
516519
]
517520
(SomePropertyKeyWithMetaData SProperties PropertiesMetaData {..}) ->
518521
map (first Just) $ toVSCodeExtensionSchema' childrenProperties
522+
523+
data PluginCustomConfig = PluginCustomConfig {
524+
pcc'Name :: T.Text,
525+
pcc'Params :: [PluginCustomConfigParam]
526+
}
527+
data PluginCustomConfigParam = PluginCustomConfigParam {
528+
pccp'Name :: T.Text,
529+
pccp'Description :: T.Text,
530+
pccp'Default :: T.Text,
531+
pccp'EnumValues :: [T.Text]
532+
}
533+

0 commit comments

Comments
 (0)