Skip to content

Commit

Permalink
Add getLine to all Ropes
Browse files Browse the repository at this point in the history
  • Loading branch information
jhrcek committed May 4, 2024
1 parent b0d3f09 commit 25c63bd
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/Data/Text/Lines/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ lines (TextLines (Text arr off len) nls) = go off (U.toList nls)
splitAtLine :: HasCallStack => Word -> TextLines -> (TextLines, TextLines)
splitAtLine k = splitAtPosition (Position k 0)

-- | Get line with given index, O(1).
-- | Get line with given 0-based index, O(1).
-- The resulting Text does not contain newlines.
-- Returns "" if the line index is out of bounds
-- Returns "" if the line index is out of bounds.
--
-- >>> :set -XOverloadedStrings
-- >>> map (\l -> getLine l "fя𐀀\n☺bar\n\n") [0..3]
Expand Down
19 changes: 19 additions & 0 deletions src/Data/Text/Mixed/Rope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module Data.Text.Mixed.Rope
, lines
, lengthInLines
, splitAtLine
, getLine
-- * Code points
, charLength
, charSplitAt
Expand Down Expand Up @@ -562,3 +563,21 @@ utf16SplitAtPosition (Utf16.Position l c) rp = do
let (beforeLine, afterLine) = splitAtLine l rp
(beforeColumn, afterColumn) <- utf16SplitAt c afterLine
Just (beforeLine <> beforeColumn, afterColumn)

-- | Get a line by its 0-based index.
-- Returns "" if the index is out of bounds.
-- The result doesn't contain newline characters.
--
-- >>> :set -XOverloadedStrings
-- >>> map (\l -> getLine l "foo\nbar\n😊😊\n\n") [0..3]
-- ["foo","bar","😊😊",""]
--
getLine :: Word -> Rope -> Text
getLine lineIdx rp =
case T.unsnoc firstLine of
Just (firstLineInit, '\n') -> firstLineInit
_ -> firstLine
where
(_, afterIndex) = splitAtLine lineIdx rp
(firstLineRope, _ ) = splitAtLine 1 afterIndex
firstLine = toText firstLineRope
19 changes: 19 additions & 0 deletions src/Data/Text/Rope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module Data.Text.Rope
, lines
, lengthInLines
, splitAtLine
, getLine
-- * Code points
, length
, splitAt
Expand Down Expand Up @@ -388,3 +389,21 @@ splitAtPosition (Position l c) rp = (beforeLine <> beforeColumn, afterColumn)
where
(beforeLine, afterLine) = splitAtLine l rp
(beforeColumn, afterColumn) = splitAt c afterLine

-- | Get a line by its 0-based index.
-- Returns "" if the index is out of bounds.
-- The result doesn't contain newline characters.
--
-- >>> :set -XOverloadedStrings
-- >>> map (\l -> getLine l "foo\nbar\n😊😊\n\n") [0..3]
-- ["foo","bar","😊😊",""]
--
getLine :: Word -> Rope -> Text
getLine lineIdx rp =
case T.unsnoc firstLine of
Just (firstLineInit, '\n') -> firstLineInit
_ -> firstLine
where
(_, afterIndex) = splitAtLine lineIdx rp
(firstLineRope, _ ) = splitAtLine 1 afterIndex
firstLine = toText firstLineRope
19 changes: 19 additions & 0 deletions src/Data/Text/Utf16/Rope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module Data.Text.Utf16.Rope
, lines
, lengthInLines
, splitAtLine
, getLine
-- * UTF-16 code units
, length
, splitAt
Expand Down Expand Up @@ -391,3 +392,21 @@ splitAtPosition (Position l c) rp = do
let (beforeLine, afterLine) = splitAtLine l rp
(beforeColumn, afterColumn) <- splitAt c afterLine
Just (beforeLine <> beforeColumn, afterColumn)

