Skip to content
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

Column width experiments #3

Open
wants to merge 93 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
d2cc629
Apply hlint suggestions
Lucus16 Apr 6, 2023
d83a00c
Generalize parsing utilities
Lucus16 Apr 6, 2023
c8ebd02
Attach leading trivia to next token
Lucus16 Apr 6, 2023
0c9d9a4
Add test for if-with-comments
Lucus16 Apr 6, 2023
f702d73
Fix argument order in Ann
Lucus16 Apr 6, 2023
5ae8e62
Allow standalone comments in lists and sets
Lucus16 Apr 29, 2023
4a05494
Add direnv
piegamesde May 5, 2023
13b7904
Update CLI flags description
piegamesde May 5, 2023
f938cfc
Add simple test runner
piegamesde May 5, 2023
9ce4ce3
Rework function declarations
piegamesde Apr 18, 2023
e05d6a2
Force-expand lists with more than one item
piegamesde Apr 18, 2023
305ed77
Expand let statements more
piegamesde Apr 18, 2023
73e7ec4
Rework if statements
piegamesde Apr 18, 2023
cd0acd3
Don't indent `in` body anymore
piegamesde May 5, 2023
8315ca3
Rework `inherit` statements
piegamesde May 5, 2023
28a54b3
fixup! Rework if statements
piegamesde May 5, 2023
7fe6a99
Add some code documentation
piegamesde May 5, 2023
e9cdac6
Rework bindings
piegamesde May 5, 2023
2e2797a
Tests: replace diff/idioms_pkgs_3
piegamesde May 7, 2023
dafec15
fixup! Rework bindings
piegamesde May 7, 2023
de7bd6d
Expand singleton lists with a multiline item
piegamesde May 7, 2023
d1ba3cd
Test: add idioms_nixos_2
piegamesde May 7, 2023
34c119e
Fix typo in README.md
wbehrens-on-gh May 16, 2023
e6bed2a
Don't absort `in` body anymore
piegamesde May 17, 2023
a226b6d
Improve group handling for bindings and inherit
piegamesde May 18, 2023
0528d78
Special case selection operator
piegamesde May 18, 2023
b3e7d0e
Merge pull request #124 from wbehrens-on-gh/patch-1
rvem May 19, 2023
8a6cb8c
Rework operators
piegamesde May 19, 2023
487d313
Rework parentheses
piegamesde May 19, 2023
6ce18ad
Rework function calls
piegamesde May 19, 2023
3802eaa
Add direnv
piegamesde May 5, 2023
7c18e90
Merge branch 'leading-comments' into rfc101-style
piegamesde May 19, 2023
10745f3
Fix function commas
piegamesde May 19, 2023
20d35b3
Move comments around a bit
piegamesde May 20, 2023
2a9ec6b
Tweak operations some more
piegamesde May 20, 2023
cb309e4
wip
piegamesde May 21, 2023
6f4791d
pretty parentheses
piegamesde May 21, 2023
8d07dc9
stupid silly bug
piegamesde May 21, 2023
b1b9313
Absorb parenthesized abstractions with multiple arguments
piegamesde Jun 6, 2023
0b26acb
Tweak operations some more
piegamesde Jun 6, 2023
3e16d20
Don't force-expand (simple) if statements anymore
piegamesde Jun 6, 2023
db2c938
Special case binary operators
piegamesde Jun 6, 2023
66ac4b4
Improve error message on verify
piegamesde Jun 8, 2023
5a8eb61
Improve layouting algorithm
piegamesde Jun 8, 2023
eeb2534
Factor out function application code
piegamesde Jun 8, 2023
792c405
Infinisil style function application
piegamesde Jun 9, 2023
b7daac9
Improve comment handling
piegamesde Jun 10, 2023
b86d8ea
Unindent semicolons again
piegamesde Jun 14, 2023
9a6cc7f
Don't absorb lambda body
piegamesde Jun 14, 2023
3da135d
Absorb parenthesized function calls again
piegamesde Jun 14, 2023
c198bda
float's can't start with just exponents
yorickvP May 5, 2023
b3ac0a8
Merge pull request #123 from serokell/fix-e2fsprogs
yorickvP Jun 16, 2023
66b6712
Binder with with: absorb less
piegamesde Jun 22, 2023
5bb0639
Rework sets and lists
piegamesde Jun 23, 2023
f7cf76f
Force-spread inherit with more than three items
piegamesde Jun 23, 2023
a7dc8bb
Binder: Always absorb strings and paths
piegamesde Jun 23, 2023
4e1ff44
Tests: add lib/systems/parse.nix
piegamesde Jun 23, 2023
c926692
Better trailing comment parsing
piegamesde Jun 22, 2023
c2702df
Function application: fix edge case
piegamesde Jun 24, 2023
538663c
Function application: don't always absorb last argument
piegamesde Jun 24, 2023
325305e
Binders: force-expand nested attribute sets
piegamesde Jun 25, 2023
a95bd3a
Abstraction: don't absorb body when there are more than two parameters
piegamesde Jun 25, 2023
32609c8
Ignore comments in line length calculation
piegamesde Jun 25, 2023
c568bfa
Ignore indentation in line length calculation
piegamesde Jun 25, 2023
1e42aa2
Merge remote-tracking branch 'upstream/master' into rfc101-style
piegamesde Jun 26, 2023
e6693ca
Absorb abstraction in binder
piegamesde Jul 4, 2023
3b26c7d
Improve mapFirstToken code style
piegamesde Jul 8, 2023
4c0007e
Tests: Add check-meta.nix
piegamesde Jul 9, 2023
ef310bb
Improved helper functions
piegamesde Jul 17, 2023
a54a01a
Improve priority group handling
piegamesde Jul 12, 2023
c4bfa2c
Rework `//` operator
piegamesde Jul 9, 2023
81d3cf8
Copy special cases over to `++` operator
piegamesde Jul 13, 2023
cc5b426
Introduce support for optional trailing commas
piegamesde Jul 17, 2023
4468e9b
Expand attrset function parameters less
piegamesde Jul 16, 2023
9ac66a9
Rework renderer again
piegamesde Jul 18, 2023
a93dcf7
Binders: be more selective about semicolon placement
piegamesde Jul 17, 2023
43dd01d
Fix comment handling
piegamesde Sep 28, 2023
549541f
Expand singleton lists again
piegamesde Oct 10, 2023
7649a1b
Put semicolons on same line again
piegamesde Oct 11, 2023
26dd294
85
piegamesde Jun 23, 2023
71e4f54
90
piegamesde Jun 23, 2023
275ab2c
95
piegamesde Jun 23, 2023
3e8ae73
100
piegamesde Jun 23, 2023
abb3df0
105
piegamesde Jun 23, 2023
0035aa8
110
piegamesde Jun 23, 2023
0812e3a
115
piegamesde Jun 23, 2023
575f5cb
120
piegamesde Jun 23, 2023
d2b6910
130
piegamesde Jun 23, 2023
1435168
140
piegamesde Jun 23, 2023
3fbf11b
150
piegamesde Jun 23, 2023
6dad609
160
piegamesde Jun 23, 2023
4bb1976
170
piegamesde Jun 23, 2023
a3a65eb
180
piegamesde Jun 23, 2023
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
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use_nix
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
/dist-newstyle
/.ghc.environment.*
/result
/.direnv
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Haskell dependencies will be built by Cabal.

