moduleSystem.Console.Haskeline.Completion(CompletionFunc,Completion(..),completeWord,completeQuotedWord,-- * Building 'CompletionFunc'snoCompletion,simpleCompletion,-- * Filename completioncompleteFilename,listFiles,filenameWordBreakChars)whereimportSystem.FilePathimportData.List(isPrefixOf)importControl.Monad(forM)importSystem.Console.Haskeline.DirectoryimportSystem.Console.Haskeline.Monads-- | Performs completions from the given line state.---- The first 'String' argument is the contents of the line to the left of the cursor,-- reversed.-- The second 'String' argument is the contents of the line to the right of the cursor.---- The output 'String' is the unused portion of the left half of the line, reversed.typeCompletionFuncm=(String,String)->m(String,[Completion])dataCompletion=Completion{replacement::String,-- ^ Text to insert in line.display::String,-- ^ Text to display when listing-- alternatives.isFinished::Bool-- ^ Whether this word should be followed by a-- space, end quote, etc.}derivingShow-- | Disable completion altogether.noCompletion::Monadm=>CompletionFuncmnoCompletion(s,_)=return(s,[])---------------- Word break functions-- | The following function creates a custom 'CompletionFunc' for use in the 'Settings.'completeWord::Monadm=>MaybeChar-- ^ An optional escape character->String-- ^ List of characters which count as whitespace->(String->m[Completion])-- ^ Function to produce a list of possible completions->CompletionFuncmcompleteWordescwsf(line,_)=dolet(word,rest)=caseescofNothing->break(`elem`ws)lineJuste->escapedBreakelinecompletions<-f(reverseword)return(rest,map(escapeReplacementescws)completions)whereescapedBreake(c:d:cs)|d==e&&c`elem`(e:ws)=let(xs,ys)=escapedBreakecsin(c:xs,ys)escapedBreake(c:cs)|not(elemcws)=let(xs,ys)=escapedBreakecsin(c:xs,ys)escapedBreak_cs=("",cs)-- | Create a finished completion out of the given word.simpleCompletion::String->CompletionsimpleCompletion=completion-- NOTE: this is the same as for readline, except that I took out the '\\'-- so they can be used as a path separator.filenameWordBreakChars::StringfilenameWordBreakChars=" \t\n`@$><=;|&{("-- A completion command for file and folder names.completeFilename::MonadIOm=>CompletionFuncmcompleteFilename=completeQuotedWord(Just'\\')"\"'"listFiles$completeWord(Just'\\')("\"\'"++filenameWordBreakChars)listFilescompletion::String->Completioncompletionstr=CompletionstrstrTruesetReplacement::(String->String)->Completion->CompletionsetReplacementfc=c{replacement=f$replacementc}escapeReplacement::MaybeChar->String->Completion->CompletionescapeReplacementescwsf=caseescofNothing->fJuste->f{replacement=escapee(replacementf)}whereescapee(c:cs)|c`elem`(e:ws)=e:c:escapeecs|otherwise=c:escapeecsescape_""=""----------- Quoted completioncompleteQuotedWord::Monadm=>MaybeChar-- ^ An optional escape character->String-- List of characters which set off quotes->(String->m[Completion])-- ^ Function to produce a list of possible completions->CompletionFuncm-- ^ Alternate completion to perform if the -- cursor is not at a quoted word->CompletionFuncmcompleteQuotedWordescqscompleteralterativeline@(left,_)=casesplitAtQuoteescqsleftofJust(w,rest)|isUnquotedescqsrest->docs<-completer(reversew)return(rest,map(addQuotes.escapeReplacementescqs)cs)_->alterativelineaddQuotes::Completion->CompletionaddQuotesc=ifisFinishedcthenc{replacement="\""++replacementc++"\""}elsec{replacement="\""++replacementc}splitAtQuote::MaybeChar->String->String->Maybe(String,String)splitAtQuoteescqsline=caselineofc:e:cs|isEscapee&&isEscapablec->do(w,rest)<-splitAtQuoteescqscsreturn(c:w,rest)q:cs|isQuoteq->Just("",cs)c:cs->do(w,rest)<-splitAtQuoteescqscsreturn(c:w,rest)""->NothingwhereisQuote=(`elem`qs)isEscapec=Justc==escisEscapablec=isEscapec||isQuotecisUnquoted::MaybeChar->String->String->BoolisUnquotedescqss=casesplitAtQuoteescqssofJust(_,s')->not(isUnquotedescqss')_->True-- | List all of the files or folders beginning with this path.listFiles::MonadIOm=>FilePath->m[Completion]listFilespath=liftIO$dofixedDir<-fixPathdirdirExists<-doesDirectoryExistfixedDir-- get all of the files in that directory, as basenamesallFiles<-ifnotdirExiststhenreturn[]elsefmap(mapcompletion.filterPrefix)$getDirectoryContentsfixedDir-- The replacement text should include the directory part, and also -- have a trailing slash if it's itself a directory.forMallFiles$\c->doisDir<-doesDirectoryExist(fixedDir</>replacementc)return$setReplacementfullName$alterIfDirisDircwhere(dir,file)=splitFileNamepathfilterPrefix=filter(\f->not(f`elem`[".",".."])&&file`isPrefixOf`f)alterIfDirFalsec=calterIfDirTruec=c{replacement=addTrailingPathSeparator(replacementc),isFinished=False}-- NOTE In order for completion to work properly, all of the alternatives-- must have the exact same prefix. As a result, </> is a little too clever;-- for example, it doesn't prepend the directory if the file looks like-- an absolute path (strange, but it can happen).-- The FilePath docs state that (++) is an exact inverse of splitFileName, so-- that's the right function to user here.fullNamef=dir++f-- turn a user-visible path into an internal version useable by System.FilePath.fixPath::String->IOStringfixPath""=return"."fixPath('~':c:path)|isPathSeparatorc=dohome<-getHomeDirectoryreturn(home</>path)fixPathpath=returnpath