on ffe6de2 help sign in
vty/src/Graphics/Vty/Terminal.hs
146 lines 6.7 kB history raw
1-- | Terminal IO device.
2--
3-- Access to the current terminal or a specific terminal device.
4--
5-- See also:
6--
7-- 1. Graphics.Vty.Terminal: This instantiates an abtract interface to the terminal interface based
8-- on the TERM and COLORTERM environment variables.
9--
10-- 2. Graphics.Vty.Terminal.Interface: Defines the generic interface all terminals need to implement.
11--
12-- 3. Graphics.Vty.Terminal.TerminfoBased: Defines a terminal instance that uses terminfo for all
13-- control strings. No attempt is made to change the character set to UTF-8 for these terminals.
14-- I don't know a way to reliably determine if that is required or how to do so.
15--
16-- 4. Graphics.Vty.Terminal.XTermColor: This module contains an interface suitable for xterm-like
17-- terminals. These are the terminals where TERM == xterm. This does use terminfo for as many
18-- control codes as possible.
19--
20-- Copyright 2009-2010 Corey O'Connor
21module Graphics.Vty.Terminal ( module Graphics.Vty.Terminal
22 , Terminal(..) -- \todo hide constructors
23 , AssumedState(..)
24 , DisplayContext(..) -- \todo hide constructors
25 , output_picture
26 , display_context
27 )
28 where
31import Graphics.Vty.DisplayRegion
32import Graphics.Vty.Terminal.Interface
33import Graphics.Vty.Terminal.MacOSX as MacOSX
34import Graphics.Vty.Terminal.XTermColor as XTermColor
35import Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
37import Control.Applicative
38import Control.Exception ( SomeException, try )
39import Control.Monad
40import Control.Monad.Trans
42import Data.List ( isPrefixOf )
44import GHC.IO.Handle
46import System.Environment
47import System.IO
49-- | Returns a `Terminal` for the current terminal.
50--
51-- The specific Terminal implementation used is hidden from the API user. All terminal
52-- implementations are assumed to perform more, or less, the same. Currently all implementations use
53-- terminfo for at least some terminal specific information. This is why platforms without terminfo
54-- are not supported. However, as mentioned before, any specifics about it being based on terminfo
55-- are hidden from the API user. If a terminal implementation is developed for a terminal for a
56-- platform without terminfo support then Vty should work as expected on that terminal.
57--
58-- Selection of a terminal is done as follows:
59--
60-- * If TERM == xterm
61-- then the terminal might be one of the Mac OS X .app terminals. Check if that might be
62-- the case and use MacOSX if so.
63-- otherwise use XTermColor.
64--
65-- * for any other TERM value TerminfoBased is used.
66--
67--
68-- The terminal has to be determined dynamically at runtime. To satisfy this requirement all
69-- terminals instances are lifted into an abstract terminal handle via existential qualification.
70-- This implies that the only equations that can used are those in the terminal class.
71--
72-- To differentiate between Mac OS X terminals this uses the TERM_PROGRAM environment variable.
73-- However, an xterm started by Terminal or iTerm *also* has TERM_PROGRAM defined since the
74-- environment variable is not reset/cleared by xterm. However a Terminal.app or iTerm.app started
75-- from an xterm under X11 on mac os x will likely be done via open. Since this does not propogate
76-- environment variables (I think?) this assumes that XTERM_VERSION will never be set for a true
77-- Terminal.app or iTerm.app session.
78--
79--
80-- The file descriptor used for output will a duplicate of the current stdout file descriptor.
81--
82-- todo: add an implementation for windows that does not depend on terminfo. Should be installable
83-- with only what is provided in the haskell platform.
84--
85-- todo: The Terminal interface does not provide any input support.
86current_terminal :: ( Applicative m, MonadIO m ) => m Terminal
87current_terminal = do
88 term_type <- liftIO $ getEnv "TERM"
89 out_handle <- liftIO $ hDuplicate stdout
90 terminal_with_name_and_io term_type out_handle
92terminal_with_name_and_io :: (Applicative m, MonadIO m) => String -> Handle -> m Terminal
93terminal_with_name_and_io term_type out_handle = do
94 t <- if "xterm" `isPrefixOf` term_type
95 then do
96 maybe_terminal_app <- get_env "TERM_PROGRAM"
97 case maybe_terminal_app of
98 Nothing
99 -> XTermColor.reserve_terminal term_type out_handle
100 Just v | v == "Apple_Terminal" || v == "iTerm.app"
101 -> do
102 maybe_xterm <- get_env "XTERM_VERSION"
103 case maybe_xterm of
104 Nothing -> MacOSX.reserve_terminal v out_handle
105 Just _ -> XTermColor.reserve_terminal term_type out_handle
106 -- Assume any other terminal that sets TERM_PROGRAM to not be an OS X terminal.app
107 -- like terminal?
108 _ -> XTermColor.reserve_terminal term_type out_handle
109 -- Not an xterm-like terminal. try for generic terminfo.
110 else TerminfoBased.reserve_terminal term_type out_handle
111 return t
112 where
113 get_env var = do
114 mv <- liftIO $ try $ getEnv var
115 case mv of
116 Left (_e :: SomeException) -> return $ Nothing
117 Right v -> return $ Just v
119-- | Sets the cursor position to the given output column and row.
120--
121-- This is not necessarially the same as the character position with the same coordinates.
122-- Characters can be a variable number of columns in width.
123--
124-- Currently, the only way to set the cursor position to a given character coordinate is to specify
125-- the coordinate in the Picture instance provided to output_picture or refresh.
126set_cursor_pos :: MonadIO m => Terminal -> Int -> Int -> m ()
127set_cursor_pos t x y = do
128 bounds <- display_bounds t
129 when (x >= 0 && x < region_width bounds && y >= 0 && y < region_height bounds) $ do
130 dc <- display_context t bounds
131 liftIO $ send_to_terminal t (move_cursor_required_bytes dc x y) (serialize_move_cursor dc x y)
133-- | Hides the cursor
134hide_cursor :: MonadIO m => Terminal -> m ()
135hide_cursor t = do
136 bounds <- display_bounds t
137 dc <- display_context t bounds
138 liftIO $ send_to_terminal t (hide_cursor_required_bytes dc) (serialize_hide_cursor dc)
140-- | Shows the cursor
141show_cursor :: MonadIO m => Terminal -> m ()
142show_cursor t = do
143 bounds <- display_bounds t
144 dc <- display_context t bounds
145 liftIO $ send_to_terminal t (show_cursor_required_bytes dc) (serialize_show_cursor dc)