## Usage

* `nixfmt < input.nix` – reads Nix code form `stdin`, formats it, and outputs to `stdout`
* `nixfmt < input.nix` – reads Nix code from `stdin`, formats it, and outputs to `stdout`
* `nixfmt file.nix` – format the file in place


Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
# nixfmt: expand
cabal-install
stylish-haskell
shellcheck
]);
});

Expand Down
6 changes: 3 additions & 3 deletions main/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ data Nixfmt = Nixfmt

options :: Nixfmt
options =
let defaultWidth = 80
let defaultWidth = 180
addDefaultHint value message =
message ++ "\n[default: " ++ show value ++ "]"
in Nixfmt
{ files = [] &= args &= typ "FILES"
, width =
defaultWidth &=
help (addDefaultHint defaultWidth "Maximum width in characters")
, check = False &= help "Check whether files are formatted"
, check = False &= help "Check whether files are formatted without modifying them"
, quiet = False &= help "Do not report errors"
, verify =
False &=
help
"Check that the output parses and formats the same as the input"
"Apply sanity checks on the output after formatting"
} &=
summary ("nixfmt v" ++ showVersion version) &=
help "Format Nix source code"
Expand Down
2 changes: 2 additions & 0 deletions nixfmt.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ library
build-depends:
base >= 4.12.0 && < 4.17
, megaparsec >= 9.0.1 && < 9.3
, mtl
, parser-combinators >= 1.0.3 && < 1.4
, scientific >= 0.3.0 && < 0.4.0
, text >= 1.2.3 && < 1.3
, transformers
default-language: Haskell2010
ghc-options:
-Wall
Expand Down
4 changes: 2 additions & 2 deletions src/Nixfmt.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Nixfmt
) where

