-- | A zipped list with special cursor at the ends. In fact it handles inserting at start and appending at end where the cursor is pointing to non existing lines.moduleEnginewhereimportTest.QuickCheckimportControl.MonadimportData.MaybeimportData.List-- | represent an action, which can fail with Nothing , an index errortypeChangea=a->Maybea-- | Pos represent the position addressed in the enginedataPos-- | the engine addresses a real line =Line{nth::Int-- ^ The index of the line starting from 1 }-- | the engine addresses before first line , if ever present|Begin-- | the engine addresses after last line|End{lns::Int-- ^ The number of lines in the engine}derivingShow-- | relative distance between two positionsdistance(Linen)(Linem)=m-n+1distanceBegin(Linem)=mdistance(Linen)(Endm)=m-ndistanceBegin(Endm)=mdistance__=0dataEngine-- | the cursor when its pointing to a real line (eg line function doesn't fail)=Inside{left::[String],-- ^ lines before the cursor (reversed order)cursor::String,-- ^ addressed lineright::[String]-- ^ lines after the cursor}-- | the cursor is pointing either to insert at the front of the file or -- append at the end of the file.|Corner{elems::Either[String][String]-- ^ Left lines is in append mode, Right is in insert at front mode.}deriving(Show,Eq)-- | An empty engineempty::Engineempty=listIn[]-- | An engine is isomorphic to Engine listlistIn::[String]->Engine-- | Extract the list from the enginelistOut::Engine->Maybe[String]-- | Extract n lines from the position addressedlinen::Int->Engine->Maybe[String]-- | Extract the addressed lineline::Engine->MaybeStringlinew=head`fmap`linen1w-- | Possibly set the addressed line to the nth linejump::Int->ChangeEngine-- | Insert some lines before the addressed lineins::[String]->ChangeEngine-- | Insert some lines after the addressed lineadd::[String]->ChangeEngine-- | Delete the addressed line , address the next onedel::ChangeEngine-- | Delete n lines from the addressed positiondeln::Int->ChangeEngine-- | Address an append positionend::ChangeEngine-- | Address before the first linestart::ChangeEngine-- | The number of the addressed linepos::Engine->Pos-- | Address the next linenext::ChangeEngine-- | Address the prev lineprev::ChangeEngine-- | Jump back n lines prevn::Int->ChangeEngineprevn0w=Justwprevnnw=prevw>>=prevn(n-1)-- | Jump ahead n linesnextn::Int->ChangeEnginenextn0w=Justwnextnnw=nextw>>=nextn(n-1)-- | Jump n lines relative to the addredded linerjump::Int->ChangeEnginerjumpn=iterateMn(ifn>0thennextelseprev)whereiterateMnfw|n>0=fw>>=iterateM(n-1)f|True=Justw-- | Create all the engines from the addressed one to the last one tillend::Engine->[Engine]-- | all the next engines from the addressed next to itself , wrapping aroundfwdcycle::Engine->[Engine]-- | Create all the engines from the start to the addressed one includedfromstart::Engine->[Engine]-- | all the prev engines from the addressed prev to itself , wrapping aroundbwdcycle::Engine->[Engine]-- | last element if presentlast::ChangeEnginelastt=endt>>=prev-- | first element if presentfirst::ChangeEnginefirstt=startt>>=nextlistInxs=Corner(Rightxs)prev(Corner(Right_))=Nothingprev(Corner(Left[]))=error"empty Corner Left"prev(Corner(Left(l:ls)))=Just$Insidelsl[]prev(Inside[]xls)=Just$Corner(Right(x:ls))prev(Inside(l:ls)xrs)=Just$Insidelsl(x:rs)next(Corner(Right[]))=Nothingnext(Corner(Right(r:rs)))=Just$Inside[]rrsnext(Corner(Left[]))=error"empty Corner Left"next(Corner(Left_))=Nothingnext(Insidelsx[])=Just$Corner(Left(x:ls))next(Insidelsx(r:rs))=Just$Inside(x:ls)rrsendw@(Corner(Left_))=Justwendw=nextw>>=endstartw@(Corner(Right_))=Justwstartw=prevw>>=startpos(Corner(Leftls))=End(lengthls+1)pos(Corner(Right_))=Beginpos(Insidels__)=Line$lengthls+1del(Corner_)=Nothingdel(Inside[]_[])=Just$Corner(Right[])del(Insidels_[])=Just$Corner(Leftls)del(Insidels_(r:rs))=Just$Insidelsrrsdelnnw|n==0=Justw|True=delw>>=deln(n-1)addxs(Corner(Left_))=Nothingaddxs(Corner(Rightrs))=Just$Corner$Right(xs++rs)addxs(Insidelsxrs)=Just$Insidelsx(xs++rs)insxsw=prevw>>=addxs>>=nextjumpnw=startw>>=rjumpnlistOutw=startw>>=\(Corner(Rightrs))->returnrslinen0_=Just[]linen_(Corner_)=Nothinglinennw@(Inside_x_)=nextw>>=linen(n-1)>>=Just.(x:)tillendw=filterisInside(runnernextw)fromstartw=reverse$filterisInside(runnerprevw)fwdcyclew=filterisInside$runnernextw++reverse(runnerprevw)++[w]bwdcyclew=filterisInside$runnerprevw++reverse(runnernextw)++[w]isInside::Engine->BoolisInside(Inside___)=TrueisInside_=Falserunner::ChangeEngine->Engine->[Engine]runneropw=maybe[](\w->(w:runneropw))(opw)