2024-12-22 22:06:32 +09:00

1047 lines
42 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{- ----------------------------------------------------------------------------
Copyright (c) 2021, Daan Leijen
This is free software; you can redistribute it and/or modify it
under the terms of the MIT License. A copy of the license can be
found in the "LICENSE" file at the root of this distribution.
---------------------------------------------------------------------------- -}
{-|
Description : Binding to the Isocline library, a portable alternative to GNU Readline
Copyright : (c) 2021, Daan Leijen
License : MIT
Maintainer : daan@effp.org
Stability : Experimental
![logo](https://raw.githubusercontent.com/daanx/isocline/main/doc/isocline-inline.svg)
A Haskell wrapper around the [Isocline C library](https://github.com/daanx/isocline#readme)
which can provide an alternative to GNU Readline.
(The Isocline library is included whole and there are no runtime dependencies).
Isocline works across Unix, Windows, and macOS, and relies on a minimal subset of ANSI escape sequences.
It has a good multi-line editing mode (use shift/ctrl-enter) which is nice for inputting small functions etc.
Other features include support for colors, history, completion, unicode, undo/redo,
incremental history search, inline hints, brace matching, syntax highlighting, rich text using bbcode
formatting, etc.
Minimal example with history:
@
import System.Console.Isocline
main :: IO ()
main = do putStrLn \"Welcome\"
`setHistory` \"history.txt\" 200
input \<- `readline` \"myprompt\" -- full prompt becomes \"myprompt> \"
`putFmtLn` (\"[gray]You wrote:[\/gray]\\n\" ++ input)
@
Or using custom completions with an interactive loop:
@
import System.Console.Isocline
import Data.Char( toLower )
main :: IO ()
main
= do `styleDef' "ic-prompt" "ansi-maroon"
`setHistory` "history.txt" 200
`enableAutoTab` `True`
interaction
interaction :: IO ()
interaction
= do s <- `readlineEx` \"hαskell\" (Just completer) Nothing
putStrLn (\"You wrote:\\n\" ++ s)
if (s == \"\" || s == \"exit\") then return () else interaction
completer :: `CompletionEnv` -> String -> IO ()
completer cenv input
= do `completeFileName` cenv input Nothing [\".\",\"\/usr\/local\"] [\".hs\"] -- use [] for any extension
`completeWord` cenv input Nothing wcompleter
wcompleter :: String -> [`Completion`]
wcompleter input
= `completionsFor` (map toLower input)
[\"print\",\"println\",\"prints\",\"printsln\",\"prompt\"]
@
See a larger [example](https://github.com/daanx/isocline/blob/main/test/Example.hs)
with syntax highlighting and more extenstive custom completion
in the [Github repository](https://github.com/daanx/isocline).
Enjoy,
-- Daan
-}
module System.Console.Isocline(
-- * Readline
readline,
readlineEx,
-- * History
setHistory,
historyClear,
historyRemoveLast,
historyAdd,
-- * Completion
CompletionEnv,
completeFileName,
completeWord,
completeQuotedWord,
completeQuotedWordEx,
Completion(..),
completion,
isPrefix,
completionsFor,
wordCompleter,
-- * Syntax Highlighting
highlightFmt,
-- * Rich text
Style, Fmt,
style,
plain,
pre,
putFmt,
putFmtLn,
styleDef,
styleOpen,
styleClose,
withStyle,
-- * Configuration
setPromptMarker,
enableAutoTab,
enableColor,
enableBeep,
enableMultiline,
enableHistoryDuplicates,
enableCompletionPreview,
enableMultilineIndent,
enableHighlight,
enableInlineHelp,
enableHint,
setHintDelay,
enableBraceMatching,
enableBraceInsertion,
setMatchingBraces,
setInsertionBraces,
-- * Advanced
setDefaultCompleter,
addCompletion,
addCompletionPrim,
addCompletions,
completeWordPrim,
completeQuotedWordPrim,
completeQuotedWordPrimEx,
readlineMaybe,
readlineExMaybe,
readlinePrim,
readlinePrimMaybe,
getPromptMarker,
getContinuationPromptMarker,
stopCompleting,
hasCompletions,
asyncStop,
-- * Low-level highlighting
HighlightEnv,
setDefaultHighlighter,
setDefaultFmtHighlighter,
-- * Low-level Terminal
termInit,
termDone,
withTerm,
termFlush,
termWrite,
termWriteLn,
termColor,
termBgColor,
termColorAnsi,
termBgColorAnsi,
termUnderline,
termReverse,
termReset
) where
import Data.List( intersperse, isPrefixOf )
import Control.Monad( when, foldM )
import Control.Exception( bracket )
import Foreign.C.String( CString, peekCString, peekCStringLen, withCString, castCharToCChar )
import Foreign.Ptr
import Foreign.C.Types
-- the following are used for utf8 encoding.
import qualified Data.ByteString as B ( useAsCString, packCString )
import qualified Data.Text as T ( pack, unpack )
import Data.Text.Encoding as TE ( decodeUtf8With, encodeUtf8)
import Data.Text.Encoding.Error ( lenientDecode )
----------------------------------------------------------------------------
-- C Types
----------------------------------------------------------------------------
data IcCompletionEnv
-- | Abstract list of current completions.
newtype CompletionEnv = CompletionEnv (Ptr IcCompletionEnv)
type CCompleterFun = Ptr IcCompletionEnv -> CString -> IO ()
type CompleterFun = CompletionEnv -> String -> IO ()
data IcHighlightEnv
-- | Abstract highlight environment
newtype HighlightEnv = HighlightEnv (Ptr IcHighlightEnv)
type CHighlightFun = Ptr IcHighlightEnv -> CString -> Ptr () -> IO ()
type HighlightFun = HighlightEnv -> String -> IO ()
----------------------------------------------------------------------------
-- Basic readline
----------------------------------------------------------------------------
foreign import ccall ic_free :: (Ptr a) -> IO ()
foreign import ccall ic_malloc :: CSize -> IO (Ptr a)
foreign import ccall ic_strdup :: CString -> IO CString
foreign import ccall ic_readline :: CString -> IO CString
foreign import ccall ic_readline_ex :: CString -> FunPtr CCompleterFun -> (Ptr ()) -> FunPtr CHighlightFun -> (Ptr ()) -> IO CString
foreign import ccall ic_async_stop :: IO CCBool
unmaybe :: IO (Maybe String) -> IO String
unmaybe action
= do mb <- action
case mb of
Nothing -> return ""
Just s -> return s
-- | @readline prompt@: Read (multi-line) input from the user with rich editing abilities.
-- Takes the prompt text as an argument. The full prompt is the combination
-- of the given prompt and the prompt marker (@\"> \"@ by default) .
-- See also 'readlineEx', 'readlineMaybe', 'enableMultiline', and 'setPromptMarker'.
readline :: String -> IO String
readline prompt
= unmaybe $ readlineMaybe prompt
-- | As 'readline' but returns 'Nothing' on end-of-file or other errors (ctrl-C/ctrl-D).
readlineMaybe:: String -> IO (Maybe String)
readlineMaybe prompt
= withUTF8String prompt $ \cprompt ->
do cres <- ic_readline cprompt
res <- peekUTF8StringMaybe cres
ic_free cres
return res
-- | @readlineEx prompt mbCompleter mbHighlighter@: as 'readline' but
-- uses the given @mbCompleter@ function to complete words on @tab@ (instead of the default completer).
-- and the given @mbHighlighter@ function to highlight the input (instead of the default highlighter).
-- See also 'readline' and 'readlineExMaybe'.
readlineEx :: String -> Maybe (CompletionEnv -> String -> IO ()) -> Maybe (String -> Fmt) -> IO String
readlineEx prompt completer highlighter
= unmaybe $ readlineExMaybe prompt completer highlighter
-- | As 'readlineEx' but returns 'Nothing' on end-of-file or other errors (ctrl-C/ctrl-D).
-- See also 'readlineMaybe'.
readlineExMaybe :: String -> Maybe (CompletionEnv -> String -> IO ()) -> Maybe (String -> Fmt) -> IO (Maybe String)
readlineExMaybe prompt completer mbhighlighter
= readlinePrimMaybe prompt completer (case mbhighlighter of
Nothing -> Nothing
Just hl -> Just (highlightFmt hl))
-- | @readlinePrim prompt mbCompleter mbHighlighter@: as 'readline' but
-- uses the given @mbCompleter@ function to complete words on @tab@ (instead of the default completer).
-- and the given @mbHighlighter@ function to highlight the input (instead of the default highlighter).
-- See also 'readlineEx' and 'readlinePrimMaybe'.
readlinePrim :: String -> Maybe (CompletionEnv -> String -> IO ()) -> Maybe (HighlightEnv -> String -> IO ()) -> IO String
readlinePrim prompt completer highlighter
= unmaybe $ readlinePrimMaybe prompt completer highlighter
-- | As 'readlinePrim' but returns 'Nothing' on end-of-file or other errors (ctrl-C/ctrl-D).
-- See also 'readlineMaybe'.
readlinePrimMaybe :: String -> Maybe (CompletionEnv -> String -> IO ()) -> Maybe (HighlightEnv -> String -> IO ()) -> IO (Maybe String)
readlinePrimMaybe prompt completer highlighter
= withUTF8String prompt $ \cprompt ->
do ccompleter <- makeCCompleter completer
chighlighter <- makeCHighlighter highlighter
cres <- ic_readline_ex cprompt ccompleter nullPtr chighlighter nullPtr
res <- peekUTF8StringMaybe cres
ic_free cres
when (ccompleter /= nullFunPtr) $ freeHaskellFunPtr ccompleter
when (chighlighter /= nullFunPtr) $ freeHaskellFunPtr chighlighter
return res
-- | Thread safe call to asynchronously send a stop event to a 'readline'
-- which behaves as if the user pressed @ctrl-C@,
-- which will return with 'Nothing' (or @\"\"@).
-- Returns 'True' if the event was successfully delivered.
asyncStop :: IO Bool
asyncStop
= uncbool $ ic_async_stop
----------------------------------------------------------------------------
-- History
----------------------------------------------------------------------------
foreign import ccall ic_set_history :: CString -> CInt -> IO ()
foreign import ccall ic_history_remove_last :: IO ()
foreign import ccall ic_history_clear :: IO ()
foreign import ccall ic_history_add :: CString -> IO ()
-- | @setHistory filename maxEntries@:
-- Enable history that is persisted to the given file path with a given maximum number of entries.
-- Use -1 for the default entries (200).
-- See also 'enableHistoryDuplicates'.
setHistory :: FilePath -> Int -> IO ()
setHistory fname maxEntries
= withUTF8String0 fname $ \cfname ->
do ic_set_history cfname (toEnum maxEntries)
-- | Isocline automatically adds input of more than 1 character to the history.
-- This command removes the last entry.
historyRemoveLast :: IO ()
historyRemoveLast
= ic_history_remove_last
-- | Clear the history.
historyClear :: IO ()
historyClear
= ic_history_clear
-- | @historyAdd entry@: add @entry@ to the history.
historyAdd :: String -> IO ()
historyAdd entry
= withUTF8String0 entry $ \centry ->
do ic_history_add centry
----------------------------------------------------------------------------
-- Completion
----------------------------------------------------------------------------
-- use our own CBool for compatibility with an older base
type CCBool = CInt
type CCharClassFun = CString -> CLong -> IO CCBool
type CharClassFun = Char -> Bool
foreign import ccall ic_set_default_completer :: FunPtr CCompleterFun -> IO ()
foreign import ccall "wrapper" ic_make_completer :: CCompleterFun -> IO (FunPtr CCompleterFun)
foreign import ccall "wrapper" ic_make_charclassfun :: CCharClassFun -> IO (FunPtr CCharClassFun)
foreign import ccall ic_add_completion_ex :: Ptr IcCompletionEnv -> CString -> CString -> CString -> IO CCBool
foreign import ccall ic_add_completion_prim :: Ptr IcCompletionEnv -> CString -> CString -> CString -> CInt -> CInt -> IO CCBool
foreign import ccall ic_complete_filename :: Ptr IcCompletionEnv -> CString -> CChar -> CString -> CString -> IO ()
foreign import ccall ic_complete_word :: Ptr IcCompletionEnv -> CString -> FunPtr CCompleterFun -> FunPtr CCharClassFun -> IO ()
foreign import ccall ic_complete_qword :: Ptr IcCompletionEnv -> CString -> FunPtr CCompleterFun -> FunPtr CCharClassFun -> IO ()
foreign import ccall ic_complete_qword_ex :: Ptr IcCompletionEnv -> CString -> FunPtr CCompleterFun -> FunPtr CCharClassFun -> CChar -> CString -> IO ()
foreign import ccall ic_has_completions :: Ptr IcCompletionEnv -> IO CCBool
foreign import ccall ic_stop_completing :: Ptr IcCompletionEnv -> IO CCBool
-- | A completion entry
data Completion = Completion {
replacement :: String, -- ^ actual replacement
display :: String, -- ^ display of the completion in the completion menu
help :: String -- ^ help message
} deriving (Eq, Show)
-- | Create a completion with just a replacement
completion :: String -> Completion
completion replacement
= Completion replacement "" ""
-- | @completionFull replacement display help@: Create a completion with a separate display and help string.
completionFull :: String -> String -> String -> Completion
completionFull replacement display help
= Completion replacement display help
-- | Is the given input a prefix of the completion replacement?
isPrefix :: String -> Completion -> Bool
isPrefix input compl
= isPrefixOf input (replacement compl)
-- | @completionsFor input replacements@: Filter those @replacements@ that
-- start with the given @input@, and return them as completions.
completionsFor :: String -> [String] -> [Completion]
completionsFor input rs
= map completion (filter (isPrefixOf input) rs)
-- | Convenience: creates a completer function directly from a list
-- of candidate completion strings. Uses `completionsFor` to filter the
-- input and `completeWord` to find the word boundary.
-- For example: @'readlineEx' \"myprompt\" (Just ('wordCompleter' completer)) Nothing@.
wordCompleter :: [String] -> (CompletionEnv -> String -> IO ())
wordCompleter completions
= (\cenv input -> completeWord cenv input Nothing (\input -> completionsFor input completions))
-- | @setDefaultCompleter completer@: Set a new tab-completion function @completer@
-- that is called by Isocline automatically.
-- The callback is called with a 'CompletionEnv' context and the current user
-- input up to the cursor.
-- By default the 'completeFileName' completer is used.
-- This overwrites any previously set completer.
setDefaultCompleter :: (CompletionEnv -> String -> IO ()) -> IO ()
setDefaultCompleter completer
= do ccompleter <- makeCCompleter (Just completer)
ic_set_default_completer ccompleter
withCCompleter :: Maybe CompleterFun -> (FunPtr CCompleterFun -> IO a) -> IO a
withCCompleter completer action
= bracket (makeCCompleter completer) (\cfun -> when (nullFunPtr /= cfun) (freeHaskellFunPtr cfun)) action
makeCCompleter :: Maybe CompleterFun -> IO (FunPtr CCompleterFun)
makeCCompleter Nothing = return nullFunPtr
makeCCompleter (Just completer)
= ic_make_completer wrapper
where
wrapper :: Ptr IcCompletionEnv -> CString -> IO ()
wrapper rpcomp cprefx
= do prefx <- peekUTF8String0 cprefx
completer (CompletionEnv rpcomp) prefx
-- | @addCompletion compl completion@: Inside a completer callback, add a new completion.
-- If 'addCompletion' returns 'True' keep adding completions,
-- but if it returns 'False' an effort should be made to return from the completer
-- callback without adding more completions.
addCompletion :: CompletionEnv -> Completion -> IO Bool
addCompletion (CompletionEnv rpc) (Completion replacement display help)
= withUTF8String replacement $ \crepl ->
withUTF8String0 display $ \cdisplay ->
withUTF8String0 help $ \chelp ->
do cbool <- ic_add_completion_ex rpc crepl cdisplay chelp
return (fromEnum cbool /= 0)
-- | @addCompletionPrim compl completion deleteBefore deleteAfter@:
-- Primitive add completion, use with care and call only directly inside a completer callback.
-- If 'addCompletion' returns 'True' keep adding completions,
-- but if it returns 'False' an effort should be made to return from the completer
-- callback without adding more completions.
addCompletionPrim :: CompletionEnv -> Completion -> Int -> Int -> IO Bool
addCompletionPrim (CompletionEnv rpc) (Completion replacement display help) deleteBefore deleteAfter
= withUTF8String replacement $ \crepl ->
withUTF8String0 display $ \cdisplay ->
withUTF8String0 help $ \chelp ->
do cbool <- ic_add_completion_prim rpc crepl cdisplay chelp (toEnum deleteBefore) (toEnum deleteAfter)
return (fromEnum cbool /= 0)
-- | @addCompletions compl completions@: add multiple completions at once.
-- If 'addCompletions' returns 'True' keep adding completions,
-- but if it returns 'False' an effort should be made to return from the completer
-- callback without adding more completions.
addCompletions :: CompletionEnv -> [Completion] -> IO Bool
addCompletions compl [] = return True
addCompletions compl (c:cs)
= do continue <- addCompletion compl c
if (continue)
then addCompletions compl cs
else return False
-- | @completeFileName compls input dirSep roots extensions@:
-- Complete filenames with the given @input@, a possible directory separator @dirSep@,
-- a list of root folders @roots@ to search from
-- (by default @["."]@), and a list of extensions to match (use @[]@ to match any extension).
-- The directory separator is used when completing directory names.
-- For example, using g @\'/\'@ as a directory separator, we get:
--
-- > /ho --> /home/
-- > /home/.ba --> /home/.bashrc
--
completeFileName :: CompletionEnv -> String -> Maybe Char -> [FilePath] -> [String] -> IO ()
completeFileName (CompletionEnv rpc) prefx dirSep roots extensions
= withUTF8String prefx $ \cprefx ->
withUTF8String0 (concat (intersperse ";" roots)) $ \croots ->
withUTF8String0 (concat (intersperse ";" extensions)) $ \cextensions ->
do let cdirSep = case dirSep of
Nothing -> toEnum 0
Just c -> castCharToCChar c
ic_complete_filename rpc cprefx cdirSep croots cextensions
-- | @completeWord compl input isWordChar completer@:
-- Complete a /word/ (or /token/) and calls the user @completer@ function with just the current word
-- (instead of the whole input)
-- Takes the 'CompletionEnv' environment @compl@, the current @input@, an possible
-- @isWordChar@ function, and a user defined
-- @completer@ function that is called with adjusted input which
-- is limited to the /word/ just before the cursor.
-- Pass 'Nothing' to @isWordChar@ for the default @not . separator@
-- where @separator = \c -> c `elem` \" \\t\\r\\n,.;:/\\\\(){}[]\"@.
completeWord :: CompletionEnv -> String -> Maybe (Char -> Bool) -> (String -> [Completion]) -> IO ()
completeWord cenv input isWordChar completer
= completeWordPrim cenv input isWordChar cenvCompleter
where
cenvCompleter cenv input
= do addCompletions cenv (completer input)
return ()
-- | @completeQuotedWord compl input isWordChar completer@:
-- Complete a /word/ taking care of automatically quoting and escaping characters.
-- Takes the 'CompletionEnv' environment @compl@, the current @input@, and a user defined
-- @completer@ function that is called with adjusted input which is unquoted, unescaped,
-- and limited to the /word/ just before the cursor.
-- For example, with a @hello world@ completion, we get:
--
-- > hel --> hello\ world
-- > hello\ w --> hello\ world
-- > hello w --> # no completion, the word is just 'w'>
-- > "hel --> "hello world"
-- > "hello w --> "hello world"
--
-- The call @('completeWord' compl prefx isWordChar fun)@ is a short hand for
-- @('completeQuotedWord' compl prefx isWordChar \'\\\\\' \"\'\\\"\" fun)@.
-- Pass 'Nothing' to @isWordChar@ for the default @not . separator@
-- where @separator = \c -> c `elem` \" \\t\\r\\n,.;:/\\\\(){}[]\"@.
completeQuotedWord :: CompletionEnv -> String -> Maybe (Char -> Bool) -> (String -> [Completion]) -> IO ()
completeQuotedWord cenv input isWordChar completer
= completeWordPrim cenv input isWordChar cenvCompleter
where
cenvCompleter cenv input
= do addCompletions cenv (completer input)
return ()
-- | @completeQuotedWordEx compl input isWordChar escapeChar quoteChars completer@:
-- Complete a /word/ taking care of automatically quoting and escaping characters.
-- Takes the 'CompletionEnv' environment @compl@, the current @input@, and a user defined
-- @completer@ function that is called with adjusted input which is unquoted, unescaped,
-- and limited to the /word/ just before the cursor.
-- Unlike 'completeQuotedWord', this function can specify
-- the /escape/ character and the /quote/ characters.
-- See also 'completeWord'.
completeQuotedWordEx :: CompletionEnv -> String -> Maybe (Char -> Bool) -> Maybe Char -> String -> (String -> [Completion]) -> IO ()
completeQuotedWordEx cenv input isWordChar escapeChar quoteChars completer
= completeQuotedWordPrimEx cenv input isWordChar escapeChar quoteChars cenvCompleter
where
cenvCompleter cenv input
= do addCompletions cenv (completer input)
return ()
-- | @completeWord compl input isWordChar completer@:
-- Complete a /word/,/token/ and calls the user @completer@ function with just the current word
-- (instead of the whole input)
-- Takes the 'CompletionEnv' environment @compl@, the current @input@, an possible
-- @isWordChar@ function, and a user defined
-- @completer@ function that is called with adjusted input which
-- is limited to the /word/ just before the cursor.
-- Pass 'Nothing' to @isWordChar@ for the default @not . separator@
-- where @separator = \c -> c `elem` \" \\t\\r\\n,.;:/\\\\(){}[]\"@.
completeWordPrim :: CompletionEnv -> String -> Maybe (Char -> Bool) -> (CompletionEnv -> String -> IO ()) -> IO ()
completeWordPrim (CompletionEnv rpc) prefx isWordChar completer
= withUTF8String prefx $ \cprefx ->
withCharClassFun isWordChar $ \cisWordChar ->
withCCompleter (Just completer) $ \ccompleter ->
do ic_complete_word rpc cprefx ccompleter cisWordChar
-- | @completeWordPrim compl input isWordChar completer@:
-- Complete a /word/ taking care of automatically quoting and escaping characters.
-- Takes the 'CompletionEnv' environment @compl@, the current @input@, and a user defined
-- @completer@ function that is called with adjusted input which is unquoted, unescaped,
-- and limited to the /word/ just before the cursor.
-- For example, with a @hello world@ completion, we get:
--
-- > hel --> hello\ world
-- > hello\ w --> hello\ world
-- > hello w --> # no completion, the word is just 'w'>
-- > "hel --> "hello world"
-- > "hello w --> "hello world"
--
-- The call @('completeWordPrim' compl prefx isWordChar fun)@ is a short hand for
-- @('completeQuotedWordPrim' compl prefx isWordChar \'\\\\\' \"\'\\\"\" fun)@.
-- Pass 'Nothing' to @isWordChar@ for the default @not . separator@
-- where @separator = \c -> c `elem` \" \\t\\r\\n,.;:/\\\\(){}[]\"@.
completeQuotedWordPrim :: CompletionEnv -> String -> Maybe (Char -> Bool) -> (CompletionEnv -> String -> IO ()) -> IO ()
completeQuotedWordPrim (CompletionEnv rpc) prefx isWordChar completer
= withUTF8String prefx $ \cprefx ->
withCharClassFun isWordChar $ \cisWordChar ->
withCCompleter (Just completer) $ \ccompleter ->
do ic_complete_qword rpc cprefx ccompleter cisWordChar
-- | @completeQuotedWordPrim compl input isWordChar escapeChar quoteChars completer@:
-- Complete a /word/ taking care of automatically quoting and escaping characters.
-- Takes the 'CompletionEnv' environment @compl@, the current @input@, and a user defined
-- @completer@ function that is called with adjusted input which is unquoted, unescaped,
-- and limited to the /word/ just before the cursor.
-- Unlike 'completeWord', this function takes an explicit function to determine /word/ characters,
-- the /escape/ character, and a string of /quote/ characters.
-- See also 'completeWord'.
completeQuotedWordPrimEx :: CompletionEnv -> String -> Maybe (Char -> Bool) -> Maybe Char -> String -> (CompletionEnv -> String -> IO ()) -> IO ()
completeQuotedWordPrimEx (CompletionEnv rpc) prefx isWordChar escapeChar quoteChars completer
= withUTF8String prefx $ \cprefx ->
withUTF8String0 quoteChars $ \cquoteChars ->
withCharClassFun isWordChar $ \cisWordChar ->
withCCompleter (Just completer) $ \ccompleter ->
do let cescapeChar = case escapeChar of
Nothing -> toEnum 0
Just c -> castCharToCChar c
ic_complete_qword_ex rpc cprefx ccompleter cisWordChar cescapeChar cquoteChars
withCharClassFun :: Maybe (Char -> Bool) -> (FunPtr CCharClassFun -> IO a) -> IO a
withCharClassFun isInClass action
= bracket (makeCharClassFun isInClass) (\cfun -> when (nullFunPtr /= cfun) (freeHaskellFunPtr cfun)) action
makeCharClassFun :: Maybe (Char -> Bool) -> IO (FunPtr CCharClassFun)
makeCharClassFun Nothing = return nullFunPtr
makeCharClassFun (Just isInClass)
= let charClassFun :: CString -> CLong -> IO CCBool
charClassFun cstr clen
= let len = (fromIntegral clen :: Int)
in if (len <= 0) then return (cbool False)
else do s <- peekCStringLen (cstr,len)
return (if null s then (cbool False) else cbool (isInClass (head s)))
in do ic_make_charclassfun charClassFun
-- | If this returns 'True' an effort should be made to stop completing and return from the callback.
stopCompleting :: CompletionEnv -> IO Bool
stopCompleting (CompletionEnv rpc)
= uncbool $ ic_stop_completing rpc
-- | Have any completions be generated so far?
hasCompletions :: CompletionEnv -> IO Bool
hasCompletions (CompletionEnv rpc)
= uncbool $ ic_has_completions rpc
----------------------------------------------------------------------------
-- Syntax highlighting
----------------------------------------------------------------------------
foreign import ccall ic_set_default_highlighter :: FunPtr CHighlightFun -> Ptr () -> IO ()
foreign import ccall "wrapper" ic_make_highlight_fun:: CHighlightFun -> IO (FunPtr CHighlightFun)
foreign import ccall ic_highlight :: Ptr IcHighlightEnv -> CLong -> CLong -> CString -> IO ()
foreign import ccall ic_highlight_formatted :: Ptr IcHighlightEnv -> CString -> CString -> IO ()
-- | Set a syntax highlighter.
-- There can only be one highlight function, setting it again disables the previous one.
setDefaultHighlighter :: (HighlightEnv -> String -> IO ()) -> IO ()
setDefaultHighlighter highlighter
= do chighlighter <- makeCHighlighter (Just highlighter)
ic_set_default_highlighter chighlighter nullPtr
makeCHighlighter :: Maybe (HighlightEnv -> String -> IO ()) -> IO (FunPtr CHighlightFun)
makeCHighlighter Nothing = return nullFunPtr
makeCHighlighter (Just highlighter)
= ic_make_highlight_fun wrapper
where
wrapper :: Ptr IcHighlightEnv -> CString -> Ptr () -> IO ()
wrapper henv cinput carg
= do input <- peekUTF8String0 cinput
highlighter (HighlightEnv henv) input
-- | @highlight henv pos len style@: Set the style of @len@ characters
-- starting at position @pos@ in the input
highlight :: HighlightEnv -> Int -> Int -> String -> IO ()
highlight (HighlightEnv henv) pos len style
= withUTF8String0 style $ \cstyle ->
do ic_highlight henv (clong (-pos)) (clong (-len)) cstyle
-- | A style for formatted strings ('Fmt').
-- For example, a style can be @"red"@ or @"b #7B3050"@.
-- See the full list of valid [properties](https://github.com/daanx/isocline#bbcode-format)
type Style = String
-- | A string with [bbcode](https://github.com/daanx/isocline#bbcode-format) formatting.
-- For example @"[red]this is red[\/]"@.n
type Fmt = String
-- | Use an rich text formatted highlighter from inside a highlighter callback.
highlightFmt :: (String -> Fmt) -> (HighlightEnv -> String -> IO ())
highlightFmt highlight (HighlightEnv henv) input
= withUTF8String0 input $ \cinput ->
withUTF8String0 (highlight input) $ \cfmt ->
do ic_highlight_formatted henv cinput cfmt
-- | Style a string, e.g. @style "b red" "bold and red"@ (which is equivalent to @"[b red]bold and red[\/]"@).
-- See the repo for a full description of all [styles](https://github.com/daanx/isocline#bbcode-format).
style :: Style -> Fmt -> Fmt
style st s
= if null st then s else ("[" ++ st ++ "]" ++ s ++ "[/]")
-- | Escape a string so no tags are interpreted as formatting.
plain :: String -> Fmt
plain s
= if (any (\c -> (c == '[' || c == ']')) s) then "[!pre]" ++ s ++ "[/pre]" else s
-- | Style a string that is printed as is without interpreting markup inside it (using `plain`).
pre :: Style -> String -> Fmt
pre st s
= style st (plain s)
-- | Set a syntax highlighter that uses a pure function that returns a bbcode
-- formatted string (using 'style', 'plain' etc). See 'highlightFmt' for more information.
-- There can only be one highlight function, setting it again disables the previous one.
setDefaultFmtHighlighter :: (String -> Fmt) -> IO ()
setDefaultFmtHighlighter highlight
= setDefaultHighlighter (highlightFmt highlight)
----------------------------------------------------------------------------
-- Print rich text
----------------------------------------------------------------------------
foreign import ccall ic_print :: CString -> IO ()
foreign import ccall ic_println :: CString -> IO ()
foreign import ccall ic_style_def :: CString -> CString -> IO ()
foreign import ccall ic_style_open :: CString -> IO ()
foreign import ccall ic_style_close :: IO ()
-- | Output rich formatted text containing [bbcode](https://github.com/daanx/isocline#bbcode-format).
-- For example: @putFmt \"[b]bold [red]and red[\/][\/]\"@
-- All unclosed tags are automatically closed (but see also 'styleOpen').
-- See the repo for more information about [formatted output](https://github.com/daanx/isocline#formatted-output).
putFmt :: Fmt -> IO ()
putFmt s
= withUTF8String0 s $ \cs ->
do ic_print cs
-- | Output rich formatted text containing bbcode's ending with a newline.
putFmtLn :: Fmt -> IO ()
putFmtLn s
= withUTF8String0 s $ \cs ->
do ic_println cs
-- | Define (or redefine) a style.
-- For example @styleDef "warning" "crimon underline"@,
-- and then use it as @'putFmtLn' "[warning]this is a warning[/]"@.
-- This can be very useful for theming your application with semantic styles.
-- See also [formatted output](https://github.com/daanx/isocline#formatted-output)
styleDef :: String -> Style -> IO ()
styleDef name style
= withUTF8String0 name $ \cname ->
withUTF8String0 style $ \cstyle ->
do ic_style_def cname cstyle
-- | Open a style that is active for all 'putFmt' and 'putFmtLn' until it is closed again (`styleClose`).
styleOpen :: Style -> IO ()
styleOpen style
= withUTF8String0 style $ \cstyle ->
do ic_style_open cstyle
-- | Close a previously opened style.
styleClose :: IO ()
styleClose
= ic_style_close
-- | Use a style over an action.
withStyle :: Style -> IO a -> IO a
withStyle style action
= bracket (styleOpen style) (\() -> styleClose) (\() -> action)
----------------------------------------------------------------------------
-- Terminal
----------------------------------------------------------------------------
foreign import ccall ic_term_init :: IO ()
foreign import ccall ic_term_done :: IO ()
foreign import ccall ic_term_flush :: IO ()
foreign import ccall ic_term_write :: CString -> IO ()
foreign import ccall ic_term_writeln :: CString -> IO ()
foreign import ccall ic_term_underline :: CCBool -> IO ()
foreign import ccall ic_term_reverse :: CCBool -> IO ()
foreign import ccall ic_term_color_ansi :: CCBool -> CInt -> IO ()
foreign import ccall ic_term_color_rgb :: CCBool -> CInt -> IO ()
foreign import ccall ic_term_style :: CString -> IO ()
foreign import ccall ic_term_reset :: IO ()
-- | Initialize the terminal for the @term@ functions.
-- Does nothing on most platforms but on windows enables UTF8 output
-- and potentially enables virtual terminal processing.
-- See also 'withTerm'.
termInit :: IO ()
termInit
= ic_term_init
-- | Done using @term@ functions.
-- See also 'withTerm'.
termDone :: IO ()
termDone
= ic_term_done
-- | Use the @term@ functions (brackets 'termInit' and 'termDone').
withTerm :: IO a -> IO a
withTerm action
= bracket termInit (\() -> termDone) (\() -> action)
-- | Flush terminal output. Happens automatically on newline (@'\\n'@) characters as well.
termFlush :: IO ()
termFlush
= ic_term_flush
-- | Write output to the terminal where ANSI CSI sequences are
-- handled portably across platforms (including Windows).
termWrite :: String -> IO ()
termWrite s
= withUTF8String0 s $ \cs -> ic_term_write cs
-- | Write output with a ending newline to the terminal where
-- ANSI CSI sequences are handled portably across platforms (including Windows).
termWriteLn :: String -> IO ()
termWriteLn s
= withUTF8String0 s $ \cs -> ic_term_writeln cs
-- | Set the terminal text color as a hexadecimal number @0x@rrggbb.
-- The color is auto adjusted for terminals with less colors.
termColor :: Int -> IO ()
termColor color
= ic_term_color_rgb (cbool True) (toEnum color)
-- | Set the terminal text background color. The color is auto adjusted for terminals with less colors.
termBgColor :: Int -> IO ()
termBgColor color
= ic_term_color_rgb (cbool False) (toEnum color)
-- | Set the terminal text color as an ANSI palette color (between @0@ and @255@). Use 256 for the default.
-- The color is auto adjusted for terminals with less colors.
termColorAnsi :: Int -> IO ()
termColorAnsi color
= ic_term_color_ansi (cbool True) (toEnum color)
-- | Set the terminal text background color as an ANSI palette color (between @0@ and @255@). Use 256 for the default.
-- The color is auto adjusted for terminals with less colors.
termBgColorAnsi :: Int -> IO ()
termBgColorAnsi color
= ic_term_color_ansi (cbool False) (toEnum color)
-- | Set the terminal attributes from a style
termStyle :: Style -> IO ()
termStyle style
= withUTF8String0 style $ \cstyle ->
do ic_term_style cstyle
-- | Set the terminal text underline mode.
termUnderline :: Bool -> IO ()
termUnderline enable
= ic_term_underline (cbool enable)
-- | Set the terminal text reverse video mode.
termReverse :: Bool -> IO ()
termReverse enable
= ic_term_reverse (cbool enable)
-- | Reset the terminal text mode to defaults
termReset :: IO ()
termReset
= ic_term_reset
----------------------------------------------------------------------------
-- Configuration
----------------------------------------------------------------------------
foreign import ccall ic_set_prompt_marker :: CString -> CString -> IO ()
foreign import ccall ic_get_prompt_marker :: IO CString
foreign import ccall ic_get_continuation_prompt_marker :: IO CString
foreign import ccall ic_enable_multiline :: CCBool -> IO CCBool
foreign import ccall ic_enable_beep :: CCBool -> IO CCBool
foreign import ccall ic_enable_color :: CCBool -> IO CCBool
foreign import ccall ic_enable_auto_tab :: CCBool -> IO CCBool
foreign import ccall ic_enable_inline_help:: CCBool -> IO CCBool
foreign import ccall ic_enable_hint :: CCBool -> IO CCBool
foreign import ccall ic_set_hint_delay :: CLong -> IO CLong
foreign import ccall ic_enable_highlight :: CCBool -> IO CCBool
foreign import ccall ic_enable_history_duplicates :: CCBool -> IO CCBool
foreign import ccall ic_enable_completion_preview :: CCBool -> IO CCBool
foreign import ccall ic_enable_multiline_indent :: CCBool -> IO CCBool
foreign import ccall ic_enable_brace_matching :: CCBool -> IO CCBool
foreign import ccall ic_enable_brace_insertion :: CCBool -> IO CCBool
foreign import ccall ic_set_matching_braces :: CString -> IO ()
foreign import ccall ic_set_insertion_braces :: CString -> IO ()
cbool :: Bool -> CCBool
cbool True = toEnum 1
cbool False = toEnum 0
uncbool :: IO CCBool -> IO Bool
uncbool action
= do i <- action
return (i /= toEnum 0)
clong :: Int -> CLong
clong l = toEnum l
-- | @setPromptMarker marker multiline_marker@: Set the prompt @marker@ (by default @\"> \"@).
-- and a possible different continuation prompt marker @multiline_marker@ for multiline
-- input (defaults to @marker@).
setPromptMarker :: String -> String -> IO ()
setPromptMarker marker multiline_marker
= withUTF8String0 marker $ \cmarker ->
withUTF8String0 multiline_marker $ \cmultiline_marker ->
do ic_set_prompt_marker cmarker cmultiline_marker
-- | Get the current prompt marker.
getPromptMarker :: IO String
getPromptMarker
= do cstr <- ic_get_prompt_marker
if (nullPtr == cstr)
then return ""
else do cstr2 <- ic_strdup cstr
peekUTF8String0 cstr2
-- | Get the current prompt continuation marker for multi-line input.
getContinuationPromptMarker :: IO String
getContinuationPromptMarker
= do cstr <- ic_get_continuation_prompt_marker
if (nullPtr == cstr)
then return ""
else do cstr2 <- ic_strdup cstr
peekUTF8String0 cstr2
-- | Disable or enable multi-line input (enabled by default).
-- Returns the previous value.
enableMultiline :: Bool -> IO Bool
enableMultiline enable
= do uncbool $ ic_enable_multiline (cbool enable)
-- | Disable or enable sound (enabled by default).
-- | A beep is used when tab cannot find any completion for example.
-- Returns the previous value.
enableBeep :: Bool -> IO Bool
enableBeep enable
= do uncbool $ ic_enable_beep (cbool enable)
-- | Disable or enable color output (enabled by default).
-- Returns the previous value.
enableColor :: Bool -> IO Bool
enableColor enable
= do uncbool $ ic_enable_color (cbool enable)
-- | Disable or enable duplicate entries in the history (duplicate entries are not allowed by default).
-- Returns the previous value.
enableHistoryDuplicates :: Bool -> IO Bool
enableHistoryDuplicates enable
= do uncbool $ ic_enable_history_duplicates (cbool enable)
-- | Disable or enable automatic tab completion after a completion
-- to expand as far as possible if the completions are unique. (disabled by default).
-- Returns the previous value.
enableAutoTab :: Bool -> IO Bool
enableAutoTab enable
= do uncbool $ ic_enable_auto_tab (cbool enable)
-- | Disable or enable short inline help message (for history search etc.) (enabled by default).
-- Pressing F1 always shows full help regardless of this setting.
-- Returns the previous value.
enableInlineHelp :: Bool -> IO Bool
enableInlineHelp enable
= do uncbool $ ic_enable_inline_help (cbool enable)
-- | Disable or enable preview of a completion selection (enabled by default)
-- Returns the previous value.
enableCompletionPreview :: Bool -> IO Bool
enableCompletionPreview enable
= do uncbool $ ic_enable_completion_preview (cbool enable)
-- | Disable or enable brace matching (enabled by default)
-- Returns the previous value.
enableBraceMatching :: Bool -> IO Bool
enableBraceMatching enable
= do uncbool $ ic_enable_brace_matching (cbool enable)
-- | Disable or enable automatic close brace insertion (enabled by default)
-- Returns the previous value.
enableBraceInsertion :: Bool -> IO Bool
enableBraceInsertion enable
= do uncbool $ ic_enable_brace_insertion (cbool enable)
-- | Set pairs of matching braces, by default @\"(){}[]\"@.
setMatchingBraces :: String -> IO ()
setMatchingBraces bracePairs
= withUTF8String0 bracePairs $ \cbracePairs ->
do ic_set_matching_braces cbracePairs
-- | Set pairs of auto insertion braces, by default @\"(){}[]\\\"\\\"\'\'\"@.
setInsertionBraces :: String -> IO ()
setInsertionBraces bracePairs
= withUTF8String0 bracePairs $ \cbracePairs ->
do ic_set_insertion_braces cbracePairs
-- | Disable or enable automatic indentation to line up the
-- multiline prompt marker with the initial prompt marker (enabled by default).
-- Returns the previous value.
-- See also 'setPromptMarker'.
enableMultilineIndent :: Bool -> IO Bool
enableMultilineIndent enable
= do uncbool $ ic_enable_multiline_indent (cbool enable)
-- | Disable or enable automatic inline hinting (enabled by default)
-- Returns the previous value.
enableHint :: Bool -> IO Bool
enableHint enable
= do uncbool $ ic_enable_hint (cbool enable)
-- | Disable or enable syntax highlighting (enabled by default).
-- Returns the previous value.
enableHighlight :: Bool -> IO Bool
enableHighlight enable
= do uncbool $ ic_enable_highlight (cbool enable)
-- | Set the delay in milliseconds before a hint is displayed (500ms by default)
-- See also 'enableHint'
setHintDelay :: Int -> IO Int
setHintDelay ms
= do cl <- ic_set_hint_delay (toEnum ms)
return (fromEnum cl)
----------------------------------------------------------------------------
-- UTF8 Strings
----------------------------------------------------------------------------
withUTF8String0 :: String -> (CString -> IO a) -> IO a
withUTF8String0 s action
= if (null s) then action nullPtr else withUTF8String s action
peekUTF8String0 :: CString -> IO String
peekUTF8String0 cstr
= if (nullPtr == cstr) then return "" else peekUTF8String cstr
peekUTF8StringMaybe :: CString -> IO (Maybe String)
peekUTF8StringMaybe cstr
= if (nullPtr == cstr) then return Nothing
else do s <- peekUTF8String cstr
return (Just s)
peekUTF8String :: CString -> IO String
peekUTF8String cstr
= do bstr <- B.packCString cstr
return (T.unpack (TE.decodeUtf8With lenientDecode bstr))
withUTF8String :: String -> (CString -> IO a) -> IO a
withUTF8String str action
= do let bstr = TE.encodeUtf8 (T.pack str)
B.useAsCString bstr action