a simple interactive text ui
Lets you navigate around the balance, register, print reports and the raw ledger, and drill down on a single account. Adds a dependency on vty.
This commit is contained in:
		
							parent
							
								
									b4bdc926dd
								
							
						
					
					
						commit
						dd040f9ca2
					
				
							
								
								
									
										44
									
								
								Options.hs
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								Options.hs
									
									
									
									
									
								
							| @ -36,27 +36,28 @@ usage = usageInfo usagehdr options ++ usageftr | |||||||
| -- | Command-line options we accept. | -- | Command-line options we accept. | ||||||
| options :: [OptDescr Opt] | options :: [OptDescr Opt] | ||||||
| options = [ | options = [ | ||||||
|  Option ['f'] ["file"]         (ReqArg File "FILE")   filehelp, |   Option ['f'] ["file"]         (ReqArg File "FILE")   filehelp | ||||||
|  Option ['b'] ["begin"]        (ReqArg Begin "DATE") "report on entries on or after this date", |  ,Option ['b'] ["begin"]        (ReqArg Begin "DATE")  "report on entries on or after this date" | ||||||
|  Option ['e'] ["end"]          (ReqArg End "DATE")   "report on entries prior to this date", |  ,Option ['e'] ["end"]          (ReqArg End "DATE")    "report on entries prior to this date" | ||||||
|  Option ['p'] ["period"]       (ReqArg Period "EXPR") ("report on entries during the specified period\n" ++ |  ,Option ['p'] ["period"]       (ReqArg Period "EXPR") ("report on entries during the specified period\n" ++ | ||||||
|                                                        "and/or with the specified reporting interval\n"), |                                                        "and/or with the specified reporting interval\n") | ||||||
|  Option ['C'] ["cleared"]      (NoArg  Cleared)       "report only on cleared entries", |  ,Option ['C'] ["cleared"]      (NoArg  Cleared)       "report only on cleared entries" | ||||||
|  Option ['B'] ["cost","basis"] (NoArg  CostBasis)     "report cost basis of commodities", |  ,Option ['B'] ["cost","basis"] (NoArg  CostBasis)     "report cost basis of commodities" | ||||||
|  Option []    ["depth"]        (ReqArg Depth "N")     "balance report: maximum account depth to show", |  ,Option []    ["depth"]        (ReqArg Depth "N")     "balance report: maximum account depth to show" | ||||||
|  Option ['d'] ["display"]      (ReqArg Display "EXPR") ("display only transactions matching simple EXPR\n" ++ |  ,Option ['d'] ["display"]      (ReqArg Display "EXPR") ("display only transactions matching simple EXPR\n" ++ | ||||||
|                                                         "(where EXPR is 'dOP[DATE]', OP is <, <=, =, >=, >)"), |                                                         "(where EXPR is 'dOP[DATE]', OP is <, <=, =, >=, >)") | ||||||
|  Option ['E'] ["empty"]        (NoArg  Empty)         "balance report: show accounts with zero balance", |  ,Option ['E'] ["empty"]        (NoArg  Empty)         "balance report: show accounts with zero balance" | ||||||
|  Option ['R'] ["real"]         (NoArg  Real)          "report only on real (non-virtual) transactions", |  ,Option ['R'] ["real"]         (NoArg  Real)          "report only on real (non-virtual) transactions" | ||||||
|  Option []    ["options-anywhere"] (NoArg OptionsAnywhere) "allow options anywhere, use ^ to negate patterns", |  ,Option []    ["options-anywhere"] (NoArg OptionsAnywhere) "allow options anywhere, use ^ to negate patterns" | ||||||
|  Option ['n'] ["collapse"]     (NoArg  Collapse)      "balance report: no grand total", |  ,Option ['n'] ["collapse"]     (NoArg  Collapse)      "balance report: no grand total" | ||||||
|  Option ['s'] ["subtotal"]     (NoArg  SubTotal)      "balance report: show subaccounts", |  ,Option ['s'] ["subtotal"]     (NoArg  SubTotal)      "balance report: show subaccounts" | ||||||
|  Option ['W'] ["weekly"]       (NoArg  WeeklyOpt)        "register report: show weekly summary", |  ,Option ['W'] ["weekly"]       (NoArg  WeeklyOpt)     "register report: show weekly summary" | ||||||
|  Option ['M'] ["monthly"]      (NoArg  MonthlyOpt)       "register report: show monthly summary", |  ,Option ['M'] ["monthly"]      (NoArg  MonthlyOpt)    "register report: show monthly summary" | ||||||
|  Option ['Y'] ["yearly"]       (NoArg  YearlyOpt)        "register report: show yearly summary", |  ,Option ['Y'] ["yearly"]       (NoArg  YearlyOpt)     "register report: show yearly summary" | ||||||
|  Option ['h'] ["help"] (NoArg  Help)                  "show this help", |  ,Option ['h'] ["help"] (NoArg  Help)                  "show this help" | ||||||
|  Option ['v'] ["verbose"]      (NoArg  Verbose)       "verbose test output", |  ,Option ['v'] ["verbose"]      (NoArg  Verbose)       "verbose test output" | ||||||
|  Option ['V'] ["version"]      (NoArg  Version)       "show version" |  ,Option ['V'] ["version"]      (NoArg  Version)       "show version" | ||||||
|  |  ,Option []    ["debug-no-ui"]  (NoArg  DebugNoUI)     "when running in ui mode, don't display anything (mostly)" | ||||||
|  ] |  ] | ||||||
|     where  |     where  | ||||||
|       filehelp = printf "ledger file; - means use standard input. Defaults\nto the %s environment variable or %s" |       filehelp = printf "ledger file; - means use standard input. Defaults\nto the %s environment variable or %s" | ||||||
| @ -83,6 +84,7 @@ data Opt = | |||||||
|     Help | |     Help | | ||||||
|     Verbose | |     Verbose | | ||||||
|     Version |     Version | ||||||
|  |     | DebugNoUI | ||||||
|     deriving (Show,Eq) |     deriving (Show,Eq) | ||||||
| 
 | 
 | ||||||
