From 51721eff299854464c75a61ae989a667359fc1fe Mon Sep 17 00:00:00 2001 From: Curtis Chin Jen Sem Date: Sat, 30 Aug 2025 11:35:35 +0200 Subject: [PATCH 1/6] Specify `UnderscoreFormatType` per lit format --- .../src/Ide/Plugin/Conversion.hs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs index cbfaa30140..29d81ef3f2 100644 --- a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs +++ b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs @@ -16,8 +16,11 @@ module Ide.Plugin.Conversion ( , toFloatDecimal , toFloatExpDecimal , toHexFloat + , intFormats + , fracFormats , AlternateFormat , ExtensionNeeded(..) + , UnderscoreFormatType(..) ) where import Data.List (delete) @@ -171,17 +174,26 @@ toBinary = toBase showBin_ "0b" showBin_ = showIntAtBase 2 intToDigit toOctal = toBase showOct "0o" +data UnderscoreFormatType + = NoUnderscores + | UseUnderscores Int + deriving (Show, Eq) toHex = toBase showHex "0x" toDecimal :: Integral a => a -> String toDecimal = toBase showInt "" -toFloatDecimal :: RealFloat a => a -> String -toFloatDecimal val = showFFloat Nothing val "" +intFormats :: Map.Map IntFormatType [UnderscoreFormatType] +intFormats = Map.fromList $ map (\t -> (t, intFormatUnderscore t)) enumerate -toFloatExpDecimal :: RealFloat a => a -> String -toFloatExpDecimal val = showEFloat Nothing val "" +intFormatUnderscore :: IntFormatType -> [UnderscoreFormatType] +intFormatUnderscore formatType = NoUnderscores : map UseUnderscores (case formatType of + IntDecimalFormat -> [3, 4] + HexFormat -> [2, 4] + OctalFormat -> [2, 4, 8] + BinaryFormat -> [4] + NumDecimalFormat -> [3, 4]) toHexFloat :: RealFloat a => a -> String toHexFloat val = showHFloat val "" From 2fdc5bb4df1b0acf621320b4abd4cfdacf2a9726 Mon Sep 17 00:00:00 2001 From: Curtis Chin Jen Sem Date: Sat, 30 Aug 2025 11:41:16 +0200 Subject: [PATCH 2/6] Emit underscore formatted literals --- .../src/Ide/Plugin/Conversion.hs | 218 ++++++++---------- 1 file changed, 93 insertions(+), 125 deletions(-) diff --git a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs index 29d81ef3f2..382a433773 100644 --- a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs +++ b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs @@ -1,14 +1,6 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE CPP #-} module Ide.Plugin.Conversion ( alternateFormat - , hexRegex - , hexFloatRegex - , binaryRegex - , octalRegex - , decimalRegex - , numDecimalRegex - , matchLineRegex , toOctal , toDecimal , toBinary @@ -20,22 +12,24 @@ module Ide.Plugin.Conversion ( , fracFormats , AlternateFormat , ExtensionNeeded(..) + , FormatType(..) + , IntFormatType(..) + , FracFormatType(..) , UnderscoreFormatType(..) ) where -import Data.List (delete) -import Data.List.Extra (enumerate, upper) -import Data.Maybe (mapMaybe) +import Data.List (intercalate) +import Data.List.Extra (chunksOf, enumerate, nubOrdOn, + upper) +import qualified Data.Map as Map import Data.Ratio (denominator, numerator) import Data.Text (Text) import qualified Data.Text as T import Development.IDE.Graph.Classes (NFData) import GHC.Generics (Generic) import GHC.LanguageExtensions.Type (Extension (..)) -import GHC.Show (intToDigit) import Ide.Plugin.Literals (Literal (..), getSrcText) import Numeric -import Text.Regex.TDFA ((=~)) data FormatType = IntFormat IntFormatType | FracFormat FracFormatType @@ -49,14 +43,14 @@ data IntFormatType = IntDecimalFormat | OctalFormat | BinaryFormat | NumDecimalFormat - deriving (Show, Eq, Generic, Bounded, Enum) + deriving (Show, Eq, Generic, Ord, Bounded, Enum) instance NFData IntFormatType data FracFormatType = FracDecimalFormat | HexFloatFormat | ExponentFormat - deriving (Show, Eq, Generic, Bounded, Enum) + deriving (Show, Eq, Generic, Ord, Bounded, Enum) instance NFData FracFormatType @@ -67,122 +61,34 @@ type AlternateFormat = (Text, ExtensionNeeded) -- | Generate alternate formats for a single Literal based on FormatType's given. alternateFormat :: Literal -> [AlternateFormat] -alternateFormat lit = case lit of - IntLiteral _ _ val -> map (alternateIntFormat val) (removeCurrentFormatInt lit) +alternateFormat lit = nubOrdOn fst $ removeIdentical $ case lit of + IntLiteral _ _ val -> alternateIntFormatsOf id val FracLiteral _ _ val -> if denominator val == 1 -- floats that can be integers we can represent as ints - then map (alternateIntFormat (numerator val)) (removeCurrentFormatInt lit) - else map (alternateFracFormat val) (removeCurrentFormatFrac lit) - -alternateIntFormat :: Integer -> IntFormatType -> AlternateFormat -alternateIntFormat val = \case - IntDecimalFormat -> (T.pack $ toDecimal val, NoExtension) - HexFormat -> (T.pack $ toHex val, NoExtension) - OctalFormat -> (T.pack $ toOctal val, NoExtension) - BinaryFormat -> (T.pack $ toBinary val, NeedsExtension BinaryLiterals) - NumDecimalFormat -> (T.pack $ toFloatExpDecimal (fromInteger @Double val), NeedsExtension NumDecimals) - -alternateFracFormat :: Rational -> FracFormatType -> AlternateFormat -alternateFracFormat val = \case - FracDecimalFormat -> (T.pack $ toFloatDecimal (fromRational @Double val), NoExtension) - ExponentFormat -> (T.pack $ toFloatExpDecimal (fromRational @Double val), NoExtension) - HexFloatFormat -> (T.pack $ toHexFloat (fromRational @Double val), NeedsExtension HexFloatLiterals) - --- given a Literal compute it's current Format and delete it from the list of available formats -removeCurrentFormat :: (Foldable t, Eq a) => [a] -> t a -> [a] -removeCurrentFormat fmts toRemove = foldl (flip delete) fmts toRemove - -removeCurrentFormatInt :: Literal -> [IntFormatType] -removeCurrentFormatInt (getSrcText -> srcText) = removeCurrentFormat intFormats (filterIntFormats $ sourceToFormatType srcText) - -removeCurrentFormatFrac :: Literal -> [FracFormatType] -removeCurrentFormatFrac (getSrcText -> srcText) = removeCurrentFormat fracFormats (filterFracFormats $ sourceToFormatType srcText) - -filterIntFormats :: [FormatType] -> [IntFormatType] -filterIntFormats = mapMaybe getIntFormat - where - getIntFormat (IntFormat f) = Just f - getIntFormat _ = Nothing - -filterFracFormats :: [FormatType] -> [FracFormatType] -filterFracFormats = mapMaybe getFracFormat - where - getFracFormat (FracFormat f) = Just f - getFracFormat _ = Nothing - -intFormats :: [IntFormatType] -intFormats = enumerate - -fracFormats :: [FracFormatType] -fracFormats = enumerate - --- | Regex to match a Haskell Hex Literal -hexRegex :: Text -hexRegex = "0[xX][a-fA-F0-9]+" - --- | Regex to match a Haskell Hex Float Literal -hexFloatRegex :: Text -hexFloatRegex = "0[xX][a-fA-F0-9]+(\\.)?[a-fA-F0-9]*(p[+-]?[0-9]+)?" - --- | Regex to match a Haskell Binary Literal -binaryRegex :: Text -binaryRegex = "0[bB][0|1]+" - --- | Regex to match a Haskell Octal Literal -octalRegex :: Text -octalRegex = "0[oO][0-8]+" - --- | Regex to match a Haskell Decimal Literal (no decimal points) -decimalRegex :: Text -decimalRegex = "[0-9]+(\\.[0-9]+)?" - --- | Regex to match a Haskell Literal with an explicit exponent -numDecimalRegex :: Text -numDecimalRegex = "[0-9]+\\.[0-9]+[eE][+-]?[0-9]+" - --- we want to be explicit in our matches --- so we need to match the beginning/end of the source text --- | Wraps a Regex with a beginning ("^") and end ("$") token -matchLineRegex :: Text -> Text -matchLineRegex regex = "^" <> regex <> "$" - -sourceToFormatType :: Text -> [FormatType] -sourceToFormatType srcText - | srcText =~ matchLineRegex hexRegex = [IntFormat HexFormat] - | srcText =~ matchLineRegex hexFloatRegex = [FracFormat HexFloatFormat] - | srcText =~ matchLineRegex octalRegex = [IntFormat OctalFormat] - | srcText =~ matchLineRegex binaryRegex = [IntFormat BinaryFormat] - -- can either be a NumDecimal or just a regular Fractional with an exponent - -- otherwise we wouldn't need to return a list - | srcText =~ matchLineRegex numDecimalRegex = [IntFormat NumDecimalFormat, FracFormat ExponentFormat] - -- just assume we are in base 10 with no decimals - | otherwise = [IntFormat IntDecimalFormat, FracFormat FracDecimalFormat] - -toBase :: (Num a, Ord a) => (a -> ShowS) -> String -> a -> String -toBase conv header n - | n < 0 = '-' : header <> upper (conv (abs n) "") - | otherwise = header <> upper (conv n "") - -#if MIN_VERSION_base(4,17,0) -toOctal, toBinary, toHex :: Integral a => a -> String -#else -toOctal, toBinary, toHex:: (Integral a, Show a) => a -> String -#endif - -toBinary = toBase showBin_ "0b" + then alternateIntFormatsOf numerator val + else alternateFracFormatsOf val where - -- this is not defined in base < 4.16 - showBin_ = showIntAtBase 2 intToDigit + removeIdentical = filter ((/= getSrcText lit) . fst) + alternateIntFormatsOf with val = [ alternateIntFormat (with val) formatType f | (formatType, formats) <- Map.toList intFormats, f <- formats] + alternateFracFormatsOf val = [ alternateFracFormat val formatType f | (formatType, formats) <- Map.toList fracFormats, f <- formats] -toOctal = toBase showOct "0o" data UnderscoreFormatType = NoUnderscores | UseUnderscores Int deriving (Show, Eq) -toHex = toBase showHex "0x" +alternateIntFormat :: Integer -> IntFormatType -> UnderscoreFormatType -> AlternateFormat +alternateIntFormat val formatType underscoreFormat = case formatType of + IntDecimalFormat -> (T.pack $ toDecimal underscoreFormat val , NoExtension) + HexFormat -> (T.pack $ toHex underscoreFormat val , NoExtension) + OctalFormat -> (T.pack $ toOctal underscoreFormat val , NoExtension) + BinaryFormat -> (T.pack $ toBinary underscoreFormat val , NeedsExtension BinaryLiterals) + NumDecimalFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromInteger @Double val) , NeedsExtension NumDecimals) -toDecimal :: Integral a => a -> String -toDecimal = toBase showInt "" +alternateFracFormat :: Rational -> FracFormatType -> UnderscoreFormatType -> AlternateFormat +alternateFracFormat val formatType underscoreFormat = case formatType of + FracDecimalFormat -> (T.pack $ toFloatDecimal underscoreFormat (fromRational @Double val), NoExtension) + ExponentFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromRational @Double val), NoExtension) + HexFloatFormat -> (T.pack $ toHexFloat underscoreFormat (fromRational @Double val), NeedsExtension HexFloatLiterals) intFormats :: Map.Map IntFormatType [UnderscoreFormatType] intFormats = Map.fromList $ map (\t -> (t, intFormatUnderscore t)) enumerate @@ -195,5 +101,67 @@ intFormatUnderscore formatType = NoUnderscores : map UseUnderscores (case format BinaryFormat -> [4] NumDecimalFormat -> [3, 4]) -toHexFloat :: RealFloat a => a -> String -toHexFloat val = showHFloat val "" +fracFormats :: Map.Map FracFormatType [UnderscoreFormatType] +fracFormats = Map.fromList $ map (\t -> (t, fracFormatUnderscore t)) enumerate + +fracFormatUnderscore :: FracFormatType -> [UnderscoreFormatType] +fracFormatUnderscore formatType = NoUnderscores : map UseUnderscores (case formatType of + FracDecimalFormat -> [3, 4] + ExponentFormat -> [3, 4] + HexFloatFormat -> [2, 4]) + +addMinus :: (Ord n, Num n) => (n -> String) -> n -> String +addMinus f n + | n < 0 = '-' : f (abs n) + | otherwise = f n + +toBase :: (a -> ShowS) -> a -> String +toBase conv n = upper (conv n "") + +toBaseFmt :: (Ord a, Num a) => (a -> ShowS) -> [Char] -> UnderscoreFormatType -> a -> [Char] +toBaseFmt conv header underscoreFormat = addMinus $ \val -> + header ++ addUnderscoresInt underscoreFormat (toBase conv val) + +toBinary :: Integral a => UnderscoreFormatType -> a -> String +toBinary = toBaseFmt showBin "0b" + +toOctal :: Integral a => UnderscoreFormatType -> a -> String +toOctal = toBaseFmt showOct "0o" + +toHex :: Integral a => UnderscoreFormatType -> a -> String +toHex = toBaseFmt showHex "0x" + +toDecimal :: Integral a => UnderscoreFormatType -> a -> String +toDecimal = toBaseFmt showInt "" + +addUnderscoresInt :: UnderscoreFormatType -> String -> String +addUnderscoresInt = \case + NoUnderscores -> id + -- Chunk starting from the least significant numeral. + UseUnderscores n -> reverse . intercalate "_" . chunksOf n . reverse + +toFracFormat :: (Ord t, Num t) => (t -> String) -> String -> UnderscoreFormatType -> t -> String +toFracFormat f header underScoreFormat = addMinus $ \val -> + header <> addUnderscoresFloat underScoreFormat (f val) + +toFloatDecimal :: RealFloat a => UnderscoreFormatType -> a -> String +toFloatDecimal = toFracFormat (\v -> showFFloat Nothing (abs v) "") "" + +toFloatExpDecimal :: RealFloat a => UnderscoreFormatType -> a -> String +toFloatExpDecimal underscoreFormat val = + let (n, e) = break (=='e') $ showEFloat Nothing (abs val) "" + in toFracFormat (const n) "" underscoreFormat val <> e + +toHexFloat :: RealFloat a => UnderscoreFormatType -> a -> String +toHexFloat underscoreFormat val = + let (header, n) = splitAt 2 $ showHFloat (abs val) "" + (n', e) = break (=='p') n + in toFracFormat (const n') header underscoreFormat val <> e + +addUnderscoresFloat :: UnderscoreFormatType -> String -> String +addUnderscoresFloat = \case + NoUnderscores -> id + UseUnderscores n -> \s -> + let (integral, decimal) = break (=='.') s + addUnderscores = reverse . intercalate "_" . chunksOf n . reverse + in intercalate "." [addUnderscores integral, intercalate "_" $ chunksOf n $ drop 1 decimal] From a68382a51be2b959d4486f86f6eec4b81b2524b4 Mon Sep 17 00:00:00 2001 From: Curtis Chin Jen Sem Date: Sat, 30 Aug 2025 11:44:35 +0200 Subject: [PATCH 3/6] Assert regex patterns on formatted literal output --- haskell-language-server.cabal | 2 +- .../test/Properties/Conversion.hs | 127 ++++++++++++++---- 2 files changed, 102 insertions(+), 27 deletions(-) diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index 91adbcbe37..d14fe82c4a 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -1018,7 +1018,6 @@ library hls-alternate-number-format-plugin , lens , lsp ^>=2.7 , mtl - , regex-tdfa , syb , text @@ -1037,6 +1036,7 @@ test-suite hls-alternate-number-format-plugin-tests main-is: Main.hs ghc-options: -fno-ignore-asserts build-depends: + , containers , filepath , haskell-language-server:hls-alternate-number-format-plugin , hls-test-utils == 2.11.0.0 diff --git a/plugins/hls-alternate-number-format-plugin/test/Properties/Conversion.hs b/plugins/hls-alternate-number-format-plugin/test/Properties/Conversion.hs index 07e4617bde..7eb1208aaa 100644 --- a/plugins/hls-alternate-number-format-plugin/test/Properties/Conversion.hs +++ b/plugins/hls-alternate-number-format-plugin/test/Properties/Conversion.hs @@ -1,42 +1,117 @@ module Properties.Conversion where +import qualified Data.Map as Map +import Data.Maybe (fromMaybe) +import Data.Text (Text) import Ide.Plugin.Conversion import Test.Hls (TestTree, testGroup) import Test.Tasty.QuickCheck (testProperty) import Text.Regex.TDFA ((=~)) conversions :: TestTree -conversions = testGroup "Conversions" $ - map (uncurry testProperty) - [ ("Match NumDecimal", prop_regexMatchesNumDecimal) - , ("Match Hex", prop_regexMatchesHex) - , ("Match Octal", prop_regexMatchesOctal) - , ("Match Binary", prop_regexMatchesBinary) - ] - <> - map (uncurry testProperty) - [ ("Match HexFloat", prop_regexMatchesHexFloat) - , ("Match FloatDecimal", prop_regexMatchesFloatDecimal) - , ("Match FloatExpDecimal", prop_regexMatchesFloatExpDecimal) +conversions = testGroup "Conversions" + [ testGroup "integral literals" + [ testGroup "Match NumDecimal" prop_regexMatchesNumDecimal + , testGroup "Match Hex" prop_regexMatchesHex + , testGroup "Match Octal" prop_regexMatchesOctal + , testGroup "Match Binary" prop_regexMatchesBinary + ] + , testGroup "fractional literals" + [ testGroup "Match HexFloat" prop_regexMatchesHexFloat + , testGroup "Match FloatDecimal" prop_regexMatchesFloatDecimal + , testGroup "Match FloatExpDecimal" prop_regexMatchesFloatExpDecimal + ] ] -prop_regexMatchesNumDecimal :: Integer -> Bool -prop_regexMatchesNumDecimal = (=~ numDecimalRegex) . toFloatExpDecimal @Double . fromInteger +allIntFormatOf :: IntFormatType -> [UnderscoreFormatType] +allIntFormatOf formatType = fromMaybe [] (Map.lookup formatType intFormats) + +prop_regexMatchesNumDecimal :: [TestTree] +prop_regexMatchesNumDecimal = + [ testProperty (show underscoreFormat) (prop underscoreFormat) + | underscoreFormat <- allIntFormatOf IntDecimalFormat ] + where + prop :: UnderscoreFormatType -> Integer -> Bool + prop underscoreFormat = (=~ numDecimalRegex) . toFloatExpDecimal @Double underscoreFormat . fromInteger + +prop_regexMatchesHex :: [TestTree] +prop_regexMatchesHex = + [ testProperty (show underscoreFormat) (prop underscoreFormat) + | underscoreFormat <- allIntFormatOf IntDecimalFormat ] + where + prop :: UnderscoreFormatType -> Integer -> Bool + prop underscoreFormat = (=~ hexRegex ) . toHex underscoreFormat + +prop_regexMatchesOctal :: [TestTree] +prop_regexMatchesOctal = + [ testProperty (show underscoreFormat) (prop underscoreFormat) + | underscoreFormat <- allIntFormatOf IntDecimalFormat ] + where + prop :: UnderscoreFormatType -> Integer -> Bool + prop underscoreFormat = (=~ octalRegex) . toOctal underscoreFormat + +prop_regexMatchesBinary :: [TestTree] +prop_regexMatchesBinary = + [ testProperty (show underscoreFormat) (prop underscoreFormat) + | underscoreFormat <- allIntFormatOf IntDecimalFormat ] + where + prop :: UnderscoreFormatType -> Integer -> Bool + prop underscoreFormat = (=~ binaryRegex) . toBinary underscoreFormat + +allFracFormatOf :: FracFormatType -> [UnderscoreFormatType] +allFracFormatOf formatType = fromMaybe [] (Map.lookup formatType fracFormats) + +prop_regexMatchesHexFloat :: [TestTree] +prop_regexMatchesHexFloat = + [ testProperty (show underscoreFormat) (prop underscoreFormat) + | underscoreFormat <- allFracFormatOf HexFloatFormat ] + where + prop :: UnderscoreFormatType -> Double -> Bool + prop underscoreFormat = (=~ hexFloatRegex) . toHexFloat underscoreFormat + +prop_regexMatchesFloatDecimal :: [TestTree] +prop_regexMatchesFloatDecimal = + [ testProperty (show underscoreFormat) (prop underscoreFormat) + | underscoreFormat <- allFracFormatOf FracDecimalFormat ] + where + prop :: UnderscoreFormatType -> Double -> Bool + prop underscoreFormat = (=~ decimalRegex ) . toFloatDecimal underscoreFormat + +prop_regexMatchesFloatExpDecimal :: [TestTree] +prop_regexMatchesFloatExpDecimal = + [ testProperty (show underscoreFormat) (prop underscoreFormat) + | underscoreFormat <- allFracFormatOf ExponentFormat ] + where + prop :: UnderscoreFormatType -> Double -> Bool + prop underscoreFormat = (=~ numDecimalRegex ) . toFloatExpDecimal underscoreFormat + +-- | Regex to match a Haskell Hex Literal +hexRegex :: Text +hexRegex = "0[xX][a-fA-F0-9_]+" + +-- | Regex to match a Haskell Hex Float Literal +hexFloatRegex :: Text +hexFloatRegex = "0[xX][a-fA-F0-9_]+(\\.)?[a-fA-F0-9_]*(p[+-]?[0-9]+)?" -prop_regexMatchesHex :: Integer -> Bool -prop_regexMatchesHex = (=~ hexRegex ) . toHex +-- | Regex to match a Haskell Binary Literal +binaryRegex :: Text +binaryRegex = "0[bB][0|1_]+" -prop_regexMatchesOctal :: Integer -> Bool -prop_regexMatchesOctal = (=~ octalRegex) . toOctal +-- | Regex to match a Haskell Octal Literal +octalRegex :: Text +octalRegex = "0[oO][0-8_]+" -prop_regexMatchesBinary :: Integer -> Bool -prop_regexMatchesBinary = (=~ binaryRegex) . toBinary +-- | Regex to match a Haskell Decimal Literal (no decimal points) +decimalRegex :: Text +decimalRegex = "[0-9_]+(\\.[0-9_]+)?" -prop_regexMatchesHexFloat :: Double -> Bool -prop_regexMatchesHexFloat = (=~ hexFloatRegex) . toHexFloat +-- | Regex to match a Haskell Literal with an explicit exponent +numDecimalRegex :: Text +numDecimalRegex = "[0-9_]+\\.[0-9_]+[eE][+-]?[0-9]+" -prop_regexMatchesFloatDecimal :: Double -> Bool -prop_regexMatchesFloatDecimal = (=~ decimalRegex ) . toFloatDecimal +-- we want to be explicit in our matches +-- so we need to match the beginning/end of the source text +-- | Wraps a Regex with a beginning ("^") and end ("$") token +matchLineRegex :: Text -> Text +matchLineRegex regex = "^" <> regex <> "$" -prop_regexMatchesFloatExpDecimal :: Double -> Bool -prop_regexMatchesFloatExpDecimal = (=~ numDecimalRegex ) . toFloatExpDecimal From f5af2d36eb99818430c43234729cfd6d6357ec9f Mon Sep 17 00:00:00 2001 From: Curtis Chin Jen Sem Date: Sat, 30 Aug 2025 11:45:04 +0200 Subject: [PATCH 4/6] Add golden tests for underscore conversions --- .../test/Main.hs | 87 ++++++++++--------- .../test/testdata/TFracDtoDU0toU3.expected.hs | 3 + .../test/testdata/TFracDtoDU0toU3.hs | 3 + .../test/testdata/TFracDtoDU3toU0.expected.hs | 3 + .../test/testdata/TFracDtoDU3toU0.hs | 3 + .../test/testdata/TFracDtoDU3toU4.expected.hs | 3 + .../test/testdata/TFracDtoDU3toU4.hs | 3 + 7 files changed, 65 insertions(+), 40 deletions(-) create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU0toU3.expected.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU0toU3.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU0.expected.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU0.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU4.expected.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU4.hs diff --git a/plugins/hls-alternate-number-format-plugin/test/Main.hs b/plugins/hls-alternate-number-format-plugin/test/Main.hs index 3a5f205e5a..78cb16c3f6 100644 --- a/plugins/hls-alternate-number-format-plugin/test/Main.hs +++ b/plugins/hls-alternate-number-format-plugin/test/Main.hs @@ -6,8 +6,7 @@ import Data.List (find) import Data.Text (Text) import qualified Data.Text as T import qualified Ide.Plugin.AlternateNumberFormat as AlternateNumberFormat -import qualified Ide.Plugin.Conversion as Conversion -import Properties.Conversion (conversions) +import qualified Properties.Conversion as Conversion import System.FilePath ((<.>), ()) import Test.Hls import Text.Regex.TDFA ((=~)) @@ -23,29 +22,32 @@ alternateNumberFormatPlugin = mkPluginTestDescriptor AlternateNumberFormat.descr -- to do with how test :: TestTree test = testGroup "alternateNumberFormat" [ - codeActionHex "TIntDtoH" 3 13 - , codeActionOctal "TIntDtoO" 3 13 - , codeActionBinary "TIntDtoB" 4 13 - , codeActionNumDecimal "TIntDtoND" 5 13 - , codeActionFracExp "TFracDtoE" 3 13 - , codeActionFloatHex "TFracDtoHF" 4 13 - , codeActionDecimal "TIntHtoD" 3 13 - , codeActionDecimal "TFracHFtoD" 4 13 + codeActionHex 0 "TIntDtoH" 3 13 + , codeActionOctal 0 "TIntDtoO" 3 13 + , codeActionBinary 0 "TIntDtoB" 4 13 + , codeActionNumDecimal 0 "TIntDtoND" 5 13 + , codeActionFracExp 0 "TFracDtoE" 3 13 + , codeActionFloatHex 0 "TFracDtoHF" 4 13 + , codeActionDecimal 0 "TIntHtoD" 3 13 + , codeActionDecimal 0 "TFracHFtoD" 4 13 + , codeActionDecimal 3 "TFracDtoDU0toU3" 3 13 + , codeActionDecimal 2 "TFracDtoDU3toU4" 3 13 + , codeActionDecimal 3 "TFracDtoDU3toU0" 3 13 -- to test we don't duplicate pragmas - , codeActionFloatHex "TFracDtoHFWithPragma" 4 13 + , codeActionFloatHex 0 "TFracDtoHFWithPragma" 4 13 , codeActionProperties "TFindLiteralIntPattern" [(4, 25), (5,25)] $ \actions -> do liftIO $ length actions @?= 8 , codeActionProperties "TFindLiteralIntCase" [(4, 29)] $ \actions -> do - liftIO $ length actions @?= 4 + liftIO $ length actions @?= 5 , codeActionProperties "TFindLiteralIntCase2" [(5, 21)] $ \actions -> do - liftIO $ length actions @?= 4 + liftIO $ length actions @?= 5 , codeActionProperties "TFindLiteralDoReturn" [(6, 10)] $ \actions -> do - liftIO $ length actions @?= 4 + liftIO $ length actions @?= 5 , codeActionProperties "TFindLiteralDoLet" [(6, 13), (7, 13)] $ \actions -> do - liftIO $ length actions @?= 8 + liftIO $ length actions @?= 12 , codeActionProperties "TFindLiteralList" [(4, 28)] $ \actions -> do - liftIO $ length actions @?= 4 - , conversions + liftIO $ length actions @?= 5 + , Conversion.conversions ] codeActionProperties :: TestName -> [(Int, Int)] -> ([CodeAction] -> Session ()) -> TestTree @@ -81,26 +83,26 @@ codeActionTest filter' fp line col = goldenAlternateFormat fp $ \doc -> do Just (InR x) -> executeCodeAction x _ -> liftIO $ assertFailure "Unable to find CodeAction" -codeActionDecimal :: FilePath -> Int -> Int -> TestTree -codeActionDecimal = codeActionTest isDecimalCodeAction +codeActionDecimal :: Int -> FilePath -> Int -> Int -> TestTree +codeActionDecimal nrUnderscores = codeActionTest (isDecimalCodeAction nrUnderscores) -codeActionHex :: FilePath -> Int -> Int -> TestTree -codeActionHex = codeActionTest isHexCodeAction +codeActionHex :: Int -> FilePath -> Int -> Int -> TestTree +codeActionHex nrUnderscores = codeActionTest (isHexCodeAction nrUnderscores) -codeActionOctal :: FilePath -> Int -> Int -> TestTree -codeActionOctal = codeActionTest isOctalCodeAction +codeActionOctal :: Int -> FilePath -> Int -> Int -> TestTree +codeActionOctal nrUnderscores = codeActionTest (isOctalCodeAction nrUnderscores) -codeActionBinary :: FilePath -> Int -> Int -> TestTree -codeActionBinary = codeActionTest isBinaryCodeAction +codeActionBinary :: Int -> FilePath -> Int -> Int -> TestTree +codeActionBinary nrUnderscores = codeActionTest (isBinaryCodeAction nrUnderscores) -codeActionNumDecimal :: FilePath -> Int -> Int -> TestTree -codeActionNumDecimal = codeActionTest isNumDecimalCodeAction +codeActionNumDecimal :: Int -> FilePath -> Int -> Int -> TestTree +codeActionNumDecimal nrUnderscores = codeActionTest (isNumDecimalCodeAction nrUnderscores) -codeActionFracExp :: FilePath -> Int -> Int -> TestTree -codeActionFracExp = codeActionTest isNumDecimalCodeAction +codeActionFracExp :: Int -> FilePath -> Int -> Int -> TestTree +codeActionFracExp nrUnderscores = codeActionTest (isNumDecimalCodeAction nrUnderscores) -codeActionFloatHex :: FilePath -> Int -> Int -> TestTree -codeActionFloatHex = codeActionTest isHexFloatCodeAction +codeActionFloatHex :: Int -> FilePath -> Int -> Int -> TestTree +codeActionFloatHex nrUnderscores = codeActionTest (isHexFloatCodeAction nrUnderscores) codeActionTitle :: (Command |? CodeAction) -> Maybe Text codeActionTitle (InR CodeAction {_title}) = Just _title @@ -123,26 +125,31 @@ octalRegex = intoInfix <> Conversion.octalRegex <> maybeExtension numDecimalRegex = intoInfix <> Conversion.numDecimalRegex <> maybeExtension decimalRegex = intoInfix <> Conversion.decimalRegex <> maybeExtension -isCodeAction :: Text -> Maybe Text -> Bool -isCodeAction userRegex (Just txt) = txt =~ Conversion.matchLineRegex (convertPrefix <> userRegex) -isCodeAction _ _ = False +isCodeAction :: Text -> Int -> Maybe Text -> Bool +isCodeAction userRegex nrUnderscores (Just txt) + | matchesUnderscores txt nrUnderscores + = txt =~ Conversion.matchLineRegex (convertPrefix <> userRegex) +isCodeAction _ _ _ = False + +matchesUnderscores :: Text -> Int -> Bool +matchesUnderscores txt nrUnderscores = T.count "_" txt == nrUnderscores -isHexCodeAction :: Maybe Text -> Bool +isHexCodeAction :: Int -> Maybe Text -> Bool isHexCodeAction = isCodeAction hexRegex -isHexFloatCodeAction :: Maybe Text -> Bool +isHexFloatCodeAction :: Int -> Maybe Text -> Bool isHexFloatCodeAction = isCodeAction hexFloatRegex -isBinaryCodeAction :: Maybe Text -> Bool +isBinaryCodeAction :: Int -> Maybe Text -> Bool isBinaryCodeAction = isCodeAction binaryRegex -isOctalCodeAction :: Maybe Text -> Bool +isOctalCodeAction :: Int -> Maybe Text -> Bool isOctalCodeAction = isCodeAction octalRegex -- This can match EITHER an integer as NumDecimal extension or a Fractional -- as in 1.23e-3 (so anything with an exponent really) -isNumDecimalCodeAction :: Maybe Text -> Bool +isNumDecimalCodeAction :: Int -> Maybe Text -> Bool isNumDecimalCodeAction = isCodeAction numDecimalRegex -isDecimalCodeAction :: Maybe Text -> Bool +isDecimalCodeAction :: Int -> Maybe Text -> Bool isDecimalCodeAction = isCodeAction decimalRegex diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU0toU3.expected.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU0toU3.expected.hs new file mode 100644 index 0000000000..dd17dbb288 --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU0toU3.expected.hs @@ -0,0 +1,3 @@ +module TFracDtoDUnderscores3 where + +convertMe = 12_345.678_912_3 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU0toU3.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU0toU3.hs new file mode 100644 index 0000000000..cd89a97a7b --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU0toU3.hs @@ -0,0 +1,3 @@ +module TFracDtoDUnderscores3 where + +convertMe = 12345.6789123 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU0.expected.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU0.expected.hs new file mode 100644 index 0000000000..cd89a97a7b --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU0.expected.hs @@ -0,0 +1,3 @@ +module TFracDtoDUnderscores3 where + +convertMe = 12345.6789123 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU0.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU0.hs new file mode 100644 index 0000000000..dd17dbb288 --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU0.hs @@ -0,0 +1,3 @@ +module TFracDtoDUnderscores3 where + +convertMe = 12_345.678_912_3 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU4.expected.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU4.expected.hs new file mode 100644 index 0000000000..cb7e3f2226 --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU4.expected.hs @@ -0,0 +1,3 @@ +module TFracDtoDUnderscores3 where + +convertMe = 1_2345.6789_123 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU4.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU4.hs new file mode 100644 index 0000000000..cd89a97a7b --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoDU3toU4.hs @@ -0,0 +1,3 @@ +module TFracDtoDUnderscores3 where + +convertMe = 12345.6789123 From 4748a8715a174d1a8be939b7104d77326b246cf7 Mon Sep 17 00:00:00 2001 From: Curtis Chin Jen Sem Date: Sat, 30 Aug 2025 14:05:37 +0200 Subject: [PATCH 5/6] Add `NumericUnderscores` extension when needed --- .../src/Ide/Plugin/AlternateNumberFormat.hs | 25 +++++++++-------- .../src/Ide/Plugin/Conversion.hs | 28 +++++++++++-------- .../test/Main.hs | 6 +++- .../test/testdata/TFracDtoEU0toU3.expected.hs | 3 ++ .../test/testdata/TFracDtoEU0toU3.hs | 3 ++ .../testdata/TFracDtoHFU0toU2.expected.hs | 6 ++++ .../test/testdata/TFracDtoHFU0toU2.hs | 4 +++ .../TIntDtoBU0toU4MultiplePragma.expected.hs | 6 ++++ .../testdata/TIntDtoBU0toU4MultiplePragma.hs | 4 +++ .../test/testdata/TIntDtoDU0toU3.expected.hs | 5 ++++ .../test/testdata/TIntDtoDU0toU3.hs | 4 +++ 11 files changed, 70 insertions(+), 24 deletions(-) create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoEU0toU3.expected.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoEU0toU3.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoHFU0toU2.expected.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoHFU0toU2.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoBU0toU4MultiplePragma.expected.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoBU0toU4MultiplePragma.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoDU0toU3.expected.hs create mode 100644 plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoDU0toU3.hs diff --git a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/AlternateNumberFormat.hs b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/AlternateNumberFormat.hs index 3b00d79d1b..048fe2a6d1 100644 --- a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/AlternateNumberFormat.hs +++ b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/AlternateNumberFormat.hs @@ -23,7 +23,7 @@ import Development.IDE.Spans.Pragmas (NextPragmaInfo, import GHC.Generics (Generic) import Ide.Logger as Logger import Ide.Plugin.Conversion (AlternateFormat, - ExtensionNeeded (NeedsExtension, NoExtension), + ExtensionNeeded (..), alternateFormat) import Ide.Plugin.Error import Ide.Plugin.Literals @@ -93,7 +93,7 @@ codeActionHandler state pId (CodeActionParams _ _ docId currRange _) = do pure $ InL actions where mkCodeAction :: NormalizedFilePath -> Literal -> [GhcExtension] -> NextPragmaInfo -> AlternateFormat -> Command |? CodeAction - mkCodeAction nfp lit enabled npi af@(alt, ext) = InR CodeAction { + mkCodeAction nfp lit enabled npi af@(alt, ExtensionNeeded exts) = InR CodeAction { _title = mkCodeActionTitle lit af enabled , _kind = Just $ CodeActionKind_Custom "quickfix.literals.style" , _diagnostics = Nothing @@ -104,10 +104,10 @@ codeActionHandler state pId (CodeActionParams _ _ docId currRange _) = do , _data_ = Nothing } where - edits = [TextEdit (realSrcSpanToRange $ getSrcSpan lit) alt] <> pragmaEdit - pragmaEdit = case ext of - NeedsExtension ext' -> [insertNewPragma npi ext' | needsExtension ext' enabled] - NoExtension -> [] + edits = [TextEdit (realSrcSpanToRange $ getSrcSpan lit) alt] <> pragmaEdit exts + pragmaEdit ext = case ext of + ext': exts -> [insertNewPragma npi ext' | needsExtension enabled ext'] <> pragmaEdit exts + [] -> [] mkWorkspaceEdit :: NormalizedFilePath -> [TextEdit] -> WorkspaceEdit mkWorkspaceEdit nfp edits = WorkspaceEdit changes Nothing Nothing @@ -115,17 +115,18 @@ codeActionHandler state pId (CodeActionParams _ _ docId currRange _) = do changes = Just $ Map.singleton (filePathToUri $ fromNormalizedFilePath nfp) edits mkCodeActionTitle :: Literal -> AlternateFormat -> [GhcExtension] -> Text -mkCodeActionTitle lit (alt, ext) ghcExts - | (NeedsExtension ext') <- ext - , needsExtension ext' ghcExts = title <> " (needs extension: " <> T.pack (show ext') <> ")" - | otherwise = title +mkCodeActionTitle lit (alt, ExtensionNeeded exts) ghcExts + | null necessaryExtensions = title + | otherwise = title <> " (needs extensions: " <> formattedExtensions <> ")" where + formattedExtensions = T.intercalate ", " $ map (T.pack . show) necessaryExtensions + necessaryExtensions = filter (needsExtension ghcExts) exts title = "Convert " <> getSrcText lit <> " into " <> alt -- | Checks whether the extension given is already enabled -needsExtension :: Extension -> [GhcExtension] -> Bool -needsExtension ext ghcExts = ext `notElem` map unExt ghcExts +needsExtension :: [GhcExtension] -> Extension -> Bool +needsExtension ghcExts ext = ext `notElem` map unExt ghcExts requestLiterals :: MonadIO m => PluginId -> IdeState -> NormalizedFilePath -> ExceptT PluginError m CollectLiteralsResult requestLiterals (PluginId pId) state = diff --git a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs index 382a433773..61cc508ff5 100644 --- a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs +++ b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs @@ -1,4 +1,5 @@ -{-# LANGUAGE CPP #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE DerivingStrategies #-} module Ide.Plugin.Conversion ( alternateFormat , toOctal @@ -54,8 +55,8 @@ data FracFormatType = FracDecimalFormat instance NFData FracFormatType -data ExtensionNeeded = NoExtension - | NeedsExtension Extension +newtype ExtensionNeeded = ExtensionNeeded [Extension] + deriving newtype (Semigroup, Monoid) type AlternateFormat = (Text, ExtensionNeeded) @@ -76,19 +77,24 @@ data UnderscoreFormatType | UseUnderscores Int deriving (Show, Eq) +underscoreExtensions :: UnderscoreFormatType -> ExtensionNeeded +underscoreExtensions = \case + NoUnderscores -> mempty + UseUnderscores _ -> ExtensionNeeded [NumericUnderscores] + alternateIntFormat :: Integer -> IntFormatType -> UnderscoreFormatType -> AlternateFormat alternateIntFormat val formatType underscoreFormat = case formatType of - IntDecimalFormat -> (T.pack $ toDecimal underscoreFormat val , NoExtension) - HexFormat -> (T.pack $ toHex underscoreFormat val , NoExtension) - OctalFormat -> (T.pack $ toOctal underscoreFormat val , NoExtension) - BinaryFormat -> (T.pack $ toBinary underscoreFormat val , NeedsExtension BinaryLiterals) - NumDecimalFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromInteger @Double val) , NeedsExtension NumDecimals) + IntDecimalFormat -> (T.pack $ toDecimal underscoreFormat val , underscoreExtensions underscoreFormat) + HexFormat -> (T.pack $ toHex underscoreFormat val , underscoreExtensions underscoreFormat) + OctalFormat -> (T.pack $ toOctal underscoreFormat val , underscoreExtensions underscoreFormat) + BinaryFormat -> (T.pack $ toBinary underscoreFormat val , underscoreExtensions underscoreFormat <> ExtensionNeeded [BinaryLiterals]) + NumDecimalFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromInteger @Double val) , underscoreExtensions underscoreFormat <> ExtensionNeeded [NumDecimals]) alternateFracFormat :: Rational -> FracFormatType -> UnderscoreFormatType -> AlternateFormat alternateFracFormat val formatType underscoreFormat = case formatType of - FracDecimalFormat -> (T.pack $ toFloatDecimal underscoreFormat (fromRational @Double val), NoExtension) - ExponentFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromRational @Double val), NoExtension) - HexFloatFormat -> (T.pack $ toHexFloat underscoreFormat (fromRational @Double val), NeedsExtension HexFloatLiterals) + FracDecimalFormat -> (T.pack $ toFloatDecimal underscoreFormat (fromRational @Double val), mempty) + ExponentFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromRational @Double val), mempty) + HexFloatFormat -> (T.pack $ toHexFloat underscoreFormat (fromRational @Double val), underscoreExtensions underscoreFormat <> ExtensionNeeded [HexFloatLiterals]) intFormats :: Map.Map IntFormatType [UnderscoreFormatType] intFormats = Map.fromList $ map (\t -> (t, intFormatUnderscore t)) enumerate diff --git a/plugins/hls-alternate-number-format-plugin/test/Main.hs b/plugins/hls-alternate-number-format-plugin/test/Main.hs index 78cb16c3f6..fa407b8bc6 100644 --- a/plugins/hls-alternate-number-format-plugin/test/Main.hs +++ b/plugins/hls-alternate-number-format-plugin/test/Main.hs @@ -25,9 +25,13 @@ test = testGroup "alternateNumberFormat" [ codeActionHex 0 "TIntDtoH" 3 13 , codeActionOctal 0 "TIntDtoO" 3 13 , codeActionBinary 0 "TIntDtoB" 4 13 + , codeActionBinary 6 "TIntDtoBU0toU4MultiplePragma" 4 13 , codeActionNumDecimal 0 "TIntDtoND" 5 13 + , codeActionDecimal 2 "TIntDtoDU0toU3" 4 13 , codeActionFracExp 0 "TFracDtoE" 3 13 + , codeActionFracExp 3 "TFracDtoEU0toU3" 3 13 , codeActionFloatHex 0 "TFracDtoHF" 4 13 + , codeActionFloatHex 6 "TFracDtoHFU0toU2" 4 13 , codeActionDecimal 0 "TIntHtoD" 3 13 , codeActionDecimal 0 "TFracHFtoD" 4 13 , codeActionDecimal 3 "TFracDtoDU0toU3" 3 13 @@ -117,7 +121,7 @@ pointRange convertPrefix, intoInfix, maybeExtension, hexRegex, hexFloatRegex, binaryRegex, octalRegex, numDecimalRegex, decimalRegex :: Text convertPrefix = "Convert (" <> T.intercalate "|" [Conversion.hexRegex, Conversion.hexFloatRegex, Conversion.binaryRegex, Conversion.octalRegex, Conversion.numDecimalRegex, Conversion.decimalRegex] <> ")" intoInfix = " into " -maybeExtension = "( \\(needs extension: .*)?" +maybeExtension = "( \\(needs extensions: .*)?" hexRegex = intoInfix <> Conversion.hexRegex <> maybeExtension hexFloatRegex = intoInfix <> Conversion.hexFloatRegex <> maybeExtension binaryRegex = intoInfix <> Conversion.binaryRegex <> maybeExtension diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoEU0toU3.expected.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoEU0toU3.expected.hs new file mode 100644 index 0000000000..cd688ecaba --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoEU0toU3.expected.hs @@ -0,0 +1,3 @@ +module TFracDtoE where + +convertMe = 1.234_567_890_123e2 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoEU0toU3.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoEU0toU3.hs new file mode 100644 index 0000000000..45cdbff05d --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoEU0toU3.hs @@ -0,0 +1,3 @@ +module TFracDtoE where + +convertMe = 1.234567890123e2 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoHFU0toU2.expected.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoHFU0toU2.expected.hs new file mode 100644 index 0000000000..3fa86f402f --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoHFU0toU2.expected.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE Haskell2010 #-} +{-# LANGUAGE HexFloatLiterals #-} +{-# LANGUAGE NumericUnderscores #-} +module TFracDtoHF where + +convertMe = 0x1.ee_cc_cc_cc_cc_cc_dp6 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoHFU0toU2.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoHFU0toU2.hs new file mode 100644 index 0000000000..84d4ba242b --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TFracDtoHFU0toU2.hs @@ -0,0 +1,4 @@ +{-# LANGUAGE Haskell2010 #-} +module TFracDtoHF where + +convertMe = 123.7 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoBU0toU4MultiplePragma.expected.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoBU0toU4MultiplePragma.expected.hs new file mode 100644 index 0000000000..de8d2ea225 --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoBU0toU4MultiplePragma.expected.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE Haskell2010 #-} +{-# LANGUAGE BinaryLiterals #-} +{-# LANGUAGE NumericUnderscores #-} +module TIntDtoB where + +convertMe = 0b111_0101_1011_1100_1101_0001_0101 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoBU0toU4MultiplePragma.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoBU0toU4MultiplePragma.hs new file mode 100644 index 0000000000..dc835e0d2a --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoBU0toU4MultiplePragma.hs @@ -0,0 +1,4 @@ +{-# LANGUAGE Haskell2010 #-} +module TIntDtoB where + +convertMe = 123456789 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoDU0toU3.expected.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoDU0toU3.expected.hs new file mode 100644 index 0000000000..f9b19c024b --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoDU0toU3.expected.hs @@ -0,0 +1,5 @@ +{-# LANGUAGE Haskell2010 #-} +{-# LANGUAGE NumericUnderscores #-} +module TIntDtoB where + +convertMe = 12_345_678 diff --git a/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoDU0toU3.hs b/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoDU0toU3.hs new file mode 100644 index 0000000000..0908c08178 --- /dev/null +++ b/plugins/hls-alternate-number-format-plugin/test/testdata/TIntDtoDU0toU3.hs @@ -0,0 +1,4 @@ +{-# LANGUAGE Haskell2010 #-} +module TIntDtoB where + +convertMe = 12345678 From d764d666e63877bb06768c90515a30763b2ca2fa Mon Sep 17 00:00:00 2001 From: Curtis Chin Jen Sem Date: Mon, 1 Sep 2025 23:55:23 +0200 Subject: [PATCH 6/6] Remove fat-fingered whitespaces Co-authored-by: fendor --- .../src/Ide/Plugin/Conversion.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs index 61cc508ff5..974dc87161 100644 --- a/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs +++ b/plugins/hls-alternate-number-format-plugin/src/Ide/Plugin/Conversion.hs @@ -84,11 +84,11 @@ underscoreExtensions = \case alternateIntFormat :: Integer -> IntFormatType -> UnderscoreFormatType -> AlternateFormat alternateIntFormat val formatType underscoreFormat = case formatType of - IntDecimalFormat -> (T.pack $ toDecimal underscoreFormat val , underscoreExtensions underscoreFormat) - HexFormat -> (T.pack $ toHex underscoreFormat val , underscoreExtensions underscoreFormat) - OctalFormat -> (T.pack $ toOctal underscoreFormat val , underscoreExtensions underscoreFormat) - BinaryFormat -> (T.pack $ toBinary underscoreFormat val , underscoreExtensions underscoreFormat <> ExtensionNeeded [BinaryLiterals]) - NumDecimalFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromInteger @Double val) , underscoreExtensions underscoreFormat <> ExtensionNeeded [NumDecimals]) + IntDecimalFormat -> (T.pack $ toDecimal underscoreFormat val, underscoreExtensions underscoreFormat) + HexFormat -> (T.pack $ toHex underscoreFormat val, underscoreExtensions underscoreFormat) + OctalFormat -> (T.pack $ toOctal underscoreFormat val, underscoreExtensions underscoreFormat) + BinaryFormat -> (T.pack $ toBinary underscoreFormat val, underscoreExtensions underscoreFormat <> ExtensionNeeded [BinaryLiterals]) + NumDecimalFormat -> (T.pack $ toFloatExpDecimal underscoreFormat (fromInteger @Double val), underscoreExtensions underscoreFormat <> ExtensionNeeded [NumDecimals]) alternateFracFormat :: Rational -> FracFormatType -> UnderscoreFormatType -> AlternateFormat alternateFracFormat val formatType underscoreFormat = case formatType of