@@ -12,7 +12,7 @@ module Data.Text.Zipper where
12import Prelude
12import Prelude
14import Control.Exception (assert)
14import Control.Exception (assert)
15−import Control.Monad.State (evalState, forM, get, put, join)
15+import Control.Monad.State (evalState, forM, get, put)
16import Data.Char (isSpace)
16import Data.Char (isSpace)
17import Data.Map (Map)
17import Data.Map (Map)
18import Data.Maybe (fromMaybe)
18import Data.Maybe (fromMaybe)
@@ -170,7 +170,7 @@ deleteLeftWord (TextZipper lb b a la) =
170-- | Insert up to n spaces to get to the next logical column that is a multiple of n
170-- | Insert up to n spaces to get to the next logical column that is a multiple of n
171tab :: Int -> TextZipper -> TextZipper
171tab :: Int -> TextZipper -> TextZipper
172tab n z@(TextZipper _ b _ _) =
172tab n z@(TextZipper _ b _ _) =
173− insert (T.replicate (fromEnum $ n - (T.length b `mod` max 1 n)) " ") z
173+ insert (T.replicate (fromEnum $ n - T.length b `mod` max 1 n) " ") z
175-- | The plain text contents of the zipper
175-- | The plain text contents of the zipper
176value :: TextZipper -> Text
176value :: TextZipper -> Text
@@ -314,9 +314,7 @@ wordsWithWhitespace :: Text -> [Text]
314wordsWithWhitespace t@(Text arr off len) = loop 0 0 False
314wordsWithWhitespace t@(Text arr off len) = loop 0 0 False
316 loop !start !n !wasSpace
316 loop !start !n !wasSpace
317− | n >= len = if start == n
319− else [Text arr (start+off) (n-start)]
317+ | n >= len = [Text arr (start+off) (n-start) | not (start == n)]
320 | isSpace c = loop start (n+d) True
318 | isSpace c = loop start (n+d) True
321 | wasSpace = Text arr (start+off) (n-start) : loop n n False
319 | wasSpace = Text arr (start+off) (n-start) : loop n n False
322 | otherwise = loop start (n+d) False
320 | otherwise = loop start (n+d) False
@@ -367,7 +365,8 @@ wrapWithOffsetAndAlignment
367 -> [WrappedLine] -- (words on that line, hidden space char, offset from beginning of line)
365 -> [WrappedLine] -- (words on that line, hidden space char, offset from beginning of line)
368wrapWithOffsetAndAlignment _ maxWidth _ _ | maxWidth <= 0 = []
366wrapWithOffsetAndAlignment _ maxWidth _ _ | maxWidth <= 0 = []
369wrapWithOffsetAndAlignment alignment maxWidth n txt = assert (n <= maxWidth) r where
367wrapWithOffsetAndAlignment alignment maxWidth n txt = assert (n <= maxWidth) r where
368+ -- we pad by offset amount with any non-space character which we will remove later so that no changes need to be made to splitWordsAtDisplayWidth
370− r' = splitWordsAtDisplayWidth maxWidth $ T.replicate n " " : wordsWithWhitespace txt
369+ r' = splitWordsAtDisplayWidth maxWidth $ wordsWithWhitespace ( T.replicate n "." <> txt)
371 fmapfn (t,b) = case alignment of
370 fmapfn (t,b) = case alignment of
372 TextAlignment_Left -> WrappedLine t b 0
371 TextAlignment_Left -> WrappedLine t b 0
373 TextAlignment_Right -> WrappedLine t b (maxWidth-l)
372 TextAlignment_Right -> WrappedLine t b (maxWidth-l)
@@ -380,7 +379,7 @@ wrapWithOffsetAndAlignment alignment maxWidth n txt = assert (n <= maxWidth) r w
381-- converts deleted eol spaces into logical lines
380-- converts deleted eol spaces into logical lines
382eolSpacesToLogicalLines :: [[WrappedLine]] -> [[(Text, Int)]]
381eolSpacesToLogicalLines :: [[WrappedLine]] -> [[(Text, Int)]]
383−eolSpacesToLogicalLines = fmap (fmap (\(WrappedLine a _ c) -> (a,c))) . join . fmap (L.groupBy (\(WrappedLine _ b _) _ -> not b))
382+eolSpacesToLogicalLines = fmap (fmap (\(WrappedLine a _ c) -> (a,c))) . concatMap (L.groupBy (\(WrappedLine _ b _) _ -> not b))
385offsetMapWithAlignmentInternal :: [[WrappedLine]] -> OffsetMapWithAlignment
384offsetMapWithAlignmentInternal :: [[WrappedLine]] -> OffsetMapWithAlignment
386offsetMapWithAlignmentInternal = offsetMapWithAlignment . eolSpacesToLogicalLines
385offsetMapWithAlignmentInternal = offsetMapWithAlignment . eolSpacesToLogicalLines
@@ -419,10 +418,13 @@ displayLinesWithAlignment alignment width tag cursorTag (TextZipper lb b a la) =
419 linesBefore = map (wrapWithOffsetAndAlignment alignment width 0) $ reverse lb
418 linesBefore = map (wrapWithOffsetAndAlignment alignment width 0) $ reverse lb
420 linesAfter :: [[WrappedLine]] -- The wrapped lines after the cursor line
419 linesAfter :: [[WrappedLine]] -- The wrapped lines after the cursor line
421 linesAfter = map (wrapWithOffsetAndAlignment alignment width 0) la
420 linesAfter = map (wrapWithOffsetAndAlignment alignment width 0) la
422+ -- simulate trailing cursor character when computing OffsetMap
423+ afterWithCursor = if T.null a then " " else a
422 offsets :: OffsetMapWithAlignment
424 offsets :: OffsetMapWithAlignment
423 offsets = offsetMapWithAlignmentInternal $ mconcat
425 offsets = offsetMapWithAlignmentInternal $ mconcat
424 [ linesBefore
426 [ linesBefore
425− , [wrapWithOffsetAndAlignment alignment width 0 $ b <> a]
427+ , [wrapWithOffsetAndAlignment alignment width 0 $ b <> afterWithCursor]
426 , linesAfter
428 , linesAfter
428 flattenLines = concatMap (fmap _wrappedLines_text)
430 flattenLines = concatMap (fmap _wrappedLines_text)
@@ -432,9 +434,8 @@ displayLinesWithAlignment alignment width tag cursorTag (TextZipper lb b a la) =
432 -- * spans that are on earlier display lines (though on the same logical line), and
434 -- * spans that are on earlier display lines (though on the same logical line), and
433 -- * spans that are on the same display line
435 -- * spans that are on the same display line
435− -- TODO cursor goes here if align right
436 (spansCurrentBefore, spansCurLineBefore) = fromMaybe ([], []) $
437 (spansCurrentBefore, spansCurLineBefore) = fromMaybe ([], []) $
437− initLast $ map ((:[]) . Span tag) $ fmap _wrappedLines_text $ (wrapWithOffsetAndAlignment alignment width 0 b)
438+ initLast $ map ((:[]) . Span tag) $ _wrappedLines_text <$> (wrapWithOffsetAndAlignment alignment width 0 b)
438 -- Calculate the number of columns on the cursor's display line before the cursor
439 -- Calculate the number of columns on the cursor's display line before the cursor
439 curLineOffset = spansWidth spansCurLineBefore
440 curLineOffset = spansWidth spansCurLineBefore
440 -- Check whether the spans on the current display line are long enough that
441 -- Check whether the spans on the current display line are long enough that
@@ -448,17 +449,29 @@ displayLinesWithAlignment alignment width tag cursorTag (TextZipper lb b a la) =
448 -- * spans that are on the same display line, and
449 -- * spans that are on the same display line, and
449 -- * spans that are on later display lines (though on the same logical line)
450 -- * spans that are on later display lines (though on the same logical line)
451− -- TODO do not show cursor if align right
452 (spansCurLineAfter, spansCurrentAfter) = fromMaybe ([], []) $
452 (spansCurLineAfter, spansCurrentAfter) = fromMaybe ([], []) $
453 headTail $ case T.uncons a of
453 headTail $ case T.uncons a of
454 Nothing -> [[Span cursorTag " "]]
454 Nothing -> [[Span cursorTag " "]]
455 Just (c, rest) ->
455 Just (c, rest) ->
456 let o = if cursorAfterEOL then cursorCharWidth else curLineOffset + cursorCharWidth
456 let o = if cursorAfterEOL then cursorCharWidth else curLineOffset + cursorCharWidth
457 cursor = Span cursorTag (T.singleton c)
457 cursor = Span cursorTag (T.singleton c)
458− in case map ((:[]) . Span tag) $ fmap _wrappedLines_text $ (wrapWithOffsetAndAlignment alignment width o rest) of
458+ in case map ((:[]) . Span tag) $ _wrappedLines_text <$> (wrapWithOffsetAndAlignment alignment width o rest) of
459 [] -> [[cursor]]
459 [] -> [[cursor]]
460 (l:ls) -> (cursor : l) : ls
460 (l:ls) -> (cursor : l) : ls
462+ curLineSpanNormalCase = if cursorAfterEOL
463+ then [ spansCurLineBefore, spansCurLineAfter ]
464+ else [ spansCurLineBefore <> spansCurLineAfter ]
466+ -- for right alignment, we want draw the cursor tag to be on the character just before the logical cursor position
467+ curLineSpan = if alignment == TextAlignment_Right && not cursorAfterEOL
468+ then case reverse spansCurLineBefore of
469+ [] -> curLineSpanNormalCase
470+ (Span _ x):xs -> case spansCurLineAfter of
471+ [] -> error "should not be possible" -- curLineSpanNormalCase
472+ (Span _ y):ys -> [reverse (Span cursorTag x:xs) <> ((Span tag y):ys)]
473+ else curLineSpanNormalCase
462 cursorY = sum
475 cursorY = sum
463 [ length spansBefore
476 [ length spansBefore
464 , length spansCurrentBefore
477 , length spansCurrentBefore
@@ -471,9 +484,7 @@ displayLinesWithAlignment alignment width tag cursorTag (TextZipper lb b a la) =
471 { _displayLines_spans = concat
484 { _displayLines_spans = concat
472 [ spansBefore
485 [ spansBefore
473 , spansCurrentBefore
486 , spansCurrentBefore
474− , if cursorAfterEOL
487+ , curLineSpan
475− then [ spansCurLineBefore, spansCurLineAfter ]
476− else [ spansCurLineBefore <> spansCurLineAfter ]
477 , spansCurrentAfter
488 , spansCurrentAfter
478 , spansAfter
489 , spansAfter
@@ -531,4 +542,4 @@ wrapWithOffset
531 -> Int -- ^ Offset for first line
542 -> Int -- ^ Offset for first line
532 -> Text -- ^ Text to be wrapped
543 -> Text -- ^ Text to be wrapped
533 -> [Text]
544 -> [Text]
534−wrapWithOffset maxWidth n xs = fmap _wrappedLines_text $ wrapWithOffsetAndAlignment TextAlignment_Left maxWidth n xs
545+wrapWithOffset maxWidth n xs = _wrappedLines_text <$> wrapWithOffsetAndAlignment TextAlignment_Left maxWidth n xs