| -- yow.. | -- yow.. | ||||||
|  | |||||||
							
								
								
									
										381
									
								
								UICommand.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								UICommand.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,381 @@ | |||||||
|  | {-|  | ||||||
|  | 
 | ||||||
|  | A simple text UI for hledger. | ||||||
|  | 
 | ||||||
|  | -} | ||||||
|  | 
 | ||||||
|  | module UICommand | ||||||
|  | where | ||||||
|  | import qualified Data.Map as Map | ||||||
|  | import Data.Map ((!)) | ||||||
|  | import Graphics.Vty | ||||||
|  | import qualified Data.ByteString.Char8 as B | ||||||
|  | import Ledger | ||||||
|  | import Options | ||||||
|  | import BalanceCommand | ||||||
|  | import RegisterCommand | ||||||
|  | import PrintCommand | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | helpmsg = "Welcome to hledger. (b)alances, (r)egister, (p)rint entries, (l)edger, (right) to drill down, (left) to back up, or (q)uit" | ||||||
|  | 
 | ||||||
|  | instance Show Vty where show v = "a Vty" | ||||||
|  | 
 | ||||||
|  | data AppState = AppState { | ||||||
|  |      av :: Vty                   -- the vty context | ||||||
|  |     ,aw :: Int                   -- window width | ||||||
|  |     ,ah :: Int                   -- window height | ||||||
|  |     ,amsg :: String              -- status message | ||||||
|  |     ,aopts :: [Opt]              -- command-line opts | ||||||
|  |     ,aargs :: [String]           -- command-line args | ||||||
|  |     ,aledger :: Ledger           -- parsed ledger | ||||||
|  |     ,abuf :: [String]            -- lines of the current buffered view | ||||||
|  |     ,alocs :: [Loc]              -- user's navigation trail within the UI | ||||||
|  |                                 -- never null, head is current location | ||||||
|  |     } deriving (Show) | ||||||
|  | 
 | ||||||
