{- ----------------------------------------------------------------------------- Copyright 2020 Kevin P. Barry Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------------- -} -- Author: Kevin P. Barry [ta0kira@gmail.com] -- | This module is for internal use. {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE Safe #-} module WEditor.Internal.Para ( EditingPara, VisibleParaAfter, VisibleParaBefore, appendToPara, atParaBack, atParaFront, catLinesAfter, catLinesBefore, countLinesAfter, countLinesBefore, editPara, getAfterLines, getBeforeLines, getCurrentLine, getParaCharCount, getParaCursorChar, getParaCursorLine, getParaEditChar, modifyPara, moveParaCursor, paraCursorMovable, parseParaAfter, parseParaBefore, prependToPara, reparsePara, seekParaBack, seekParaFront, setParaCursorChar, setParaEditChar, splitPara, unparsePara, unparseParaAfter, unparseParaBefore, viewAfterLines, viewBeforeLines, viewParaAfter, viewParaBefore, ) where import WEditor.Base.Editor import WEditor.Base.Line import WEditor.Base.Para import WEditor.Base.Parser import WEditor.Internal.Line data VisibleParaBefore c b = VisibleParaBefore { vpbLines :: [VisibleLine c b], -- Reversed. vpbSize :: Int } deriving (Show) data VisibleParaAfter c b = VisibleParaAfter { vpaLines :: [VisibleLine c b] } deriving (Show) data EditingPara c b = EditingPara { epBefore :: [VisibleLine c b], -- Reversed. epEditing :: EditingLine c b, epAfter :: [VisibleLine c b], epSizeBefore :: Int } deriving (Show) viewBeforeLines :: VisibleParaBefore c b -> [VisibleLine c b] viewBeforeLines = reverse . vpbLines viewAfterLines :: VisibleParaAfter c b -> [VisibleLine c b] viewAfterLines = vpaLines visibleParaBefore :: [VisibleLine c b] -> VisibleParaBefore c b visibleParaBefore ls = VisibleParaBefore ls (sum $ map (length . vlText) ls) parseParaBefore :: FixedFontParser a c => a -> UnparsedPara c -> VisibleParaBefore c (BreakType a) parseParaBefore parser (UnparsedPara cs) = VisibleParaBefore (reverse $ breakLines parser cs) (length cs) parseParaAfter :: FixedFontParser a c => a -> UnparsedPara c -> VisibleParaAfter c (BreakType a) parseParaAfter parser (UnparsedPara cs) = VisibleParaAfter $ breakLines parser cs unparseParaBefore :: VisibleParaBefore c b -> UnparsedPara c unparseParaBefore (VisibleParaBefore ls _) = UnparsedPara $ joinLines $ reverse ls unparseParaAfter :: VisibleParaAfter c b -> UnparsedPara c unparseParaAfter (VisibleParaAfter ls) = UnparsedPara $ joinLines ls editPara :: FixedFontParser a c => a -> UnparsedPara c -> EditingPara c (BreakType a) editPara parser (UnparsedPara cs) = EditingPara [] (editLine line) after 0 where (line:after) = nonempty $ breakLines parser cs nonempty [] = [emptyLine parser] nonempty ls = ls unparsePara :: EditingPara c b -> UnparsedPara c unparsePara (EditingPara bs l as _) = UnparsedPara $ joinLines ls where ls = reverse bs ++ [viewLine l] ++ as reparsePara :: FixedFontParser a c => a -> EditingPara c (BreakType a) -> EditingPara c (BreakType a) reparsePara parser (EditingPara bs l as n) = reparseParaTail parser revised where revised = EditingPara bs2 l2 as (sum $ map (length . vlText) bs2) bs' = reverse $ breakLines parser $ joinLines (reverse bs) (l2,bs2) | null bs' = (l,[]) | otherwise = (head bs' `prependToLine` l,tail bs') viewParaBefore :: EditingPara c b -> VisibleParaBefore c b viewParaBefore (EditingPara bs l as _) = visibleParaBefore ls where ls = reverse as ++ [viewLine l] ++ bs viewParaAfter :: EditingPara c b -> VisibleParaAfter c b viewParaAfter (EditingPara bs l as _) = VisibleParaAfter ls where ls = reverse bs ++ [viewLine l] ++ as getBeforeLines :: EditingPara c b -> VisibleParaBefore c b getBeforeLines = visibleParaBefore . epBefore getCurrentLine :: EditingPara c b -> VisibleLine c b getCurrentLine = viewLine . epEditing getAfterLines :: EditingPara c b -> VisibleParaAfter c b getAfterLines = VisibleParaAfter . epAfter catLinesBefore :: [VisibleParaBefore c b] -> [VisibleLine c b] catLinesBefore = concat . map vpbLines catLinesAfter :: [VisibleParaAfter c b] -> [VisibleLine c b] catLinesAfter = concat . map vpaLines countLinesBefore :: Int -> [VisibleParaBefore c b] -> Int countLinesBefore n = length . take n . catLinesBefore countLinesAfter :: Int -> [VisibleParaAfter c b] -> Int countLinesAfter n = length . take n . catLinesAfter getParaCursorLine :: EditingPara c b -> Int getParaCursorLine = length . epBefore getParaCursorChar :: EditingPara c b -> Int getParaCursorChar = getLineCursor . epEditing setParaCursorChar :: Int -> EditingPara c b -> EditingPara c b setParaCursorChar k e@(EditingPara bs l as n) = (EditingPara bs (setLineCursor k l) as n) getParaCharCount :: EditingPara c b -> Int getParaCharCount = length . upText . unparsePara getParaEditChar :: EditingPara c b -> Int getParaEditChar (EditingPara _ l _ n) = n + getLineCursor l setParaEditChar :: Int -> EditingPara c b -> EditingPara c b setParaEditChar k p | getParaEditChar p > k && not (atParaFront p) = setParaEditChar k $ moveParaCursor MovePrev p | getParaEditChar p < k && not (atParaBack p) = setParaEditChar k $ moveParaCursor MoveNext p | otherwise = p splitPara :: FixedFontParser a c => a -> EditingPara c (BreakType a) -> (UnparsedPara c,UnparsedPara c) splitPara parser (EditingPara bs l as _) = let (b,a) = splitLineAtCursor (splitLine parser) l in (unparseParaBefore $ VisibleParaBefore (b:bs) 0, unparseParaAfter $ VisibleParaAfter (a:as)) paraCursorMovable :: MoveDirection -> EditingPara c b -> Bool paraCursorMovable d | d == MoveUp = not . atParaTop | d == MoveDown = not . atParaBottom | d == MovePrev = not . atParaFront | d == MoveNext = not . atParaBack | d == MoveHome || d == MoveEnd = const True | otherwise = const False moveParaCursor :: MoveDirection -> EditingPara c b -> EditingPara c b moveParaCursor d p@(EditingPara bs l as n) = revised where revised | d == MoveHome || d == MoveEnd = EditingPara bs (moveLineCursor d l) as n | d == MoveUp && atParaTop p = EditingPara bs (moveLineCursor MoveUp l) as n | d == MoveDown && atParaBottom p = EditingPara bs (moveLineCursor MoveDown l) as n | not (paraCursorMovable d p) = p | d == MoveUp = setParaCursorChar (getLineCursor l) $ EditingPara (tail bs) (editLine $ head bs) (viewLine l:as) (n-length (vlText $ head bs)) | d == MoveDown = setParaCursorChar (getLineCursor l) $ EditingPara (viewLine l:bs) (editLine $ head as) (tail as) (n+length (vlText $ viewLine l)) | lineCursorMovable d l = EditingPara bs (moveLineCursor d l) as n | d == MovePrev = setBack $ moveParaCursor MoveUp p | d == MoveNext = setFront $ moveParaCursor MoveDown p setBack (EditingPara bs l as n) = (EditingPara bs (moveLineCursor MoveDown l) as n) setFront (EditingPara bs l as n) = (EditingPara bs (moveLineCursor MoveUp l) as n) atParaFront :: EditingPara c b -> Bool atParaFront p@(EditingPara _ l _ _) = atParaTop p && atLineFront l atParaBack :: EditingPara c b -> Bool atParaBack p@(EditingPara _ l _ _) = atParaBottom p && atLineBack l seekParaFront :: EditingPara c b -> EditingPara c b seekParaFront p | atParaFront p = p | otherwise = seekParaFront $ moveParaCursor MoveUp p seekParaBack :: EditingPara c b -> EditingPara c b seekParaBack p | atParaBack p = p | otherwise = seekParaBack $ moveParaCursor MoveDown p appendToPara :: FixedFontParser a c => a -> EditingPara c (BreakType a) -> VisibleParaAfter c (BreakType a) -> EditingPara c (BreakType a) appendToPara parser (EditingPara bs l as n) (VisibleParaAfter cs) = reparseParaTail parser revised where revised = EditingPara bs l (as ++ cs) n prependToPara :: FixedFontParser a c => a -> VisibleParaBefore c (BreakType a) -> EditingPara c (BreakType a) -> EditingPara c (BreakType a) prependToPara parser (VisibleParaBefore cs _) (EditingPara bs l as _) = reparseParaTail parser revised where revised = EditingPara bs2 l2 as n2 (VisibleParaBefore bs' n') = parseParaBefore parser $ unparseParaBefore $ visibleParaBefore (bs ++ cs) (l2,bs2,n2) = if null bs' then (l,[],0) else (head bs' `prependToLine` l,tail bs',n'-length (vlText $ head bs')) modifyPara :: FixedFontParser a c => a -> EditAction c -> EditDirection -> EditingPara c (BreakType a) -> EditingPara c (BreakType a) modifyPara parser m d p = reparseParaTail parser revised where (EditingPara bs l as n) = mergeForEdit p revised = EditingPara bs (modifyLine m d l) as n -- Private below here. reparseParaTail :: FixedFontParser a c => a -> EditingPara c (BreakType a) -> EditingPara c (BreakType a) reparseParaTail parser p@(EditingPara bs l as n) = setParaEditChar offset revised where offset = getParaEditChar p revised = EditingPara bs (editLine line) after n (line:after) = breakLines parser $ joinLines (viewLine l:as) mergeForEdit :: EditingPara c b -> EditingPara c b mergeForEdit (EditingPara bs l as n) = EditingPara bs2 l2 as2 n2 where l2 = addAfter as $ addBefore bs l where addAfter (v:_) l = l `appendToLine` v addAfter _ l = l addBefore (v:_) l = v `prependToLine` l addBefore _ l = l bs2 = if null bs then [] else tail bs as2 = if null as then [] else tail as n2 = n - (if null bs then 0 else length (vlText $ head bs)) atParaTop :: EditingPara c b -> Bool atParaTop (EditingPara bs _ _ _) = null bs atParaBottom :: EditingPara c b -> Bool atParaBottom (EditingPara _ _ as _) = null as