fix-imports 0.1.1 → 0.1.2
raw patch · 3 files changed
+71/−71 lines, 3 files
Files
- fix-imports.cabal +1/−1
- src/FixImports.hs +50/−55
- vimrc +20/−15
fix-imports.cabal view
@@ -1,5 +1,5 @@ name: fix-imports-version: 0.1.1+version: 0.1.2 cabal-version: >= 1.6 build-type: Simple synopsis: Program to manage the imports of a haskell module
src/FixImports.hs view
@@ -34,9 +34,6 @@ imports, and groups them by their toplevel module name (before the first dot). Small groups are combined. They go in alphabetical order by default, but a per-project order may be defined.-- - If there are no imports to be added or removed, the file is returned- unchanged. This means it won't sort the imports in this case. -} module FixImports where import Prelude hiding (mod)@@ -44,18 +41,18 @@ import qualified Control.Monad as Monad import qualified Data.Either as Either import qualified Data.Generics.Uniplate.Data as Uniplate+import qualified Data.List as List import qualified Data.Map as Map import qualified Data.Maybe as Maybe import qualified Data.Set as Set -import qualified Language.Preprocessor.Cpphs as Cpphs import qualified Language.Haskell.Exts.Annotated as Haskell-+import qualified Language.Preprocessor.Cpphs as Cpphs import qualified System.Directory as Directory import qualified System.Environment import qualified System.Exit import qualified System.FilePath as FilePath-import System.FilePath ( (</>) )+import System.FilePath ((</>)) import qualified System.IO as IO import qualified System.Process as Process @@ -150,39 +147,35 @@ , Cpphs.warnings = False } --- | Take a parsed module along with its unparsed text. If the imports should--- change, generate a new import block with proper spacing, formatting, and--- comments. Then snip out the import block on the import file, and replace--- it. Otherwise, the unparsed input module is returned unchanged.+-- | Take a parsed module along with its unparsed text. Generate a new import+-- block with proper spacing, formatting, and comments. Then snip out the+-- import block on the import file, and replace it. fixImports :: Config.Config -> FilePath -> Types.Module -> [Haskell.Comment] -> String -> IO (Either String Result)-fixImports config modulePath mod cmts text- | Set.null newImports && Set.null unusedImports =- return $ Right $ Result text Set.empty Set.empty- | otherwise = do- -- Don't bother loading the index if I'm not going to use it.- -- TODO actually, only load it if I don't find local imports- -- I guess Data.Binary's laziness will serve me there- index <- if Set.null newImports- then return Index.empty- else Index.loadIndex (Config.configIndex config)- mbNew <- mapM (mkImportLine modulePath index) (Set.toList newImports)- mbExisting <- mapM findImport imports- let existing = map (Types.importDeclModule . fst) imports- let (notFound, importLines) = Either.partitionEithers $- zipWith mkError- (map toModule (Set.toList newImports) ++ existing)- (mbNew ++ mbExisting)- mkError _ (Just imp) = Right imp- mkError mod Nothing = Left mod- return $ case notFound of- _ : _ -> Left $ "modules not found: "- ++ Util.join ", " (map Types.moduleName notFound)- [] -> Right $ Result- (substituteImports (showImports importLines) range text)- (Set.fromList (map (Types.importDeclModule . Types.importDecl)- (Maybe.catMaybes mbNew)))- unusedImports+fixImports config modulePath mod cmts text = do+ -- Don't bother loading the index if I'm not going to use it.+ -- TODO actually, only load it if I don't find local imports+ -- I guess Data.Binary's laziness will serve me there+ index <- if Set.null newImports+ then return Index.empty+ else Index.loadIndex (Config.configIndex config)+ mbNew <- mapM (mkImportLine modulePath index) (Set.toList newImports)+ mbExisting <- mapM findImport imports+ let existing = map (Types.importDeclModule . fst) imports+ let (notFound, importLines) = Either.partitionEithers $+ zipWith mkError+ (map toModule (Set.toList newImports) ++ existing)+ (mbNew ++ mbExisting)+ mkError _ (Just imp) = Right imp+ mkError mod Nothing = Left mod+ return $ case notFound of+ _ : _ -> Left $ "modules not found: "+ ++ Util.join ", " (map Types.moduleName notFound)+ [] -> Right $ Result+ (substituteImports (showImports importLines) range text)+ (Set.fromList (map (Types.importDeclModule . Types.importDecl)+ (Maybe.catMaybes mbNew)))+ unusedImports where (newImports, unusedImports, imports, range) = importInfo mod cmts toModule (Types.Qualification name) = Types.ModuleName name@@ -321,30 +314,31 @@ -- | Pair ImportDecls up with the comments that apply to them. Comments--- below the last import are dropped. Comments that are separated from an--- import by a blank line will also be lost. It could be fixed but I don't--- think I write those comments.+-- below the last import are dropped, but there shouldn't be any of those+-- because they should have been omitted from the comment block. ----- Also misses cmts that have leading whitespace. Don't do that.--- To do it right I'd have to sort imports and cmts and pull cmts off with--- a foldl.+-- Spaces between comments above an import will be lost, and multiple comments+-- to the right of an import (e.g. commenting a complicated import list) will+-- probably be messed up. TODO Fix it if it becomes a problem. associateComments :: [Types.ImportDecl] -> [Haskell.Comment] -> [(Types.ImportDecl, [Types.Comment])]-associateComments imports cmts = [(imp, cmtsFor imp) | imp <- imports]+associateComments imports cmts = snd $ List.mapAccumL associate cmts imports where- cmtsFor imp = Util.mapMaybe (associate src) cmts- where src = Haskell.srcInfoSpan (Haskell.importAnn imp)- associate src (Haskell.Comment block csrc text)- | start csrc == end csrc && start csrc `within` src =- Just $ Types.Comment Types.CmtRight cmt- | Haskell.srcSpanStartColumn csrc == 1 && end csrc + 1 == start src =- Just $ Types.Comment Types.CmtAbove cmt- | otherwise = Nothing+ associate cmts imp = (after, (imp, associated)) where- cmt = if block then "{-" ++ text ++ "-}" else "--" ++ text+ associated = map (Types.Comment Types.CmtAbove . cmtText) above+ ++ map (Types.Comment Types.CmtRight . cmtText) right+ -- cmts that end before the import beginning are above it+ (above, rest) = List.span ((< start impSpan) . end . cmtSpan) cmts+ -- remaining cmts that start before or at the import's end are right+ -- of it+ (right, after) = List.span ((<= end impSpan) . start . cmtSpan) rest+ impSpan = Haskell.srcInfoSpan (Haskell.importAnn imp)+ cmtSpan (Haskell.Comment _ span _) = span+ cmtText (Haskell.Comment True _ s) = "{-" ++ s ++ "-}"+ cmtText (Haskell.Comment False _ s) = "--" ++ s start = Haskell.srcSpanStartLine end = Haskell.srcSpanEndLine- within line src = line >= start src && line <= end src parse :: String -> (Types.Module -> [Haskell.Comment] -> a) -> Either String a parse text f = case Haskell.parseFileContentsWithComments mode text of@@ -377,9 +371,9 @@ | Haskell.Qual _ m _ <- Uniplate.universeBi mod] -{- -- * test +{- test = do res <- fixModule (Config.defaultConfig ["base"]) "TestMod.hs" tmod case res of@@ -403,6 +397,7 @@ \) where\n\ \import qualified Data.List as C\n\ \-- I want this comment\n\+\-- And this one\n\ \import qualified Util -- cmt right\n\ \import qualified Extra as Biz {- block cmt -}\n\ \import Data.Map (a,\n\
vimrc view
@@ -15,21 +15,26 @@ execute 'silent !FixImports -v' expand('%') '<' tmp '>' out '2>' err let errs = readfile(err) if v:shell_error == 0- " Is there an easier way to replace the buffer with a file?- let old_line = line('.')- let old_col = col('.')- let old_total = line('$')- %d- execute 'silent :read' out- 0d- let new_total = line('$')- " If the import fix added or removed lines I need to take that into- " account. This will be wrong if the cursor was above the import- " block.- call cursor(old_line + (new_total - old_total), old_col)- " The reload will forget fold state. It was open, right?- if foldclosed('.') != -1- execute 'normal zO'+ " Don't replace the buffer if there's no change, this way I won't+ " mess up fold and undo state.+ call system('cmp -s ' . tmp . ' ' . out)+ if v:shell_error != 0+ " Is there an easier way to replace the buffer with a file?+ let old_line = line('.')+ let old_col = col('.')+ let old_total = line('$')+ %d+ execute 'silent :read' out+ 0d+ let new_total = line('$')+ " If the import fix added or removed lines I need to take that+ " into account. This will be wrong if the cursor was above the+ " import block.+ call cursor(old_line + (new_total - old_total), old_col)+ " The reload will forget fold state. It was open, right?+ if foldclosed('.') != -1+ execute 'normal zO'+ endif endif endif call delete(out)