diff --git a/cabal.project b/cabal.project index 9a49ac4fa5..40959759ff 100644 --- a/cabal.project +++ b/cabal.project @@ -30,6 +30,7 @@ packages: ./plugins/hls-change-type-signature-plugin ./plugins/hls-stan-plugin ./plugins/hls-gadt-plugin + ./plugins/hls-hindent-plugin ./plugins/hls-explicit-fixity-plugin -- Standard location for temporary packages needed for particular environments diff --git a/docs/features.md b/docs/features.md index 793b66a61e..d791cdfb0e 100644 --- a/docs/features.md +++ b/docs/features.md @@ -106,6 +106,7 @@ Format your code with various Haskell code formatters. | Fourmolu | `hls-fourmolu-plugin` | | Ormolu | `hls-ormolu-plugin` | | Stylish Haskell | `hls-stylish-haskell-plugin` | +| HIndent | `hls-hindent-plugin` | ## Document symbols diff --git a/docs/supported-versions.md b/docs/supported-versions.md index 687ecccc94..30ded65dc7 100644 --- a/docs/supported-versions.md +++ b/docs/supported-versions.md @@ -72,6 +72,7 @@ Sometimes a plugin will be supported in the pre-built binaries but not in a HLS | `hls-tactics-plugin` | 9.2 | | `hls-code-range-plugin` | | | `hls-gadt-plugin` | | +| `hls-hindent-plugin` | | ### Using deprecated GHC versions diff --git a/exe/Plugins.hs b/exe/Plugins.hs index 8dafaa3495..2321793c72 100644 --- a/exe/Plugins.hs +++ b/exe/Plugins.hs @@ -118,6 +118,11 @@ import qualified Ide.Plugin.StylishHaskell as StylishHaskell import qualified Ide.Plugin.Brittany as Brittany #endif +#if hindent +import qualified Ide.Plugin.HIndent as HIndent +#endif + + data Log = forall a. (Pretty a) => Log a instance Pretty Log where @@ -166,6 +171,9 @@ idePlugins recorder includeExamples = pluginDescToIdePlugins allPlugins #if hls_brittany Brittany.descriptor "brittany" : #endif +#if hindent + HIndent.descriptor "hindent" : +#endif #if hls_callHierarchy CallHierarchy.descriptor : #endif diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index d786e71530..1d74720e79 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -228,6 +228,12 @@ flag brittany default: True manual: True +flag hindent + description: Enable hindent plugin + default: True + manual: True + + flag dynamic description: Build with the dyn rts default: True @@ -361,6 +367,11 @@ common stylishHaskell build-depends: hls-stylish-haskell-plugin ^>= 1.0 cpp-options: -Dhls_stylishHaskell +common hindent + if flag(hindent) + build-depends: hls-hindent-plugin ^>= 1.0 + cpp-options: -Dhindent + common brittany if flag(brittany) && (impl(ghc < 9.0.2) || flag(ignore-plugins-ghc-bounds)) build-depends: hls-brittany-plugin ^>= 1.0 @@ -398,6 +409,7 @@ executable haskell-language-server , ormolu , stylishHaskell , brittany + , hindent main-is: Main.hs hs-source-dirs: exe diff --git a/plugins/hls-hindent-plugin/LICENSE b/plugins/hls-hindent-plugin/LICENSE new file mode 100644 index 0000000000..16502c47e2 --- /dev/null +++ b/plugins/hls-hindent-plugin/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 The Haskell IDE team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/hls-hindent-plugin/hls-hindent-plugin.cabal b/plugins/hls-hindent-plugin/hls-hindent-plugin.cabal new file mode 100644 index 0000000000..edf1fba6c3 --- /dev/null +++ b/plugins/hls-hindent-plugin/hls-hindent-plugin.cabal @@ -0,0 +1,53 @@ +cabal-version: 2.4 +name: hls-hindent-plugin +version: 1.0.0.0 +synopsis: Integration with the HIndent code formatter +description: + Please see the README on GitHub at +license: Apache-2.0 +license-file: LICENSE +author: The Haskell IDE Team +copyright: The Haskell IDE Team +maintainer: uhbif19@gmail.com +category: Development +build-type: Simple +extra-source-files: + LICENSE + test/testdata/*.hs + +library + exposed-modules: + Ide.Plugin.HIndent + other-modules: + Path.Find + hs-source-dirs: src + build-depends: + , base >=4.12 && <5 + , binary + , bytestring + , deepseq + , exceptions + , filepath + , ghcide ^>=1.7 + , hindent ^>=5.3.3 + , hls-plugin-api ^>=1.4 + , lsp-types + , path + , path-io + , text + , unix-compat + , yaml + + default-language: Haskell2010 + +test-suite tests + type: exitcode-stdio-1.0 + default-language: Haskell2010 + hs-source-dirs: test + main-is: Main.hs + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: + , base + , filepath + , hls-hindent-plugin + , hls-test-utils ^>=1.3 diff --git a/plugins/hls-hindent-plugin/src/Ide/Plugin/HIndent.hs b/plugins/hls-hindent-plugin/src/Ide/Plugin/HIndent.hs new file mode 100644 index 0000000000..80987ab757 --- /dev/null +++ b/plugins/hls-hindent-plugin/src/Ide/Plugin/HIndent.hs @@ -0,0 +1,70 @@ +{-# LANGUAGE OverloadedStrings #-} +module Ide.Plugin.HIndent + ( + descriptor + , provider + ) +where + +import Control.Monad.IO.Class (liftIO) +import Data.Binary.Builder (toLazyByteString) +import qualified Data.ByteString as B (concat) +import qualified Data.ByteString.Lazy as BL (toChunks) +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE +import qualified Data.Yaml +import Development.IDE (IdeState, fromNormalizedFilePath) +import HIndent (reformat) +import HIndent.Types (Config, defaultConfig) +import Ide.PluginUtils (extractRange, fullRange, normalize, + responseError) +import Ide.Types (FormattingHandler, + FormattingType (FormatRange, FormatText), + PluginDescriptor (pluginHandlers), + PluginId, defaultPluginDescriptor, + mkFormattingHandlers) +import Language.LSP.Types as J (List (List), TextEdit (TextEdit)) +import Path (filename, toFilePath) +import qualified Path.Find as Path +import qualified Path.IO as Path + +descriptor :: PluginId -> PluginDescriptor IdeState +descriptor plId = (defaultPluginDescriptor plId) + { pluginHandlers = mkFormattingHandlers provider + } + +provider :: FormattingHandler IdeState +provider ide typ contents fp _opts = do + let file = fromNormalizedFilePath fp + let (range, selectedContents) = case typ of + FormatText -> (fullRange contents, contents) + FormatRange r -> (normalize r, extractRange r contents) + let extensions = Nothing -- Extensions should be covered by Config + config <- liftIO getHIndentConfig + let result = HIndent.reformat config extensions (Just file) (TE.encodeUtf8 selectedContents) + convertToTextEdit result range + where + builderToText = TE.decodeUtf8 . B.concat . BL.toChunks . toLazyByteString + convertToTextEdit result range = case result of + Left err -> return $ Left $ responseError $ T.pack $ "hindent: " ++ err + Right new -> return $ Right $ J.List [TextEdit range (builderToText new)] + +-- Copied from +-- https://github.com/mihaimaruseac/hindent/blob/master/src/main/Main.hs#L77 +-- Can be removed with Path.Find and it dependences, once this issue got closed: +-- https://github.com/mihaimaruseac/hindent/issues/585 + + +getHIndentConfig :: IO Config +getHIndentConfig = do + cur <- Path.getCurrentDir + homeDir <- Path.getHomeDir + mfile <- + Path.findFileUp cur ((== ".hindent.yaml") . toFilePath . filename) (Just homeDir) + case mfile of + Nothing -> return defaultConfig + Just file -> do + result <- Data.Yaml.decodeFileEither (toFilePath file) + case result of + Left e -> error (show e) + Right config -> return config diff --git a/plugins/hls-hindent-plugin/src/Path/Find.hs b/plugins/hls-hindent-plugin/src/Path/Find.hs new file mode 100644 index 0000000000..662695ffe5 --- /dev/null +++ b/plugins/hls-hindent-plugin/src/Path/Find.hs @@ -0,0 +1,99 @@ +{-# LANGUAGE DataKinds #-} + +-- | Finding files. + +-- Copied from HIndent, where it was lifted from Stack. + +module Path.Find + (findFileUp + ,findDirUp + ,findFiles + ,findInParents) + where + +import Control.DeepSeq (force) +import Control.Exception (evaluate) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.IO.Class +import Data.List +import Path +import Path.IO hiding (findFiles) +import System.IO.Error (isPermissionError) +import System.PosixCompat.Files (getSymbolicLinkStatus, + isSymbolicLink) + +-- | Find the location of a file matching the given predicate. +findFileUp :: (MonadIO m,MonadThrow m) + => Path Abs Dir -- ^ Start here. + -> (Path Abs File -> Bool) -- ^ Predicate to match the file. + -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory. + -> m (Maybe (Path Abs File)) -- ^ Absolute file path. +findFileUp = findPathUp snd + +-- | Find the location of a directory matching the given predicate. +findDirUp :: (MonadIO m,MonadThrow m) + => Path Abs Dir -- ^ Start here. + -> (Path Abs Dir -> Bool) -- ^ Predicate to match the directory. + -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory. + -> m (Maybe (Path Abs Dir)) -- ^ Absolute directory path. +findDirUp = findPathUp fst + +-- | Find the location of a path matching the given predicate. +findPathUp :: (MonadIO m,MonadThrow m) + => (([Path Abs Dir],[Path Abs File]) -> [Path Abs t]) + -- ^ Choose path type from pair. + -> Path Abs Dir -- ^ Start here. + -> (Path Abs t -> Bool) -- ^ Predicate to match the path. + -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory. + -> m (Maybe (Path Abs t)) -- ^ Absolute path. +findPathUp pathType dir p upperBound = + do entries <- listDir dir + case find p (pathType entries) of + Just path -> return (Just path) + Nothing | Just dir == upperBound -> return Nothing + | parent dir == dir -> return Nothing + | otherwise -> findPathUp pathType (parent dir) p upperBound + +-- | Find files matching predicate below a root directory. +-- +-- NOTE: this skips symbolic directory links, to avoid loops. This may +-- not make sense for all uses of file finding. +-- +-- TODO: write one of these that traverses symbolic links but +-- efficiently ignores loops. +findFiles :: Path Abs Dir -- ^ Root directory to begin with. + -> (Path Abs File -> Bool) -- ^ Predicate to match files. + -> (Path Abs Dir -> Bool) -- ^ Predicate for which directories to traverse. + -> IO [Path Abs File] -- ^ List of matching files. +findFiles dir p traversep = + do (dirs,files) <- catchJust (\ e -> if isPermissionError e + then Just () + else Nothing) + (listDir dir) + (\ _ -> return ([], [])) + filteredFiles <- evaluate $ force (filter p files) + filteredDirs <- filterM (fmap not . isSymLink) dirs + subResults <- + forM filteredDirs + (\entry -> + if traversep entry + then findFiles entry p traversep + else return []) + return (concat (filteredFiles : subResults)) + +isSymLink :: Path Abs t -> IO Bool +isSymLink = fmap isSymbolicLink . getSymbolicLinkStatus . toFilePath + +-- | @findInParents f path@ applies @f@ to @path@ and its 'parent's until +-- it finds a 'Just' or reaches the root directory. +findInParents :: MonadIO m => (Path Abs Dir -> m (Maybe a)) -> Path Abs Dir -> m (Maybe a) +findInParents f path = do + mres <- f path + case mres of + Just res -> return (Just res) + Nothing -> do + let next = parent path + if next == path + then return Nothing + else findInParents f next diff --git a/plugins/hls-hindent-plugin/test/Main.hs b/plugins/hls-hindent-plugin/test/Main.hs new file mode 100644 index 0000000000..72c8eb3ca1 --- /dev/null +++ b/plugins/hls-hindent-plugin/test/Main.hs @@ -0,0 +1,31 @@ +{-# LANGUAGE OverloadedStrings #-} +module Main + ( main + ) where + +import qualified Ide.Plugin.HIndent as HIndent +import System.FilePath (()) +import Test.Hls (FormattingOptions (..), Position (..), + Range (..), Session, TestName, TestTree, + TextDocumentIdentifier, def, + defaultTestRunner, formatDoc, formatRange, + goldenWithHaskellDocFormatter, testGroup) + +main :: IO () +main = defaultTestRunner tests + +tests :: TestTree +tests = testGroup "hindent" [ + goldenWithHIndent "formats a document" "HIndent" "formatted_document" $ \doc -> do + formatDoc doc (FormattingOptions 2 True Nothing Nothing Nothing) + -- formatting only first line + , goldenWithHIndent "formats a range" "HIndent" "formatted_range" $ \doc -> do + formatRange doc (FormattingOptions 2 True Nothing Nothing Nothing) (Range (Position 0 0) (Position 0 20)) + ] + + +goldenWithHIndent :: TestName -> FilePath -> FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree +goldenWithHIndent title fp desc = goldenWithHaskellDocFormatter (HIndent.descriptor "hindent") "hindent" def title testDataDir fp desc "hs" + +testDataDir :: FilePath +testDataDir = "test" "testdata" diff --git a/plugins/hls-hindent-plugin/test/testdata/.hindent.yaml b/plugins/hls-hindent-plugin/test/testdata/.hindent.yaml new file mode 100644 index 0000000000..44959972e8 --- /dev/null +++ b/plugins/hls-hindent-plugin/test/testdata/.hindent.yaml @@ -0,0 +1 @@ +indent-size: 4 \ No newline at end of file diff --git a/plugins/hls-hindent-plugin/test/testdata/HIndent.formatted_document.hs b/plugins/hls-hindent-plugin/test/testdata/HIndent.formatted_document.hs new file mode 100644 index 0000000000..63b8eb3ce5 --- /dev/null +++ b/plugins/hls-hindent-plugin/test/testdata/HIndent.formatted_document.hs @@ -0,0 +1,7 @@ +example1 = + case x of + Just p -> foo bar' + +example2 = + case x of + Just p -> foo bar' diff --git a/plugins/hls-hindent-plugin/test/testdata/HIndent.formatted_range.hs b/plugins/hls-hindent-plugin/test/testdata/HIndent.formatted_range.hs new file mode 100644 index 0000000000..7046a35a17 --- /dev/null +++ b/plugins/hls-hindent-plugin/test/testdata/HIndent.formatted_range.hs @@ -0,0 +1,5 @@ +example1 = + case x of + Just p -> foo bar' + +example2 = case x of Just p -> foo bar' \ No newline at end of file diff --git a/plugins/hls-hindent-plugin/test/testdata/HIndent.hs b/plugins/hls-hindent-plugin/test/testdata/HIndent.hs new file mode 100644 index 0000000000..9eda43b80f --- /dev/null +++ b/plugins/hls-hindent-plugin/test/testdata/HIndent.hs @@ -0,0 +1,3 @@ +example1 = case x of Just p -> foo bar' + +example2 = case x of Just p -> foo bar' \ No newline at end of file diff --git a/plugins/hls-hindent-plugin/test/testdata/hie.yaml b/plugins/hls-hindent-plugin/test/testdata/hie.yaml new file mode 100644 index 0000000000..824558147d --- /dev/null +++ b/plugins/hls-hindent-plugin/test/testdata/hie.yaml @@ -0,0 +1,3 @@ +cradle: + direct: + arguments: [] diff --git a/stack-lts16.yaml b/stack-lts16.yaml index 5460e8940f..ae4eacad08 100644 --- a/stack-lts16.yaml +++ b/stack-lts16.yaml @@ -33,6 +33,7 @@ packages: - ./plugins/hls-change-type-signature-plugin - ./plugins/hls-gadt-plugin - ./plugins/hls-explicit-fixity-plugin + - ./plugins/hls-hindent-plugin ghc-options: "$everything": -haddock @@ -102,6 +103,8 @@ extra-deps: - trial-tomland-0.0.0.0@sha256:743a9baaa36891ed3a44618fdfd5bc4ed9afc39cf9b9fa23ea1b96f3787f5ec0,2526 - text-rope-0.2 - co-log-core-0.3.1.0 + - hindent-5.3.4 + - haskell-src-exts-1.23.1 configure-options: ghcide: diff --git a/stack-lts19.yaml b/stack-lts19.yaml index 219dc7e820..cd57ec8fc5 100644 --- a/stack-lts19.yaml +++ b/stack-lts19.yaml @@ -32,6 +32,7 @@ packages: - ./plugins/hls-change-type-signature-plugin - ./plugins/hls-gadt-plugin - ./plugins/hls-explicit-fixity-plugin + - ./plugins/hls-hindent-plugin ghc-options: "$everything": -haddock diff --git a/stack.yaml b/stack.yaml index 6ea299746c..6cba14e071 100644 --- a/stack.yaml +++ b/stack.yaml @@ -32,6 +32,7 @@ packages: - ./plugins/hls-change-type-signature-plugin - ./plugins/hls-gadt-plugin - ./plugins/hls-explicit-fixity-plugin +- ./plugins/hls-hindent-plugin extra-deps: - floskell-0.10.6@sha256:e77d194189e8540abe2ace2c7cb8efafc747ca35881a2fefcbd2d40a1292e036,3819 diff --git a/test/functional/Format.hs b/test/functional/Format.hs index af90fc7a9c..224128cdf9 100644 --- a/test/functional/Format.hs +++ b/test/functional/Format.hs @@ -6,6 +6,7 @@ import Control.Lens ((^.)) import Control.Monad.IO.Class import Data.Aeson import qualified Data.ByteString.Lazy as BS +import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.IO as T import Language.LSP.Test @@ -47,8 +48,7 @@ providerTests = testGroup "formatting provider" [ testCase "respects none" $ runSessionWithConfig (formatConfig "none") hlsCommand fullCaps "test/testdata/format" $ do doc <- openDoc "Format.hs" "haskell" resp <- request STextDocumentFormatting $ DocumentFormattingParams Nothing doc (FormattingOptions 2 True Nothing Nothing Nothing) - liftIO $ resp ^. LSP.result @?= Left (ResponseError InvalidRequest "No plugin enabled for STextDocumentFormatting, available:\nPluginId \"floskell\"\nPluginId \"fourmolu\"\nPluginId \"ormolu\"\nPluginId \"stylish-haskell\"\nPluginId \"brittany\"\n" Nothing) - + liftIO $ foramattingNoneResultCorrect (resp ^. LSP.result) @? "Result incorrect" , requiresOrmoluPlugin . requiresFloskellPlugin $ testCase "can change on the fly" $ runSession hlsCommand fullCaps "test/testdata/format" $ do formattedOrmolu <- liftIO $ T.readFile "test/testdata/format/Format.ormolu.formatted.hs" formattedFloskell <- liftIO $ T.readFile "test/testdata/format/Format.floskell.formatted.hs" @@ -81,6 +81,10 @@ providerTests = testGroup "formatting provider" [ formatDoc doc (FormattingOptions 2 True Nothing Nothing Nothing) documentContents doc >>= liftIO . (@?= formattedFloskell) ] + where + foramattingNoneResultCorrect (Left (ResponseError InvalidRequest text Nothing)) = + T.isPrefixOf "No plugin enabled for STextDocumentFormatting" text + foramattingNoneResultCorrect _ = False formatLspConfig :: Value -> Value formatLspConfig provider = object [ "haskell" .= object ["formattingProvider" .= (provider :: Value)] ]