-- | Get a line by its 0-based index.
-- Returns "" if the index is out of bounds.
-- The result doesn't contain newline characters.
--
-- >>> :set -XOverloadedStrings
-- >>> map (\l -> getLine l "foo\nbar\n😊😊\n\n") [0..3]
-- ["foo","bar","😊😊",""]
--
getLine :: Word -> Rope -> Text
getLine lineIdx rp =
case T.unsnoc firstLine of
Just (firstLineInit, '\n') -> firstLineInit
_ -> firstLine
where
(_, afterIndex) = splitAtLine lineIdx rp
(firstLineRope, _ ) = splitAtLine 1 afterIndex
firstLine = toText firstLineRope
19 changes: 19 additions & 0 deletions src/Data/Text/Utf8/Rope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module Data.Text.Utf8.Rope
, lines
, lengthInLines
, splitAtLine
, getLine
-- * UTF-8 code units
, length
, splitAt
Expand Down Expand Up @@ -386,3 +387,21 @@ splitAtPosition (Position l c) rp = do
let (beforeLine, afterLine) = splitAtLine l rp
(beforeColumn, afterColumn) <- splitAt c afterLine
Just (beforeLine <> beforeColumn, afterColumn)

-- | Get a line by its 0-based index.
-- Returns "" if the index is out of bounds.
-- The result doesn't contain newline characters.
--
-- >>> :set -XOverloadedStrings
-- >>> map (\l -> getLine l "foo\nbar\n😊😊\n\n") [0..3]
-- ["foo","bar","😊😊",""]
--
getLine :: Word -> Rope -> Text
getLine lineIdx rp =
case T.unsnoc firstLine of
Just (firstLineInit, '\n') -> firstLineInit
_ -> firstLine
where
(_, afterIndex) = splitAtLine lineIdx rp
(firstLineRope, _ ) = splitAtLine 1 afterIndex
firstLine = toText firstLineRope
15 changes: 13 additions & 2 deletions test/CharRope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ module CharRope
( testSuite
) where

import Prelude ()
import Prelude ((+), (-))
import Data.Function (($))
import Data.Semigroup ((<>))
import Data.Monoid (mempty)
import qualified Data.List as L
import qualified Data.Text.Lines as Lines
import qualified Data.Text.Rope as Rope
import Test.Tasty (testGroup, TestTree)
import Test.Tasty.QuickCheck (testProperty, (===), (.&&.))
import Test.Tasty.QuickCheck (Positive(..), conjoin, testProperty, (===), (.&&.))

import Utils ()

Expand Down Expand Up @@ -48,4 +50,13 @@ testSuite = testGroup "Char Rope"
, testProperty "splitAtPosition 2" $
\i x -> case (Rope.splitAtPosition i x, Lines.splitAtPosition i (Lines.fromText $ Rope.toText x)) of
((y, z), (y', z')) -> Lines.fromText (Rope.toText y) === y' .&&. Lines.fromText (Rope.toText z) === z'

, testProperty "forall i in bounds: getLine i x == lines x !! i" $
\x -> let lns = Rope.lines x in
conjoin $ L.zipWith (\idx ln -> Rope.getLine idx x === ln) [0..] lns
, testProperty "forall i out of bounds: getLine i x == mempty" $
\x (Positive offset) ->
let maxIdx = L.genericLength (Rope.lines x) - 1
outOfBoundsIdx = maxIdx + offset
in Rope.getLine outOfBoundsIdx x === mempty
]
13 changes: 12 additions & 1 deletion test/MixedRope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import Prelude ((+), (-))
import Data.Bool (Bool(..), (&&))
import Data.Function (($))
import Data.Maybe (Maybe(..), isJust)
import Data.Monoid (mempty)
import Data.Semigroup ((<>))
import qualified Data.List as L
import qualified Data.Text.Lines as Char
import qualified Data.Text.Utf8.Lines as Utf8
import qualified Data.Text.Utf16.Lines as Utf16
import qualified Data.Text.Mixed.Rope as Mixed
import Test.Tasty (testGroup, TestTree)
import Test.Tasty.QuickCheck (testProperty, (===), property, (.&&.), counterexample)
import Test.Tasty.QuickCheck (Positive(..), conjoin, counterexample, property, testProperty, (===), (.&&.))

import Utils ()

