Skip to content

Commit 781c006

Browse files
authored
Merge of #4716
2 parents ffbf013 + d764d66 commit 781c006

19 files changed

+343
-217
lines changed

haskell-language-server.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,6 @@ library hls-alternate-number-format-plugin
10711071
, lens
10721072
, lsp ^>=2.7
10731073
, mtl
1074-
, regex-tdfa
10751074
, syb
10761075
, text
10771076

@@ -1090,6 +1089,7 @@ test-suite hls-alternate-number-format-plugin-tests
10901089
main-is: Main.hs
10911090
ghc-options: -fno-ignore-asserts
10921091
build-depends:
1092+
, containers
10931093
, filepath
10941094
, haskell-language-server:hls-alternate-number-format-plugin
10951095
, hls-test-utils == 2.12.0.0

plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/AlternateNumberFormat.hs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import Development.IDE.Spans.Pragmas (NextPragmaInfo,
2323
import GHC.Generics (Generic)
2424
import Ide.Logger as Logger
2525
import Ide.Plugin.Conversion (AlternateFormat,
26-
ExtensionNeeded (NeedsExtension, NoExtension),
26+
ExtensionNeeded (..),
2727
alternateFormat)
2828
import Ide.Plugin.Error
2929
import Ide.Plugin.Literals
@@ -93,7 +93,7 @@ codeActionHandler state pId (CodeActionParams _ _ docId currRange _) = do
9393
pure $ InL actions
9494
where
9595
mkCodeAction :: NormalizedFilePath -> Literal -> [GhcExtension] -> NextPragmaInfo -> AlternateFormat -> Command |? CodeAction
96-
mkCodeAction nfp lit enabled npi af@(alt, ext) = InR CodeAction {
96+
mkCodeAction nfp lit enabled npi af@(alt, ExtensionNeeded exts) = InR CodeAction {
9797
_title = mkCodeActionTitle lit af enabled
9898
, _kind = Just $ CodeActionKind_Custom "quickfix.literals.style"
9999
, _diagnostics = Nothing
@@ -104,28 +104,29 @@ codeActionHandler state pId (CodeActionParams _ _ docId currRange _) = do
104104
, _data_ = Nothing
105105
}
106106
where
107-
edits = [TextEdit (realSrcSpanToRange $ getSrcSpan lit) alt] <> pragmaEdit
108-
pragmaEdit = case ext of
109-
NeedsExtension ext' -> [insertNewPragma npi ext' | needsExtension ext' enabled]
110-
NoExtension -> []
107+
edits = [TextEdit (realSrcSpanToRange $ getSrcSpan lit) alt] <> pragmaEdit exts
108+
pragmaEdit ext = case ext of
109+
ext': exts -> [insertNewPragma npi ext' | needsExtension enabled ext'] <> pragmaEdit exts
110+
[] -> []
111111

112112
mkWorkspaceEdit :: NormalizedFilePath -> [TextEdit] -> WorkspaceEdit
113113
mkWorkspaceEdit nfp edits = WorkspaceEdit changes Nothing Nothing
114114
where
115115
changes = Just $ Map.singleton (filePathToUri $ fromNormalizedFilePath nfp) edits
116116

117117
mkCodeActionTitle :: Literal -> AlternateFormat -> [GhcExtension] -> Text
118-
mkCodeActionTitle lit (alt, ext) ghcExts
119-
| (NeedsExtension ext') <- ext
120-
, needsExtension ext' ghcExts = title <> " (needs extension: " <> T.pack (show ext') <> ")"
121-
| otherwise = title
118+
mkCodeActionTitle lit (alt, ExtensionNeeded exts) ghcExts
119+
| null necessaryExtensions = title
120+
| otherwise = title <> " (needs extensions: " <> formattedExtensions <> ")"
122121
where
122+
formattedExtensions = T.intercalate ", " $ map (T.pack . show) necessaryExtensions
123+
necessaryExtensions = filter (needsExtension ghcExts) exts
123124
title = "Convert " <> getSrcText lit <> " into " <> alt
124125

125126

126127
-- | Checks whether the extension given is already enabled
127-
needsExtension :: Extension -> [GhcExtension] -> Bool
128-
needsExtension ext ghcExts = ext `notElem` map unExt ghcExts
128+
needsExtension :: [GhcExtension] -> Extension -> Bool
129+
needsExtension ghcExts ext = ext `notElem` map unExt ghcExts
129130

130131
requestLiterals :: MonadIO m => PluginId -> IdeState -> NormalizedFilePath -> ExceptT PluginError m CollectLiteralsResult
131132
requestLiterals (PluginId pId) state =
Lines changed: 123 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
1-
{-# LANGUAGE CPP #-}
2-
{-# LANGUAGE ViewPatterns #-}
1+
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE DerivingStrategies #-}
33
module Ide.Plugin.Conversion (
44
alternateFormat
5-
, hexRegex
6-
, hexFloatRegex
7-
, binaryRegex
8-
, octalRegex
9-
, decimalRegex
10-
, numDecimalRegex
11-
, matchLineRegex
125
, toOctal
136
, toDecimal
147
, toBinary
158
, toHex
169
, toFloatDecimal
1710
, toFloatExpDecimal
1811
, toHexFloat
12+
, intFormats
13+
, fracFormats
1914
, AlternateFormat
2015
, ExtensionNeeded(..)
16+
, FormatType(..)
17+
, IntFormatType(..)
18+
, FracFormatType(..)
19+
, UnderscoreFormatType(..)
2120
) where
2221

23-
import Data.List (delete)
24-
import Data.List.Extra (enumerate, upper)
25-
import Data.Maybe (mapMaybe)
22+
import Data.List (intercalate)
23+
import Data.List.Extra (chunksOf, enumerate, nubOrdOn,
24+
upper)
25+
import qualified Data.Map as Map
2626
import Data.Ratio (denominator, numerator)
2727
import Data.Text (Text)
2828
import qualified Data.Text as T
2929
import Development.IDE.Graph.Classes (NFData)
3030
import GHC.Generics (Generic)
3131
import GHC.LanguageExtensions.Type (Extension (..))
32-
import GHC.Show (intToDigit)
3332
import Ide.Plugin.Literals (Literal (..), getSrcText)
3433
import Numeric
35-
import Text.Regex.TDFA ((=~))
3634

3735
data FormatType = IntFormat IntFormatType
3836
| FracFormat FracFormatType
@@ -46,142 +44,130 @@ data IntFormatType = IntDecimalFormat
4644
| OctalFormat
4745
| BinaryFormat
4846
| NumDecimalFormat
49-
deriving (Show, Eq, Generic, Bounded, Enum)
47+
deriving (Show, Eq, Generic, Ord, Bounded, Enum)
5048

5149
instance NFData IntFormatType
5250

5351
data FracFormatType = FracDecimalFormat
5452
| HexFloatFormat
5553
| ExponentFormat
56-
deriving (Show, Eq, Generic, Bounded, Enum)
54+
deriving (Show, Eq, Generic, Ord, Bounded, Enum)
5755

5856
instance NFData FracFormatType
5957

60-
data ExtensionNeeded = NoExtension
61-
| NeedsExtension Extension
58+
newtype ExtensionNeeded = ExtensionNeeded [Extension]
59+
deriving newtype (Semigroup, Monoid)
6260

6361
type AlternateFormat = (Text, ExtensionNeeded)
6462

6563
-- | Generate alternate formats for a single Literal based on FormatType's given.
6664
alternateFormat :: Literal -> [AlternateFormat]
67-
alternateFormat lit = case lit of
68-
IntLiteral _ _ val -> map (alternateIntFormat val) (removeCurrentFormatInt lit)
65+
alternateFormat lit = nubOrdOn fst $ removeIdentical $ case lit of
66+
IntLiteral _ _ val -> alternateIntFormatsOf id val
6967
FracLiteral _ _ val -> if denominator val == 1 -- floats that can be integers we can represent as ints
70-
then map (alternateIntFormat (numerator val)) (removeCurrentFormatInt lit)
71-
else map (alternateFracFormat val) (removeCurrentFormatFrac lit)
72-
73-
alternateIntFormat :: Integer -> IntFormatType -> AlternateFormat
74-
alternateIntFormat val = \case
75-
IntDecimalFormat -> (T.pack $ toDecimal val, NoExtension)
76-
HexFormat -> (T.pack $ toHex val, NoExtension)
77-
OctalFormat -> (T.pack $ toOctal val, NoExtension)
78-
BinaryFormat -> (T.pack $ toBinary val, NeedsExtension BinaryLiterals)
79-
NumDecimalFormat -> (T.pack $ toFloatExpDecimal (fromInteger @Double val), NeedsExtension NumDecimals)
80-
81-
alternateFracFormat :: Rational -> FracFormatType -> AlternateFormat
82-
alternateFracFormat val = \case
83-
FracDecimalFormat -> (T.pack $ toFloatDecimal (fromRational @Double val), NoExtension)
84-
ExponentFormat -> (T.pack $ toFloatExpDecimal (fromRational @Double val), NoExtension)
85-
HexFloatFormat -> (T.pack $ toHexFloat (fromRational @Double val), NeedsExtension HexFloatLiterals)
86-
87-
-- given a Literal compute it's current Format and delete it from the list of available formats
88-
removeCurrentFormat :: (Foldable t, Eq a) => [a] -> t a -> [a]
89-
removeCurrentFormat fmts toRemove = foldl (flip delete) fmts toRemove
90-
91-
removeCurrentFormatInt :: Literal -> [IntFormatType]
92-
removeCurrentFormatInt (getSrcText -> srcText) = removeCurrentFormat intFormats (filterIntFormats $ sourceToFormatType srcText)
93-
94-
removeCurrentFormatFrac :: Literal -> [FracFormatType]
95-
removeCurrentFormatFrac (getSrcText -> srcText) = removeCurrentFormat fracFormats (filterFracFormats $ sourceToFormatType srcText)
96-
97-
filterIntFormats :: [FormatType] -> [IntFormatType]
98-
filterIntFormats = mapMaybe getIntFormat
99-
where
100-
getIntFormat (IntFormat f) = Just f
101-
getIntFormat _ = Nothing
102-
103-
filterFracFormats :: [FormatType] -> [FracFormatType]
104-
filterFracFormats = mapMaybe getFracFormat
105-
where
106-
getFracFormat (FracFormat f) = Just f
107-
getFracFormat _ = Nothing
108-
109-
intFormats :: [IntFormatType]
110-
intFormats = enumerate
111-
112-
fracFormats :: [FracFormatType]
113-
fracFormats = enumerate
114-
115-
-- | Regex to match a Haskell Hex Literal
116-
hexRegex :: Text
117-
hexRegex = "0[xX][a-fA-F0-9]+"
118-
119-
-- | Regex to match a Haskell Hex Float Literal
120-
hexFloatRegex :: Text
121-
hexFloatRegex = "0[xX][a-fA-F0-9]+(\\.)?[a-fA-F0-9]*(p[+-]?[0-9]+)?"
122-
123-
-- | Regex to match a Haskell Binary Literal
124-
binaryRegex :: Text
125-
binaryRegex = "0[bB][0|1]+"
126-
127-
-- | Regex to match a Haskell Octal Literal
128-
octalRegex :: Text
129-
octalRegex = "0[oO][0-8]+"
130-
131-
-- | Regex to match a Haskell Decimal Literal (no decimal points)
132-
decimalRegex :: Text
133-
decimalRegex = "[0-9]+(\\.[0-9]+)?"
134-
135-
-- | Regex to match a Haskell Literal with an explicit exponent
136-
numDecimalRegex :: Text
137-
numDecimalRegex = "[0-9]+\\.[0-9]+[eE][+-]?[0-9]+"
138-
139-
-- we want to be explicit in our matches
140-
-- so we need to match the beginning/end of the source text
141-
-- | Wraps a Regex with a beginning ("^") and end ("$") token
142-
matchLineRegex :: Text -> Text
143-
matchLineRegex regex = "^" <> regex <> "$"
144-
145-
sourceToFormatType :: Text -> [FormatType]
146-
sourceToFormatType srcText
147-
| srcText =~ matchLineRegex hexRegex = [IntFormat HexFormat]
148-
| srcText =~ matchLineRegex hexFloatRegex = [FracFormat HexFloatFormat]
149-
| srcText =~ matchLineRegex octalRegex = [IntFormat OctalFormat]
150-
| srcText =~ matchLineRegex binaryRegex = [IntFormat BinaryFormat]
151-
-- can either be a NumDecimal or just a regular Fractional with an exponent
152-
-- otherwise we wouldn't need to return a list
153-
| srcText =~ matchLineRegex numDecimalRegex = [IntFormat NumDecimalFormat, FracFormat ExponentFormat]
154-
-- just assume we are in base 10 with no decimals
155-
| otherwise = [IntFormat IntDecimalFormat, FracFormat FracDecimalFormat]
156-
157-
toBase :: (Num a, Ord a) => (a -> ShowS) -> String -> a -> String
158-
toBase conv header n
159-
| n < 0 = '-' : header <> upper (conv (abs n) "")
160-
| otherwise = header <> upper (conv n "")
161-
162-
#if MIN_VERSION_base(4,17,0)
163-
toOctal, toBinary, toHex :: Integral a => a -> String
164-
#else
165-
toOctal, toBinary, toHex:: (Integral a, Show a) => a -> String
166-
#endif
167-
168-
toBinary = toBase showBin_ "0b"
68+
then alternateIntFormatsOf numerator val
69+
else alternateFracFormatsOf val
16970
where
170-
-- this is not defined in base < 4.16
171-
showBin_ = showIntAtBase 2 intToDigit
172-
173-
toOctal = toBase showOct "0o"
174-
175-
toHex = toBase showHex "0x"
176-
177-
toDecimal :: Integral a => a -> String
178-
toDecimal = toBase showInt ""
179-
180-
toFloatDecimal :: RealFloat a => a -> String
181-
toFloatDecimal val = showFFloat Nothing val ""
182-
183-
toFloatExpDecimal :: RealFloat a => a -> String
184-
toFloatExpDecimal val = showEFloat Nothing val ""
185-
186-
toHexFloat :: RealFloat a => a -> String
187-
toHexFloat val = showHFloat val ""
71+
removeIdentical = filter ((/= getSrcText lit) . fst)
72+
alternateIntFormatsOf with val = [ alternateIntFormat (with val) formatType f | (formatType, formats) <- Map.toList intFormats, f <- formats]
73+
alternateFracFormatsOf val = [ alternateFracFormat val formatType f | (formatType, formats) <- Map.toList fracFormats, f <- formats]
74+
75+
data UnderscoreFormatType
76+
= NoUnderscores
77+
| UseUnderscores Int
78+
deriving (Show, Eq)
79+
80+
underscoreExtensions :: UnderscoreFormatType -> ExtensionNeeded
81+
underscoreExtensions = \case
82+
NoUnderscores -> mempty
83+
UseUnderscores _ -> ExtensionNeeded [NumericUnderscores]
84+
85+
alternateIntFormat :: Integer -> IntFormatType -> UnderscoreFormatType -> AlternateFormat
86+
alternateIntFormat val formatType underscoreFormat = case formatType of
87+
IntDecimalFormat -> (T.pack $ toDecimal underscoreFormat val, underscoreExtensions underscoreFormat)
88+
HexFormat -> (T.pack $ toHex underscoreFormat val, underscoreExtensions underscoreFormat)
89+
OctalFormat -> (T.pack $ toOctal underscoreFormat val, underscoreExtensions underscoreFormat)
90+
BinaryFormat -> (T.pack $ toBinary underscoreFormat val, underscoreExtensions underscoreFormat <> ExtensionNeeded [BinaryLiterals])
91+
NumDecimalFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromInteger @Double val), underscoreExtensions underscoreFormat <> ExtensionNeeded [NumDecimals])
92+
93+
alternateFracFormat :: Rational -> FracFormatType -> UnderscoreFormatType -> AlternateFormat
94+
alternateFracFormat val formatType underscoreFormat = case formatType of
95+
FracDecimalFormat -> (T.pack $ toFloatDecimal underscoreFormat (fromRational @Double val), mempty)
96+
ExponentFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromRational @Double val), mempty)
97+
HexFloatFormat -> (T.pack $ toHexFloat underscoreFormat (fromRational @Double val), underscoreExtensions underscoreFormat <> ExtensionNeeded [HexFloatLiterals])
98+
99+
intFormats :: Map.Map IntFormatType [UnderscoreFormatType]
100+
intFormats = Map.fromList $ map (\t -> (t, intFormatUnderscore t)) enumerate
101+
102+
intFormatUnderscore :: IntFormatType -> [UnderscoreFormatType]
103+
intFormatUnderscore formatType = NoUnderscores : map UseUnderscores (case formatType of
104+
IntDecimalFormat -> [3, 4]
105+
HexFormat -> [2, 4]
106+
OctalFormat -> [2, 4, 8]
107+
BinaryFormat -> [4]
108+
NumDecimalFormat -> [3, 4])
109+
110+
fracFormats :: Map.Map FracFormatType [UnderscoreFormatType]
111+
fracFormats = Map.fromList $ map (\t -> (t, fracFormatUnderscore t)) enumerate
112+
113+
fracFormatUnderscore :: FracFormatType -> [UnderscoreFormatType]
114+
fracFormatUnderscore formatType = NoUnderscores : map UseUnderscores (case formatType of
115+
FracDecimalFormat -> [3, 4]
116+
ExponentFormat -> [3, 4]
117+
HexFloatFormat -> [2, 4])
118+
119+
addMinus :: (Ord n, Num n) => (n -> String) -> n -> String
120+
addMinus f n
121+
| n < 0 = '-' : f (abs n)
122+
| otherwise = f n
123+
124+
toBase :: (a -> ShowS) -> a -> String
125+
toBase conv n = upper (conv n "")
126+
127+
toBaseFmt :: (Ord a, Num a) => (a -> ShowS) -> [Char] -> UnderscoreFormatType -> a -> [Char]
128+
toBaseFmt conv header underscoreFormat = addMinus $ \val ->
129+
header ++ addUnderscoresInt underscoreFormat (toBase conv val)
130+
131+
toBinary :: Integral a => UnderscoreFormatType -> a -> String
132+
toBinary = toBaseFmt showBin "0b"
133+
134+
toOctal :: Integral a => UnderscoreFormatType -> a -> String
135+
toOctal = toBaseFmt showOct "0o"
136+
137+
toHex :: Integral a => UnderscoreFormatType -> a -> String
138+
toHex = toBaseFmt showHex "0x"
139+
140+
toDecimal :: Integral a => UnderscoreFormatType -> a -> String
141+
toDecimal = toBaseFmt showInt ""
142+
143+
addUnderscoresInt :: UnderscoreFormatType -> String -> String
144+
addUnderscoresInt = \case
145+
NoUnderscores -> id
146+
-- Chunk starting from the least significant numeral.
147+
UseUnderscores n -> reverse . intercalate "_" . chunksOf n . reverse
148+
149+
toFracFormat :: (Ord t, Num t) => (t -> String) -> String -> UnderscoreFormatType -> t -> String
150+
toFracFormat f header underScoreFormat = addMinus $ \val ->
151+
header <> addUnderscoresFloat underScoreFormat (f val)
152+
153+
toFloatDecimal :: RealFloat a => UnderscoreFormatType -> a -> String
154+
toFloatDecimal = toFracFormat (\v -> showFFloat Nothing (abs v) "") ""
155+
156+
toFloatExpDecimal :: RealFloat a => UnderscoreFormatType -> a -> String
157+
toFloatExpDecimal underscoreFormat val =
158+
let (n, e) = break (=='e') $ showEFloat Nothing (abs val) ""
159+
in toFracFormat (const n) "" underscoreFormat val <> e
160+
161+
toHexFloat :: RealFloat a => UnderscoreFormatType -> a -> String
162+
toHexFloat underscoreFormat val =
163+
let (header, n) = splitAt 2 $ showHFloat (abs val) ""
164+
(n', e) = break (=='p') n
165+
in toFracFormat (const n') header underscoreFormat val <> e
166+
167+
addUnderscoresFloat :: UnderscoreFormatType -> String -> String
168+
addUnderscoresFloat = \case
169+
NoUnderscores -> id
170+
UseUnderscores n -> \s ->
171+
let (integral, decimal) = break (=='.') s
172+
addUnderscores = reverse . intercalate "_" . chunksOf n . reverse
173+
in intercalate "." [addUnderscores integral, intercalate "_" $ chunksOf n $ drop 1 decimal]

0 commit comments

Comments
 (0)