Skip to content

Commit 2f886bf

Browse files
authored
handle trailing comma in import list properly (#3035)
* handle trailing comma in import list properly * no longer backup .ghcup in gitpod * fix for ghc < 9 * fix it without using CPP * explain gitpod change * read trailing comma before adding one * refine imports * refine gitpod * gitpod store ghcide and hie-bios cache These cache directories are small, but not preserving them requires HLS to compile all modules in local project on workspace restarts. * fix code styling
1 parent fa868b5 commit 2f886bf

File tree

4 files changed

+110
-51
lines changed

4 files changed

+110
-51
lines changed

.gitpod.Dockerfile

+11-5
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@ FROM gitpod/workspace-full
22

33
RUN sudo install-packages build-essential curl libffi-dev libffi7 libgmp-dev libgmp10 \
44
libncurses-dev libncurses5 libtinfo5 && \
5-
BOOTSTRAP_HASKELL_NONINTERACTIVE=1 \
6-
BOOTSTRAP_HASKELL_MINIMAL=1 \
7-
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh && \
5+
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_MINIMAL=1 sh && \
86
echo 'source $HOME/.ghcup/env' >> $HOME/.bashrc && \
97
echo 'export PATH=$HOME/.cabal/bin:$HOME/.local/bin:$PATH' >> $HOME/.bashrc && \
108
. /home/gitpod/.ghcup/env && \
11-
ghcup install ghc --set && \
9+
# Install all verions of GHC that HLS supports. Putting GHC into Docker image makes workspace start much faster.
10+
ghcup install ghc 8.6.5 && \
11+
ghcup install ghc 8.8.4 && \
12+
ghcup install ghc 8.10.7 && \
13+
ghcup install ghc 9.0.2 && \
14+
ghcup install ghc 9.2.2 && \
15+
ghcup install ghc 9.2.3 --set && \
1216
ghcup install hls --set && \
1317
ghcup install cabal --set && \
1418
ghcup install stack --set && \
1519
cabal update && \
16-
cabal install stylish-haskell hoogle implicit-hie && \
20+
cabal install --disable-executable-dynamic --install-method copy --constraint "stylish-haskell +ghc-lib" \
21+
stylish-haskell implicit-hie hoogle && \
22+
rm -rf $HOME/.cabal/store && \
1723
pip install pre-commit && \
1824
npm install -g http-server

.gitpod.yml

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ tasks:
1010
$HOME/.local
1111
$HOME/.cabal
1212
$HOME/.stack
13-
$HOME/.ghcup
13+
$HOME/.cache/ghcide
14+
$HOME/.cache/hie-bios
1415
/nix
1516
)
1617
for DIR in "${CACHE_DIRS[@]}"; do
@@ -41,9 +42,7 @@ tasks:
4142
echo '}' >> .vscode/settings.json
4243
fi
4344
44-
pushd docs
45-
pip install -r requirements.txt
46-
popd
45+
pip install -r docs/requirements.txt
4746
init: |
4847
cabal update
4948
cabal configure --enable-executable-dynamic

ghcide/src/Development/IDE/Plugin/CodeAction/ExactPrint.hs

