-- Copyright (C) 2002-2003 David Roundy---- This program is free software; you can redistribute it and/or modify-- it under the terms of the GNU General Public License as published by-- the Free Software Foundation; either version 2, or (at your option)-- any later version.---- This program is distributed in the hope that it will be useful,-- but WITHOUT ANY WARRANTY; without even the implied warranty of-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-- GNU General Public License for more details.---- You should have received a copy of the GNU General Public License-- along with this program; see the file COPYING. If not, write to-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,-- Boston, MA 02110-1301, USA.{-# OPTIONS_GHC -fglasgow-exts #-}{-# LANGUAGE CPP, ForeignFunctionInterface #-}-- , ScopedTypeVariables, TypeOperators, PatternGuards #-}#include "gadts.h"moduleDarcs.SelectChanges(selectChanges,WhichChanges(..),viewChanges,withSelectedPatchFromRepo,filterOutConflicts,runSelection,selectionContextPrim,selectionContext)whereimportSystem.IOimportData.List(intersperse)importData.Maybe(catMaybes,isJust,fromJust)importData.Char(toUpper)importControl.Monad(when,(>=>))importControl.Monad.Trans(liftIO)importControl.Monad.Reader(ReaderT,Reader,asks,runReader,runReaderT)importControl.Monad.State(State(..),StateT,modify,gets,execStateT)importSystem.Exit(exitWith,ExitCode(ExitSuccess))importEnglish(Noun(..),englishNum)importDarcs.Arguments(showFriendly)importDarcs.Hopefully(PatchInfoAnd,hopefully,n2pia)importDarcs.Repository(Repository,readRepo,unrecordedChanges)importDarcs.Patch(RepoPatch,Patchy,Prim,summary,invert,listTouchedFiles,commuteFLorComplain,fromPrims,anonymous)importDarcs.Patch.Set(newset2RL)importqualifiedDarcs.Patch(thing,things)importDarcs.Patch.Split(Splitter(..))importDarcs.Witnesses.Ordered(FL(..),RL(..),(:>)(..),(:||:)(..),(+>+),lengthFL,concatRL,mapFL_FL,spanFL,spanFL_M,reverseFL,(+<+),mapFL,filterFL)importDarcs.Witnesses.WZipper(FZipper(..),left,right,rightmost,lengthFZ,focus,toEnd)importDarcs.Patch.Choices(PatchChoices,patchChoices,patchChoicesTpsSub,forceFirst,forceLast,makeUncertain,tag,getChoices,refineChoices,separateFirstFromMiddleLast,patchSlot',selectAllMiddles,forceMatchingLast,forceMatchingFirst,makeEverythingLater,makeEverythingSooner,TaggedPatch,tpPatch,Slot(..),substitute)importDarcs.Patch.Permutations(partitionConflictingFL,selfCommuter,commuterIdRL)importqualifiedDarcs.Patch.TouchesFilesasTouchesFilesimportDarcs.PrintPatch(printFriendly,printPatch,printPatchPager)importDarcs.Match(haveNonrangeMatch,matchAPatch,matchAPatchread)importDarcs.Flags(DarcsFlag(Summary,DontGrabDeps,Verbose,DontPromptForDependencies,SkipConflicts),isInteractive)importDarcs.Witnesses.Sealed(FlippedSeal(..),flipSeal,seal2,unseal2,Sealed(..),Sealed2(..))importDarcs.Utils(askUser,promptChar,PromptConfig(..))importDarcs.Lock(editText)importPrinter(prefix,putDocLn)-- | When asking about patches, we either ask about them in-- oldest-first or newest first (with respect to the current ordering-- of the repository), and we either want an initial segment or a-- final segment of the poset of patches.---- @First@: ask for an initial-- segment, first patches first (default for all pull-like commands)---- @FirstReversed@: ask for an initial segment, last patches first-- (used to ask about dependencies in record, and for pull-like-- commands with the @--reverse@ flag).---- @LastReversed@: ask for a final segment, last patches first. (default-- for unpull-like commands, except for selecting *primitive* patches in-- rollback)---- @Last@: ask for a final segment, first patches first. (used for selecting-- primitive patches in rollback, and for unpull-like commands with the-- @--reverse@ flagdataWhichChanges=Last|LastReversed|First|FirstReversedderiving(Eq,Show)-- | A @WhichChanges@ is backwards if the order in which patches are presented-- is the opposite of the order of dependencies for that operation.backward::WhichChanges->Boolbackwardw=w==Last||w==FirstReversed-- | The type of the function we use to filter patches when @--match@ is-- given.typeMatchCriterionp=WhichChanges->[DarcsFlag]->Sealed2p->Bool-- | A @PatchSelectionContext@ contains all the static settings for selecting-- patches. See "PatchSelectionM"dataPatchSelectionContextp=PSC{opts::[DarcsFlag],splitter::Maybe(Splitterp),files::[FilePath],matchCriterion::MatchCriterionp,jobname::String}-- | A 'PatchSelectionContext' for selecting 'Prim' patches.selectionContextPrim::String->[DarcsFlag]->Maybe(SplitterPrim)->[FilePath]->PatchSelectionContextPrimselectionContextPrimjnosplfs=PSC{opts=o,splitter=spl,files=fs,matchCriterion=triv,jobname=jn}-- | A 'PatchSelectionContext' for selecting full patches ('PatchInfoAnd' patches)selectionContext::(RepoPatchp)=>String->[DarcsFlag]->Maybe(Splitter(PatchInfoAndp))->[FilePath]->PatchSelectionContext(PatchInfoAndp)selectionContextjnosplfs=PSC{opts=o,splitter=spl,files=fs,matchCriterion=iswanted,jobname=jn}-- | The dynamic parameters for interactive selection of patches.dataInteractiveSelectionContextpC(xy)=ISC{total::Int-- ^ total number of patches,current::Int-- ^ number of already-seen patches,tps::FZipper(TaggedPatchp)C(xy)-- ^ the patches we offer,choices::PatchChoicespC(xy)-- ^ the user's choices}typePatchSelectionMpa=ReaderT(PatchSelectionContextp)atypeInteractiveSelectionMpC(xy)a=StateT(InteractiveSelectionContextpC(xy))(PatchSelectionMpIO)atypePatchSelectionpC(xy)=PatchSelectionMpIO((FLp:>FLp)C(xy))-- Common match criteria-- | For commands without @--match@, 'triv' matches all patchestriv::MatchCriterionptriv=\___->True-- | 'iswanted' selects patches according to the @--match@ flag in-- opts'iswanted::Patchyp=>MatchCriterion(PatchInfoAndp)iswantedwhchopts'=unseal2(iwwhchopts')whereiwFirsto=matchAPatcho.hopefullyiwLasto=matchAPatcho.hopefullyiwLastReversedo=matchAPatcho.hopefully.invertiwFirstReversedo=matchAPatcho.hopefully.invertliftR::Monadm=>Readerra->ReaderTrmaliftR=asks.runReader-- | runs a 'PatchSelection' action in the given 'PatchSelectionContext'.runSelection::(Patchyp)=>PatchSelectionpC(xy)->PatchSelectionContextp->IO((FLp:>FLp)C(xy))runSelection=runReaderT-- | Select patches from a @FL@.selectChanges::forallpC(xy).Patchyp=>WhichChanges->FLpC(xy)->PatchSelectionpC(xy)selectChangesFirst=sc1FirstselectChangesLast=sc1LastselectChangesFirstReversed=return.invert>=>sc1FirstReversed>=>return.invertCselectChangesLastReversed=return.invert>=>sc1LastReversed>=>return.invertCsc1::forallpC(xy).Patchyp=>WhichChanges->FLpC(xy)->PatchSelectionpC(xy)sc1whch=((liftR.patchesToConsiderwhch)>=>realSelectChangeswhch>=>return.selectedPatcheswhch>=>(liftR.canonizeAfterSplitter))-- | inverses the choices that have been madeinvertC::(Patchyp)=>(FLp:>FLp)C(xy)->(FLp:>FLp)C(yx)invertC(a:>b)=(invertb):>(inverta)-- | Shows the patch that is actually being selected the way the user-- should see it.repr::(Patchyp)=>WhichChanges->Sealed2p->Sealed2preprFirst(Sealed2p)=Sealed2preprLastReversed(Sealed2p)=Sealed2(invertp)reprLast(Sealed2p)=Sealed2preprFirstReversed(Sealed2p)=Sealed2(invertp)-- | The equivalent of 'selectChanges' for the @darcs changes@ commandviewChanges::Patchyp=>[DarcsFlag]->[Sealed2p]->IO()viewChangesoptsps=textViewoptsNothing0[]ps-- | The type of the answers to a "shall I [wiggle] that [foo]?" question-- They are found in a [[KeyPress]] bunch, each list representing a set of-- answers which belong togetherdataKeyPress=KeyPress{kp::Char,kpHelp::String}-- | Generates the help for a set of basic and advanced 'KeyPress' groups.helpFor::String->[[KeyPress]]->[[KeyPress]]->StringhelpForjnbasicKeypressesadvancedKeyPresses=unlines$["How to use "++jn++":"]++(concat$intersperse[""]$map(maphelp)keypresses)++["","?: show this help","","<Space>: accept the current default (which is capitalized)"]wherehelpi=kpi:(": "++kpHelpi)keypresses=basicKeypresses++advancedKeyPresses-- | The keys used by a list of 'keyPress' groups.keysFor::[[KeyPress]]->[Char]keysFor=concatMap(mapkp)-- | The function for selecting a patch to amend record. Read at your own risks.withSelectedPatchFromRepo::forallpC(rut).RepoPatchp=>String->RepositorypC(rut)->[DarcsFlag]->(FORALL(a)(FL(PatchInfoAndp):>PatchInfoAndp)C(ar)->IO())->IO()withSelectedPatchFromRepojnrepositoryojob=dop_s<-readReporepositorysp<-wspfrjn(matchAPatchreado)(newset2RLp_s)NilFLcasespofJust(FlippedSeal(skipped:>selected))->job(skipped:>selected)Nothing->doputStrLn$"Cancelling "++jn++" since no patch was selected."-- | This ensures that the selected patch commutes freely with the skipped patches, including pending-- and also that the skipped sequences has an ending context that matches the recorded state, z,-- of the repository.wspfr::RepoPatchp=>String->(FORALL(ab)(PatchInfoAndp)C(ab)->Bool)->RL(PatchInfoAndp)C(xy)->FL(PatchInfoAndp)C(yu)->IO(Maybe(FlippedSeal(FL(PatchInfoAndp):>(PatchInfoAndp))C(u)))wspfr__NilRL_=returnNothingwspfrjnmatches(p:<:pps)skipped|not$matchesp=wspfrjnmatchespps(p:>:skipped)|otherwise=casecommuteFLorComplain(p:>skipped)ofLeft_->doputStrLn"\nSkipping depended-upon patch:"printFriendly[]pwspfrjnmatchespps(p:>:skipped)Right(skipped':>p')->doprintFriendly[]pletrepeat_this=wspfrjnmatches(p:<:pps)skippedbasic_options=[[KeyPress'y'(jn++" this patch"),KeyPress'n'("don't "++jn++" it")]]advanced_options=[[KeyPress'v'"view this patch in full",KeyPress'p'"view this patch in full with pager",KeyPress'x'"view a summary of this patch",KeyPress'q'("cancel "++jn)]]letprompt="Shall I "++jn++" this patch?"yorn<-promptChar$PromptConfig{pPrompt=prompt,pBasicCharacters=keysForbasic_options,pAdvancedCharacters=keysForadvanced_options,pDefault=Just'n',pHelp="?h"}caseyornof'y'->return$Just$flipSeal$skipped':>p''n'->wspfrjnmatchespps(p:>:skipped)'v'->printPatchp>>repeat_this'p'->printPatchPagerp>>repeat_this'x'->doputDocLn$prefix" "$summaryprepeat_this'q'->doputStrLn$jn_cap++" cancelled."exitWith$ExitSuccess_->doputStrLn$helpForjnbasic_optionsadvanced_optionsrepeat_thiswherejn_cap=(toUpper$headjn):tailjn-- After selecting with a splitter, the results may not be canonicalcanonizeAfterSplitter::(FLp:>FLp)C(xy)->Reader(PatchSelectionContextp)((FLp:>FLp)C(xy))canonizeAfterSplitter(x:>y)=dospl'<-askssplittercasespl'ofNothing->return(x:>y)Justspl->return$canonizeSplitsplx:>canonizeSplitsplyrealSelectChanges::forallpC(xy).Patchyp=>WhichChanges->PatchChoicespC(xy)->PatchSelectionMpIO(PatchChoicespC(xy))realSelectChangeswhchautoChoices=doo<-asksoptsifnot$isInteractiveothenreturn$promoteautoChoiceselsefliprefineChoicesautoChoices$textSelectwhchwhereforward=not$backwardwhchpromote=ifforwardthenmakeEverythingSoonerelsemakeEverythingLater-- | When using @--match@, remove unmatched patches not depended upon by matched-- patches.deselectUnwanted::forallpC(xy).Patchyp=>WhichChanges->PatchChoicespC(xy)->Reader(PatchSelectionContextp)(PatchChoicespC(xy))deselectUnwantedwhichchpc=doo<-asksoptsc<-(asksmatchCriterion)letiswanted_=cwhichcho.seal2.tpPatchselect=ifforwardthenforceMatchingFirstiswanted_elseforceMatchingLastiswanted_deselect=ifforwardthenforceMatchingLast(not.iswanted_)elseforceMatchingFirst(not.iswanted_)ifhaveNonrangeMatchothenifDontGrabDeps`elem`othenreturn$deselectpcelsedoreturn.demote$selectpcelsereturnpcwhereforward=not$backwardwhichchdemote=ifforwardthenmakeEverythingLaterelsemakeEverythingSooner-- | Selects the patches matching the match criterion, and puts them first or last-- according to whch, while respecting any dependencies.patchesToConsider::Patchyp=>WhichChanges->FLpC(xy)->Reader(PatchSelectionContextp)(PatchChoicespC(xy))patchesToConsiderwhchps=dofs<-asksfileso<-asksoptsletdeselectNotTouching=casewhchofFirst->TouchesFiles.deselectNotTouchingLast->TouchesFiles.selectNotTouchingFirstReversed->TouchesFiles.selectNotTouchingLastReversed->TouchesFiles.deselectNotTouchingeverything=patchChoicespsifnullfs&&not(haveNonrangeMatcho)thenreturneverythingelsedonotUnwanted<-deselectUnwantedwhcheverythingreturn$deselectNotTouchingfsnotUnwanted-- | Returns the results of a patch selection user interactionselectedPatches::Patchyp=>WhichChanges->PatchChoicespC(yz)->(FLp:>FLp)C(yz)selectedPatchesLastpc=casegetChoicespcoffc:>mc:>lc->mapFL_FLtpPatch(fc+>+mc):>mapFL_FLtpPatchlcselectedPatchesFirstpc=caseseparateFirstFromMiddleLastpcofxs:>ys->mapFL_FLtpPatchxs:>mapFL_FLtpPatchysselectedPatchesLastReversedpc=caseseparateFirstFromMiddleLastpcofxs:>ys->mapFL_FLtpPatch(xs):>(mapFL_FLtpPatch(ys))selectedPatchesFirstReversedpc=casegetChoicespcoffc:>mc:>lc->(mapFL_FLtpPatch(fc+>+mc)):>(mapFL_FLtpPatchlc)-- | Runs a function on the underlying @PatchChoices@ objectliftChoices::forallpaC(xy).Patchyp=>State(PatchChoicespC(xy))a->InteractiveSelectionMpC(xy)aliftChoicesact=doch<-getschoiceslet(result,ch')=runStateactchmodify$\isc->isc{choices=ch}returnresult-- | @justDone n@ notes that @n@ patches have just been processedjustDone::Patchyp=>Int->InteractiveSelectionMpC(xy)()justDonen=modify$\isc->isc{current=currentisc+n}-- | The actual interactive selection process.textSelect::forallpC(xy).Patchyp=>WhichChanges->FL(TaggedPatchp)C(xy)->PatchChoicespC(xy)->PatchSelectionMpIO(PatchChoicespC(xy))textSelectwhchtpspcs=douserSelection<-execStateT(skipMundanewhch>>showCurwhch>>textSelect'whch)$ISC{total=lengthFLtps,current=0,tps=FZipperNilRLtps,choices=pcs}return$choicesuserSelectiontextSelect'::Patchyp=>WhichChanges->InteractiveSelectionMpC(xy)()textSelect'whch=doz<-getstpswhen(not$rightmostz)$dotextSelectOnewhchtextSelect'whchoptionsBasic::[Char]->[Char]->[KeyPress]optionsBasicjnaThing=[KeyPress'y'(jn++" this "++aThing),KeyPress'n'("don't "++jn++" it"),KeyPress'w'("wait and decide later, defaulting to no")]optionsFile::[Char]->[KeyPress]optionsFilejn=[KeyPress's'("don't "++jn++" the rest of the changes to this file"),KeyPress'f'(jn++" the rest of the changes to this file")]optionsView::String->String->[KeyPress]optionsViewaThingsomeThings=[KeyPress'v'("view this "++aThing++" in full"),KeyPress'p'("view this "++aThing++" in full with pager"),KeyPress'l'("list all selected "++someThings)]optionsSummary::String->[KeyPress]optionsSummaryaThing=[KeyPress'x'("view a summary of this "++aThing)]optionsQuit::String->String->[KeyPress]optionsQuitjnsomeThings=[KeyPress'd'(jn++" selected "++someThings++", skipping all the remaining "++someThings),KeyPress'a'(jn++" all the remaining "++someThings),KeyPress'q'("cancel "++jn)]optionsNav::String->[KeyPress]optionsNavaThing=[KeyPress'j'("skip to next "++aThing),KeyPress'k'("back up to previous "++aThing)]optionsSplit::Maybe(Splittera)->String->[KeyPress]optionsSplitsplitaThing|Just_<-split=[KeyPress'e'("interactively edit this "++aThing)]|otherwise=[]options::forallpC(xy).(Patchyp)=>Bool->InteractiveSelectionMpC(xy)([[KeyPress]],[[KeyPress]])optionssingle=dosplit<-askssplitterjn<-asksjobnameaThing<-thingsomeThings<-thingso<-asksoptsreturn$([optionsBasicjnaThing],[optionsSplitsplitaThing]++(ifsinglethen[optionsFilejn]else[])++[optionsViewaThingsomeThings++ifSummary`elem`othen[]elseoptionsSummaryaThing]++[optionsQuitjnsomeThings]++[optionsNavaThing])-- | Returns a @Sealed2@ version of the patch we are asking the user-- about.currentPatch::forallpC(xy).Patchyp=>InteractiveSelectionMpC(xy)(Maybe(Sealed2(TaggedPatchp)))currentPatch=do(FZipper_tps_todo)::FZipper(TaggedPatchp)C(xy)<-getstpscasetps_todoofNilFL->returnNothing(tp:>:_)->return$Just(Sealed2tp)-- | Returns the patches we have yet to ask the user about.todo::forallpC(xy).Patchyp=>InteractiveSelectionMpC(xy)(FlippedSeal(FL(TaggedPatchp))C(y))todo=do(FZipper_tps_todo)<-getstpsreturn(FlippedSealtps_todo)-- | Modify the underlying @PatchChoices@ by some functionmodChoices::forallpC(xy).Patchyp=>(PatchChoicespC(xy)->PatchChoicespC(xy))->InteractiveSelectionMpC(xy)()modChoicesf=modify$\isc->isc{choices=f$choicesisc}-- | returns @Just f@ if the 'currentPatch' only modifies @f@,-- @Nothing@ otherwise.currentFile::forallpC(xy).Patchyp=>InteractiveSelectionMpC(xy)(MaybeFilePath)currentFile=doc<-currentPatchreturn$casecofNothing->NothingJust(Sealed2tp)->caselistTouchedFilestpof[f]->Justf_->Nothing-- | @decide True@ selects the current patch, and @decide False@ deselects-- it.decide::forallpC(xytu).Patchyp=>WhichChanges->Bool->TaggedPatchpC(tu)->InteractiveSelectionMpC(xy)()decidewhchtakeOrDroptp=ifbackwardwhch==takeOrDrop-- we go backward xor we are droppingthenmodChoices$forceLast(tagtp)elsemodChoices$forceFirst(tagtp)-- | like 'decide', but for all patches touching @file@decideWholeFile::forallpC(xy).Patchyp=>WhichChanges->FilePath->Bool->InteractiveSelectionMpC(xy)()decideWholeFilewhchfiletakeOrDrop=doFlippedSealtps_todo<-todoletpatches_to_skip=filterFL(\tp'->listTouchedFilestp'==[file])tps_todomapM_(unseal2$decidewhchtakeOrDrop)patches_to_skip-- | Undecide the current patch.postponeNext::forallpC(xy).Patchyp=>InteractiveSelectionMpC(xy)()postponeNext=doJust(Sealed2tp)<-currentPatchmodChoices$makeUncertain(tagtp)-- | Focus the next patch.skipOne::forallpC(xy).Patchyp=>InteractiveSelectionMpC(xy)()skipOne=modifysowheresox=x{tps=right(tpsx),current=currentx+1}-- | Focus the previous patch.backOne::forallpC(xy).Patchyp=>InteractiveSelectionMpC(xy)()backOne=modifysowheresoisc=isc{tps=left(tpsisc),current=max(currentisc-1)0}-- | Split the current patch (presumably a hunk), and add the replace it-- with its parts.splitCurrent::forallpC(xy).Patchyp=>Splitterp->InteractiveSelectionMpC(xy)()splitCurrents=doFZippertps_done(tp:>:tps_todo)<-getstpscase(applySplitters(tpPatchtp))ofNothing->return()Just(text,parse)->donewText<-liftIO$editText"darcs-patch-edit"textcaseparsenewTextofNothing->return()Justps->dotps_new<-liftIO.return.snd$patchChoicesTpsSub(Just(tagtp))psmodify$\isc->isc{total=(totalisc+lengthFLtps_new-1),tps=(FZippertps_done(tps_new+>+tps_todo)),choices=(substitute(seal2(tp:||:tps_new))(choicesisc))}-- | Returns a list of the currently selected patches, in-- their original context, i.e., not commuted past unselected-- patches.selected::forallpC(xy).Patchyp=>WhichChanges->InteractiveSelectionMpC(xy)[Sealed2p]selectedwhichch=doc<-getschoices(first_chs:>_:>last_chs)<-return$getChoicescreturn$ifbackwardwhichchthenmapFL(reprwhichch.Sealed2.tpPatch)$last_chselsemapFL(reprwhichch.Sealed2.tpPatch)$first_chs-- | Prints the list of the selected patches. See 'selected'.printSelected::Patchyp=>WhichChanges->InteractiveSelectionMpC(xy)()printSelectedwhichch=dosomeThings<-thingso<-asksoptss<-selectedwhichchliftIO$doputStrLn$"---- Already selected "++someThings++" ----"mapM_(putDocLn.unseal2(showFriendlyo))sputStrLn$"---- end of already selected "++someThings++" ----"printSummary::forallpC(xy).Patchyp=>pC(xy)->IO()printSummary=putDocLn.prefix" ".summary-- | Skips all remaining patches.skipAll::forallpC(xy).Patchyp=>InteractiveSelectionMpC(xy)()skipAll=domodify$\isc->isc{tps=toEnd$tpsisc}isSingleFile::Patchyp=>pC(xy)->BoolisSingleFilep=length(listTouchedFilesp)==1askConfirmation::forallpC(xy).Patchyp=>InteractiveSelectionMpC(xy)()askConfirmation=dojn<-asksjobnameliftIO$ifjn`elem`["unpull","unrecord","obliterate"]thendoyorn<-askUser$"Really "++jn++" all undecided patches? "caseyornof('y':_)->return()_->exitWith$ExitSuccesselsereturn()-- | The singular form of the noun for items of type @p@.thing::Patchyp=>InteractiveSelectionMpC(xy)Stringthing=getschoices>>=return.Darcs.Patch.thing.helperwherehelper::PatchChoicespC(ab)->pC(ab)helper=undefined-- | The plural form of the noun for items of type @p@.things::Patchyp=>InteractiveSelectionMpC(xy)Stringthings=getschoices>>=return.Darcs.Patch.things.helperwherehelper::PatchChoicespC(ab)->pC(ab)helper=undefined-- | The question to ask about one patch.prompt::Patchyp=>InteractiveSelectionMpC(xy)Stringprompt=dojn<-asksjobnameaThing<-thingn<-getscurrentn_max<-getstotalreturn$"Shall I "++jn++" this "++aThing++"? "++"("++show(n+1)++"/"++shown_max++") "-- | Asks the user about one patch, returns their answer.promptUser::forallpC(xy).Patchyp=>Bool->Char->InteractiveSelectionMpC(xy)CharpromptUsersingledef=dothePrompt<-prompt(basicOptions,advancedOptions)<-optionssingleliftIO$promptChar$PromptConfig{pPrompt=thePrompt,pBasicCharacters=keysForbasicOptions,pAdvancedCharacters=keysForadvancedOptions,pDefault=Justdef,pHelp="?h"}-- | Ask the user what to do with the next patch.textSelectOne::forallpC(xy).Patchyp=>WhichChanges->InteractiveSelectionMpC(xy)()textSelectOnewhichch=doc<-currentPatchcasecofNothing->return()Just(Sealed2tp)->dojn<-asksjobnamespl<-askssplittero<-asksoptsletsingleFile=isSingleFile(tpPatchtp)reprCur=reprwhichch(Sealed2(tpPatchtp))(basicOptions,advancedOptions)<-optionssingleFiletheSlot<-liftChoices$patchSlot'tpletthe_default=getDefault(whichch==Last||whichch==FirstReversed)theSlotjn_cap=(toUpper$headjn):tailjnyorn<-promptUsersingleFilethe_defaultletnextPatch=skipMundanewhichch>>showCurwhichchcaseyornof'y'->decidewhichchTruetp>>skipOne>>nextPatch'n'->decidewhichchFalsetp>>skipOne>>nextPatch'w'->postponeNext>>skipOne>>nextPatch'e'|(Justs)<-spl->splitCurrents>>showCurwhichch's'->currentFile>>=maybe(return())(\f->decideWholeFilewhichchfFalse)>>nextPatch'f'->currentFile>>=maybe(return())(\f->decideWholeFilewhichchfTrue)>>nextPatch'v'->liftIO$unseal2printPatchreprCur'p'->liftIO$unseal2printPatchPagerreprCur'l'->printSelectedwhichch>>showCurwhichch'x'->liftIO$unseal2printSummaryreprCur'd'->skipAll'a'->doaskConfirmationmodChoices$selectAllMiddles(whichch==Last||whichch==FirstReversed)skipAll'q'->liftIO$doputStrLn$jn_cap++" cancelled."exitWith$ExitSuccess'j'->skipOne>>showCurwhichch'k'->backOne>>showCurwhichch_->doliftIO.putStrLn$helpForjnbasicOptionsadvancedOptions-- | Shows the current patch as it should be seen by the user.showCur::forallpC(xy).Patchyp=>WhichChanges->InteractiveSelectionMpC(xy)()showCurwhichch=doo<-asksoptsc<-currentPatchcasecofNothing->return()Just(Sealed2tp)->doletreprCur=reprwhichch(Sealed2(tpPatchtp))liftIO.(unseal2(printFriendlyo))$reprCur-- | The interactive part of @darcs changes@textView::forallpC(xyurs).Patchyp=>[DarcsFlag]->MaybeInt->Int->[Sealed2p]->[Sealed2p]->IO()textView____[]=return()textViewon_maxnps_doneps_todo@(p:ps_todo')=dounseal2(printFriendlyo)prepeat_this-- prompt the userwhereprev_patch::IO()prev_patch=caseps_doneof[]->repeat_this(p':ps_done')->textViewon_max(n-1)ps_done'(p':ps_todo)next_patch::IO()next_patch=caseps_todo'of[]->-- May as well work out the length now we have all-- the patches in memorytextViewon_maxnps_done[]_->textViewon_max(n+1)(p:ps_done)ps_todo'options_yn=[KeyPress'y'"view this patch and go to the next",KeyPress'n'"skip to the next patch"]optionsView=[KeyPress'v'"view this patch in full",KeyPress'p'"view this patch in full with pager"]optionsSummary=[KeyPress'x'"view a summary of this patch"]optionsNav=[KeyPress'q'"quit view changes",KeyPress'k'"back up to previous patch",KeyPress'j'"skip to next patch",KeyPress'c'"count total patch number"]basicOptions=[options_yn]advancedOptions=[optionsView++ifSummary`elem`othen[]elseoptionsSummary]++[optionsNav]prompt="Shall I view this patch? "++"("++show(n+1)++"/"++maybe"?"shown_max++")"repeat_this::IO()repeat_this=doyorn<-promptChar(PromptConfigprompt(keysForbasicOptions)(keysForadvancedOptions)(Just'n')"?h")caseyornof'y'->unseal2printPatchp>>next_patch'n'->next_patch'v'->unseal2printPatchp>>repeat_this'p'->unseal2printPatchPagerp>>repeat_this'x'->doputDocLn$prefix" "$unseal2summaryprepeat_this'q'->exitWithExitSuccess'k'->prev_patch'j'->next_patch'c'->textViewocount_n_maxnps_doneps_todo_->doputStrLn$helpFor"view changes"basicOptionsadvancedOptionsrepeat_thiscount_n_max|isJustn_max=n_max|otherwise=Just$lengthps_done+lengthps_todo-- | Skips patches we should not ask the user aboutskipMundane::Patchyp=>WhichChanges->InteractiveSelectionMpC(xy)()skipMundanewhichch=do(FZippertps_donetps_todo)<-getstpso<-asksoptscrit<-asksmatchCriterionjn<-asksjobname(skipped:>unskipped)<-liftChoices$spanFL_M(patchSlot'>=>return.decided)tps_todoletnumSkipped=lengthFLskippedwhen(numSkipped>0).liftIO$show_skippedojnnumSkippedskippedletboringThenInteresting=ifDontPromptForDependencies`elem`othenspanFL(not.(critwhichcho).seal2.tpPatch)$unskippedelseNilFL:>unskippedcaseboringThenInterestingofboring:>interesting->dojustDone$lengthFLboring+numSkippedmodify$\isc->isc{tps=(FZipper(reverseFLboring+<+reverseFLskipped+<+tps_done)interesting)}whereshow_skippedojnnps=doputStrLn$_nevermind_jn++_these_n++"."when(Verbose`elem`o)$showskippedpatchps_nevermind_jn="Will not ask whether to "++jn++" "_these_n=shown++" already decided "++_elem_n""_elem_n=englishNumn(Noun"patch")showskippedpatch::Patchyp=>FL(TaggedPatchp)C(yt)->IO()showskippedpatch=sequence_.mapFL(printSummary.tpPatch)decided::Slot->BooldecidedInMiddle=Falsedecided_=True-- | The action bound to space, depending on the current status of the-- patch.getDefault::Bool->Slot->ChargetDefault_InMiddle='w'getDefaultTrueInFirst='n'getDefaultTrueInLast='y'getDefaultFalseInFirst='y'getDefaultFalseInLast='n'-- |Optionally remove any patches (+dependencies) from a sequence that-- conflict with the recorded or unrecorded changes in a repofilterOutConflicts::RepoPatchp=>[DarcsFlag]-- ^Command-line options. Only 'SkipConflicts' is-- significant; filtering will happen iff it is present->RL(PatchInfoAndp)C(xt)-- ^Recorded patches from repository, starting from-- same context as the patches to filter->RepositorypC(rut)-- ^Repository itself, used for grabbing unrecorded changes->FL(PatchInfoAndp)C(xz)-- ^Patches to filter->IO(Bool,Sealed(FL(PatchInfoAndp)C(x)))-- ^(True iff any patches were removed, possibly filtered patches)filterOutConflictsousrepositorythem|SkipConflicts`elem`o=doletcommuter=commuterIdRLselfCommuterunrec<-fmapn2pia.(anonymous.fromPrims)=<<unrecordedChanges[]repository[]them':>rest<-return$partitionConflictingFLcommuterthem(unrec:<:us)return(checkrest,Sealedthem')|otherwise=return(False,Sealedthem)wherecheck::FLpC(ab)->BoolcheckNilFL=Falsecheck_=True