-
Notifications
You must be signed in to change notification settings - Fork 67
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
Add import and module support #83
Conversation
Nice work! This looks (sufficiently) well-tested and the code looks fine too, so I could have merged as-is, really.
I have done mostly some superficial refactoring, hope you don't mind. Mostly replacing |
As for the bikeshedding: I think I prefer to have all imports layouted in a certain way, that is -- 111111111 22222222222222222222222222222222 3333333333333333333
import Foo
import Foo ( foo )
import qualified Foo
import Foo as Foo
import Foo ( foo
, bar
) where the columns as marked are "reserved" for certain things: 1 for the potential "qualified" keyword, 2 for the module name, 3 for the binding names. further:
If that is the goal, the current approach of using module Language.Haskell.Brittany.Internal.Layouters.Import (layoutImport) where
#include "prelude.inc"
import Language.Haskell.Brittany.Internal.Types
-- ..
import RdrName (RdrName(..))
import GHC
( unLoc
, runGhc
, GenLocated(L)
, moduleNameString
, AnnKeywordId(..)
, Located
)
import HsSyn
-- ..
import qualified FastString
import BasicTypes
Stylish-haskell seems to have sensible behaviour for imports, too. I'd have to do some more testing with it to figure out how they treat some of the more special cases. (a good alternative for |
Ah, I see. I can do that. I am a little confused by the |
It is for cases like import qualified Brick.ReflexMain as Brick
import qualified Reflex as R
import qualified Reflex.Host.Class as R
import qualified Reflex.PerformEvent.Base as R
import qualified Reflex.Host.App as RH
import qualified Graphics.Vty as V
import qualified Control.Monad.Ref as Ref where you cannot the actual names at all on a glance. Seems to be much nicer as import qualified Brick.ReflexMain as Brick
import qualified Reflex as R
import qualified Reflex.Host.Class as R
import qualified Reflex.PerformEvent.Base as R
import qualified Reflex.Host.App as RH
import qualified Graphics.Vty as V
import qualified Control.Monad.Ref as Ref For import Foo as F
import Foo as F ( abc )
import Foo as F ( abc
, def
)
import Foo as F hiding ( abc )
import Foo as F hiding ( abc
, def
)
import Foo ( abc )
import Foo ( abc
, def
)
import Foo hiding ( abc )
import Foo hiding ( abc
, def
) but I think i mostly use import+whitelist, import+blacklist, import-qualified, and import-qualified-as. So I don't really know about the other combinations. |
stylish-haskell turns the last example (plus some more items to test the linebreaking behaviour) into: import Foo as F
import Foo as F (abc)
import Foo as F (abc, def, def, def, def, def, def, def, def, def,
def, def, def)
import Foo as F hiding (abc)
import Foo as F hiding (abc, def, def, def, def, def, def, def, def,
def)
import Foo (abc)
import Foo (abc, def, def, def, def, def, def, def, def, def, def,
def, def, def)
import Foo hiding (abc)
import Foo hiding (abc, def, def, def, def, def, def, def, def, def,
def, def, def, def, def, def, def, def, def, def, def) |
Well, even if the use cases are rare, the formatter has to do something, preferably nice. How about expanding a little vertical space to maintain the horizontal alignment: import qualified Foo hiding ( foo )
import qualified Foo as F
import qualified Foo as F
( foo )
import qualified Foo as F
hiding ( foo )
import qualified Foo as F
import Foo hiding ( foo ) This maintains the binding column entirely. The downside is that it makes it a little harder to see what module the binding under an alias belongs to. It even looks reasonable if the module name overlaps the column: import qualified FooFooFooFooFooFooFooFooFooFooFoo
as F
hiding ( foo ) |
I like that. |
Looks good! I noticed the I noticed and fixed this case in the above commit: module TestModule where
import Prelude ( (<$>) ) Two to-dos:
|
I’m excited for Brittany to get this functionality! Thanks for opening this pull request, @sniperrifle2004. I looked over the context free test cases and they seem a little weird to me. I would expect no extra spaces when formatting imports in a context free manner. In other words, no spaces for Am I reading those test cases right? |
@lspitzner There is obviously a trade off between room for module names and room for aliases/ThingsWith/long(ish) import bindings. I suspect twenty characters is a little tight for bindings, so I think 50 is probably better. Concerning the lrdrNameToTextAnn issue, I think that there may be a potential instance in the ThingWith case of layoutIE (For classes with operators). |
@tfausak You are reading them right. The import layout is completely context free. The alignment is defined purely around the elements in the import node and the importColumn config value, and is not affected by the surrounding elements or the alignment rules. So the context free layout is the same as the layout with context. |
I feel like the imports don't match the rest of the the context free tests. I get that it's not exactly context sensitive. We had a discussion about what that means before: #66 (comment) I would prefer some way to make import Data.List (sort)
import Data.Maybe (fromMaybe)
import qualified Data.Map as Map
import Data.HashMap.Strict as HashMap That is, don't align anything. |
Well, it is always possible to add a configuration option for that. |
I thought that's what the "context free" tests were testing, which is why I remarked on them. Specifically the settings are |
Column alignment mode deals with alignment elements over multiple syntactic constructs, like binding lines and IndentPolicyLeft means that britanny may not increase the indentation level to improve alignment. And the spacing isn't technically indentation (if I understand the mechanics correctly) nor added by the britanny transformer. It is applied by the layouter itself. |
I had a long-wided reply typed, but that took too long, so I'll make it brief: I think you are both correct. The current behaviour is context-free but the tests never were meant to test that (apparently i indeed should have demanded a different name in the other PR..) We can always make a flag in the opposite direction: One that relaxes @sniperrifle2004 it is up to you if you want to implement this additional behaviour. If not, I might be lazy and simply disable reformatting of imports in the presence of IndPolLeft or CAMDisabled. But then it probably is relatively straight-forward to actually implement it.. considering that, @tfausak maybe you want to contribute this bit? |
I fixed the ommitted comments for the prelude, but it positions them a little awkwardly because it retains the absolute position for most of them |
@sniperrifle2004 there is one further (special) case: Some very long identifier: import Foo ( loooooooooooooooooooooooooooooooooooong ) This overflows the column limit. I am fine with that, but it is maybe worth a testcase to make it explicit. |
I'll construct a condensed layout based on the indent policy being IndentPolicyLeft. Would such a layout also try to keep bindings on the import line? Or would it still expand them by default? |
there still are these broken:
I can fix those too if this comment stuff is becoming too annoying. Probably involves
You mean whether to permit |
It isn't really. I want to get a feeling for how to deal with them, since I want to contribute more in the future so I had better learn how to deal with them. I am having difficulty anticipating both brittany and ghc-exact-print and improving bad results with more specific instructions to brittany is also harder than I expect. Let's take your tests as an example: #test import-with-comments-2
import Test ( abc
, def
-- comment
)
#test import-with-comments-3
import Test ( abc
-- comment
) Both these comments end up on the (Located [LIE]) and not on the LIE for abc and def. This means that ghc-exact-print thinks that the comment belongs to the entire binding list or rather to the ) which semantically means the same thing. It goes even further than that: #test import-with-comments-4
import Test ( abc
, def -- comment
) I would consider any comments on the right of some element to belong to the element on its left (There might be a case, where there is a more semantically correct element directly below the comment), but ghc-exact-print still associates this with the llies. I find this annoying and confusing because I suspected all comments for LIEs to be handled by layoutIE. So I go through hoops to get britanny to process this annotation for llies with the last LIE. And when I do that the result is this: import Test ( abc
-- comment
, def
) What? Why is that even a thing? I can understand that it now considers the comment to be a part of the BriDoc, but the annotation's location data should still indicate that the placement should be below the LIE. And why the So the behaviour of both ghc-exact-print and brittany is far from intuitive to me. End rant 😛 |
I figured it out, but those delta positions sure are odd. I am not sure why certain comments are attached to the llies at all, while the delta positions clearly still indicate they are relative to def or abc. |
@sniperrifle2004 agreed. See the discussion at alanz/ghc-exactprint#53. I made some suggestions there that I think would be saner approaches, but then I have no much of an idea of how the actual parsing and processing logic for DPs works. (There is also #31 where the strange DPs make things very hard/impossible for brittany to do the correct thing.) |
Also refactored a little to improve reuse of the docWrapNode logic
Ah, and of course new-build treats "--ghc-options" differently. I was so evil to revert that merge, let me fix |
50fc394
to
5dac6dd
Compare
@sniperrifle2004 thanks for humoring my alignment-free lifestyle 😄 This PR is looking great! |
@sniperrifle2004 and you are right, import Test ( abc
-- comment
, def
) looks really strange, and is probably a bug hiding in the lower layers of Do you currently have uncommitted/unpushed stuff? There is a couple of minor fixups I wish to make. |
@lspitzner It was partly because I did not use ( abc
-- comment
, def
) If I also change the ( abc
-- comment
, def
) I do not currently have any pending changes (I have been a bit busy). |
- remove unnecessary docWrapNodeRest - make sure that sharing is correct and non-redundant
@sniperrifle2004 oh, i should probably remark on that last refactor: My main concern was not that the code was bad, but it was not clear to me how the sharing worked (i.e. usage of |
Also let layoutLLIEs deal with comments
I added a compact layout triggered when the indentPolicy is IndentPolicyLeft. In addition, I removed the fieldLabel part of the ThingWith. I realized this couldn't be executed at all, since the information to distinguish fields and other functions isn't available yet. I also added code to handle the case where there are comments in a ThingWith. This also means those lists can now expand over multiple lines. The only case where this is troublesome is the case where a single item with a list would expand resulting in this: import Test ( Long( List
, Of
, Things
) ) I tried several things to get this to look a little better, but nothing quite worked. So it seems this is the price for keeping such a line from (excessively) overflowing the column limit. @lspitzner I think that is closer to what I intended to do (ie. share the list between alternatives). I wasn't really concerned with whether it had to be shared. I just thought that it would be better if it was. |
import TestJustShortEnoughModuleNameLike hiding () | ||
import TestJustAbitToLongModuleNameLikeTh hiding () | ||
import MoreThanSufficientlyLongModuleNameWithSome ( items | ||
, that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually not context free anymore, right? The indentation here depends on the length of the module name being imported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More on the position of the first binding, which I chose to stick to the import line. So you rather want this:
import MoreThanSufficientlyLongModuleNameWithSome
(items, that, ...)
An eventually:
import MoreThanSufficientlyLongModuleNameWithSome
( items
, that
, ...
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Yup!
@tfausak can/did you have another look at the last commit? @sniperrifle2004 i have not forgotten about this; i have been toying a bit with the |
) | ||
import Test | ||
( Thing( With | ||
-- Comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the only ones that look weird to me. The indentation depends on the length of the Thing
identifier. I don't think I've ever actually had an import like this, but I think I'd format it as:
import Test
( Thing
( With
-- Comments
, and
-- also
, items
-- !
)
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I should have expected this. I'll take a look.
@lspitzner I have nothing else planned really. I do suspect that it might be a good idea to add an option to choose the compact layout independent of the indent policy, but I don't know that for certain. Adding that later is no problem. Another thing that comes to mind is that the import column is still set to 60. This is probably the best time to change it, since only the tests depend on it. |
For the record, this was not closed intentionally, in fact I did not ever press any such button, and I was not aware that deleting a branch would have this effect. Sorry again for leaving this PR hanging for so long, it is certainly still on my to-do list for the next release. |
Adds import and module Layouters. I refer to the tests for the layout choices I made (I tried to keep close to the style used in the brittany source).
Things to note:
I was not able to figure out how to deal with the importColumn config value. I suspected the column limit should be altered locally, but I couldn't find a way to do that from the layouter.
I had to use what feels like a trick to get the comments of a module to keep appearing above the module, since using docWrapNode on the module doc caused them to appear right after the where.
I added tests to cover reasonably expected use cases and certain edge cases, but I do not guarantee that there aren't things I overlooked.