+74-42
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# LANGUAGE RankNTypes #-}
55
{-# LANGUAGE CPP #-}
66
{-# LANGUAGE FlexibleInstances #-}
7+
{-# LANGUAGE ScopedTypeVariables #-}
78

89
module Development.IDE.Plugin.CodeAction.ExactPrint (
910
Rewrite (..),
@@ -23,41 +24,47 @@ module Development.IDE.Plugin.CodeAction.ExactPrint (
2324
wildCardSymbol
2425
) where
2526

26-
import Control.Applicative
2727
import Control.Monad
28-
import Control.Monad.Extra (whenJust)
2928
import Control.Monad.Trans
30-
import Data.Char (isAlphaNum)
31-
import Data.Data (Data)
32-
import Data.Functor
33-
import Data.Generics (listify)
34-
import qualified Data.Map.Strict as Map
35-
import Data.Maybe (fromJust, isNothing,
36-
mapMaybe)
37-
import qualified Data.Text as T
38-
import Development.IDE.GHC.Compat hiding (Annotation)
29+
import Data.Char (isAlphaNum)
30+
import Data.Data (Data)
31+
import Data.Generics (listify)
32+
import qualified Data.Text as T
33+
import Development.IDE.GHC.Compat hiding (Annotation)
3934
import Development.IDE.GHC.Error
4035
import Development.IDE.GHC.ExactPrint
36+
import Development.IDE.GHC.Util
4137
import Development.IDE.Spans.Common
42-
import GHC.Exts (IsList (fromList))
38+
import GHC.Exts (IsList (fromList))
39+
import GHC.Stack (HasCallStack)
4340
import Language.Haskell.GHC.ExactPrint
44-
#if !MIN_VERSION_ghc(9,2,0)
41+
import Language.LSP.Types
42+
43+
-- GHC version specific imports. For any supported GHC version, make sure there is no warning in imports.
44+
#if MIN_VERSION_ghc(9,2,0)
45+
import Control.Lens (_head, _last, over)
46+
import Data.Bifunctor (first)
47+
import Data.Default (Default (..))
48+
import Data.Maybe (fromJust, fromMaybe, mapMaybe)
49+
import GHC (AddEpAnn (..), AnnContext (..), AnnList (..),
50+
AnnParen (..), DeltaPos (SameLine), EpAnn (..),
51+
EpaLocation (EpaDelta),
52+
IsUnicodeSyntax (NormalSyntax),
53+
NameAdornment (NameParens),
54+
TrailingAnn (AddCommaAnn), addAnns, ann,
55+
emptyComments, reAnnL)
56+
#else
57+
import Control.Applicative (Alternative ((<|>)))
58+
import Control.Monad.Extra (whenJust)
59+
import Data.Foldable (find)
60+
import Data.Functor (($>))
61+
import qualified Data.Map.Strict as Map
62+
import Data.Maybe (fromJust, isJust,
63+
isNothing, mapMaybe)
4564
import qualified Development.IDE.GHC.Compat.Util as Util
4665
import Language.Haskell.GHC.ExactPrint.Types (DeltaPos (DP),
4766
KeywordId (G), mkAnnKey)
48-
#else
49-
import Data.Default
50-
import GHC (AddEpAnn (..), AnnContext (..), AnnParen (..),
51-
DeltaPos (SameLine), EpAnn (..), EpaLocation (EpaDelta),
52-
IsUnicodeSyntax (NormalSyntax),
53-
NameAdornment (NameParens), NameAnn (..), addAnns, ann, emptyComments,
54-
reAnnL, AnnList (..), TrailingAnn (AddCommaAnn), addTrailingAnnToA)
5567
#endif
56-
import Language.LSP.Types
57-
import Development.IDE.GHC.Util
58-
import Data.Bifunctor (first)
59-
import Control.Lens (_head, _last, over)
60-
import GHC.Stack (HasCallStack)
6168

6269
------------------------------------------------------------------------------
6370

@@ -367,17 +374,28 @@ extendImportTopLevel thing (L l it@ImportDecl{..})
367374
then lift (Left $ thing <> " already imported")
368375
else do
369376
#if !MIN_VERSION_ghc(9,2,0)
370-
when hasSibling $
371-
addTrailingCommaT (last lies)
377+
anns <- getAnnsT
378+
maybe (pure ()) addTrailingCommaT (lastMaybe lies)
372379
addSimpleAnnT x (DP (0, if hasSibling then 1 else 0)) []
373380
addSimpleAnnT rdr dp00 [(G AnnVal, dp00)]
381+
382+
-- When the last item already has a trailing comma, we append a trailing comma to the new item.
383+
let isAnnComma (G AnnComma, _) = True
384+
isAnnComma _ = False
385+
shouldAddTrailingComma = maybe False nodeHasComma (lastMaybe lies)
386+
&& not (nodeHasComma (L l' lies))
387+
388+
nodeHasComma :: Data a => Located a -> Bool
389+
nodeHasComma x = isJust $ Map.lookup (mkAnnKey x) anns >>= find isAnnComma . annsDP
390+
when shouldAddTrailingComma (addTrailingCommaT x)
391+
374392
-- Parens are attachted to `lies`, so if `lies` was empty previously,
375393
-- we need change the ann key from `[]` to `:` to keep parens and other anns.
376394
unless hasSibling $
377395
transferAnn (L l' lies) (L l' [x]) id
378396
return $ L l it{ideclHiding = Just (hide, L l' $ lies ++ [x])}
379397
#else
380-
lies' <- addCommaInImportList lies x
398+
let lies' = addCommaInImportList lies x
381399
return $ L l it{ideclHiding = Just (hide, L l' lies')}
382400
#endif
383401
extendImportTopLevel _ _ = lift $ Left "Unable to extend the import list"
@@ -514,30 +532,44 @@ extendImportViaParent df parent child (L l it@ImportDecl{..})
514532
listAnn = epAnn srcParent [AddEpAnn AnnOpenP (epl 1), AddEpAnn AnnCloseP (epl 0)]
515533
x :: LIE GhcPs = reLocA $ L l'' $ IEThingWith listAnn parentLIE NoIEWildcard [childLIE]
516534

517-
lies' <- addCommaInImportList (reverse pre) x
535+
lies' = addCommaInImportList (reverse pre) x
518536
#endif
519537
return $ L l it{ideclHiding = Just (hide, L l' lies')}
520538
extendImportViaParent _ _ _ _ = lift $ Left "Unable to extend the import list via parent"
521539

522540
#if MIN_VERSION_ghc(9,2,0)
523541
-- Add an item in an import list, taking care of adding comma if needed.
524-
addCommaInImportList :: Monad m =>
542+
addCommaInImportList ::
525543
-- | Initial list
526544
[LocatedAn AnnListItem a]
527545
-- | Additionnal item
528546
-> LocatedAn AnnListItem a
529-
-> m [LocatedAn AnnListItem a]
530-
addCommaInImportList lies x = do
531-
let hasSibling = not (null lies)
532-
-- Add the space before the comma
533-
x <- pure $ setEntryDP x (SameLine $ if hasSibling then 1 else 0)
534-
535-
-- Add the comma (if needed)
536-
let
537-
fixLast = if hasSibling then first addComma else id
538-
lies' = over _last fixLast lies ++ [x]
539-
540-
pure lies'
547+
-> [LocatedAn AnnListItem a]
548+
addCommaInImportList lies x =
549+
fixLast lies ++ [newItem]
550+
where
551+
isTrailingAnnComma :: TrailingAnn -> Bool
552+
isTrailingAnnComma (AddCommaAnn _) = True
553+
isTrailingAnnComma _ = False
554+
555+
-- check if there is an existing trailing comma
556+
existingTrailingComma = fromMaybe False $ do
557+
L lastItemSrcAnn _ <- lastMaybe lies
558+
lastItemAnn <- case ann lastItemSrcAnn of
559+
EpAnn _ lastItemAnn _ -> pure lastItemAnn
560+
_ -> Nothing
561+
pure $ any isTrailingAnnComma (lann_trailing lastItemAnn)
562+
563+
hasSibling = not . null $ lies
564+
565+
-- Setup the new item. It should have a preceding whitespace if it has siblings, and a trailing comma if the
566+
-- preceding item already has one.
567+
newItem = first (if existingTrailingComma then addComma else id) $
568+
setEntryDP x (SameLine $ if hasSibling then 1 else 0)
569+
570+
-- Add the comma (if needed)
571+
fixLast :: [LocatedAn AnnListItem a] -> [LocatedAn AnnListItem a]
572+
fixLast = over _last (first (if existingTrailingComma then id else addComma))
541573
#endif
542574

543575
unIEWrappedName :: IEWrappedName (IdP GhcPs) -> String

ghcide/test/exe/Main.hs

+22
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,28 @@ extendImportTests = testGroup "extend import actions"
18821882
, " )"
18831883
, "main = print (stuffA, stuffB)"
18841884
])
1885+
, testSession "extend multi line import with trailing comma" $ template
1886+
[("ModuleA.hs", T.unlines
1887+
[ "module ModuleA where"
1888+
, "stuffA :: Double"
1889+
, "stuffA = 0.00750"
1890+
, "stuffB :: Integer"
1891+
, "stuffB = 123"
1892+
])]
1893+
("ModuleB.hs", T.unlines
1894+
[ "module ModuleB where"
1895+
, "import ModuleA (stuffB,"
1896+
, " )"
1897+
, "main = print (stuffA, stuffB)"
1898+
])
1899+
(Range (Position 3 17) (Position 3 18))
1900+
["Add stuffA to the import list of ModuleA"]
1901+
(T.unlines
1902+
[ "module ModuleB where"
1903+
, "import ModuleA (stuffB, stuffA,"
1904+
, " )"
1905+
, "main = print (stuffA, stuffB)"
1906+
])
18851907
, testSession "extend single line import with method within class" $ template
18861908
[("ModuleA.hs", T.unlines
18871909
[ "module ModuleA where"

0 commit comments

Comments
 (0)