Skip to content

[Plugin] stylish-haskell formatter #146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions exe/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import Ide.Plugin.Example2 as Example2
import Ide.Plugin.GhcIde as GhcIde
import Ide.Plugin.Floskell as Floskell
import Ide.Plugin.Ormolu as Ormolu
import Ide.Plugin.StylishHaskell as StylishHaskell
#if AGPL
import Ide.Plugin.Brittany as Brittany
#endif
Expand Down Expand Up @@ -129,6 +130,7 @@ idePlugins includeExamples = pluginDescToIdePlugins allPlugins
-- , genericDescriptor "generic"
-- , ghcmodDescriptor "ghcmod"
, Ormolu.descriptor "ormolu"
, StylishHaskell.descriptor "stylish-haskell"
#if AGPL
, Brittany.descriptor "brittany"
#endif
Expand Down
2 changes: 2 additions & 0 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ library
Ide.Plugin.Pragmas
Ide.Plugin.Floskell
Ide.Plugin.Formatter
Ide.Plugin.StylishHaskell
Ide.PluginUtils
Ide.Types
Ide.Version
Expand Down Expand Up @@ -84,6 +85,7 @@ library
, process
, regex-tdfa >= 1.3.1.0
, shake >= 0.17.5
, stylish-haskell == 0.11.*
, text
, transformers
, unordered-containers
Expand Down
1 change: 1 addition & 0 deletions src/Ide/Plugin/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ instance Default Config where
-- , formattingProvider = "brittany"
, formattingProvider = "ormolu"
-- , formattingProvider = "floskell"
-- , formattingProvider = "stylish-haskell"
}

-- TODO: Add API for plugins to expose their own LSP config options
Expand Down
59 changes: 59 additions & 0 deletions src/Ide/Plugin/StylishHaskell.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module Ide.Plugin.StylishHaskell
(
descriptor
, provider
)
where

import Control.Monad.IO.Class
import Data.Text (Text)
import qualified Data.Text as T
import Ide.Plugin.Formatter
import Ide.PluginUtils
import Ide.Types
import Language.Haskell.Stylish
import Language.Haskell.LSP.Types as J

import System.Directory
import System.FilePath

descriptor :: PluginId -> PluginDescriptor
descriptor plId = (defaultPluginDescriptor plId)
{ pluginFormattingProvider = Just provider
}

-- | Formatter provider of stylish-haskell.
-- Formats the given source in either a given Range or the whole Document.
-- If the provider fails an error is returned that can be displayed to the user.
provider :: FormattingProvider IO
provider _lf _ideState typ contents fp _opts = do
let file = fromNormalizedFilePath fp
config <- liftIO $ loadConfigFrom file
let (range, selectedContents) = case typ of
FormatText -> (fullRange contents, contents)
FormatRange r -> (normalize r, extractRange r contents)
result = runStylishHaskell file config selectedContents
case result of
Left err -> return $ Left $ responseError $ T.pack $ "stylishHaskellCmd: " ++ err
Right new -> return $ Right $ J.List [TextEdit range new]

-- | Recursively search in every directory of the given filepath for .stylish-haskell.yaml.
-- If no such file has been found, return default config.
loadConfigFrom :: FilePath -> IO Config
loadConfigFrom file = do
currDir <- getCurrentDirectory
setCurrentDirectory (takeDirectory file)
config <- loadConfig (makeVerbose False) Nothing
setCurrentDirectory currDir
return config

-- | Run stylish-haskell on the given text with the given configuration.
runStylishHaskell :: FilePath -- ^ Location of the file being formatted. Used for error message
-> Config -- ^ Configuration for stylish-haskell
-> Text -- ^ Text to format
-> Either String Text -- ^ Either formatted Text or an error message
runStylishHaskell file config = fmap fromLines . fmt . toLines
where
fromLines = T.pack . unlines
fmt = runSteps (configLanguageExtensions config) (Just file) (configSteps config)
toLines = lines . T.unpack
3 changes: 3 additions & 0 deletions stack-8.6.4.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ extra-deps:
- hlint-2.2.8
- hoogle-5.0.17.11
- hsimport-0.11.0@rev:2
- HsYAML-0.2.1.0@rev:1
- HsYAML-aeson-0.2.0.0@rev:1
- lens-4.18
- lsp-test-0.10.3.0
- microlens-th-0.4.2.3@rev:1
Expand All @@ -48,6 +50,7 @@ extra-deps:
# - shake-0.18.5
- github: wz1000/shake
commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef
- stylish-haskell-0.11.0.0
- syz-0.2.0.0
- tasty-rerun-1.1.17
- temporary-1.2.1.1
Expand Down
3 changes: 3 additions & 0 deletions stack-8.6.5.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ extra-deps:
- haskell-lsp-0.22.0.0
- haskell-lsp-types-0.22.0.0
- hie-bios-0.5.0
- HsYAML-0.2.1.0@rev:1
- HsYAML-aeson-0.2.0.0@rev:1
- indexed-profunctors-0.1
- lens-4.18
- lsp-test-0.10.3.0
Expand All @@ -40,6 +42,7 @@ extra-deps:
- semialign-1.1
- github: wz1000/shake
commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef
- stylish-haskell-0.11.0.0
- tasty-rerun-1.1.17
- temporary-1.2.1.1
- type-equality-1
Expand Down
3 changes: 3 additions & 0 deletions stack-8.8.2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ extra-deps:
- hlint-2.2.8
- hoogle-5.0.17.11
- hsimport-0.11.0
- HsYAML-0.2.1.0@rev:1
- HsYAML-aeson-0.2.0.0@rev:1
- ilist-0.3.1.0
- lsp-test-0.10.3.0
- monad-dijkstra-0.1.1.2
Expand All @@ -31,6 +33,7 @@ extra-deps:
- semigroups-0.18.5
- github: wz1000/shake
commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef
- stylish-haskell-0.11.0.0
- temporary-1.2.1.1