|  | data Loc = Loc { | ||||||
|  |      scr :: Screen               -- ui screen | ||||||
|  |     ,sy :: Int                   -- viewport y scroll position | ||||||
|  |     ,cy :: Int                   -- cursor y position | ||||||
|  |     } deriving (Show) | ||||||
|  | 
 | ||||||
|  | data Screen = BalanceScreen     -- like "hledger balance".. shows accounts | ||||||
|  |             | RegisterScreen    -- like "hledger register".. shows transactions | ||||||
|  |             | PrintScreen       -- like "hledger print".. shows entries | ||||||
|  |             | LedgerScreen      -- shows the raw ledger | ||||||
|  |               deriving (Eq,Show) | ||||||
|  | 
 | ||||||
|  | -- | Run the interactive text ui. | ||||||
|  | ui :: [Opt] -> [String] -> Ledger -> IO () | ||||||
|  | ui opts args l = do | ||||||
|  |   v <- mkVty | ||||||
|  |   (w,h) <- getSize v | ||||||
|  |   let opts' = SubTotal:opts | ||||||
|  |   let a = enter BalanceScreen $  | ||||||
|  |           AppState { | ||||||
|  |                   av=v | ||||||
|  |                  ,aw=w | ||||||
|  |                  ,ah=h | ||||||
|  |                  ,amsg=helpmsg | ||||||
|  |                  ,aopts=opts' | ||||||
|  |                  ,aargs=args | ||||||
|  |                  ,aledger=l | ||||||
|  |                  ,abuf=[] | ||||||
|  |                  ,alocs=[] | ||||||
|  |                  } | ||||||
|  |   go a  | ||||||
|  | 
 | ||||||
|  | -- | Update the screen, wait for the next event, repeat. | ||||||
|  | go :: AppState -> IO () | ||||||
|  | go a@AppState{av=av,aw=aw,ah=ah,abuf=buf,amsg=amsg,aopts=opts,aargs=args,aledger=l} = do | ||||||
|  |   when (not $ DebugNoUI `elem` opts) $ update av (renderScreen a) | ||||||
|  |   k <- getEvent av | ||||||
|  |   case k of  | ||||||
|  |     EvResize x y                -> go $ resize x y a | ||||||
|  |     EvKey (KASCII 'l') [MCtrl]  -> refresh av >> go a{amsg=helpmsg} | ||||||
|  |     EvKey (KASCII 'b') []       -> go $ resetTrailAndEnter BalanceScreen a | ||||||
|  |     EvKey (KASCII 'r') []       -> go $ resetTrailAndEnter RegisterScreen a | ||||||
|  |     EvKey (KASCII 'p') []       -> go $ resetTrailAndEnter PrintScreen a | ||||||
|  |     EvKey (KASCII 'l') []       -> go $ resetTrailAndEnter LedgerScreen a | ||||||
|  |     EvKey KRight []             -> go $ drilldown a | ||||||
|  |     EvKey KEnter []             -> go $ drilldown a | ||||||
|  |     EvKey KLeft  []             -> go $ backout a | ||||||
|  |     EvKey KUp    []             -> go $ moveUpAndPushEdge a | ||||||
|  |     EvKey KDown  []             -> go $ moveDownAndPushEdge a | ||||||
|  |     EvKey KHome  []             -> go $ moveToTop a | ||||||
|  |     EvKey KUp    [MCtrl]        -> go $ moveToTop a | ||||||
|  |     EvKey KUp    [MShift]       -> go $ moveToTop a | ||||||
|  |     EvKey KEnd   []             -> go $ moveToBottom a | ||||||
|  |     EvKey KDown  [MCtrl]        -> go $ moveToBottom a | ||||||
|  |     EvKey KDown  [MShift]       -> go $ moveToBottom a | ||||||
|  |     EvKey KPageUp []            -> go $ prevpage a | ||||||
|  |     EvKey KBS []                -> go $ prevpage a | ||||||
|  |     EvKey (KASCII ' ') [MShift] -> go $ prevpage a | ||||||
|  |     EvKey KPageDown []          -> go $ nextpage a | ||||||
|  |     EvKey (KASCII ' ') []       -> go $ nextpage a | ||||||
|  |     EvKey (KASCII 'q') []       -> shutdown av >> return () | ||||||
|  | --    EvKey KEsc   []           -> shutdown av >> return () | ||||||
|  |     _                           -> go a | ||||||
|  |     where | ||||||
|  |       bh = length buf | ||||||
|  |       y = posY a | ||||||
|  | 
 | ||||||
