From fb44e0031c6c767d67569e56af47d99427f665a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=B7=F0=90=91=91=F0=90=91=B4=F0=90=91=95=F0=90=91=91?= =?UTF-8?q?=F0=90=91=A9=F0=90=91=A4?= Date: Thu, 25 Jun 2026 22:52:31 +0700 Subject: [PATCH] support tabs --- main/Main.hs | 12 ++++++++---- src/Nixfmt/Predoc.hs | 26 +++++++++++++------------- src/Nixfmt/Util.hs | 7 +++++-- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/main/Main.hs b/main/Main.hs index 92684f3..ad4f672 100644 --- a/main/Main.hs +++ b/main/Main.hs @@ -50,6 +50,7 @@ data Nixfmt = Nixfmt { files :: [FilePath], width :: Width, indent :: Int, + tabs :: Bool, check :: Bool, mergetool :: Bool, quiet :: Bool, @@ -75,8 +76,9 @@ options = width = defaultWidth &= help (addDefaultHint defaultWidth "Maximum width in characters"), - indent = defaultIndent &= help (addDefaultHint defaultIndent "Number of spaces to use for indentation"), - check = False &= help "Check whether files are formatted without modifying them", + indent = defaultIndent &= help (addDefaultHint defaultIndent "Number of spaces to use for indentation"), + tabs = False &= help "Use tabs for indentation instead of spaces", + check = False &= help "Check whether files are formatted without modifying them", mergetool = False &= help "Whether to run in git mergetool mode, see https://github.com/NixOS/nixfmt?tab=readme-ov-file#git-mergetool for more info", quiet = False &= help "Do not report errors", strict = False &= help "Enable a stricter formatting mode that isn't influenced as much by how the input is formatted", @@ -181,8 +183,10 @@ type Formatter = FilePath -> Text -> Either String Text toFormatter :: Nixfmt -> Formatter toFormatter Nixfmt{ast = True} = Nixfmt.printAst toFormatter Nixfmt{ir = True} = Nixfmt.printIR -toFormatter Nixfmt{width, indent, verify = True, strict} = Nixfmt.formatVerify (layout width indent strict) -toFormatter Nixfmt{width, indent, verify = False, strict} = Nixfmt.format (layout width indent strict) +toFormatter Nixfmt{width, indent, tabs, verify = True, strict} = + Nixfmt.formatVerify (layout width (if tabs then 1 else indent) (if tabs then '\t' else ' ') strict) +toFormatter Nixfmt{width, indent, tabs, verify = False, strict} = + Nixfmt.format (layout width (if tabs then 1 else indent) (if tabs then '\t' else ' ') strict) type Operation = Formatter -> Target -> IO Result diff --git a/src/Nixfmt/Predoc.hs b/src/Nixfmt/Predoc.hs index 8d8a02c..5c80523 100644 --- a/src/Nixfmt/Predoc.hs 1970-01-01 07:00:01.000000000 +0700 +++ b/src/Nixfmt/Predoc.hs 2026-07-10 23:38:56.053308065 +0700 @@ -47,11 +47,11 @@ import Data.Functor (($>), (<&>)) import Data.Functor.Identity (runIdentity) import Data.List (intersperse) -import Data.List.NonEmpty (NonEmpty (..), singleton, (<|)) +import Data.List.NonEmpty (NonEmpty (..), (<|)) import qualified Data.List.NonEmpty as NonEmpty import Data.Maybe (fromMaybe) import qualified Data.Sequence as Seq -import Data.Text as Text (Text, concat, length, replicate, strip, stripStart, takeWhileEnd) +import Data.Text as Text (Text, concat, length, replicate, singleton, strip, stripStart, takeWhileEnd) import GHC.Stack (HasCallStack) import Nixfmt.Types ( Ann (..), @@ -421,8 +421,8 @@ mergeSpacings _ (Newlines x) = Newlines (x + 1) mergeSpacings _ y = y -layout :: (Pretty a, LanguageElement a) => Int -> Int -> Bool -> a -> Text -layout width indentWidth strict a = finalize $ layoutGreedy width indentWidth doc +layout :: (Pretty a, LanguageElement a) => Int -> Int -> Char -> Bool -> a -> Text +layout width indentWidth indentChar strict a = finalize $ layoutGreedy width indentWidth indentChar doc where doc = fixup @@ -569,8 +569,8 @@ newlines n = Text.replicate n "\n" -- | Create `n` spaces -indent :: Int -> Text -indent n = Text.replicate n " " +indent :: Char -> Int -> Text +indent c n = Text.replicate n (Text.singleton c) -- All state is (cc, indents, suppress) -- cc: current column (within the current line, *not including indentation*) @@ -583,8 +583,8 @@ type St = (Int, NonEmpty (Int, Int), Bool) -- tw Target Width -layoutGreedy :: Int -> Int -> Doc -> Text -layoutGreedy tw iw doc = Text.concat $ evalState (go [Group RegularG doc] []) (0, singleton (0, 0), False) +layoutGreedy :: Int -> Int -> Char -> Doc -> Text +layoutGreedy tw iw ic doc = Text.concat $ evalState (go [Group RegularG doc] []) (0, NonEmpty.singleton (0, 0), False) where -- Simple helpers around `put` with a tuple state -- NOTE: making putL strict removes the allocation for the Int @@ -624,7 +624,7 @@ go' = do (cc, (ci, _) :| _, _) <- get putL (if withIndent then cc + textWidth t else max 1 (cc + textWidth t)) - let !i'd = indent (ci + textOffset) + let !i'd = indent ic (ci + textOffset) emit $! if cc == 0 && withIndent then [i'd, t] else [t] -- Simply put text without caring about line-start indentation diff --git a/src/Nixfmt/Util.hs b/src/Nixfmt/Util.hs index c2ae182..bc53845 100644 --- a/src/Nixfmt/Util.hs +++ b/src/Nixfmt/Util.hs @@ -69,12 +69,15 @@ commonPrefix a b = Nothing -> empty Just (prefix, _, _) -> prefix +isIndentChar :: Char -> Bool +isIndentChar c = c == ' ' || c == '\t' + -- | The longest common prefix consisting of only whitespace. The longest common -- prefix of zero texts is infinite, represented as Nothing. commonIndentation :: [Text] -> Maybe Text commonIndentation [] = Nothing -commonIndentation [x] = Just $ Text.takeWhile (== ' ') x +commonIndentation [x] = Just $ Text.takeWhile isIndentChar x commonIndentation (x : y : xs) = commonIndentation (commonPrefix x y : xs) isSpaces :: Text -> Bool -isSpaces = Text.all (== ' ') +isSpaces = Text.all isIndentChar -- 2.54.0