-- Copyright (C) 2008 JP Bernardy -- | emacs-style rectangle manipulation functions. module Yi.Rectangle where import Yi.Prelude import Prelude (subtract) import Data.List (splitAt, unzip) import Control.Applicative import Data.Char import Data.List (sort, length, zipWith, 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 $ do forM_ text $ \t -> do savingPointB $ insertN t lineDown