diff --git a/hls-plugin-api/src/Ide/Plugin/ConfigUtils.hs b/hls-plugin-api/src/Ide/Plugin/ConfigUtils.hs index 8ee6110d29..bc3b1831a1 100644 --- a/hls-plugin-api/src/Ide/Plugin/ConfigUtils.hs +++ b/hls-plugin-api/src/Ide/Plugin/ConfigUtils.hs @@ -15,8 +15,15 @@ import qualified Data.Dependent.Sum as DSum import Data.List.Extra (nubOrd) import Data.String (IsString (fromString)) import qualified Data.Text as T +import GHC.TypeLits (symbolVal) import Ide.Plugin.Config -import Ide.Plugin.Properties (toDefaultJSON, +import Ide.Plugin.Properties (KeyNameProxy, MetaData (..), + PluginCustomConfig (..), + PluginCustomConfigParam (..), + Properties (..), + SPropertyKey (..), + SomePropertyKeyWithMetaData (..), + toDefaultJSON, toVSCodeExtensionSchema) import Ide.Types import Language.LSP.Protocol.Message @@ -139,3 +146,92 @@ pluginsToVSCodeExtensionSchema IdePlugins {..} = A.object $ mconcat $ singlePlug ] withIdPrefix x = "haskell.plugin." <> pId <> "." <> x toKey' = fromString . T.unpack . withIdPrefix + + +-- | Generates markdown tables for custom config +pluginsCustomConfigToMarkdownTables :: IdePlugins a -> T.Text +pluginsCustomConfigToMarkdownTables IdePlugins {..} = T.unlines + $ map renderCfg + $ filter (\(PluginCustomConfig _ params) -> not $ null params) + $ map toPluginCustomConfig ipMap + where + toPluginCustomConfig :: PluginDescriptor ideState -> PluginCustomConfig + toPluginCustomConfig PluginDescriptor {pluginConfigDescriptor = ConfigDescriptor {configCustomConfig = c}, pluginId = PluginId pId} = + PluginCustomConfig { pcc'Name = pId, pcc'Params = toPluginCustomConfigParams c} + toPluginCustomConfigParams :: CustomConfig -> [PluginCustomConfigParam] + toPluginCustomConfigParams (CustomConfig p) = toPluginCustomConfigParams' p + toPluginCustomConfigParams' :: Properties r -> [PluginCustomConfigParam] + toPluginCustomConfigParams' EmptyProperties = [] + toPluginCustomConfigParams' (ConsProperties (keyNameProxy :: KeyNameProxy s) (k :: SPropertyKey k) (m :: MetaData t) xs) = + toEntry (SomePropertyKeyWithMetaData k m) : toPluginCustomConfigParams' xs + where + toEntry :: SomePropertyKeyWithMetaData -> PluginCustomConfigParam + toEntry (SomePropertyKeyWithMetaData SNumber MetaData {..}) = + PluginCustomConfigParam { + pccp'Name = T.pack $ symbolVal keyNameProxy, + pccp'Description = description, + pccp'Default = T.pack $ show defaultValue, + pccp'EnumValues = [] + } + toEntry (SomePropertyKeyWithMetaData SInteger MetaData {..}) = + PluginCustomConfigParam { + pccp'Name = T.pack $ symbolVal keyNameProxy, + pccp'Description = description, + pccp'Default = T.pack $ show defaultValue, + pccp'EnumValues = [] + } + toEntry (SomePropertyKeyWithMetaData SString MetaData {..}) = + PluginCustomConfigParam { + pccp'Name = T.pack $ symbolVal keyNameProxy, + pccp'Description = description, + pccp'Default = T.pack $ show defaultValue, + pccp'EnumValues = [] + } + toEntry (SomePropertyKeyWithMetaData SBoolean MetaData {..}) = + PluginCustomConfigParam { + pccp'Name = T.pack $ symbolVal keyNameProxy, + pccp'Description = description, + pccp'Default = T.pack $ show defaultValue, + pccp'EnumValues = [] + } + toEntry (SomePropertyKeyWithMetaData (SObject _) MetaData {..}) = + PluginCustomConfigParam { + pccp'Name = T.pack $ symbolVal keyNameProxy, + pccp'Description = description, + pccp'Default = "TODO: nested object", -- T.pack $ show defaultValue, + pccp'EnumValues = [] + } + toEntry (SomePropertyKeyWithMetaData (SArray _) MetaData {..}) = + PluginCustomConfigParam { + pccp'Name = T.pack $ symbolVal keyNameProxy, + pccp'Description = description, + pccp'Default = "TODO: Array values", -- T.pack $ show defaultValue, + pccp'EnumValues = [] + } + toEntry (SomePropertyKeyWithMetaData (SEnum _) EnumMetaData {..}) = + PluginCustomConfigParam { + pccp'Name = T.pack $ symbolVal keyNameProxy, + pccp'Description = description, + pccp'Default = T.pack $ show defaultValue, + pccp'EnumValues = map (T.pack . show) enumValues + } + toEntry (SomePropertyKeyWithMetaData SProperties PropertiesMetaData {..}) = + PluginCustomConfigParam { + pccp'Name = T.pack $ symbolVal keyNameProxy, + pccp'Description = description, + pccp'Default = T.pack $ show defaultValue, + pccp'EnumValues = [] + } + renderCfg :: PluginCustomConfig -> T.Text + renderCfg (PluginCustomConfig pId pccParams) = + T.unlines (pluginHeader : tableHeader : rows pccParams) + where + pluginHeader = "## " <> pId + tableHeader = + "| Property | Description | Default | Allowed values |" <> "\n" <> + "| --- | --- | --- | --- |" + rows = map renderRow + renderRow PluginCustomConfigParam {..} = + "| `" <> pccp'Name <> "` | " <> pccp'Description <> " | `" <> pccp'Default <> "` | " <> renderEnum pccp'EnumValues <> " |" + renderEnum [] = "   " -- Placeholder to prevent missing cells + renderEnum vs = "" diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index dda2bb7e33..49a45721b4 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -21,9 +21,10 @@ module Ide.Plugin.Properties MetaData (..), PropertyKey (..), SPropertyKey (..), + SomePropertyKeyWithMetaData (..), KeyNameProxy (..), KeyNamePath (..), - Properties, + Properties(..), HasProperty, HasPropertyByPath, emptyProperties, @@ -42,6 +43,8 @@ module Ide.Plugin.Properties usePropertyByPathEither, usePropertyByPath, (&), + PluginCustomConfig(..), + PluginCustomConfigParam(..), ) where @@ -516,3 +519,15 @@ toVSCodeExtensionSchema' ps = case ps of ] (SomePropertyKeyWithMetaData SProperties PropertiesMetaData {..}) -> map (first Just) $ toVSCodeExtensionSchema' childrenProperties + +data PluginCustomConfig = PluginCustomConfig { + pcc'Name :: T.Text, + pcc'Params :: [PluginCustomConfigParam] +} +data PluginCustomConfigParam = PluginCustomConfigParam { + pccp'Name :: T.Text, + pccp'Description :: T.Text, + pccp'Default :: T.Text, + pccp'EnumValues :: [T.Text] +} + diff --git a/src/Ide/Arguments.hs b/src/Ide/Arguments.hs index 733da2e557..be7f35e455 100644 --- a/src/Ide/Arguments.hs +++ b/src/Ide/Arguments.hs @@ -33,6 +33,7 @@ data Arguments | BiosMode BiosAction | Ghcide GhcideArguments | VSCodeExtensionSchemaMode + | PluginsCustomConfigMarkdownReferenceMode | DefaultConfigurationMode | PrintLibDir @@ -69,6 +70,7 @@ getArguments exeName plugins = execParser opts <|> hsubparser ( command "vscode-extension-schema" extensionSchemaCommand <> command "generate-default-config" generateDefaultConfigCommand + <> command "plugins-custom-config-markdown-reference" pluginsCustomConfigMarkdownReferenceCommand ) <|> listPluginsParser <|> BiosMode <$> biosParser @@ -86,6 +88,9 @@ getArguments exeName plugins = execParser opts generateDefaultConfigCommand = info (pure DefaultConfigurationMode) (fullDesc <> progDesc "Print config supported by the server with default values") + pluginsCustomConfigMarkdownReferenceCommand = + info (pure PluginsCustomConfigMarkdownReferenceMode) + (fullDesc <> progDesc "Print markdown reference for plugins custom config") printVersionParser :: String -> Parser PrintVersion printVersionParser exeName = diff --git a/src/Ide/Main.hs b/src/Ide/Main.hs index 33b1d51a11..f122b53fa6 100644 --- a/src/Ide/Main.hs +++ b/src/Ide/Main.hs @@ -15,6 +15,7 @@ import Data.Function ((&)) import Data.List (sortOn) import Data.Text (Text) import qualified Data.Text as T +import qualified Data.Text.IO as T (putStrLn) import Data.Text.Lazy.Encoding (decodeUtf8) import qualified Data.Text.Lazy.IO as LT import Development.IDE.Core.Rules hiding (Log) @@ -28,7 +29,8 @@ import HIE.Bios.Types hiding (Log) import qualified HIE.Bios.Types as HieBios import Ide.Arguments import Ide.Logger as G -import Ide.Plugin.ConfigUtils (pluginsToDefaultConfig, +import Ide.Plugin.ConfigUtils (pluginsCustomConfigToMarkdownTables, + pluginsToDefaultConfig, pluginsToVSCodeExtensionSchema) import Ide.Types (IdePlugins, PluginId (PluginId), describePlugin, ipMap, pluginId) @@ -103,6 +105,8 @@ defaultMain recorder args idePlugins = do VSCodeExtensionSchemaMode -> do LT.putStrLn $ decodeUtf8 $ encodePrettySorted $ pluginsToVSCodeExtensionSchema idePlugins + PluginsCustomConfigMarkdownReferenceMode -> do + T.putStrLn $ pluginsCustomConfigToMarkdownTables idePlugins DefaultConfigurationMode -> do LT.putStrLn $ decodeUtf8 $ encodePrettySorted $ pluginsToDefaultConfig idePlugins PrintLibDir -> do diff --git a/test/functional/ConfigSchema.hs b/test/functional/ConfigSchema.hs index 3dbbe0ce2f..2ece6972e9 100644 --- a/test/functional/ConfigSchema.hs +++ b/test/functional/ConfigSchema.hs @@ -31,6 +31,9 @@ tests = testGroup "generate schema" , goldenGitDiff "generate-default-config" (defaultConfigFp ghcVersion) $ do stdout <- readProcess hlsExeCommand ["generate-default-config"] "" pure $ BS.pack stdout + , goldenGitDiff "plugins-custom-config-markdown-reference" (markdownReferenceFp ghcVersion) $ do + stdout <- readProcess hlsExeCommand ["plugins-custom-config-markdown-reference"] "" + pure $ BS.pack stdout ] vscodeSchemaFp :: GhcVersion -> FilePath @@ -39,11 +42,17 @@ vscodeSchemaFp ghcVer = "test" "testdata" "schema" prettyGhcVersion defaultConfigFp :: GhcVersion -> FilePath defaultConfigFp ghcVer = "test" "testdata" "schema" prettyGhcVersion ghcVer generateDefaultConfigJson +markdownReferenceFp :: GhcVersion -> FilePath +markdownReferenceFp ghcVer = "test" "testdata" "schema" prettyGhcVersion ghcVer markdownReferenceMd + vscodeSchemaJson :: FilePath vscodeSchemaJson = "vscode-extension-schema.golden.json" generateDefaultConfigJson :: FilePath generateDefaultConfigJson = "default-config.golden.json" +markdownReferenceMd :: FilePath +markdownReferenceMd = "markdown-reference.md" + prettyGhcVersion :: GhcVersion -> String prettyGhcVersion ghcVer = map toLower (show ghcVer) diff --git a/test/testdata/schema/ghc94/markdown-reference.md b/test/testdata/schema/ghc94/markdown-reference.md new file mode 100644 index 0000000000..668323ce66 --- /dev/null +++ b/test/testdata/schema/ghc94/markdown-reference.md @@ -0,0 +1,66 @@ +## hlint +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `flags` | Flags used by hlint | `TODO: Array values` |   | + +## cabal-fmt +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `path` | Set path to 'cabal-fmt' executable | `"cabal-fmt"` |   | + +## ghcide-completions +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `autoExtendOn` | Extends the import list automatically when completing a out-of-scope identifier | `True` |   | +| `snippetsOn` | Inserts snippets when using code completions | `True` |   | + +## eval +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `exception` | Enable marking exceptions with `*** Exception:` similarly to doctest and GHCi. | `False` |   | +| `diff` | Enable the diff output (WAS/NOW) of eval lenses | `True` |   | + +## ghcide-type-lenses +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `mode` | Control how type lenses are shown | `Always` | | + +## ormolu +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `external` | Call out to an external "ormolu" executable, rather than using the bundled library | `False` |   | + +## rename +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `crossModule` | Enable experimental cross-module renaming | `False` |   | + +## semanticTokens +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `variableToken` | LSP semantic token type to use for variables | `SemanticTokenTypes_Variable` | | +| `functionToken` | LSP semantic token type to use for functions | `SemanticTokenTypes_Function` | | +| `dataConstructorToken` | LSP semantic token type to use for data constructors | `SemanticTokenTypes_EnumMember` | | +| `typeVariableToken` | LSP semantic token type to use for type variables | `SemanticTokenTypes_TypeParameter` | | +| `classMethodToken` | LSP semantic token type to use for typeclass methods | `SemanticTokenTypes_Method` | | +| `patternSynonymToken` | LSP semantic token type to use for pattern synonyms | `SemanticTokenTypes_Macro` | | +| `typeConstructorToken` | LSP semantic token type to use for type constructors | `SemanticTokenTypes_Enum` | | +| `classToken` | LSP semantic token type to use for typeclasses | `SemanticTokenTypes_Class` | | +| `typeSynonymToken` | LSP semantic token type to use for type synonyms | `SemanticTokenTypes_Type` | | +| `typeFamilyToken` | LSP semantic token type to use for type families | `SemanticTokenTypes_Interface` | | +| `recordFieldToken` | LSP semantic token type to use for record fields | `SemanticTokenTypes_Property` | | +| `operatorToken` | LSP semantic token type to use for operators | `SemanticTokenTypes_Operator` | | +| `moduleToken` | LSP semantic token type to use for modules | `SemanticTokenTypes_Namespace` | | + +## fourmolu +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `external` | Call out to an external "fourmolu" executable, rather than using the bundled library. | `False` |   | +| `path` | Set path to executable (for "external" mode). | `"fourmolu"` |   | + +## cabal-gild +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `path` | Set path to 'cabal-gild' executable | `"cabal-gild"` |   | + + diff --git a/test/testdata/schema/ghc96/markdown-reference.md b/test/testdata/schema/ghc96/markdown-reference.md new file mode 100644 index 0000000000..668323ce66 --- /dev/null +++ b/test/testdata/schema/ghc96/markdown-reference.md @@ -0,0 +1,66 @@ +## hlint +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `flags` | Flags used by hlint | `TODO: Array values` |   | + +## cabal-fmt +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `path` | Set path to 'cabal-fmt' executable | `"cabal-fmt"` |   | + +## ghcide-completions +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `autoExtendOn` | Extends the import list automatically when completing a out-of-scope identifier | `True` |   | +| `snippetsOn` | Inserts snippets when using code completions | `True` |   | + +## eval +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `exception` | Enable marking exceptions with `*** Exception:` similarly to doctest and GHCi. | `False` |   | +| `diff` | Enable the diff output (WAS/NOW) of eval lenses | `True` |   | + +## ghcide-type-lenses +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `mode` | Control how type lenses are shown | `Always` | | + +## ormolu +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `external` | Call out to an external "ormolu" executable, rather than using the bundled library | `False` |   | + +## rename +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `crossModule` | Enable experimental cross-module renaming | `False` |   | + +## semanticTokens +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `variableToken` | LSP semantic token type to use for variables | `SemanticTokenTypes_Variable` | | +| `functionToken` | LSP semantic token type to use for functions | `SemanticTokenTypes_Function` | | +| `dataConstructorToken` | LSP semantic token type to use for data constructors | `SemanticTokenTypes_EnumMember` | | +| `typeVariableToken` | LSP semantic token type to use for type variables | `SemanticTokenTypes_TypeParameter` | | +| `classMethodToken` | LSP semantic token type to use for typeclass methods | `SemanticTokenTypes_Method` | | +| `patternSynonymToken` | LSP semantic token type to use for pattern synonyms | `SemanticTokenTypes_Macro` | | +| `typeConstructorToken` | LSP semantic token type to use for type constructors | `SemanticTokenTypes_Enum` | | +| `classToken` | LSP semantic token type to use for typeclasses | `SemanticTokenTypes_Class` | | +| `typeSynonymToken` | LSP semantic token type to use for type synonyms | `SemanticTokenTypes_Type` | | +| `typeFamilyToken` | LSP semantic token type to use for type families | `SemanticTokenTypes_Interface` | | +| `recordFieldToken` | LSP semantic token type to use for record fields | `SemanticTokenTypes_Property` | | +| `operatorToken` | LSP semantic token type to use for operators | `SemanticTokenTypes_Operator` | | +| `moduleToken` | LSP semantic token type to use for modules | `SemanticTokenTypes_Namespace` | | + +## fourmolu +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `external` | Call out to an external "fourmolu" executable, rather than using the bundled library. | `False` |   | +| `path` | Set path to executable (for "external" mode). | `"fourmolu"` |   | + +## cabal-gild +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `path` | Set path to 'cabal-gild' executable | `"cabal-gild"` |   | + + diff --git a/test/testdata/schema/ghc98/markdown-reference.md b/test/testdata/schema/ghc98/markdown-reference.md new file mode 100644 index 0000000000..668323ce66 --- /dev/null +++ b/test/testdata/schema/ghc98/markdown-reference.md @@ -0,0 +1,66 @@ +## hlint +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `flags` | Flags used by hlint | `TODO: Array values` |   | + +## cabal-fmt +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `path` | Set path to 'cabal-fmt' executable | `"cabal-fmt"` |   | + +## ghcide-completions +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `autoExtendOn` | Extends the import list automatically when completing a out-of-scope identifier | `True` |   | +| `snippetsOn` | Inserts snippets when using code completions | `True` |   | + +## eval +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `exception` | Enable marking exceptions with `*** Exception:` similarly to doctest and GHCi. | `False` |   | +| `diff` | Enable the diff output (WAS/NOW) of eval lenses | `True` |   | + +## ghcide-type-lenses +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `mode` | Control how type lenses are shown | `Always` | | + +## ormolu +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `external` | Call out to an external "ormolu" executable, rather than using the bundled library | `False` |   | + +## rename +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `crossModule` | Enable experimental cross-module renaming | `False` |   | + +## semanticTokens +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `variableToken` | LSP semantic token type to use for variables | `SemanticTokenTypes_Variable` | | +| `functionToken` | LSP semantic token type to use for functions | `SemanticTokenTypes_Function` | | +| `dataConstructorToken` | LSP semantic token type to use for data constructors | `SemanticTokenTypes_EnumMember` | | +| `typeVariableToken` | LSP semantic token type to use for type variables | `SemanticTokenTypes_TypeParameter` | | +| `classMethodToken` | LSP semantic token type to use for typeclass methods | `SemanticTokenTypes_Method` | | +| `patternSynonymToken` | LSP semantic token type to use for pattern synonyms | `SemanticTokenTypes_Macro` | | +| `typeConstructorToken` | LSP semantic token type to use for type constructors | `SemanticTokenTypes_Enum` | | +| `classToken` | LSP semantic token type to use for typeclasses | `SemanticTokenTypes_Class` | | +| `typeSynonymToken` | LSP semantic token type to use for type synonyms | `SemanticTokenTypes_Type` | | +| `typeFamilyToken` | LSP semantic token type to use for type families | `SemanticTokenTypes_Interface` | | +| `recordFieldToken` | LSP semantic token type to use for record fields | `SemanticTokenTypes_Property` | | +| `operatorToken` | LSP semantic token type to use for operators | `SemanticTokenTypes_Operator` | | +| `moduleToken` | LSP semantic token type to use for modules | `SemanticTokenTypes_Namespace` | | + +## fourmolu +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `external` | Call out to an external "fourmolu" executable, rather than using the bundled library. | `False` |   | +| `path` | Set path to executable (for "external" mode). | `"fourmolu"` |   | + +## cabal-gild +| Property | Description | Default | Allowed values | +| --- | --- | --- | --- | +| `path` | Set path to 'cabal-gild' executable | `"cabal-gild"` |   | + +