packages feed

yi-0.4: Yi/Mode/Haskell.hs

module Yi.Mode.Haskell (haskellMode, cleverHaskellMode, 
                        haskellUnCommentSelectionB, haskellCommentSelectionB, haskellToggleCommentSelectionB)  where

import Prelude (unwords)
import Yi.Prelude
import Data.Maybe (maybe, listToMaybe)
import Data.List (isPrefixOf, dropWhile, takeWhile, filter, groupBy, drop)
import Yi.Buffer
import Yi.Buffer.HighLevel
import Yi.Buffer.Normal
import Yi.Buffer.Region
import Yi.Indent
import Yi.Prelude
import Yi.Syntax
import Yi.String
import Yi.Region
import Yi.Lexer.Alex (Tok(..),Posn(..),tokBegin,tokEnd,tokRegion)
import Yi.Lexer.Haskell (Token(..), ReservedType(..), startsLayout)
import qualified Yi.Syntax.Linear as Linear
import qualified Yi.IncrementalParse as IncrParser
import qualified Yi.Lexer.Alex as Alex
import qualified Yi.Lexer.Haskell as Haskell
import Yi.Syntax.Paren as Paren
import Control.Applicative

haskellMode :: Mode (Linear.Result (Tok Token))
haskellMode = emptyMode 
   {
    modeHL = ExtHL $
    mkHighlighter (Linear.incrScanner . haskellLexer) (\begin end pos -> Linear.getStrokes begin end pos . fmap tokenToStroke)
   , modeIndent = \_ast -> autoIndentHaskellB
   }

cleverHaskellMode :: Mode (Expr (Tok Haskell.Token))
cleverHaskellMode = emptyMode {
    modeIndent = cleverAutoIndentHaskellB,
    modeHL = ExtHL $
{--    lexer `withScanner` IncrParser.mkHighlighter Fractal.parse
      (\begin end -> fmap tokenToStroke . Fractal.getStrokes begin end) id -}
--}

{--
    lexer `withScanner` IncrParser.mkHighlighter Paren.parse
      (\begin end t -> Paren.getStrokes begin end t) id
--}

--
    mkHighlighter (IncrParser.scanner parse . indentScanner . haskellLexer)
      (\point begin end t -> Paren.getStrokes point begin end t)
--}                              
  , modeAdjustBlock = adjustBlock
  , modePrettify = cleverPrettify
 }

haskellLexer = Alex.lexScanner Haskell.alexScanToken Haskell.initState 


adjustBlock :: Expr (Tok Token) -> Int -> BufferM ()
adjustBlock e len = do
  p <- pointB
  l <- curLn
  let t = getIndentingSubtree e p l
  case t of
    Nothing -> return ()
    Just it -> 
           savingExcursionB $ do
                   let (_startOfs, height) = getSubtreeSpan it
                   col <- curCol
                   forM_ [1..height] $ \_ -> do
                               lineDown
                               indent <- indentOfB =<< readLnB
                               -- it might be that we have 1st column comments in the block,
                               -- which should not be changed.
                               when (indent >= col) $
                                if len >= 0
                                 then do insertN (replicate len ' ') 
                                         leftN len
                                 else do
                                    deleteN (negate len)
indentLevel :: Int
indentLevel = 4

insideGroup :: Token -> Bool
insideGroup (Special c) = not $ c `elem` "',;})" 
insideGroup _ = True

