{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances, TypeOperators #-}-- Copyright (c) 2005,2007,2008 Jean-Philippe Bernardy{-
This module is aimed at being a helper for the Emacs keybindings.
In particular this should be useful for anyone that has a custom
keymap derived from or based on the Emacs one.
-}moduleYi.Keymap.Emacs.Utils(UnivArgument,argToInt,askQuitEditor,askSaveEditor,modifiedQuitEditor,withMinibuffer,queryReplaceE,isearchKeymap,cabalConfigureE,cabalBuildE,reloadProjectE,executeExtendedCommandE,evalRegionE,readUniversalArg,scrollDownE,scrollUpE,switchBufferE,killBufferE,insertNextC,findFile,findFileNewTab,promptFile,promptTag,justOneSep,joinLinesE)where{- Standard Library Module Imports -}importPrelude(take)importData.List((\\))importData.Maybe(maybe)importSystem.FriendlyPath()importSystem.FilePath(takeDirectory,takeFileName,(</>))importSystem.Directory(doesDirectoryExist)importControl.Monad.Trans(MonadIO(..)){- External Library Module Imports -}{- Local (yi) module imports -}importControl.Monad(filterM,replicateM_)importYi.Command(cabalConfigureE,cabalBuildE,reloadProjectE)importYi.CoreimportYi.DiredimportYi.EvalimportYi.FileimportYi.MiniBufferimportYi.Misc(promptFile)importYi.RegeximportYi.TagimportYi.SearchimportYi.Window{- End of Module Imports -}typeUnivArgument=MaybeInt------------------------------ | Quits the editor if there are no unmodified buffers-- if there are unmodified buffers then we ask individually for-- each modified buffer whether or not the user wishes to save-- it or not. If we get to the end of this list and there are still-- some modified buffers then we ask again if the user wishes to-- quit, but this is then a simple yes or no.askQuitEditor,askSaveEditor::YiM()askQuitEditor=askIndividualSaveTrue=<<getModifiedBuffersaskSaveEditor=askIndividualSaveFalse=<<getModifiedBuffersgetModifiedBuffers::YiM[FBuffer]getModifiedBuffers=filterMdeservesSave=<<getsbufferSetdeservesSave::FBuffer->YiMBooldeservesSaveb|isUnchangedBufferb=returnFalse|otherwise=isFileBufferb-- | Is there a proper file associated with the buffer?-- In other words, does it make sense to offer to save it?isFileBuffer::(Functorm,MonadIOm)=>FBuffer->mBoolisFileBufferb=caseb^.identAofLeft_->returnFalseRightfn->not<$>liftIO(doesDirectoryExistfn)---------------------------------------------------- Takes in a list of buffers which have been identified-- as modified since their last save.askIndividualSave::Bool->[FBuffer]->YiM()askIndividualSaveTrue[]=modifiedQuitEditoraskIndividualSaveFalse[]=return()askIndividualSavehasQuitallBuffers@(firstBuffer:others)=withEditor(spawnMinibufferEsaveMessage(constaskKeymap))>>return()wheresaveMessage=concat["do you want to save the buffer: ",bufferName,"? (y/n/"++(ifhasQuitthen"q/"else"")++"c/!)"]bufferName=identStringfirstBufferaskKeymap=choice([char'n'?>>!noAction,char'y'?>>!yesAction,char'!'?>>!allAction,oneOf[char'c',ctrl$char'g']>>!closeBufferAndWindowE-- cancel]++[char'q'?>>!quitEditor|hasQuit])yesAction=dofwriteBufferE(bkeyfirstBuffer)withEditorcloseBufferAndWindowEcontinuenoAction=dowithEditorcloseBufferAndWindowEcontinueallAction=domapM_fwriteBufferE$fmapbkeyallBufferswithEditorcloseBufferAndWindowEaskIndividualSavehasQuit[]continue=askIndividualSavehasQuitothers-------------------------------------------------------- | Quits the editor if there are no unmodified buffers-- if there are then simply confirms with the user that they-- with to quit.modifiedQuitEditor::YiM()modifiedQuitEditor=domodifiedBuffers<-getModifiedBuffersifnullmodifiedBuffersthenquitEditorelsewithEditor$spawnMinibufferEmodifiedMessage(constaskKeymap)>>return()wheremodifiedMessage="Modified buffers exist really quit? (y/n)"askKeymap=choice[char'n'?>>!noAction,char'y'?>>!quitEditor]noAction=closeBufferAndWindowE------------------------------- isearchselfSearchKeymap::KeymapselfSearchKeymap=doEvent(KASCIIc)[]<-anyEventwrite(isearchAddE[c])searchKeymap::KeymapsearchKeymap=selfSearchKeymap<|>choice[-- ("C-g", isearchDelE) -- Only if string is not empty.ctrl(char'r')?>>!isearchPrevE,ctrl(char's')?>>!isearchNextE,ctrl(char'w')?>>!isearchWordE,meta(char'p')?>>!isearchHistory1,meta(char'n')?>>!isearchHistory(-1),specKBS?>>!isearchDelE]isearchKeymap::Direction->KeymapisearchKeymapdir=dowrite$isearchInitEdirmanysearchKeymapchoice[ctrl(char'g')?>>!isearchCancelE,oneOf[ctrl(char'm'),specKEnter]>>!isearchFinishE]<||writeisearchFinishE------------------------------ query-replacequeryReplaceE::YiM()queryReplaceE=dowithMinibufferFree"Replace:"$\replaceWhat->dowithMinibufferFree"With:"$\replaceWith->dob<-getscurrentBufferwin<-getAcurrentWindowAletreplaceKm=choice[char'n'?>>!qrNextwinbre,char'!'?>>!qrReplaceAllwinbrereplaceWith,oneOf[char'y',char' ']>>!qrReplaceOnewinbrereplaceWith,oneOf[char'q',ctrl(char'g')]>>!qrFinish]Rightre=makeSearchOptsM[]replaceWhatwithEditor$dosetRegexErespawnMinibufferE("Replacing "++replaceWhat++" with "++replaceWith++" (y,n,q,!):")(constreplaceKm)qrNextwinbreexecuteExtendedCommandE::YiM()executeExtendedCommandE=withMinibuffer"M-x"(constgetAllNamesInScope)execEditorActionevalRegionE::YiM()evalRegionE=dowithBuffer(getSelectRegionB>>=readRegionB)>>=return-- FIXME: do something sensible.return()-- * Code for various commands-- This ideally should be put in their own module,-- without a prefix, so M-x ... would be easily implemented-- by looking up that module's contents-- | Insert next character, "raw"insertNextC::UnivArgument->KeymapM()insertNextCa=doc<-anyEventwrite$replicateM_(argToInta)$insertB(eventToCharc)-- | Convert the universal argument to a number of repetitionsargToInt::UnivArgument->IntargToInta=caseaofNothing->1Justx->xdigit::(Event->Event)->KeymapMChardigitf=charOff'0''9'-- TODO: replace tt by digit meta tt::KeymapMChartt=doEvent(KASCIIc)_<-foldr1(<|>)$fmap(event.metaCh)['0'..'9']returnc-- doing the argument precisely is kind of tedious.-- read: http://www.gnu.org/software/emacs/manual/html_node/Arguments.html-- and: http://www.gnu.org/software/emacs/elisp-manual/html_node/elisp_318.htmlreadUniversalArg::KeymapM(MaybeInt)readUniversalArg=Just<$>((ctrlCh'u'?>>(read<$>some(digitid)<|>pure4))<|>(read<$>(somett)))<|>pureNothing-- | Open a file using the minibuffer. We have to set up some stuff to allow hints-- and auto-completion.findFile::YiM()findFile=promptFile"find file:"$\filename->domsgEditor$"loading "++filenamefnewEfilename-- | Open a file in a new tab using the minibuffer.findFileNewTab::YiM()findFileNewTab=promptFile"find file (new tab): "$\filename->dowithEditornewTabEmsgEditor$"loading "++filenamefnewEfilenamescrollDownE::UnivArgument->BufferM()scrollDownEa=caseaofNothing->downScreenBJustn->scrollBnscrollUpE::UnivArgument->BufferM()scrollUpEa=caseaofNothing->upScreenBJustn->scrollB(negaten)switchBufferE::YiM()switchBufferE=doopenBufs<-fmapbufkey.toList<$>getAwindowsAnames<-withEditor$dobs<-fmapbkey<$>getBufferStackletchoices=(bs\\openBufs)++openBufs-- put the open buffers at the end.prefix<-getscommonNamePrefixforMchoices$\k->gets(shortIdentStringprefix.findBufferWithk)withMinibufferFin"switch to buffer:"names(withEditor.switchToBufferWithNameE)killBufferE::BufferRef:::ToKill->YiM()killBufferE(Docb)=dobuf<-withEditor$gets$findBufferWithbch<-deservesSavebufletaskKeymap=choice[char'n'?>>!closeBufferAndWindowE,char'y'?>>!delBuf>>closeBufferAndWindowE,ctrlCh'g'?>>!closeBufferAndWindowE]delBuf=deleteBufferbwithEditor$ifchthen(spawnMinibufferE(identStringbuf++" changed, close anyway? (y/n)")(constaskKeymap))>>return()elsedelBuf-- | If on separators (space, tab, unicode seps), reduce multiple-- separators to just a single separator.justOneSep::BufferM()justOneSep=doIfCharBisAnySep$dogenMaybeMoveBunitSepThisLine(Backward,InsideBound)BackwardmoveBCharacterForwarddoIfCharBisAnySep$deleteBunitSepThisLineForward-- | Join this line to previous (or next N if universal)joinLinesE::UnivArgument->BufferM()joinLinesEa=docaseaofNothing->return()Just_n->moveBVLineForwardmoveToSol>>transformB(\_->" ")CharacterBackward>>justOneSep-- | Shortcut to use a default list when a blank list is given.-- Used for default values to emacs queriesmaybeList::[a]->[a]->[a]maybeListdef[]=defmaybeList_ls=ls---------------------------------------------------- TAGS - See Yi.Tag for more info-- | Prompt the user to give a tag and then jump to that tagpromptTag::YiM()promptTag=do-- default tag is where the buffer is ondefaultTag<-withBuffer$readUnitBunitWord-- if we have tags use them to generate hintstagTable<-withEditorgetTags-- Hints are expensive - only lazily generate 10lethinter=return.take10.maybefailhintTagstagTable-- Completions are super-cheap. Go wildletcompleter=return.maybeidcompleteTagtagTablewithMinibufferGen""hinter("Find tag: (default "++defaultTag++")")completer$-- if the string is "" use the defaultTaggotoTag.maybeListdefaultTag-- | Opens the file that contains @tag@. Uses the global tag table and prompts-- the user to open one if it does not existgotoTag::Tag->YiM()gotoTagtag=visitTagTable$\tagTable->caselookupTagtagtagTableofNothing->fail$"No tags containing "++tagJust(filename,line)->dofnewE$filenamewithBuffer$gotoLnlinereturn()-- | Call continuation @act@ with the TagTable. Uses the global table-- and prompts the user if it doesn't existvisitTagTable::(TagTable->YiM())->YiM()visitTagTableact=doposTagTable<-withEditorgetTags-- does the tagtable exist?caseposTagTableofJusttagTable->acttagTableNothing->promptFile("Visit tags table: (default tags)")$\path->do-- default emacs behavior, append tagsletfilename=maybeList"tags"$takeFileNamepathtagTable<-io$importTagTable$takeDirectorypath</>filenamewithEditor$setTagstagTableacttagTableresetTagTable::YiM()resetTagTable=withEditorresetTags