|  | -- app state modifiers | ||||||
|  | 
 | ||||||
|  | -- | The number of lines currently available for the main data display area. | ||||||
|  | pageHeight :: AppState -> Int | ||||||
|  | pageHeight a = ah a - 1 | ||||||
|  | 
 | ||||||
|  | setLocCursorY, setLocScrollY :: Int -> Loc -> Loc | ||||||
|  | setLocCursorY y l = l{cy=y} | ||||||
|  | setLocScrollY y l = l{sy=y} | ||||||
|  | 
 | ||||||
|  | cursorY, scrollY, posY :: AppState -> Int | ||||||
|  | cursorY = cy . loc | ||||||
|  | scrollY = sy . loc | ||||||
|  | posY a = scrollY a + cursorY a | ||||||
|  | 
 | ||||||
|  | setCursorY, setScrollY, setPosY :: Int -> AppState -> AppState | ||||||
|  | setCursorY y a@AppState{alocs=(l:locs)} = a{alocs=(l':locs)} where l' = setLocCursorY y l | ||||||
|  | setScrollY y a@AppState{alocs=(l:locs)} = a{alocs=(l':locs)} where l' = setLocScrollY y l | ||||||
|  | setPosY y a@AppState{alocs=(l:locs)} = a{alocs=(l':locs)} | ||||||
|  |     where  | ||||||
|  |       l' = setLocScrollY sy $ setLocCursorY cy l | ||||||
|  |       ph = pageHeight a | ||||||
|  |       cy = y `mod` ph | ||||||
|  |       sy = y - cy | ||||||
|  | 
 | ||||||
|  | updateCursorY, updateScrollY, updatePosY :: (Int -> Int) -> AppState -> AppState | ||||||
|  | updateCursorY f a = setCursorY (f $ cursorY a) a | ||||||
|  | updateScrollY f a = setScrollY (f $ scrollY a) a | ||||||
|  | updatePosY f a = setPosY (f $ posY a) a | ||||||
|  | 
 | ||||||
|  | resize x y a = setCursorY cy' a{aw=x,ah=y} | ||||||
|  |     where | ||||||
|  |       cy = cursorY a | ||||||
|  |       cy' = min cy (y-2) | ||||||
|  | 
 | ||||||
|  | moveToTop a = setPosY 0 a | ||||||
|  | 
 | ||||||
|  | moveToBottom a = setPosY (length $ abuf a) a | ||||||
|  | 
 | ||||||
|  | moveUpAndPushEdge a@AppState{alocs=(Loc{sy=sy,cy=cy}:_)} | ||||||
|  |     | cy > 0 = updateCursorY (subtract 1) a | ||||||
|  |     | sy > 0 = updateScrollY (subtract 1) a | ||||||
|  |     | otherwise = a | ||||||
|  | 
 | ||||||
|  | moveDownAndPushEdge a@AppState{alocs=(Loc{sy=sy,cy=cy}:_)} | ||||||
|  |     | sy+cy >= bh = a | ||||||
|  |     | cy < ph-1 = updateCursorY (+1) a | ||||||
|  |     | otherwise = updateScrollY (+1) a | ||||||
|  |     where  | ||||||
|  |       ph = pageHeight a | ||||||
|  |       bh = length $ abuf a | ||||||
|  | 
 | ||||||
|  | -- | Scroll down by page height or until we can just see the last line, | ||||||
|  | -- without moving the cursor, or if we are already scrolled as far as | ||||||
|  | -- possible then move the cursor to the last line. | ||||||
|  | nextpage (a@AppState{abuf=b}) | ||||||
|  |     | sy < bh-jump = setScrollY sy' a | ||||||
|  |     | otherwise    = setCursorY (bh-sy) a | ||||||
|  |     where | ||||||
|  |       sy = scrollY a | ||||||
|  |       jump = pageHeight a - 1 | ||||||
|  |       bh = length b | ||||||
|  |       sy' = min (sy+jump) (bh-jump) | ||||||
|  | 
 | ||||||
|  | -- | Scroll up by page height or until we can just see the first line, | ||||||
|  | -- without moving the cursor, or if we are scrolled as far as possible | ||||||
|  | -- then move the cursor to the first line. | ||||||
|  | prevpage (a@AppState{abuf=b}) | ||||||
|  |     | sy > 0    = setScrollY sy' a | ||||||
|  |     | otherwise = setCursorY 0 a | ||||||
|  |     where | ||||||
|  |       sy = scrollY a | ||||||
|  |       jump = pageHeight a - 1 | ||||||
|  |       sy' = max (sy-jump) 0 | ||||||
|  | 
 | ||||||
|  | -- | Push a new UI location on to the stack. | ||||||
|  | pushLoc :: Loc -> AppState -> AppState | ||||||
|  | pushLoc l a = a{alocs=(l:alocs a)} | ||||||
|  | 
 | ||||||
|  | popLoc :: AppState -> AppState | ||||||
|  | popLoc a@AppState{alocs=locs} | ||||||
|  |     | length locs > 1 = a{alocs=drop 1 locs} | ||||||
|  |     | otherwise = a | ||||||
|  | 
 | ||||||
|  | clearLocs :: AppState -> AppState | ||||||
|  | clearLocs a = a{alocs=[]} | ||||||
|  | 
 | ||||||
|  | exit :: AppState -> AppState  | ||||||
|  | exit = popLoc | ||||||
|  | 
 | ||||||
|  | loc :: AppState -> Loc | ||||||
|  | loc = head . alocs | ||||||
|  | 
 | ||||||
|  | screen :: AppState -> Screen | ||||||
|  | screen a = scr where (Loc scr _ _) = loc a | ||||||
|  | 
 | ||||||
|  | -- | Enter a new screen, saving the old ui location on the stack. | ||||||
|  | enter :: Screen -> AppState -> AppState  | ||||||
|  | enter scr@BalanceScreen a  = updateData $ pushLoc Loc{scr=scr,sy=0,cy=0} a | ||||||
|  | enter scr@RegisterScreen a = updateData $ pushLoc Loc{scr=scr,sy=0,cy=0} a | ||||||
|  | enter scr@PrintScreen a    = updateData $ pushLoc Loc{scr=scr,sy=0,cy=0} a | ||||||
|  | enter scr@LedgerScreen a   = updateData $ pushLoc Loc{scr=scr,sy=0,cy=0} a | ||||||
|  | 
 | ||||||
|  | resetTrailAndEnter scr a = enter scr $ clearLocs a | ||||||
|  | 
 | ||||||
|  | -- | Regenerate the display data based on current UI location. | ||||||
|  | updateData :: AppState -> AppState | ||||||
|  | updateData a@AppState{aopts=opts,aargs=args,aledger=l} | ||||||
|  |     | scr == BalanceScreen  = a{abuf=lines $ showBalanceReport opts [] l, aargs=[]} | ||||||
|  |     | scr == RegisterScreen = a{abuf=lines $ showRegisterReport opts args l} | ||||||
|  |     | scr == PrintScreen    = a{abuf=lines $ showEntries opts args l} | ||||||
|  |     | scr == LedgerScreen   = a{abuf=lines $ rawledgertext l} | ||||||
|  |     where scr = screen a | ||||||
|  | 
 | ||||||
|  | backout :: AppState -> AppState | ||||||
|  | backout a | ||||||
|  |     | screen a == BalanceScreen = a | ||||||
|  |     | otherwise = updateData $ popLoc a | ||||||
|  | 
 | ||||||
|  | drilldown :: AppState -> AppState | ||||||
|  | drilldown a | ||||||
|  |     | screen a == BalanceScreen  = enter RegisterScreen a{aargs=[currentAccountName a]} | ||||||
|  |     | screen a == RegisterScreen = scrollToEntry e $ enter PrintScreen a | ||||||
|  |     | screen a == PrintScreen   = enter LedgerScreen a | ||||||
|  |     | screen a == LedgerScreen   = a | ||||||
|  |     where e = currentEntry a | ||||||
|  | 
 | ||||||
|  | -- | Get the account name currently highlighted by the cursor on the | ||||||
|  | -- balance screen. Results undefined while on other screens. | ||||||
|  | currentAccountName :: AppState -> AccountName | ||||||
|  | currentAccountName a = accountNameAt (abuf a) (posY a) | ||||||
|  | 
 | ||||||
|  | -- | Get the full name of the account being displayed at a specific line | ||||||
|  | -- within the balance command's output. | ||||||
|  | accountNameAt :: [String] -> Int -> AccountName | ||||||
|  | accountNameAt buf lineno = accountNameFromComponents anamecomponents | ||||||
|  |     where | ||||||
|  |       namestohere = map (drop 22) $ take (lineno+1) buf | ||||||
|  |       (indented, nonindented) = span (" " `isPrefixOf`) $ reverse namestohere | ||||||
|  |       thisbranch = indented ++ take 1 nonindented | ||||||
|  |       anamecomponents = reverse $ map strip $ dropsiblings thisbranch | ||||||
|  | 
 | ||||||
|  |       dropsiblings :: [AccountName] -> [AccountName] | ||||||
|  |       dropsiblings [] = [] | ||||||
|  |       dropsiblings (x:xs) = [x] ++ dropsiblings xs' | ||||||
|  |           where | ||||||
|  |             xs' = dropWhile moreindented xs | ||||||
|  |             moreindented = (>= myindent) . indentof | ||||||
|  |             myindent = indentof x | ||||||
|  |             indentof = length . takeWhile (==' ') | ||||||
|  | 
 | ||||||
|  | -- currentEntry/scrollToEntry doesn't work | ||||||
|  | -- | If on the print screen, move the cursor to highlight the specified entry | ||||||
|  | -- (or a reasonable guess). | ||||||
|  | scrollToEntry :: Entry -> AppState -> AppState | ||||||
|  | scrollToEntry e a@AppState{abuf=buf} = setCursorY cy $ setScrollY sy a | ||||||
|  |     where | ||||||
|  |       entryfirstline = head $ lines $ showEntry $ e | ||||||
|  |       halfph = pageHeight a `div` 2 | ||||||
|  |       y = fromMaybe 0 $ findIndex (== entryfirstline) buf | ||||||
|  |       sy = max 0 $ y - halfph | ||||||
|  |       cy = y - sy | ||||||
|  | 
 | ||||||
|  | -- | Get the entry containing the transaction currently highlighted by the | ||||||
|  | -- cursor on the register screen (or best guess). Results undefined while | ||||||
|  | -- on other screens. | ||||||
|  | currentEntry :: AppState -> Entry | ||||||
|  | currentEntry a@AppState{aledger=l,abuf=buf} = entryContainingTransaction a t | ||||||
|  |     where | ||||||
|  |       t = safehead nulltxn $ filter ismatch $ ledgerTransactions l | ||||||
|  |       ismatch t = date t == (parsedate $ take 10 datedesc) | ||||||
|  |                   && (take 70 $ showtxn False t nullmixedamt) == (datedesc ++ acctamt) | ||||||
|  |       datedesc = take 32 $ fromMaybe "" $ find (not . (" " `isPrefixOf`)) $ [safehead "" rest] ++ reverse above | ||||||
|  |       acctamt = drop 32 $ safehead "" rest | ||||||
|  |       safehead d ls = if null ls then d else head ls | ||||||
|  |       (above,rest) = splitAt y buf | ||||||
|  |       y = posY a | ||||||
|  | 
 | ||||||
|  | -- | Get the entry which contains the given transaction. | ||||||
|  | -- Will raise an error if there are problems. | ||||||
|  | entryContainingTransaction :: AppState -> Transaction -> Entry | ||||||
|  | entryContainingTransaction AppState{aledger=l} t = (entries $ rawledger l) !! entryno t | ||||||
|  | 
 | ||||||
|  | -- renderers | ||||||
|  | 
 | ||||||
|  | renderScreen :: AppState -> Picture | ||||||
|  | renderScreen (a@AppState{aw=w,ah=h,abuf=buf,amsg=msg}) = | ||||||
|  |     pic {pCursor = Cursor cx cy, | ||||||
|  |          pImage = mainimg | ||||||
|  |                   <-> | ||||||
|  |                   renderStatus w msg | ||||||
|  |         } | ||||||
|  |     where  | ||||||
|  |       (cx, cy) = (0, cursorY a) | ||||||
|  |       sy = scrollY a | ||||||
|  |       -- trying for more speed | ||||||
|  |       mainimg = (vertcat $ map (render defaultattr) above) | ||||||
|  |                <-> | ||||||
|  |                (render currentlineattr thisline) | ||||||
|  |                <-> | ||||||
|  |                (vertcat $ map (render defaultattr) below) | ||||||
|  |       render attr = renderBS attr . B.pack | ||||||
|  |       (thisline,below) | null rest = (blankline,[]) | ||||||
|  |                        | otherwise = (head rest, tail rest) | ||||||
|  |       (above,rest) = splitAt cy linestorender | ||||||
|  |       linestorender = map padclipline $ take (h-1) $ drop sy $ buf ++ replicate h blankline | ||||||
|  |       padclipline l = take w $ l ++ blankline | ||||||
|  |       blankline = replicate w ' ' | ||||||
|  | --       mainimg = (renderString attr $ unlines $ above) | ||||||
|  | --           <-> | ||||||
|  | --           (renderString reverseattr $ thisline) | ||||||
|  | --           <-> | ||||||
|  | --           (renderString attr $ unlines $ below) | ||||||
|  | --       (above,(thisline:below))  | ||||||
|  | --           | null ls   = ([],[""]) | ||||||
|  | --           | otherwise = splitAt y ls | ||||||
|  | --       ls = lines $ fitto w (h-1) $ unlines $ drop as $ buf | ||||||
|  | 
 | ||||||
|  | padClipString :: Int -> Int -> String -> [String] | ||||||
|  | padClipString h w s = rows | ||||||
|  |     where | ||||||
|  |       rows = map padclipline $ take h $ lines s ++ replicate h blankline | ||||||
|  |       padclipline l = take w $ l ++ blankline | ||||||
|  |       blankline = replicate w ' ' | ||||||
|  | 
 | ||||||
|  | renderString :: Attr -> String -> Image | ||||||
|  | renderString attr s = vertcat $ map (renderBS attr . B.pack) rows | ||||||
|  |     where | ||||||
|  |       rows = lines $ fitto w h s | ||||||
|  |       w = maximum $ map length $ ls | ||||||
|  |       h = length ls | ||||||
|  |       ls = lines s | ||||||
|  | 
 | ||||||
|  | renderStatus :: Int -> String -> Image | ||||||
|  | renderStatus w s = renderBS statusattr (B.pack $ take w (s ++ repeat ' '))  | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- the all-important theming engine | ||||||
|  | 
 | ||||||
|  | theme = 1 | ||||||
|  | 
 | ||||||
|  | (defaultattr,  | ||||||
|  |  currentlineattr,  | ||||||
|  |  statusattr | ||||||
|  |  ) =  | ||||||
|  |     case theme of | ||||||
|  |       1 -> ( -- restrained | ||||||
|  |            attr | ||||||
|  |           ,setBold attr | ||||||
|  |           ,setRV attr | ||||||
|  |           ) | ||||||
|  |       2 -> ( -- colorful | ||||||
|  |            setRV attr | ||||||
|  |           ,setFG white $ setBG red $ attr | ||||||
|  |           ,setFG black $ setBG green $ attr | ||||||
|  |           ) | ||||||
|  |       3 -> ( --  | ||||||
|  |            setRV attr | ||||||
|  |           ,setFG white $ setBG red $ attr | ||||||
|  |           ,setRV attr | ||||||
|  |           ) | ||||||
|  | 
 | ||||||
|  | halfbrightattr = setHalfBright attr | ||||||
|  | reverseattr = setRV attr | ||||||
|  | redattr = setFG red attr | ||||||
|  | greenattr = setFG green attr | ||||||
|  | reverseredattr = setRV $ setFG red attr | ||||||
|  | reversegreenattr= setRV $ setFG green attr | ||||||
|  | 
 | ||||||
|  | --     pic { pCursor = Cursor x y, | ||||||
|  | --           pImage = renderFill pieceA ' ' w y  | ||||||
|  | --           <-> | ||||||
|  | --           renderHFill pieceA ' ' x <|> renderChar pieceA '@' <|> renderHFill pieceA ' ' (w - x - 1)  | ||||||
|  | --           <-> | ||||||
|  | --           renderFill pieceA ' ' w (h - y - 1)  | ||||||
|  | --           <-> | ||||||
|  | --           renderStatus w msg | ||||||
|  | --         } | ||||||
| @ -20,7 +20,7 @@ Cabal-Version:  >= 1.2 | |||||||
| 
 | 
 | ||||||
| Executable hledger | Executable hledger | ||||||
|   Build-Depends:  base, containers, haskell98, directory, parsec, regex-compat, regexpr |   Build-Depends:  base, containers, haskell98, directory, parsec, regex-compat, regexpr | ||||||
|                   old-locale, time, HUnit |                   old-locale, time, HUnit, vty | ||||||
|   Main-Is:        hledger.hs |   Main-Is:        hledger.hs | ||||||
|   Other-Modules:   |   Other-Modules:   | ||||||
|                   BalanceCommand |                   BalanceCommand | ||||||
|  | |||||||
| @ -39,6 +39,7 @@ module Main ( | |||||||
|              module BalanceCommand, |              module BalanceCommand, | ||||||
|              module PrintCommand, |              module PrintCommand, | ||||||
|              module RegisterCommand, |              module RegisterCommand, | ||||||
|  |              module UICommand, | ||||||
| ) | ) | ||||||
| where | where | ||||||
| import qualified Data.Map as Map (lookup) | import qualified Data.Map as Map (lookup) | ||||||
| @ -48,6 +49,7 @@ import Options | |||||||
| import BalanceCommand | import BalanceCommand | ||||||
| import PrintCommand | import PrintCommand | ||||||
| import RegisterCommand | import RegisterCommand | ||||||
|  | import UICommand | ||||||
| import Tests | import Tests | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -62,6 +64,7 @@ main = do | |||||||
|        | cmd `isPrefixOf` "balance"  = parseLedgerAndDo opts args balance |        | cmd `isPrefixOf` "balance"  = parseLedgerAndDo opts args balance | ||||||
|        | cmd `isPrefixOf` "print"    = parseLedgerAndDo opts args print' |        | cmd `isPrefixOf` "print"    = parseLedgerAndDo opts args print' | ||||||
|        | cmd `isPrefixOf` "register" = parseLedgerAndDo opts args register |        | cmd `isPrefixOf` "register" = parseLedgerAndDo opts args register | ||||||
|  |        | cmd `isPrefixOf` "ui"       = parseLedgerAndDo opts args ui | ||||||
|        | cmd `isPrefixOf` "test"     = runtests opts args >> return () |        | cmd `isPrefixOf` "test"     = runtests opts args >> return () | ||||||
|        | otherwise                   = putStr $ usage |        | otherwise                   = putStr $ usage | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user