packages feed

cornelis-0.2.0.0: src/Cornelis/Subscripts.hs

module Cornelis.Subscripts where

import           Cornelis.Offsets
import           Cornelis.Vim (getWindowCursor, getBufferLine, replaceInterval, setWindowCursor, reportError)
import           Data.Foldable (asum, foldl', for_)
import           Data.Map (Map)
import qualified Data.Map as M
import           Data.Maybe (fromMaybe)
import           Data.Proxy
import qualified Data.Text as T
import           Data.Void (Void)
import           Neovim (Neovim)
import           Neovim.API.Text (vim_get_current_window, window_get_buffer)
import           Text.Megaparsec

type Parser = Parsec Void T.Text


data Flavor a
  = Digits a
  | Subscript a
  | Superscript a
  deriving (Eq, Ord, Show, Functor)

extract :: Flavor a -> a
extract (Digits a) = a
extract (Subscript a) = a
extract (Superscript a) = a

parseNum :: Num a => Flavor (Char, String) -> Parser (Flavor a)
parseNum f = do
  r <- option id (negate <$ satisfy (== fst (extract f)) ) <*> parseDigits (fmap snd f)
  pure $ r <$ f


parseDigits :: Num a => Flavor String -> Parser a
parseDigits fv = mkNum <$> takeWhile1P (Just "digit") (flip elem $ extract fv)
  where
    mkNum = foldl' step 0 . chunkToTokens (Proxy :: Proxy T.Text)
    step a c = a * 10 + fromIntegral (digitToInt fv c)


digitToInt :: Flavor String -> Char -> Int
digitToInt fv
  = fromMaybe (error "digitToInt: not a digit")
  . flip lookup (zip (extract fv) [0..])


subscripts :: Flavor (Char, String)
subscripts = Subscript ('₋', "₀₁₂₃₄₅₆₇₈₉")


superscripts :: Flavor (Char, String)
superscripts = Superscript ('⁻', "⁰¹²³⁴⁵⁶⁷⁸⁹")


digits :: Flavor (Char, String)
digits = Digits ('-', "0123456789")


mkReplacement :: (Char, String) -> (Char, String) -> Map Char Char
mkReplacement (m1, s1) (m2, s2) = M.fromList $ zip (m1 : s1) (m2 : s2)


replace :: Map Char Char -> String -> String
replace m = fmap (\c -> fromMaybe c $ M.lookup c m)


parseFlavor :: Parser (Flavor Int)
parseFlavor = asum
  [ parseNum digits
  , parseNum superscripts
  , parseNum subscripts
  ]

parseLine :: Parser (String, Flavor Int)
parseLine = manyTill_ anySingle $ try parseFlavor


unparse :: Flavor Int -> String
unparse (Digits n) = unparseWith (extract digits) n
unparse (Subscript n) = unparseWith (extract subscripts) n
unparse (Superscript n) = unparseWith (extract superscripts) n

unparseWith :: (Char, String) -> Int -> String
unparseWith to n =
  let from = extract digits
      rep = mkReplacement from to
   in replace rep $ show n


applyOver :: (Int -> Int) -> Parser (T.Text, (Int, Int))
applyOver f = do
  (start_str, fv) <- parseLine
  let n = unparse $ fmap f fv
      start = T.pack start_str
  pure
    ( T.pack n
    , (T.length start, length $ show $ extract fv)
    )


incNextDigitSeq :: Neovim env ()
incNextDigitSeq = overNextDigitSeq (+1)


decNextDigitSeq :: Neovim env ()
decNextDigitSeq = overNextDigitSeq (subtract 1)


overNextDigitSeq ::  (Int -> Int) -> Neovim env ()
overNextDigitSeq f = do
  w <- vim_get_current_window
  b <- window_get_buffer w
  Pos line col <- getWindowCursor w
  txt <- getBufferLine b (zeroIndex line)
  let later = T.drop (fromZeroIndexed (zeroIndex col)) txt

  reportError $ T.pack $ show later
  for_ (parse (applyOver f) "" later) $ \(result, (before, target)) ->  do
    reportError result
    let start_col = col .+ Offset before
        end_col = start_col .+ Offset target
        start_pos = Pos line start_col
        end_pos = Pos line end_col

    replaceInterval b (Interval start_pos end_pos) result

    setWindowCursor w start_pos