import Data.Bifunctor (bimap, first)
import Data.Text (Text)
import Data.Text (Text, unpack)
import qualified Text.Megaparsec as Megaparsec (parse)
import Text.Megaparsec.Error (errorBundlePretty)

Expand Down Expand Up @@ -40,7 +40,7 @@ formatVerify width path unformatted = do
if formattedOnceParsed /= unformattedParsed
then pleaseReport "Parses differently after formatting."
else if formattedOnce /= formattedTwice
then pleaseReport "Nixfmt is not idempotent."
then flip first (pleaseReport "Nixfmt is not idempotent.") $ \x -> (x <> "\nAfter one formatting:\n" <> unpack formattedOnce <> "\nAfter two:\n" <> unpack formattedTwice)
else Right formattedOnce
where
parse = first errorBundlePretty . Megaparsec.parse file path
Expand Down
79 changes: 59 additions & 20 deletions src/Nixfmt/Lexer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,40 @@
- SPDX-License-Identifier: MPL-2.0
-}

{-# LANGUAGE LambdaCase, OverloadedStrings #-}
{-# LANGUAGE BlockArguments, FlexibleContexts, LambdaCase, OverloadedStrings #-}

module Nixfmt.Lexer (lexeme) where
module Nixfmt.Lexer (lexeme, pushTrivia, takeTrivia, whole) where

import Control.Monad.State (MonadState, evalStateT, get, modify, put)
import Data.Char (isSpace)
import Data.List (dropWhileEnd)
import Data.Maybe (fromMaybe)
import Data.Text as Text
(Text, intercalate, length, lines, null, pack, replace, replicate, strip,
stripEnd, stripPrefix, stripStart, takeWhile)
(Text, length, lines, null, pack, replace, replicate, strip, stripEnd,
stripPrefix, stripStart, takeWhile, unwords)
import Data.Void (Void)
import Text.Megaparsec
(SourcePos(..), anySingle, chunk, getSourcePos, hidden, many, manyTill, some,
try, unPos, (<|>))
(Parsec, SourcePos(..), Pos, anySingle, chunk, getSourcePos, hidden, many,
manyTill, some, try, unPos, (<|>))
import Text.Megaparsec.Char (eol)

import Nixfmt.Types (Ann(..), Parser, TrailingComment(..), Trivia, Trivium(..))
import Nixfmt.Types
(Ann(..), Whole(..), Parser, TrailingComment(..), Trivia, Trivium(..))
import Nixfmt.Util (manyP)
-- import Debug.Trace (traceShow, traceShowId)

data ParseTrivium
= PTNewlines Int
| PTLineComment Text
-- Track the column where the comment starts
| PTLineComment Text Pos
| PTBlockComment [Text]
deriving (Show)

preLexeme :: Parser a -> Parser a
preLexeme p = p <* manyP (\x -> isSpace x && x /= '\n' && x /= '\r')

newlines :: Parser ParseTrivium
newlines = PTNewlines <$> Prelude.length <$> some (preLexeme eol)
newlines = PTNewlines . Prelude.length <$> some (preLexeme eol)

splitLines :: Text -> [Text]
splitLines = dropWhile Text.null . dropWhileEnd Text.null
Expand All @@ -50,8 +55,11 @@ fixLines n (h : t) = strip h
: map (stripIndentation $ commonIndentationLength n $ filter (/="") t) t

lineComment :: Parser ParseTrivium
lineComment = preLexeme $ chunk "#" *>
(PTLineComment <$> manyP (\x -> x /= '\n' && x /= '\r'))
lineComment = preLexeme $ do
SourcePos{sourceColumn = col} <- getSourcePos
_ <- chunk "#"
text <- manyP (\x -> x /= '\n' && x /= '\r')
return (PTLineComment text col)

blockComment :: Parser ParseTrivium
blockComment = try $ preLexeme $ do
Expand All @@ -60,40 +68,71 @@ blockComment = try $ preLexeme $ do
chars <- manyTill anySingle $ chunk "*/"
return $ PTBlockComment $ fixLines (unPos pos) $ splitLines $ pack chars

-- This should be called with zero or one elements, as per `span isTrailing`
convertTrailing :: [ParseTrivium] -> Maybe TrailingComment
convertTrailing = toMaybe . join . map toText
where toText (PTLineComment c) = strip c
where toText (PTLineComment c _) = strip c
toText (PTBlockComment [c]) = strip c
toText _ = ""
join = intercalate " " . filter (/="")
join = Text.unwords . filter (/="")
toMaybe "" = Nothing
toMaybe c = Just $ TrailingComment c

convertLeading :: [ParseTrivium] -> Trivia
convertLeading = concatMap (\case
PTNewlines 1 -> []
PTNewlines _ -> [EmptyLine]
PTLineComment c -> [LineComment c]
PTLineComment c _ -> [LineComment c]
PTBlockComment [] -> []
PTBlockComment [c] -> [LineComment $ " " <> strip c]
PTBlockComment cs -> [BlockComment cs])

isTrailing :: ParseTrivium -> Bool
isTrailing (PTLineComment _) = True
isTrailing (PTLineComment _ _) = True
isTrailing (PTBlockComment []) = True
isTrailing (PTBlockComment [_]) = True
isTrailing _ = False

convertTrivia :: [ParseTrivium] -> (Maybe TrailingComment, Trivia)
convertTrivia pts =
convertTrivia :: [ParseTrivium] -> Pos -> (Maybe TrailingComment, Trivia)
convertTrivia pts nextCol =
let (trailing, leading) = span isTrailing pts
in (convertTrailing trailing, convertLeading leading)
in case (trailing, leading) of
-- Special case: if the trailing comment visually forms a block with the start of the following line,
-- then treat it like part of those comments instead of a distinct trailing comment.
-- This happens especially often after `{` or `[` tokens, where the comment of the first item
-- starts on the same line ase the opening token.
([PTLineComment _ pos], (PTNewlines 1):(PTLineComment _ pos'):_) | pos == pos' -> (Nothing, convertLeading pts)
([PTLineComment _ pos], [(PTNewlines 1)]) | pos == nextCol -> (Nothing, convertLeading pts)
_ -> (convertTrailing trailing, convertLeading leading)

trivia :: Parser [ParseTrivium]
trivia = many $ hidden $ lineComment <|> blockComment <|> newlines

-- The following primitives to interact with the state monad that stores trivia
-- are designed to prevent trivia from being dropped or duplicated by accident.

takeTrivia :: MonadState Trivia m => m Trivia
takeTrivia = get <* put []

pushTrivia :: MonadState Trivia m => Trivia -> m ()
pushTrivia t = modify (<>t)

lexeme :: Parser a -> Parser (Ann a)
lexeme p = do
lastLeading <- takeTrivia
token <- preLexeme p
(trailing, leading) <- convertTrivia <$> trivia
return $ Ann token trailing leading
parsedTrivia <- trivia
-- This is the position of the next lexeme after the currently parsed one
SourcePos{sourceColumn = col} <- getSourcePos
let (trailing, nextLeading) = convertTrivia parsedTrivia col
pushTrivia nextLeading
return $ Ann lastLeading token trailing

-- | Tokens normally have only leading trivia and one trailing comment on the same
-- line. A whole x also parses and stores final trivia after the x. A whole also
-- does not interact with the trivia state of its surroundings.
whole :: Parser a -> Parsec Void Text (Whole a)
whole pa = flip evalStateT [] do
preLexeme $ pure ()
pushTrivia . convertLeading =<< trivia
Whole <$> pa <*> takeTrivia
Loading