module Yavie.EditorCore (
EditorC
, moveToLine
, moveInline
, moveToChar
, resetYank
, deleteToLine
, deleteInline
, deleteToChar
, setMonitorY
, insertChar
, insertNL
, inInsertMode
, outInsertMode
, isInsertMode
, yankToLine
, yankLinesCursTo
, yankLinesNum
, yankLinesFromTo
, getCursX
, getCursY
, saveToEditorCore
, resaveToEditorCore
, moveCursorForCursorIn
, scrollForCursorIn
, modifyContainer
, getsContainer
, getsContainerWithMonitorY
, getCursorYInMonitor
, getFileName
, linesForDisplay
, resizeMonitor
, addEmptyIfNoLine
, concatTwoLines
, saveToHistory
, undo
, redo
, flipCase
, pasteYanked
, pasteYankedAfter
, getBufferContents
, lineNum
, isModified
, resetModified
, resetStatus
, setStatus
, addStatus
, bsStatus
, setToCursY
, resetIOAction
, setIOAction
, runIOAction
, saveToFileC
, saveToTmpFile
) where
import Yavie.Tools ( lastIndex, inFromMToN, intoNToM, zeroToN,
opfs, takeXY, dropXY, reverseXY, findIndexXY )
import Data.Maybe ( fromMaybe )
import Data.Char ( isLower, isUpper, toLower, toUpper )
import Control.Arrow ( first )
import System.FilePath
data EditorC c = Editor {
fileName :: String ,
buffer :: [ String ] ,
status :: String ,
cursXX :: Int ,
cursX :: Int ,
cursY :: Int ,
monWidth :: Int ,
monHeight :: Int ,
monY :: Int ,
inlineYank :: Bool ,
yankedLines :: [ String ] ,
yankedString :: String ,
insertMode :: Bool ,
bufferHistory :: [ [ String ] ] ,
redoHistory :: [ [ String ] ] ,
udHistCur :: [ ( Int, Int ) ] ,
crHistCur :: ( Int, Int ) ,
rdHistCur :: [ ( Int, Int ) ] ,
modified :: Int ,
container :: c ,
exCommand :: EditorC c -> IO ( EditorC c ) ,
ioAction :: EditorC c -> IO ( EditorC c )
}
type ToLineMoveRule = Int -> Int -> Int -> Int -> Int
type InlineMoveRule = String -> Int -> Maybe Int
type ToCharMoveRule = Char -> Char -> Bool
type BufferFun = [ String ] -> Int -> Int -> ( Int, Int )
intoLine :: EditorC c -> EditorC c
intoLine ed@Editor { cursX = cx } =
ed { cursX = inFromMToN ( minCurX ed ) ( maxCurX ed ) cx }
setCursY :: Int -> EditorC c -> EditorC c
setCursY cy ed@Editor { buffer = buf, cursXX = cx } = scrollForCursorIn $
intoLine ed { cursY = inFromMToN 0 ( lastIndex buf ) cy, cursX = cx }
setCursPos :: Int -> Int -> EditorC c -> EditorC c
setCursPos cx cy ed =
intoLine ed { cursX = cx, cursXX = cx,
cursY = inFromMToN 0 ( lastIndex $ buffer ed ) cy }
toEditorFun :: BufferFun -> EditorC c -> EditorC c
toEditorFun f ed@Editor { buffer = buf, cursX = cx, cursY = cy } =
uncurry setCursPos ( f buf cx cy ) ed
moveToLine :: ToLineMoveRule -> EditorC c -> EditorC c
moveToLine f ed =
setCursY ( f ( numOfLines ed ) ( monHeight ed ) ( monY ed ) ( cursY ed ) ) ed
moveInline :: Bool -> Bool -> Bool -> InlineMoveRule -> EditorC c -> EditorC c
moveInline next back wrap = toEditorFun . getInline next back wrap
moveToChar :: Bool -> Bool -> ToCharMoveRule -> EditorC c -> EditorC c
moveToChar next back = toEditorFun . getToChar next back
getInline :: Bool -> Bool -> Bool -> InlineMoveRule -> BufferFun
getInline next back wrap mv buf cx cy = fromMaybe ( cx, cy ) $ gi wrap ncx cy
where
ncx = if next then goNext cx else cx
goNext = if back then (+ (1)) else (+ 1)
wrapped = if back then lastIndex buf else 0
lineTop y = if back then lastIndex $ buf !! y else 0
needWrap y = if back then y <= 0 else y >= lastIndex buf
gi w x y = case ( mv ( buf !! y ) x, mny ) of
( Just nx, _ ) -> Just ( nx, y )
( _ , Just ny ) -> gi nw ( lineTop ny ) ny
_ -> Nothing
where
mny = case ( needWrap y, w ) of
( False, _ ) -> Just $ goNext y
( _ , True ) -> Just wrapped
_ -> Nothing
nw = w && not ( needWrap y )
getToChar ::
Bool -> Bool -> ToCharMoveRule -> [ String ] -> Int -> Int -> ( Int, Int )
getToChar next back test buf =
curry $ renext . bufPos . uncurry ( gtc test nbuf ) . nbufPos . getnext
where
getnext = if next then uncurry nextPos else id
renext = if next then uncurry beforePos else id
nextPos = if back then getBackOverlineMore buf else getForwardOverline buf
beforePos = if back then getForwardOverline buf else getBackOverline buf
nbuf = if back then map ('\n':) buf else map (++"\n") buf
nbufPos = if back then first (+ 1) else id
bufPos = if back then first (+ ( 1)) else id
gtc p lns x y = newPos
where
newPos@( _, ny ) = ( baseX, y ) `addPos` dffPos
dffPos@( _, dy ) = fromMaybe ( 0, 0 ) $
findIndexXY ( p $ lns !! y !! x ) field
field = if back then reverseXY $ takeXY ( x + 1 ) y lns
else dropXY x y lns
baseX = if dy == 0 then x else if back then lastX else 0
lastX = lastIndex $ lns !! ny
addPos = if back then () `opfs` () else (+) `opfs` (+)
resetYank :: EditorC c -> EditorC c
resetYank ed = ed { inlineYank = False, yankedLines = [ ], yankedString = "" }
deleteToPos :: Int -> Int -> EditorC c -> EditorC c
deleteToPos nx ny ed@Editor { buffer = buf, cursX = cx, cursY = cy } =
ed { buffer = deleted,
inlineYank = True,
yankedString = yankedString ed ++ yanked,
cursY = nny, cursX = nnx }
where
( deleted, yanked ) = getDeleteToPos cx cy nx ny buf
( nny, nnx ) = min ( cy, cx ) ( ny, nx )
getDeleteToPos :: Int -> Int -> Int -> Int -> [ String ]
-> ( [ String ], String )
getDeleteToPos xmin ymin xmax ymax buf
| ( ymin, xmin ) <= ( ymax, xmax ) = ( deleted, yanked )
| otherwise = getDeleteToPos xmax ymax xmin ymin buf
where
deleted = take ymin buf ++ [ take xmin minLn ++ drop xmax maxLn ]
++ drop ( ymax + 1 ) buf
yanked
| ymin == ymax = drop xmin $ take xmax minLn
| otherwise = init $ unlines $ drop xmin minLn : drop ( ymin + 2 ) ( take ymax buf )
++ [ take xmax maxLn ]
minLn = buf !! ymin
maxLn = buf !! ymax
deleteToLine :: ToLineMoveRule -> EditorC c -> EditorC c
deleteToLine f ed =
let ny = f ( length $ buffer ed ) ( monHeight ed ) ( monY ed ) ( cursY ed )
in deleteLinesFromTo ( cursY ed ) ny ed
deleteLinesFromTo :: Int -> Int -> EditorC c -> EditorC c
deleteLinesFromTo y1 y2 ed@Editor { buffer = buf }
| y1 <= y2 = setCursY y1
ed { buffer = take y1 buf ++ drop ( y2 + 1 ) buf ,
yankedLines = drop y1 $ take ( y2 + 1 ) buf ,
inlineYank = False }
| otherwise = deleteLinesFromTo y2 y1 ed
deleteInline :: Bool -> Bool -> InlineMoveRule -> EditorC c -> EditorC c
deleteInline next back mv ed@Editor { buffer = buf, cursX = cx, cursY = cy } =
uncurry deleteToPos ( getInline next back False mv buf cx cy ) ed
deleteToChar :: Bool -> Bool -> ToCharMoveRule -> EditorC c -> EditorC c
deleteToChar next backward p
ed@Editor { buffer = buf, cursX = cx, cursY = cy } = deleteToPos nx ny ed
where
cx_ = if next
then if backward then cx 1
else cx + 1
else cx
( nx_, ny ) = getToChar False backward p buf cx_ cy
nx = if next
then if backward then nx_ + 1
else nx_ 1
else nx_
setMonitorY :: ( Int -> Int -> Int -> Int ) -> EditorC c -> EditorC c
setMonitorY f ed@Editor { buffer = buf, cursY = cy, monY = my, monHeight = mh } =
let nmy = ( if length buf > mh then zeroToN ( length buf mh ) else const 0 ) $
f mh cy my
in moveCursorForCursorIn ed { monY = nmy }
saveToEditorCore :: c -> Int -> Int -> String -> String -> EditorC c
saveToEditorCore c w h fn cnt = Editor {
buffer = if null cnt then [ "" ] else lines cnt ,
bufferHistory = [ ] ,
udHistCur = [ ] ,
crHistCur = ( 0, 0 ) ,
redoHistory = [ ] ,
rdHistCur = [ ] ,
fileName = fn ,
status = "" ,
cursXX = 0 ,
cursX = 0 ,
cursY = 0 ,
monWidth = w ,
monHeight = h 1 ,
monY = 0 ,
inlineYank = False ,
yankedLines = [ ] ,
yankedString = "" ,
insertMode = False ,
modified = 0 ,
container = c ,
exCommand = return ,
ioAction = return
}
setIOAction ::
( EditorC c -> IO ( EditorC c ) ) -> EditorC c -> EditorC c
setIOAction io ed = ed { ioAction = io }
runIOAction :: EditorC c -> IO ( EditorC c )
runIOAction ed = fmap resetIOAction $ ioAction ed ed
resetIOAction :: EditorC c -> EditorC c
resetIOAction ed = ed { ioAction = return }
saveToTmpFile :: EditorC c -> IO ( EditorC c )
saveToTmpFile ed = do
let lns = lineNum ed
fn = getFileName ed
putStr $ take ( lns lns ) "dummy"
writeFile ( takeDirectory fn ++ "/.yavie." ++ takeFileName fn ) ( getBufferContents ed )
return $ setStatus "written tmp" ed
saveToFileC :: EditorC c -> IO ( EditorC c )
saveToFileC ed =
do let lns = lineNum ed
putStr $ take ( lns lns ) "dummy"
writeFile ( getFileName ed ) ( getBufferContents ed )
return $ setStatus "written" $ resetModified ed
resaveToEditorCore :: String -> String -> EditorC c -> EditorC c
resaveToEditorCore fn cnt ed = ed {
buffer = if null cnt then [ "" ] else lines cnt ,
fileName = fn ,
bufferHistory = [ ] ,
udHistCur = [ ] ,
crHistCur = ( 0, 0 ) ,
redoHistory = [ ] ,
rdHistCur = [ ] ,
status = "new file " ++ fn ,
cursXX = if fileName ed == fn then cursX ed else 0 ,
cursX = if fileName ed == fn then cursXX ed else 0 ,
cursY = if fileName ed == fn then cursY ed else 0 ,
monY = if fileName ed == fn then monY ed else 0 ,
modified = 0
}
getFileName :: EditorC c -> String
getFileName = fileName
getBackOverline, getBackOverlineMore, getForwardOverline ::
[ String ] -> Int -> Int -> ( Int, Int )
getBackOverlineMore _ (1) 0 = ( 1, 0 )
getBackOverlineMore _ 0 0 = ( 1, 0 )
getBackOverlineMore buf (1) cy = let nx = length $ buf !! ( cy 1 )
in ( nx, cy 1 )
getBackOverlineMore _ cx cy = ( cx 1, cy )
getBackOverline _ (1) 0 = ( 1, 0 )
getBackOverline _ 0 0 = ( 1, 0 )
getBackOverline buf 0 cy = let nx = length $ buf !! ( cy 1 )
in ( nx, cy 1 )
getBackOverline _ cx cy = ( cx 1, cy )
getForwardOverline buf cx cy
| cy == length buf && cx == length ( buf !! cy ) = ( cx , cy )
| cx == length ( buf !! cy ) = ( 0 , cy + 1 )
| otherwise = ( cx + 1, cy )
modifyContainer :: ( c -> c ) -> EditorC c -> EditorC c
modifyContainer f ed@Editor { container = c } = ed { container = f c }
getsContainer :: ( c -> a ) -> EditorC c -> a
getsContainer f = f . container
getsContainerWithMonitorY :: ( c -> a ) -> ( a -> Int -> b ) -> EditorC c -> b
getsContainerWithMonitorY f op Editor { monY = my, container = c } =
op ( f c ) my
getCursorYInMonitor :: EditorC c -> Int
getCursorYInMonitor Editor { cursY = cy, monY = my } = cy my + 1
scrollForCursorIn :: EditorC c -> EditorC c
scrollForCursorIn = setMonitorY cursorIn
where cursorIn mh cy = intoNToM ( cy mh + 1 ) cy
moveCursorForCursorIn :: EditorC c -> EditorC c
moveCursorForCursorIn ed@Editor { cursY = cy, monHeight = mh, monY = my }
| cy < my = setCursY my ed
| cy > my + mh 1 = setCursY ( my + mh 1 ) ed
| otherwise = ed
linesForDisplay :: EditorC c -> ( ( Int, Int ), [ String ] )
linesForDisplay Editor
{ buffer = buf, status = stat, cursX = cx, cursY = cy,
monHeight = mh, monY = my, monWidth = mw } =
( ( cx, cy my ),
addToNA mh "" ( take mh ( drop my buf ) ) ++ [ statLn ] )
where
prcnt = if length buf > mh then my * 100 `div` ( length buf mh ) else 100
statLn = stat ++ addToNB ( mw length stat 16 ) ' '
( show ( cy + 1 ) ++ "," ++ show ( cx + 1 ) )
++ addToNB 16 ' ' ( show prcnt ++ "%" )
addToNB n x xs = replicate ( n length xs ) x ++ xs
addToNA n x xs = xs ++ replicate ( n length xs ) x
resizeMonitor :: Int -> Int -> EditorC c -> EditorC c
resizeMonitor w h ed = ed { monWidth = w, monHeight = h }
addEmptyIfNoLine :: EditorC c -> EditorC c
addEmptyIfNoLine ed@Editor { buffer = [ ] } = ed { buffer = [ "" ] }
addEmptyIfNoLine ed = ed
concatTwoLines :: EditorC c -> EditorC c
concatTwoLines ed@Editor { buffer = buf, cursY = cy }
= ed { buffer = take cy buf ++
[ buf !! cy ++ buf !! ( cy + 1 ) ] ++
drop ( cy + 2 ) buf }
insertChar :: Char -> EditorC c -> EditorC c
insertChar ch ed@Editor { buffer = buf, cursX = cx, cursY = cy }
= let thisLn = buf !! cy
ned = ed { buffer = take cy buf ++
[ take cx thisLn ++ [ ch ] ++ drop cx thisLn ] ++
drop ( cy + 1 ) buf ,
cursX = if cx < 0 then 1 else cx + 1 ,
cursXX = cursX ned }
in ned
insertNL :: EditorC c -> EditorC c
insertNL ed@Editor { buffer = buf, cursX = cx, cursY = cy }
= let thisLn = buf !! cy
ned = ed { buffer = take cy buf ++
[ take cx thisLn, drop cx thisLn ] ++
drop ( cy + 1 ) buf ,
cursX = 0 ,
cursY = cy + 1 ,
cursXX = cursX ned }
in scrollForCursorIn ned
yankToLine :: ToLineMoveRule -> EditorC c -> EditorC c
yankToLine = error "yet"
yankLinesCursTo :: Int -> EditorC c -> EditorC c
yankLinesCursTo ny ed@Editor { cursY = cy }
= yankLinesFromTo cy ny ed
yankLinesNum :: Int -> EditorC c -> EditorC c
yankLinesNum ln ed@Editor { cursY = cy }
= yankLinesCursTo ( cy + ln 1 ) ed
yankLinesFromTo :: Int -> Int -> EditorC c -> EditorC c
yankLinesFromTo y1 y2 ed@Editor { buffer = buf }
| y1 >= y2 = ed { yankedLines = take ( y1 y2 + 1 ) $ drop y2 buf,
inlineYank = False }
| otherwise = ed { yankedLines = take ( y2 y1 + 1 ) $ drop y1 buf,
inlineYank = False }
saveToHistory :: EditorC c -> EditorC c
saveToHistory ed@Editor
{ buffer = buf, bufferHistory = bufHst,
cursX = cx, cursY = cy, udHistCur = uhc, modified = m } =
ed { bufferHistory = buf : bufHst, udHistCur = ( cx, cy ) : uhc ,
redoHistory = [], rdHistCur = [],
modified = m + 1 }
undo :: EditorC c -> EditorC c
undo ed@Editor
{ buffer = buf, bufferHistory = bufHst, redoHistory = rdHst ,
udHistCur = uhc, rdHistCur = rhc, crHistCur = chc ,
modified = m } =
if not $ null bufHst
then scrollForCursorIn $
ed { buffer = head bufHst ,
bufferHistory = tail bufHst ,
redoHistory = buf : rdHst ,
cursX = fst $ head uhc, cursY = snd $ head uhc ,
crHistCur = head uhc,
udHistCur = tail uhc, rdHistCur = chc : rhc ,
modified = m 1 }
else ed
redo :: EditorC c -> EditorC c
redo ed@Editor
{ buffer = buf, bufferHistory = bufHst, redoHistory = rdHst ,
udHistCur = uhc, rdHistCur = rhc, crHistCur = chc ,
modified = m } =
if not $ null rdHst
then scrollForCursorIn $
ed { buffer = head rdHst ,
bufferHistory = buf : bufHst ,
redoHistory = tail rdHst,
cursX = fst chc, cursY = snd chc ,
crHistCur = head rhc ,
udHistCur = chc : uhc, rdHistCur = tail rhc ,
modified = m + 1 }
else ed
flipCase :: EditorC c -> EditorC c
flipCase ed@Editor { buffer = buf, cursX = cx, cursY = cy }
= let thisLn = buf !! cy
in ed { buffer =
take cy buf ++
[ take cx thisLn ++ [ fc ( thisLn !! cx ) ]
++ drop ( cx + 1 ) thisLn ] ++
drop ( cy + 1 ) buf }
where
fc c | isUpper c = toLower c
| isLower c = toUpper c
| otherwise = c
pasteYankedInline :: EditorC c -> EditorC c
pasteYankedInline ed@Editor { buffer = buf, cursY = cy, cursX = cx }
= ed { buffer = nbuf }
where
thisLn = buf !! cy
y = lines_ $ yankedString ed
nbuf = case y of
[ ] -> error "yet"
[ sy ] -> take cy buf ++ [ take cx thisLn ++ sy ++ drop cx thisLn ]
++ drop ( cy + 1 ) buf
( fy : ry ) -> take cy buf ++ [ take cx thisLn ++ fy ]
++ init ry
++ [ last ry ++ drop cx thisLn ]
++ drop ( cy + 1 ) buf
lines_ :: String -> [ String ]
lines_ = splitBy '\n'
splitBy :: Eq a => a -> [ a ] -> [ [ a ] ]
splitBy _ [ ] = [ ]
splitBy x xs = takeWhile (/= x) xs : splitBy x ( tailIfX $ dropWhile (/= x) xs )
where
tailIfX ( y : ys ) | x == y = ys
tailIfX ya = ya
pasteYankedInlineAfter :: EditorC c -> EditorC c
pasteYankedInlineAfter ed@Editor { buffer = buf, cursY = cy, cursX = cx }
= let thisLn = buf !! cy
in ed { buffer = take cy buf ++
[ take ( cx + 1 ) thisLn ++ y ++ drop ( cx + 1 ) thisLn ] ++
drop ( cy + 1 ) buf }
where
y = yankedString ed
getCursX, getCursY :: EditorC c -> Int
getCursX = cursX
getCursY = cursY
getBufferContents :: EditorC c -> String
getBufferContents Editor { buffer = buf } = unlines buf
lineNum :: EditorC c -> Int
lineNum = length . buffer
pasteYankedLine :: EditorC c -> EditorC c
pasteYankedLine ed@Editor { buffer = buf }
= ed { buffer = take cy buf ++ y ++ drop cy buf }
where
cy = getCursY ed
y = yankedLines ed
pasteYankedLineAfter :: EditorC c -> EditorC c
pasteYankedLineAfter ed@Editor { buffer = buf }
= ed { buffer = take ( cy + 1 ) buf ++ y ++ drop ( cy + 1 ) buf }
where
cy = getCursY ed
y = yankedLines ed
pasteYanked :: EditorC c -> EditorC c
pasteYanked ed =
if inlineYank ed then pasteYankedInline ed
else pasteYankedLine ed
pasteYankedAfter :: EditorC c -> EditorC c
pasteYankedAfter ed =
if inlineYank ed then pasteYankedInlineAfter ed
else pasteYankedLineAfter ed
isModified :: EditorC c -> Bool
isModified = (0 /=) . modified
resetModified :: EditorC c -> EditorC c
resetModified ed = ed { modified = 0 }
addStatus :: Char -> EditorC c -> EditorC c
addStatus ch ed@Editor { status = stat } = ed { status = stat ++ [ ch ] }
bsStatus :: EditorC c -> EditorC c
bsStatus ed@Editor { status = stat } = ed { status = init_ stat }
init_ :: [ a ] -> [ a ]
init_ [ ] = [ ]
init_ [ x ] = [ x ]
init_ lst = init lst
resetStatus :: EditorC c -> EditorC c
resetStatus ed = ed { status = "" }
setStatus :: String -> EditorC c -> EditorC c
setStatus str ed = ed { status = str }
setToCursY :: ( Int -> c -> c ) -> EditorC c -> EditorC c
setToCursY f ed@Editor { cursY = cy, container = c } =
ed { container = f cy c }
inInsertMode, outInsertMode :: EditorC c -> EditorC c
inInsertMode ed = ed { insertMode = True }
outInsertMode ed = intoLine $ ed { insertMode = False }
isInsertMode :: EditorC c -> Bool
isInsertMode = insertMode
maxCurX :: EditorC c -> Int
maxCurX ed@Editor { insertMode = im }
| im = length $ cursorLine ed
| otherwise = lastIndex $ cursorLine ed
minCurX :: EditorC c -> Int
minCurX ed@Editor { insertMode = im }
| im = 1
| null $ cursorLine ed = 1
| otherwise = 0
numOfLines :: EditorC c -> Int
numOfLines Editor { buffer = buf } = length buf
cursorLine :: EditorC c -> String
cursorLine Editor { buffer = buf, cursY = cy } = buf !! cy