packages feed

mptcpanalyzer-0.0.2.0: src/MptcpAnalyzer/Utils/Completion.hs

{-# LANGUAGE CPP #-}
{-|
Module: MptcpAnalyzer.Utils.Completion
Maintainer  : matt
License     : GPL-3


  missingArgP :: ParseError -> Completer -> m a
  errorP :: ParseError -> m a

parseError peut renvoyer un missingArgP qui du coup aura le completer, qu'on n'a plus qu'a transcrire dans un completer haskeline !

-}
module MptcpAnalyzer.Utils.Completion (
  generateHaskelineCompleterFromParserInfo
  , completePath
  , readFilename
)
where

import Data.Char (isSpace)
import Data.List (isPrefixOf, isSuffixOf, stripPrefix)
import Data.Maybe (fromJust, fromMaybe, listToMaybe)
import Debug.Trace
import Options.Applicative
import Options.Applicative.Common
import Options.Applicative.Help (Doc, renderPretty)
import Options.Applicative.Help.Chunk
import Options.Applicative.Help.Pretty (displayS)
import Options.Applicative.Internal hiding (Completion)
import Options.Applicative.Types hiding (replacement)
import System.Console.Haskeline
       (Completion(..), CompletionFunc, completeFilename, noCompletion)
import System.Console.Haskeline.Completion (listFiles)
-- import Options.Applicative.Help (parserHelp)
import System.IO.Unsafe (unsafePerformIO)
import System.Posix (fileExist, getFileStatus, isRegularFile)

defaultCompleteFunc :: CompletionFunc IO
defaultCompleteFunc = completeFilename


-- | We use unsafePerformIO to work around optparse-applicative limitation
readFilename :: String -> Either String FilePath
readFilename path =
  -- fileExist seems to consider files and folders alike
  let exists = unsafePerformIO $ do
        t <- fileExist path
        if t then
          getFileStatus path >>= return . isRegularFile
        else
          return False
  in
  case exists of
    True ->
#ifdef DEBUG_COMPLETION
      trace "right path"
#endif
      Right path
    False ->
#ifdef DEBUG_COMPLETION
      trace ("path " ++ path ++ " DOES NOT EXIST (returning Left)")
#endif
      Left "Path does not exist"

-- optparse2haskelineCompletion
oa2hl :: CompletionItem -> Completion
oa2hl (CompletionItem replacement' display' isFinished') =
  Completion replacement' display' isFinished'

hl2oa :: Completion -> CompletionItem
hl2oa (Completion replacement' display' isFinished') =
  CompletionItem replacement' display' isFinished'

-- newtype Completer = Completer
--   { runCompleter :: String -> IO [String] }
-- type CompletionFunc m = (String, String) -> m (String, [Completion])
-- "optparse-applicative" wrapper around haskelinec>s 'completeFilename'
-- 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.
-- TODO this should accept a filter ? like for which type of file
completePath :: Completer
completePath = mkCompleter $ \entry -> do
  -- case words entry of
  --   [] -> ""
  --   x -> tail
  (_, completions) <-
#ifdef DEBUG_COMPLETION
    trace "completeFilename called with entry"
#endif
    completeFilename (reverse entry, "")
  let completions' = map hl2oa completions
  putStrLn $ "completePath called !! with entry: [" ++ entry ++ "]"
  return $
#ifdef DEBUG_COMPLETION
    trace ("completions: " ++ show completions)
#endif
    completions'

-- drop 1 was for progname ?
-- TODO make it so that it returns [Completion] instead
-- runParser and runParserStep
haskelineCompletionQuery :: ParserInfo a -> ParserPrefs
  -> [String]
  -- ^ words , Should be Args ?
  -- -> Int
  -- ^ current word (to remove ?)
  -> String -> IO [Completion]
haskelineCompletionQuery pinfo pprefs ws rest = case runCompletion compl pprefs of
  Just (Left (SomeParser p, a)) ->
#ifdef DEBUG_COMPLETION
    trace "listing options\n"
#endif
    list_options a p
  -- terminal case
  Just (Right c) ->
#ifdef DEBUG_COMPLETION
    trace "terminal completer\n"
#endif
    run_completer c
  Nothing ->
#ifdef DEBUG_COMPLETION
    trace "runCompletion into Nothing\n"
#endif
    return []
  where
    --current word
    -- runParserInfo te renvoie une (Completion a)
    -- drop 1 looks necesary here ?
    compl =
#ifdef DEBUG_COMPLETION
      traceShow ("Passing args " ++ show ws ++ "\n")
#endif
      runParserInfo pinfo ws
    -- runParserInfo calls runParserFully
    -- compl = runParserInfo pinfo ws
    -- trace ("runCompleter: ws=" ++ show ws ++ " i=" ++ show i ++ "ws''= " ++ show ws'' ++ " rest=" ++ show rest)

    list_options a
      = fmap concat
      . sequence
      . mapParser (opt_completions a)

    -- Prior to 0.14 there was a subtle bug which would
    -- mean that completions from positional arguments
    -- further into the parse would be shown.
    --
    -- We therefore now check to see that
    -- hinfoUnreachableArgs is off before running the
    -- completion for position arguments.
    --
    -- For options and flags, ensure that the user
    -- hasn't disabled them with `--`.
    -- opt_completions :: ArgPolicy -> ArgumentReachability -> Option a -> m [Completion]
    -- trace ("argPolicy " ++ show argPolicy ++ "\n")
    opt_completions argPolicy reachability opt = case  optMain opt of
      OptReader ns _ _
         | argPolicy /= AllPositionals ->
#ifdef DEBUG_COMPLETION
            trace "unreachable OptReader\n"
#endif
            return . add_opt_help opt $ show_names ns
         -- trace "optreader\n"
         | otherwise ->  return []
      FlagReader ns _
         | argPolicy /= AllPositionals ->
#ifdef DEBUG_COMPLETION
            trace "unreachableflag reader\n"
#endif
            return . add_opt_help opt $ show_names ns
         -- trace "flagreader\n"
         | otherwise ->  return []
      ArgReader rdr
         | argumentIsUnreachable reachability -> 
#ifdef DEBUG_COMPLETION
            trace "unreachable\n " 
#endif
            return []
         -- TODO restore arg Reader with file autocomplete
         --  | otherwise -> return []
         -- trace "argreader\n"
         | otherwise -> run_completer (crCompleter rdr)
         -- >>= \x -> return $ Completion x "argreader help" True
      CmdReader _ ns p
         | argumentIsUnreachable reachability ->
#ifdef DEBUG_COMPLETION
              trace "unreachable cmdreader\n" 
#endif

              return []
         -- trace "cmdreader\n"
         | otherwise -> return . add_cmd_help p $ filter_names ns

    -- When doing enriched completions, add any help specified
    -- to the completion variables (tab separated).
    add_opt_help :: Functor f => Option a -> f String -> f Completion
    add_opt_help opt = fmap $ \o ->
          let h = unChunk $ optHelp opt
              len = 80
          in  maybe (Completion o "option help" True) (\h' -> Completion o (o ++ "\t" ++ render_line len h' ) False) h

    -- When doing enriched completions, add the command description
    -- to the completion variables (tab separated).
    add_cmd_help :: Functor f => (String -> Maybe (ParserInfo a)) -> f String -> f Completion
    add_cmd_help p = fmap $ \cmd -> let
            len = 80
            h = p cmd >>= unChunk . infoProgDesc
          in
            -- if there is a parser info we add help
            maybe (Completion cmd "cmd help" True) (\h' -> Completion cmd (cmd ++ "\t" ++ render_line len h') False) h

    show_names :: [OptName] -> [String]
    show_names = filter_names . map showOption

    -- We only want to show a single line in the completion results description.
    -- If there was a line break, it would come across as a different completion
    -- possibility.
    render_line :: Int -> Doc -> String
    render_line len doc = case lines (displayS (renderPretty 1 len doc) "") of
      [] -> ""
      [x] -> x
      x : _ -> x ++ "..."

    filter_names :: [String] -> [String]
    filter_names = filter (isPrefixOf currentArg)

    -- TODO fix the arg
    run_completer :: Completer -> IO [Completion]
    -- (fromMaybe "" (listToMaybe [currentArg]))
    run_completer c =

#ifdef DEBUG_COMPLETION
      trace ("running completer against " ++ currentArg)
#endif
      runCompleter c currentArg >>= return . map oa2hl

    currentArg :: String
    currentArg = case ws of
      [] -> ""
      ws' -> last ws'

-- The output String is the unused portion of the left half of the line, reversed.
-- type CompletionFunc m = (String, String) -> m (String, [Completion]
-- haskeline System.Console.Haskeline.Completion
-- 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
generateHaskelineCompleterFromParserInfo :: ParserPrefs -> ParserInfo a -> CompletionFunc IO
generateHaskelineCompleterFromParserInfo parserPrefs pinfo =
  \(rleft, right) ->
  let
    leftArgs = words $ reverse rleft
    fullArgs = reverse rleft ++ right
    fullArgs' = words fullArgs ++ if trailingSpace then [""] else []
    currentWord = last leftArgs
    parserResult =
#ifdef DEBUG_COMPLETION 
        trace ("\nParsing args " ++ reverse rleft ++ "\n")
#endif

        execParserPure parserPrefs pinfo leftArgs
    trailingSpace = " " `isSuffixOf` fullArgs
    leftRes = case fullArgs' of
      [] -> ""
      _ -> mconcat (init fullArgs') ++ if trailingSpace then " " else ""
  in do
    candidates <- haskelineCompletionQuery pinfo parserPrefs (trace ("\n" ++ show fullArgs' ++ "\n" ) fullArgs') ""
    -- putStrLn $ "Returned candidates : " ++ show candidates

    -- now onto converting candidates
    -- TODO stripper le commonPrefix
    -- map (\x -> System.Console.Haskeline.Completion x x False)


    pure (" " ++ reverse leftRes,  map (
      id
      -- \x -> x { replacement = fromMaybe "error" (stripPrefix currentWord (display x)) }
      )
      candidates)


-- runParserInfo on a subparser only goes one level deep
-- so we need to run it twice
-- customHaskelineParser :: ParserInfo a -> ParserPrefs -> CompletionFunc IO
-- customHaskelineParser pinfo pprefs =
--   \(rleft, right) ->
--   let
--     leftArgs = words $ reverse rleft
--     -- TODO rename to str
--     fullArgs = reverse rleft ++ right
--     fullArgs' = words fullArgs ++ if trailingSpace then [""] else []
--   in do
--     if len fullArgs' > 1 then
--       generateHaskelineCompleterFromParserInfo (

-- generateHaskelineCompleterFromParserInfo = haskelineCompletionQuery
-- generateHaskelineCompleterFromParser pprefs (infoParser pinfo)

-- generateHaskelineCompleterFromParser :: ParserPrefs -> Parser a -> CompletionFunc IO
-- generateHaskelineCompleterFromParser parserPrefs (OptP opt) = generateHaskelineCompleterFromOption opt
-- generateHaskelineCompleterFromParser _ _ = error "undefined "

-- argument completeWith / complete
placeholderCompletion :: System.Console.Haskeline.Completion
placeholderCompletion = Completion "itworked" "display" False

constCompletionFunc :: CompletionFunc IO
constCompletionFunc (left, right) = pure ("worked", [placeholderCompletion])

-- generateHaskelineCompleterFromOption :: Option a -> CompletionFunc IO
-- generateHaskelineCompleterFromOption (Option main _properties) = generateHaskelineCompleterFromOptreader main

-- generateHaskelineCompleterFromOptreader :: OptReader a -> CompletionFunc IO
-- generateHaskelineCompleterFromOptreader (CmdReader mbGrpCommand arrStr func) =
--   \(rleft, right) -> let
--     filtered = filter (isPrefixOf prefix) arrStr
--     -- genCompletions :: String -> IO [Completion]
--     -- genCompletions prefix = map (genCompletion prefix)
--     -- "TODO show help for " ++
--     genCompletion entry =  Completion entry ( entry) True
--     prefix = reverse rleft
--     longestCommonPrefix entries = rleft
--     completions = map (genCompletion) filtered
--   in
--     -- TODO call execParserPure ParserInfo a
--     trace "completion called" (pure (
--     -- return longest common prefixes
--     "", completions
--     ))

-- -- TODO convert the C
-- -- generateHaskelineCompleterFromOptreader (ArgReader (CReader completer _)) =
-- generateHaskelineCompleterFromOptreader _ = error "undefined generateHaskelineCompleterFromOptreader"