{-# LANGUAGE DeriveDataTypeable #-}-- Copyright (C) 2008 JP BernardymoduleYi.Buffer.HighLevelwhereimportControl.Monad.RWS.Strict(ask)importControl.Monad.StateimportData.CharimportData.List(isPrefixOf,sort,lines,drop,filter,length,takeWhile,dropWhile,reverse)importqualifiedData.RopeasRimportData.Maybe(fromMaybe,listToMaybe)importData.Time(UTCTime)importPrelude(FilePath,map)importYi.PreludeimportYi.Buffer.BasicimportYi.Buffer.MiscimportYi.Buffer.NormalimportYi.Buffer.RegionimportYi.StringimportYi.WindowimportYi.Config.Misc(ScrollStyle(SingleLine))-- ----------------------------------------------------------------------- Movement operations-- | Move point to start of linemoveToSol::BufferM()moveToSol=maybeMoveBLineBackward-- | Move point to end of linemoveToEol::BufferM()moveToEol=maybeMoveBLineForward-- | Move cursor to origintopB::BufferM()topB=moveTo0-- | Move cursor to end of bufferbotB::BufferM()botB=moveTo=<<sizeB-- | Move left if on eol, but not on blank lineleftOnEol::BufferM()leftOnEol=doeol<-atEolsol<-atSolwhen(eol&&notsol)leftB-- | Move @x@ chars back, or to the sol, whichever is lessmoveXorSol::Int->BufferM()moveXorSolx=replicateM_x$doc<-atSol;when(notc)leftB-- | Move @x@ chars forward, or to the eol, whichever is lessmoveXorEol::Int->BufferM()moveXorEolx=replicateM_x$doc<-atEol;when(notc)rightB-- | Move to first char of next word forwardsnextWordB::BufferM()nextWordB=moveBunitWordForward-- | Move to first char of next word backwardsprevWordB::BufferM()prevWordB=moveBunitWordBackward-- * Char-based movement actions.-- | Move to the next occurence of @c@nextCInc::Char->BufferM()nextCIncc=doUntilB_((c==)<$>readB)rightB-- | Move to the character before the next occurence of @c@nextCExc::Char->BufferM()nextCExcc=nextCIncc>>leftB-- | Move to the previous occurence of @c@prevCInc::Char->BufferM()prevCIncc=doUntilB_((c==)<$>readB)leftB-- | Move to the character after the previous occurence of @c@prevCExc::Char->BufferM()prevCExcc=prevCIncc>>rightB-- | Move to first non-space character in this linefirstNonSpaceB::BufferM()firstNonSpaceB=domoveToSoluntilB_((||)<$>atEol<*>((not.isSpace)<$>readB))rightB-- | Move to the last non-space character in this linelastNonSpaceB::BufferM()lastNonSpaceB=domoveToEoluntilB_((||)<$>atSol<*>((not.isSpace)<$>readB))leftB-- | Go to the first non space character in the line;-- if already there, then go to the beginning of the line.moveNonspaceOrSol::BufferM()moveNonspaceOrSol=doprev<-readPreviousOfLnBifand.mapisSpace$prevthenmoveToSolelsefirstNonSpaceB-------------- | Move down next @n@ paragraphsnextNParagraphs::Int->BufferM()nextNParagraphsn=replicateM_n$moveBunitEmacsParagraphForward-- | Move up prev @n@ paragraphsprevNParagraphs::Int->BufferM()prevNParagraphsn=replicateM_n$moveBunitEmacsParagraphBackward-- ! Examples:-- @goUnmatchedB Backward '(' ')'@-- Move to the previous unmatched '('-- @goUnmatchedB Forward '{' '}'@-- Move to the next unmatched '}'goUnmatchedB::Direction->Char->Char->BufferM()goUnmatchedBdircStart'cStop'=stepB>>readB>>=go(0::Int)wheregoopenedc|c==cStop&&opened==0=return()|c==cStop=stepB>>readB>>=go(opened-1)|c==cStart=stepB>>readB>>=go(opened+1)|otherwise=stepB>>readB>>=goopened(stepB,cStart,cStop)|dir==Forward=(rightB,cStart',cStop')|otherwise=(leftB,cStop',cStart')------------------------------------------------------------------------- Queries-- | Return true if the current point is the start of a lineatSol::BufferMBoolatSol=atBoundaryBLineBackward-- | Return true if the current point is the end of a lineatEol::BufferMBoolatEol=atBoundaryBLineForward-- | True if point at start of fileatSof::BufferMBoolatSof=atBoundaryBDocumentBackward-- | True if point at end of fileatEof::BufferMBoolatEof=atBoundaryBDocumentForward-- | Get the current line and column numbergetLineAndCol::BufferM(Int,Int)getLineAndCol=(,)<$>curLn<*>curCol-- | Read the line the point is onreadLnB::BufferMStringreadLnB=readUnitBLinereadCharB::BufferM(MaybeChar)readCharB=fmaplistToMaybe(readUnitBCharacter)-- | Read from point to end of linereadRestOfLnB::BufferMStringreadRestOfLnB=readRegionB=<<regionOfPartBLineForward-- | Read from point to beginning of linereadPreviousOfLnB::BufferMStringreadPreviousOfLnB=readRegionB=<<regionOfPartBLineBackwardhasWhiteSpaceBefore::BufferMBoolhasWhiteSpaceBefore=prevPointB>>=readAtB>>=return.isSpace-- | Get the previous point, unless at the beginning of the fileprevPointB::BufferMPointprevPointB=dosof<-atSofifsofthenpointBelsedop<-pointBreturn$Point(fromPointp-1)-- | Get the next point, unless at the end of the filenextPointB::BufferMPointnextPointB=doeof<-atEofifeofthenpointBelsedop<-pointBreturn$Point(fromPointp+1)readPrevWordB::BufferMStringreadPrevWordB=readPrevUnitBunitViWordOnLine--------------------------- Deletes-- | Delete one character backwardbdeleteB::BufferM()bdeleteB=deleteBCharacterBackward-- | Delete forward whitespace or non-whitespace depending on-- the character under point.killWordB::BufferM()killWordB=deleteBunitWordForward-- | Delete backward whitespace or non-whitespace depending on-- the character before point.bkillWordB::BufferM()bkillWordB=deleteBunitWordBackward------------------------------------------ Transform operations-- | capitalise the word under the cursoruppercaseWordB::BufferM()uppercaseWordB=transformB(fmaptoUpper)unitWordForward-- | lowerise word under the cursorlowercaseWordB::BufferM()lowercaseWordB=transformB(fmaptoLower)unitWordForward-- | capitalise the first letter of this wordcapitaliseWordB::BufferM()capitaliseWordB=transformBcapitalizeFirstunitWordForward-- | Delete to the end of line, excluding it.deleteToEol::BufferM()deleteToEol=deleteRegionB=<<regionOfPartBLineForward-- | Delete whole line moving to the next linedeleteLineForward::BufferM()deleteLineForward=domoveToSol-- Move to the start of the linedeleteToEol-- Delete the rest of the line not including the newline chardeleteN1-- Delete the newline character-- | Transpose two characters, (the Emacs C-t action)swapB::BufferM()swapB=doeol<-atEolwheneolleftBtransposeBCharacterForward-- | Delete trailing whitespace from all linesdeleteTrailingSpaceB::BufferM()deleteTrailingSpaceB=modifyRegionCleverdeleteSpaces=<<regionOfBDocumentwheredeleteSpaces=mapLines$reverse.dropWhile(' '==).reverse-- ------------------------------------------------------ | Marks-- | Set the current buffer selection marksetSelectionMarkPointB::Point->BufferM()setSelectionMarkPointBp=flipsetMarkPointBp=<<selMark<$>askMarks-- | Get the current buffer selection markgetSelectionMarkPointB::BufferMPointgetSelectionMarkPointB=getMarkPointB=<<selMark<$>askMarks-- | Exchange point & mark.exchangePointAndMarkB::BufferM()exchangePointAndMarkB=dom<-getSelectionMarkPointBp<-pointBsetSelectionMarkPointBpmoveTomgetBookmarkB::String->BufferMMarkgetBookmarkB=getMarkB.Just-- ----------------------------------------------------------------------- Buffer operationsdataBufferFileInfo=BufferFileInfo{bufInfoFileName::FilePath,bufInfoSize::Int,bufInfoLineNo::Int,bufInfoColNo::Int,bufInfoCharNo::Point,bufInfoPercent::String,bufInfoModified::Bool}-- | File info, size in chars, line no, col num, char num, percentbufInfoB::BufferMBufferFileInfobufInfoB=dos<-sizeBp<-pointBm<-getsisUnchangedBufferl<-curLnc<-curColnm<-getsidentStringletbufInfo=BufferFileInfo{bufInfoFileName=nm,bufInfoSize=fromIntegrals,bufInfoLineNo=l,bufInfoColNo=c,bufInfoCharNo=p,bufInfoPercent=getPercentps,bufInfoModified=notm}returnbufInfo------------------------------- Window-related operationsupScreensB::Int->BufferM()upScreensB=scrollScreensB.negatedownScreensB::Int->BufferM()downScreensB=scrollScreensB-- | Scroll up 1 screenupScreenB::BufferM()upScreenB=scrollScreensB(-1)-- | Scroll down 1 screendownScreenB::BufferM()downScreenB=scrollScreensB1-- | Scroll by n screens (negative for up)scrollScreensB::Int->BufferM()scrollScreensBn=doh<-askWindowheightscrollB$n*max0(h-3)-- subtract some amount to get some overlap (emacs-like).-- | Scroll according to function passed. The function takes the-- | Window height in lines, its result is passed to scrollB-- | (negative for up)scrollByB::(Int->Int)->Int->BufferM()scrollByBfn=doh<-askWindowheightscrollB$n*fh-- | Same as scrollB, but also moves the cursorvimScrollB::Int->BufferM()vimScrollBn=doscrollBndiscard$lineMoveReln-- | Same as scrollByB, but also moves the cursorvimScrollByB::(Int->Int)->Int->BufferM()vimScrollByBfn=doh<-askWindowheightvimScrollB$n*fh-- | Move to middle line in screenscrollToCursorB::BufferM()scrollToCursorB=doMarkSetfi_<-markLinesh<-askWindowheightletm=f+(h`div`2)scrollB$i-m-- | Move cursor to the top of the screenscrollCursorToTopB::BufferM()scrollCursorToTopB=doMarkSetfi_<-markLinesscrollB$i-f-- | Move cursor to the bottom of the screenscrollCursorToBottomB::BufferM()scrollCursorToBottomB=doMarkSet_i_<-markLinesr<-winRegionBt<-lineOf(regionEndr-1)scrollB$i-t-- | Scroll by n lines.scrollB::Int->BufferM()scrollBn=doMarkSetfr__<-askMarkssavingPointB$domoveTo=<<getMarkPointBfrdiscard$gotoLnFromnsetMarkPointBfr=<<pointBw<-askWindowwkeymodApointFollowsWindowA(\oldw'->ifw==w'thenTrueelseoldw')-- | Move the point to inside the viewable regionsnapInsB::BufferM()snapInsB=domovePoint<-getApointFollowsWindowAw<-askWindowwkeywhen(movePointw)$dor<-winRegionBp<-pointBmoveTo$max(regionStartr)$min(regionEndr)$p-- | return index of Sol on line @n@ above current lineindexOfSolAbove::Int->BufferMPointindexOfSolAboven=pointAt$gotoLnFrom(negaten)dataRelPosition=Above|Below|Withinderiving(Show)-- | return relative position of the point @p@-- relative to the region defined by the points @rs@ and @re@pointScreenRelPosition::Point->Point->Point->RelPositionpointScreenRelPositionprsre|rs>p&&p>re=Within|p<rs=Above|p>re=BelowpointScreenRelPosition___=Within-- just to disable the non-exhaustive pattern match warning-- | Move the visible region to include the pointsnapScreenB::MaybeScrollStyle->BufferMBoolsnapScreenBstyle=domovePoint<-getApointFollowsWindowAw<-askWindowwkeyifmovePointwthenreturnFalseelsedoinWin<-pointInWindowB=<<pointBifinWinthenreturnFalseelsedoh<-askWindowactualLinesr<-winRegionBp<-pointBletgap=casestyleofJustSingleLine->casepointScreenRelPositionp(regionStartr)(regionEndr)ofAbove->0Below->h-1Within->0-- Impossible but handle it anyway_->h`div`2i<-indexOfSolAbovegapf<-fromMark<$>askMarkssetMarkPointBfireturnTrue-- | Move to @n@ lines down from top of screendownFromTosB::Int->BufferM()downFromTosBn=domoveTo=<<getMarkPointB=<<fromMark<$>askMarksreplicateM_nlineDown-- | Move to @n@ lines up from the bottom of the screenupFromBosB::Int->BufferM()upFromBosBn=dor<-winRegionBmoveTo(regionEndr-1)moveToSolreplicateM_nlineUp-- | Move to middle line in screenmiddleB::BufferM()middleB=dow<-askf<-fromMark<$>askMarksmoveTo=<<getMarkPointBfreplicateM_(heightw`div`2)lineDownpointInWindowB::Point->BufferMBoolpointInWindowBp=nearRegionp<$>winRegionB-- do w <- winRegionB; trace ("pointInWindowB " ++ show w ++ " p = " ++ show p)------------------------------- Region-related operations-- | Return the region between point and markgetRawestSelectRegionB::BufferMRegiongetRawestSelectRegionB=dom<-getSelectionMarkPointBp<-pointBreturn$mkRegionpm-- | Return the empty region if the selection is not visible.getRawSelectRegionB::BufferMRegiongetRawSelectRegionB=dos<-getAhighlightSelectionAifsthengetRawestSelectRegionBelsedop<-pointBreturn$mkRegionpp-- | Get the current region boundaries. Extended to the current selection unit.getSelectRegionB::BufferMRegiongetSelectRegionB=doregionStyle<-getAregionStyleAr<-getRawSelectRegionBmkRegionOfStyleB(regionStartr)(regionEndr)regionStyle-- | Select the given region: set the selection mark at the 'regionStart'-- and the current point at the 'regionEnd'.setSelectRegionB::Region->BufferM()setSelectRegionBregion=dosetSelectionMarkPointB$regionStartregionmoveTo$regionEndregion-- | Extend the selection mark using the given region.extendSelectRegionB::Region->BufferM()extendSelectRegionBregion=(setSelectRegionB.unionRegionregion)=<<getSelectRegionB-------------------------------------------- Some line related movements/operationsdeleteBlankLinesB::BufferM()deleteBlankLinesB=doisThisBlank<-isBlank<$>readLnBwhenisThisBlank$dop<-pointB-- go up to the 1st blank line in the groupdiscard$whileB(isBlank<$>getNextLineBBackward)lineUpq<-pointB-- delete the whole blank region.deleteRegionB$mkRegionpq-- | Get a (lazy) stream of lines in the buffer, starting at the /next/ line-- in the given direction.lineStreamB::Direction->BufferM[String]lineStreamBdir=drop1.fmaprev.lines'.R.toString<$>(streamBdir=<<pointB)whererev=casedirofForward->idBackward->reverse{-
| Get the next line of text in the given direction. This returns simply 'Nothing' if there
is no such line.
-}getMaybeNextLineB::Direction->BufferM(MaybeString)getMaybeNextLineBdir=listToMaybe<$>lineStreamBdir{-
| The same as 'getMaybeNextLineB' but avoids the use of the 'Maybe'
type in the return by returning the empty string if there is no next line.
-}getNextLineB::Direction->BufferMStringgetNextLineBdir=fromMaybe""<$>getMaybeNextLineBdir{-
| Get closest line to the current line (not including the current line) in the given direction
which satisfies the given condition. Returns 'Nothing' if there is
no line which satisfies the condition.
-}getNextLineWhichB::Direction->(String->Bool)->BufferM(MaybeString)getNextLineWhichBdircond=listToMaybe.filtercond<$>lineStreamBdir{-
| Returns the closest line to the current line which is non-blank, in the given direction.
Returns the empty string if there is no such line (for example if
we are on the top line already).
-}getNextNonBlankLineB::Direction->BufferMStringgetNextNonBlankLineBdir=fromMaybe""<$>getNextLineWhichBdir(not.isBlank)-------------------------------------------------- Some more utility functions involving-- regions (generally that which is selected)-- | Uses a string modifying function to modify the current selection-- Currently unsets the mark such that we have no selection, arguably-- we could instead work out where the new positions should be-- and move the mark and point accordingly.modifySelectionB::(String->String)->BufferM()modifySelectionB=modifyExtendedSelectionBCharactermodifyExtendedSelectionB::TextUnit->(String->String)->BufferM()modifyExtendedSelectionBunittransform=modifyRegionBtransform=<<unitWiseRegionunit=<<getSelectRegionB-- | Prefix each line in the selection using-- the given string.linePrefixSelectionB::String-- ^ The string that starts a line comment->BufferM()-- The returned buffer actionlinePrefixSelectionBs=modifyExtendedSelectionBLine$skippingLast$mapLines(s++)whereskippingLastfxs=f(initxs)++[lastxs]-- | Uncomments the selection using the given line comment-- starting string. This only works for the comments which-- begin at the start of the line.unLineCommentSelectionB::String-- ^ The string which begins a line comment->String-- ^ A potentially shorter string that begins a comment->BufferM()unLineCommentSelectionBs1s2=modifyExtendedSelectionBLine$mapLinesunCommentLinewhereunCommentLine::String->StringunCommentLineline|isPrefixOfs1line=drop(lengths1)line|isPrefixOfs2line=drop(lengths2)line|otherwise=line-- | Toggle line comments in the selection by adding or removing a prefix to each-- line.toggleCommentSelectionB::String->String->BufferM()toggleCommentSelectionBinsPrefixdelPrefix=dol<-readUnitBLineifdelPrefix`isPrefixOf`lthenunLineCommentSelectionBinsPrefixdelPrefixelselinePrefixSelectionBinsPrefix-- | Justifies all the lines of the selection to be the same as-- the top line.-- NOTE: if the selection begins part way along a line, the other-- lines will be justified only with respect to the part of the indentation-- which is selected.justifySelectionWithTopB::BufferM()justifySelectionWithTopB=modifySelectionBjustifyLineswherejustifyLines::String->StringjustifyLinesinput=caselinesinputof[]->""[one]->one(top:_)->mapLinesjustifyLineinputwhere-- The indentation of the top line.topIndent=takeWhileisSpacetop-- Justify a single line by removing its current-- indentation and replacing it with that of the top-- line. Note that this will work even if the indentation-- contains tab characters.justifyLine::String->StringjustifyLine""=""justifyLinel=topIndent++dropWhileisSpacel-- | Replace the contents of the buffer with some stringreplaceBufferContent::String->BufferM()replaceBufferContentnewvalue=dor<-regionOfBDocumentreplaceRegionBrnewvalue-- | Fill the text in the region so it fits nicely 80 columns.fillRegion::Region->BufferM()fillRegion=modifyRegionClever(unlines'.fillText80)fillParagraph::BufferM()fillParagraph=fillRegion=<<regionOfBunitParagraph-- | Sort the lines of the region.sortLines::BufferM()sortLines=modifyExtendedSelectionBLine(onLinessort)-- | Helper function: revert the buffer contents to its on-disk versionrevertB::Rope->UTCTime->BufferM()revertBsnow=dor<-regionOfBDocumentifR.lengths<=smallBufferSize-- for large buffers, we must avoid building strings, because we'll end up using huge amounts of memorythenreplaceRegionCleverr(R.toStrings)elsereplaceRegionB'rsmarkSavedBnowsmallBufferSize::IntsmallBufferSize=1000000