nbparts-0.1.0.0: src/Nbparts/Util/Text.hs
module Nbparts.Util.Text where
import Control.Monad qualified as Monad
import Data.List qualified as List
import Data.Ord qualified as Ord
import Data.Text (Text)
import Data.Text qualified as Text
lineColToIndex :: [Text] -> Int -> Int -> Maybe Int
lineColToIndex textLines line col
| line <= 0 || col <= 0 = Nothing
| otherwise = go 1 0 textLines
where
go :: Int -> Int -> [Text] -> Maybe Int
go _ _ [] = Nothing -- Not enough lines.
go currentLine charsBefore (l : ls)
| currentLine == line =
if col <= Text.length l + 1
then Just $ charsBefore + col - 1
else Nothing
| otherwise =
-- +1 to `Text.length l` for the newline `Text.lines` removed
go (currentLine + 1) (charsBefore + Text.length l + 1) ls
-- NOTE: Returns Nothing when slices overlap.
replaceSlices :: Text -> [((Int, Int), Text)] -> Maybe Text
replaceSlices input replacements = go 0 sortedReplacements
where
sortedReplacements = List.sortBy (Ord.comparing fst) replacements
go :: Int -> [((Int, Int), Text)] -> Maybe Text
go pos [] = Just $ Text.drop pos input
go pos (((start, end), replacement) : rs) = do
-- Bounds checking.
Monad.unless
( start >= 0
&& end >= 0
&& start <= end
&& start >= pos
)
Nothing
let current = Text.drop pos input
relStart = start - pos
relEnd = end - pos
let (before, remainder) = Text.splitAt relStart current
-- If the length of the replaced text we got is less than
-- the expected length of `relEnd - relStart`, that means
-- the indices are out of range.
let replaced = Text.take (relEnd - relStart) remainder
Monad.when (Text.length replaced < relEnd - relStart) Nothing
after <- go end rs
Just $ before <> replacement <> after
splitKeepNewlines :: Text -> [Text]
splitKeepNewlines txt
| Text.null txt = []
| otherwise =
let (before, rest) = Text.break (== '\n') txt
in case Text.uncons rest of
Just ('\n', rest') -> (before `Text.snoc` '\n') : splitKeepNewlines rest'
_ -> [before]
findSliceBetween :: Int -> Int -> Text -> Text -> Maybe (Int, Int)
findSliceBetween searchStart searchEnd haystack = findSliceFrom searchStart (Text.take searchEnd haystack)
findSliceFrom :: Int -> Text -> Text -> Maybe (Int, Int)
findSliceFrom searchStart haystack needle = do
(startIdx, endIdx) <- findSlice (Text.drop searchStart haystack) needle
pure (startIdx + searchStart, endIdx + searchStart)
findSlice :: Text -> Text -> Maybe (Int, Int)
findSlice haystack needle = do
startIdx <- findSliceStart haystack needle
let endIdx = startIdx + Text.length needle
pure (startIdx, endIdx)
findSliceStart :: Text -> Text -> Maybe Int
findSliceStart haystack needle =
if Text.null remaining
then Nothing
else Just $ Text.length prefix
where
(prefix, remaining) = Text.breakOn needle haystack