Expand Down Expand Up @@ -106,4 +108,13 @@ testSuite = testGroup "Utf16 Mixed"
\i x -> case Mixed.utf16SplitAtPosition i x of
Just{} -> True
Nothing -> isJust (Mixed.utf16SplitAtPosition (i <> Utf16.Position 0 1) x)

, testProperty "forall i in bounds: getLine i x == lines x !! i" $
\x -> let lns = Mixed.lines x in
conjoin $ L.zipWith (\idx ln -> Mixed.getLine idx x === ln) [0..] lns
, testProperty "forall i out of bounds: getLine i x == mempty" $
\x (Positive offset) ->
let maxIdx = L.genericLength (Mixed.lines x) - 1
outOfBoundsIdx = maxIdx + offset
in Mixed.getLine outOfBoundsIdx x === mempty
]
15 changes: 13 additions & 2 deletions test/Utf16Rope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import Prelude ((+), (-))
import Data.Bool (Bool(..), (&&))
import Data.Function (($))
import Data.Maybe (Maybe(..), isJust)
import Data.Monoid (mempty)
import Data.Semigroup ((<>))
import qualified Data.Text.Utf16.Lines as Lines
import qualified Data.List as L
import qualified Data.Text.Utf16.Lines as Lines
import qualified Data.Text.Utf16.Rope as Rope
import Test.Tasty (testGroup, TestTree)
import Test.Tasty.QuickCheck (testProperty, (===), property, (.&&.), counterexample)
import Test.Tasty.QuickCheck (Positive(..), conjoin, counterexample, property, testProperty, (===), (.&&.))

import Utils ()

Expand Down Expand Up @@ -66,4 +68,13 @@ testSuite = testGroup "Utf16 Rope"
\i x -> case Rope.splitAtPosition i x of
Just{} -> True
Nothing -> isJust (Rope.splitAtPosition (i <> Lines.Position 0 1) x)

, testProperty "forall i in bounds: getLine i x == lines x !! i" $
\x -> let lns = Rope.lines x in
conjoin $ L.zipWith (\idx ln -> Rope.getLine idx x === ln) [0..] lns
, testProperty "forall i out of bounds: getLine i x == mempty" $
\x (Positive offset) ->
let maxIdx = L.genericLength (Rope.lines x) - 1
outOfBoundsIdx = maxIdx + offset
in Rope.getLine outOfBoundsIdx x === mempty
]
15 changes: 13 additions & 2 deletions test/Utf8Rope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ module Utf8Rope
( testSuite
) where

import Prelude ()
import Prelude ((+), (-))
import Data.Bool (Bool(..))
import Data.Function (($))
import Data.Maybe (Maybe(..))
import Data.Monoid (mempty)
import Data.Semigroup ((<>))
import qualified Data.List as L
import qualified Data.Text.Utf8.Lines as Lines
import qualified Data.Text.Utf8.Rope as Rope
import Test.Tasty (testGroup, TestTree)
import Test.Tasty.QuickCheck (testProperty, (===), property, (.&&.), counterexample)
import Test.Tasty.QuickCheck (Positive(..), conjoin, counterexample, property, testProperty, (===), (.&&.))

import Utils ()

Expand Down Expand Up @@ -53,4 +55,13 @@ testSuite = testGroup "Utf8 Rope"
(Nothing, Just{}) -> counterexample "can split TextLines, but not Rope" False
(Just{}, Nothing) -> counterexample "can split Rope, but not TextLines" False
(Just (y, z), Just (y', z')) -> Lines.fromText (Rope.toText y) === y' .&&. Lines.fromText (Rope.toText z) === z'

, testProperty "forall i in bounds: getLine i x == lines x !! i" $
\x -> let lns = Rope.lines x in
conjoin $ L.zipWith (\idx ln -> Rope.getLine idx x === ln) [0..] lns
, testProperty "forall i out of bounds: getLine i x == mempty" $
\x (Positive offset) ->
let maxIdx = L.genericLength (Rope.lines x) - 1
outOfBoundsIdx = maxIdx + offset
in Rope.getLine outOfBoundsIdx x === mempty
]

0 comments on commit 25c63bd

Please sign in to comment.