cleverAutoIndentHaskellB :: Expr TT -> IndentBehaviour -> BufferM ()
cleverAutoIndentHaskellB e behaviour = do
  previousLine   <- getNextNonBlankLineB Backward
  previousIndent <- indentOfB previousLine
  solPnt <- pointAt moveToSol
  eolPnt <- pointAt moveToEol
  let stopsOf (g@(Group open _ close):ts) 
          | isErrorTok (tokT close) || getLastOffset g >= solPnt
              = [shiftGroup + (posnCol . tokPosn $ open)]  -- stop here: we want to be "inside" that group.
          | otherwise = stopsOf ts -- this one is closed on before this line; just skip it.
      stopsOf ((Atom (Tok {tokT = t})):_) | startsLayout t = [previousIndent + indentLevel]
      stopsOf ((Atom _):ts) = stopsOf ts
         -- of; where; etc. we want to start the block here.
      stopsOf (t@(Stmt _):ts) = shiftBlock + maybe 0 (posnCol . tokPosn) (getFirstToken t) : stopsOf ts
      stopsOf (Error _:ts) = stopsOf ts
      stopsOf [] = []
      firstTokOnLine = fmap tokT $ listToMaybe $ 
          dropWhile ((solPnt >) . tokBegin) $ 
          takeWhile ((eolPnt >) . tokBegin) $ -- for laziness.
          filter (not . isErrorTok . tokT) $ allToks e
      shiftBlock = case firstTokOnLine of
        Just (Reserved t) | t `elem` [Where, Deriving] -> 4
        _ -> 0
      deepInGroup = maybe True insideGroup firstTokOnLine
      shiftGroup = if deepInGroup then 1 else 0
                     

        
  case getLastPath e solPnt of
    Nothing -> return ()
    Just path -> let stops = stopsOf path
                 in trace ("Stops = " ++ show stops) $      
                    trace ("firstTokOnLine = " ++ show firstTokOnLine) $      
                    cycleIndentsB behaviour stops
         
allToks :: Expr TT -> [TT]
allToks = concatMap toList

tokText :: Tok t -> BufferM String
tokText = readRegionB . tokRegion

cleverPrettify :: Expr TT -> BufferM ()
cleverPrettify e = do
  pnt <- pointB
  let toks = allToks e
      groups = groupBy ((==) `on` (tokTyp . tokT)) toks
      isCommentGroup g = (tokTyp $ tokT $ head $ g) `elem` fmap Just [Haskell.Line] --  Haskell.Text]
      thisCommentGroup = listToMaybe $ dropWhile ((pnt >) . tokEnd . last) $ filter isCommentGroup $ groups
                         -- FIXME: laziness
  case thisCommentGroup of
    Nothing -> return ()
    Just g -> do let region = mkRegion (tokBegin . head $ g) (tokEnd . last $ g)
                 text <- unwords . fmap (drop 2) <$> mapM tokText g
                 modifyRegionB (const $ unlines' $ fmap ("-- " ++) $ fillText 80 $ text) region
                 
tokTyp :: Token -> Maybe Haskell.CommentType 
tokTyp (Comment t) = Just t
tokTyp _ = Nothing

-- | Keyword-based auto-indenter for haskell.
autoIndentHaskellB :: IndentBehaviour -> BufferM ()
autoIndentHaskellB =
  autoIndentWithKeywordsB [ "if"
                          , "then"
                          , "else"
                          , "|"
                          , "->"
                          , "case" -- hmm
                          , "in"
                          -- Note tempted by having '=' in here that would
                          -- potentially work well for 'data' declarations
                          -- but I think '=' is so common in other places
                          -- that it would introduce many spurious/annoying
                          -- hints.
                          ]
                          [ "where"
                          , "let"
                          , "do"
                          , "mdo"
                          , "{-"
                          , "{-|"
                          , "--"
                          ]


-- | Comments the region using haskell line comments
haskellCommentSelectionB :: BufferM ()
haskellCommentSelectionB = linePrefixSelectionB "-- "

-- | uncomments a region of haskell line commented code
haskellUnCommentSelectionB :: BufferM ()
haskellUnCommentSelectionB = unLineCommentSelectionB "-- "

haskellToggleCommentSelectionB :: BufferM ()
haskellToggleCommentSelectionB = do
  l <- readUnitB Line
  if ("--" `isPrefixOf` l)
    then haskellUnCommentSelectionB
    else haskellCommentSelectionB