-- Copyright (C) 2008 JP Bernardy -- | emacs-style rectangle manipulation functions. module Yi.Rectangle where import Control.Applicative import Control.Monad import Data.List (sort, transpose) import Yi.Buffer import Yi.Editor import Yi.String import Text.Regex.TDFA alignRegion :: String -> BufferM () alignRegion str = modifyRegionClever (alignText str) =<< unitWiseRegion Line =<< getSelectRegionB where regexSplit :: String -> String -> [String] regexSplit regex l = case l =~ regex of AllTextSubmatches (_:matches) -> matches _ -> error "regexSplit: text does not match" alignText :: String -> String -> String alignText regex text = unlines' ls' where ls, ls' :: [String] ls = lines' text columns, columns' :: [[String]] columns = fmap (regexSplit regex) ls columnsWidth :: [Int] columnsWidth = fmap (maximum . fmap length) $ transpose columns columns' = fmap (zipWith padLeft columnsWidth) columns ls' = fmap concat columns' -- | Align each line of the region on the given regex. -- Fails if it is not found in any line. alignRegionOn :: String -> BufferM () alignRegionOn s = alignRegion ("^(.*)(" ++ s ++ ")(.*)") -- | Get the selected region as a rectangle. -- Returns the region extended to lines, plus the start and end columns of the rectangle. getRectangle :: BufferM (Region, Int, Int) getRectangle = do r <- getSelectRegionB extR <- unitWiseRegion Line r [lowCol,highCol] <- sort <$> mapM colOf [regionStart r, regionEnd r] return (extR, lowCol, highCol) -- | Split a list at the boundaries given multiSplit :: [Int] -> [a] -> [[a]] multiSplit [] l = [l] multiSplit (x:xs) l = left : multiSplit (fmap (subtract x) xs) right where (left,right) = splitAt x l onRectangle :: (Int -> Int -> String -> String) -> BufferM () onRectangle f = do (reg, l, r) <- getRectangle modifyRegionB (mapLines (f l r)) reg openRectangle :: BufferM () openRectangle = onRectangle openLine where openLine l r line = left ++ replicate (r-l) ' ' ++ right where (left,right) = splitAt l line stringRectangle :: String -> BufferM () stringRectangle inserted = onRectangle stringLine where stringLine l r line = left ++ inserted ++ right where [left,_,right] = multiSplit [l,r] line killRectangle :: EditorM () killRectangle = do cutted <- withBuffer0 $ do (reg, l, r) <- getRectangle text <- readRegionB reg let (cutted, rest) = unzip $ fmap cut $ lines' text cut line = let [left,mid,right] = multiSplit [l,r] line in (mid, left ++ right) replaceRegionB reg (unlines' rest) return cutted setRegE (unlines' cutted) yankRectangle :: EditorM () yankRectangle = do text <- lines' <$> getRegE withBuffer0 $ forM_ text $ \t -> do savingPointB $ insertN t lineDown