flags:
Expand Down
1 change: 1 addition & 0 deletions stack-8.8.3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extra-deps:
- semigroups-0.18.5
- github: wz1000/shake
commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef
- stylish-haskell-0.11.0.0
- temporary-1.2.1.1

flags:
Expand Down
3 changes: 3 additions & 0 deletions stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ extra-deps:
- haskell-lsp-0.22.0.0
- haskell-lsp-types-0.22.0.0
- hie-bios-0.5.0
- HsYAML-0.2.1.0@rev:1
- HsYAML-aeson-0.2.0.0@rev:1
- indexed-profunctors-0.1
- lens-4.18
- lsp-test-0.10.3.0
Expand All @@ -40,6 +42,7 @@ extra-deps:
- semialign-1.1
- github: wz1000/shake
commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef
- stylish-haskell-0.11.0.0
- tasty-rerun-1.1.17
- temporary-1.2.1.1
- type-equality-1
Expand Down
33 changes: 33 additions & 0 deletions test/functional/Format.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ tests = testGroup "format document" [
documentContents doc >>= liftIO . (`shouldBe` formattedDocTabSize5)
, rangeTests
, providerTests
, stylishHaskellTests
, brittanyTests
, ormoluTests
]
Expand Down Expand Up @@ -68,6 +69,38 @@ providerTests = testGroup "formatting provider" [
documentContents doc >>= liftIO . (`shouldBe` formattedBrittanyPostFloskell)
]

stylishHaskellTests :: TestTree
stylishHaskellTests = testGroup "stylish-haskell" [
testCase "formats a file" $ runSession hieCommand fullCaps "test/testdata" $ do
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (formatLspConfig "stylish-haskell"))
doc <- openDoc "StylishHaskell.hs" "haskell"
formatDoc doc (FormattingOptions 2 True)
contents <- documentContents doc
liftIO $ contents `shouldBe`
"import Data.Char\n\
\import qualified Data.List\n\
\import Data.String\n\
\\n\
\bar :: Maybe (Either String Integer) -> Integer\n\
\bar Nothing = 0\n\
\bar (Just (Left _)) = 0\n\
\bar (Just (Right x)) = x\n"
, testCase "formats a range" $ runSession hieCommand fullCaps "test/testdata" $ do
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (formatLspConfig "stylish-haskell"))
doc <- openDoc "StylishHaskell.hs" "haskell"
formatRange doc (FormattingOptions 2 True) (Range (Position 0 0) (Position 2 21))
contents <- documentContents doc
liftIO $ contents `shouldBe`
"import Data.Char\n\
\import qualified Data.List\n\
\import Data.String\n\
\\n\
\bar :: Maybe (Either String Integer) -> Integer\n\
\bar Nothing = 0\n\
\bar (Just (Left _)) = 0\n\
\bar (Just (Right x)) = x\n"
]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I was writing this, I thought it might be better to add a golden test facility.
Any opinions/ideas?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, should be easy to integrate with https://hackage.haskell.org/package/tasty-golden

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try that in a separate PR. Thank you for the suggestion :)

brittanyTests :: TestTree
brittanyTests = testGroup "brittany" [
ignoreTestBecause "Broken" $ testCase "formats a document with LF endings" $ runSession hieCommand fullCaps "test/testdata" $ do
Expand Down
8 changes: 8 additions & 0 deletions test/testdata/StylishHaskell.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Data.Char
import qualified Data.List
import Data.String

bar :: Maybe (Either String Integer) -> Integer
bar Nothing = 0
bar (Just (Left _)) = 0
bar (Just (Right x)) = x