packages feed

todos-0.5: Todos/Default/Curses.hs

{-# LANGUAGE UnicodeSyntax #-}

module Todos.Default.Curses (cursesPrintTodos) where

import Control.Monad
import Control.Monad.Reader
import System.IO
import System.Console.ANSI as ANSI
import UI.HSCurses.Curses  as Curses
import System.Locale.SetLocale

import Todos.Types
import Todos.Tree
import Todos.Default.Config
import Todos.Default.Print (showTodos, defaultPrintTodos)
import Todos.Formatters hiding (outItem)

toCursesColor ∷ ANSI.Color → Curses.Color
toCursesColor ANSI.Black   = Curses.Color 0
toCursesColor ANSI.Red     = Curses.Color 1
toCursesColor ANSI.Green   = Curses.Color 2
toCursesColor ANSI.Yellow  = Curses.Color 3
toCursesColor ANSI.Blue    = Curses.Color 4
toCursesColor ANSI.Magenta = Curses.Color 5
toCursesColor ANSI.Cyan    = Curses.Color 6
toCursesColor ANSI.White   = Curses.Color 7

setColor ∷ Window → ANSI.ColorIntensity → ANSI.Color → IO ()
setColor pad int clr = do
  let (Curses.Color c) = toCursesColor clr
  (attr, _) ← wAttrGet pad
  let attr' = case int of
               ANSI.Vivid → Curses.setBold attr True
               ANSI.Dull  → attr
  wAttrSet pad (attr', Pair c)

setBoldW ∷ Window → IO ()
setBoldW pad = do
  (attr, pair) ← wAttrGet pad
  wAttrSet pad (Curses.setBold attr True, pair)
  
reset ∷ Window → IO ()
reset pad = wAttrSet pad (attr0, Pair 0)

-- | Print OutItem to console
outItem ∷  Window → OutItem → IO ()
outItem pad (OutString s)     = wAddStr pad s
outItem pad (OutSetColor i c) = setColor pad i c
outItem pad SetBold           = setBoldW pad
outItem pad ResetAll          = reset pad

cursesPrintTodos ∷ (TodoItem → IO ()) → PrintConfig DefaultConfig → [Todo] → IO ()
cursesPrintTodos run cfg lst = do
  tty ← hIsTerminalDevice stdout
  if not tty
    then defaultPrintTodos cfg lst
    else do
      todos <- if hasCycles lst
                then do
                  hPutStrLn stderr "Warning: cycles in TODOs; `execute' will not work"
                  return lst
                else return $ markLevels $ enumerateTodos lst
      setLocale LC_ALL Nothing
      initCurses
      (lines,cols) ← scrSize
      let nLines = fromIntegral $ treeLines todos
      if nLines >= lines
        then do
          np ← colorPairs
          nc ← colors
          let m = min np nc
          forM_ [0..m-1] $ \i →
            initPair (Pair i) (Curses.Color i) (Curses.Color (-1))
          pad ← newPad nLines cols
          let lst' = runReader (showTodos todos) cfg
          forM lst' (outItem pad)
          echo False
          scrollPad pad nLines lines cols (runByNumber run todos)
          delWin pad
          endWin
        else do
          endWin
          defaultPrintTodos cfg lst

runByNumber ∷ (TodoItem → IO ()) → [Todo] → Int → IO ()
runByNumber run todos i =
  case itemByNumber todos (fromIntegral i) of
    Nothing → putStrLn $ "Not found: " ++ show i
    Just x  → run x
  
scrollPad ∷ Window → Int → Int → Int → (Int → IO ()) → IO ()
scrollPad pad padLines lines cols run = do
    wMove pad 0 0
    scrollToLine 0
    waitForKeys 0
  where
    scrollToLine i = pRefresh pad i 0 0 0 (lines-1) (cols-1)
    
    scroll i = do
      scrollToLine i
      waitForKeys i
    
    moveDown i = do
      (y,x) ← getYX pad
      wMove pad (min (padLines-1) (y+1)) x
      if y-i == lines - 1
        then scroll $ min (padLines - lines) (i+1)
        else scroll i
               
    moveUp i = do
      (y,x) ← getYX pad
      wMove pad (max 0 (y-1)) x
      if y == i
        then scroll $ max 0 (i-1)
        else scroll i

    moveRight i = do
      (y,x) ← getYX pad
      wMove pad y $ min (cols-1) (x+1)
      scroll i

    moveLeft i = do
      (y,x) ← getYX pad
      wMove pad y $ max 0 (x-1)
      scroll i

    runSelected = do
      (y,_) ← getYX pad
      wAddStr pad (show y)
      run (y+1)

    waitForKeys i = do
      c ← getCh
      case c of
        KeyChar 'q' → return ()
        KeyChar 'j' → moveDown i
        KeyChar 'k' → moveUp i
        KeyChar 'h' → moveLeft i
        KeyChar 'l' → moveRight i
        KeyChar 'g' → scroll 0
        KeyChar 'G' → scroll (padLines - lines)
        KeyChar 'e' → runSelected
        _           → beep >> waitForKeys i