on master help sign in
use alloca instead of mallocBytes for exception safety

Ignore-this: 1d31bef2e227fefafe8bb4b6f9511e1d darcs-hash:20090904172928-f0a0d-987b6931294fd33f7825eddc0adf04c81716a4dd.gz

commit7b38759846759068e58468115a580c99edda886a authorcoreyoconnor <coreyoconnor@gmail.com> dateFri 4 Sep 2009, 10:29 (17 years ago)
57 files changed+5245
LICENSE+31 README+54 Setup.lhs+3 TODO+13 cbits/gwinsz.c+9 cbits/gwinsz.h+1 cbits/mk_wcwidth.c+309 cbits/set_term_timing.c+14 src/Codec/Binary/UTF8/Debug.hs+17 src/Codec/Binary/UTF8/Width.hs+32 src/Data/Marshalling.hs+29 src/Data/Sequence/Extra.hs+12 src/Data/Terminfo/Eval.hs+159 src/Data/Terminfo/Parse.hs+215 src/Graphics/Vty.hs+131 src/Graphics/Vty/Attributes.hs+192 src/Graphics/Vty/Debug.hs+81 src/Graphics/Vty/DisplayRegion.hs+11 src/Graphics/Vty/Image.hs+301 src/Graphics/Vty/LLInput.hs+197 src/Graphics/Vty/Picture.hs+72 src/Graphics/Vty/PictureToSpans.hs+7 src/Graphics/Vty/Span.hs+239 src/Graphics/Vty/Terminal.hs+107 src/Graphics/Vty/Terminal/Debug.hs+115 src/Graphics/Vty/Terminal/Generic.hs+376 src/Graphics/Vty/Terminal/MacOSX.hs+76 src/Graphics/Vty/Terminal/TerminfoBased.hs+200 src/Graphics/Vty/Terminal/XTermColor.hs+79 test/Bench.hs+60 test/Bench2.hs+30 test/BenchRenderChar.hs+32 test/ControlTable.hs+14 test/HereDoc.hs+14 test/Makefile+44 test/Test.hs+42 test/Test2.hs+12 test/Verify.hs+75 test/Verify/Data/Terminfo/Parse.hs+114 test/Verify/Graphics/Vty/Attributes.hs+40 test/Verify/Graphics/Vty/Image.hs+69 test/Verify/Graphics/Vty/Picture.hs+50 test/Verify/Graphics/Vty/Span.hs+29 test/Verify/Graphics/Vty/WinRegion.hs+27 test/interactive_terminal_test.hs+847 test/make_tests.sh+31 test/verify_attribute_ops.hs+8 test/verify_control_strings.hs+102 test/verify_debug_terminal.hs+115 test/verify_image_ops.hs+57 test/verify_image_trans.hs+30 test/verify_picture_ops.hs+9 test/verify_picture_to_span.hs+10 test/verify_span_ops.hs+179 test/verify_utf8_width.hs+17 test/vty_issue_18.hs+43 vty.cabal+63
LICENSEnew file+31
@@ -0,0 +1,31 @@
1+Copyright Stefan O'Rear 2006, Corey O'Connor 2008, Corey O'Connor 2009
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Stefan O'Rear nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
READMEnew file+54
@@ -0,0 +1,54 @@
1+vty is a very simple terminal interface library.
2+
3+Vty currently provides:
4+
5+* Automatic handling of suspend/resume (SIGTSTP+SIGCONT).
6+
7+* Automatic handling of window resizes.
8+
9+* Supports Unicode characters on output, automatically setting and
10+ resetting UTF-8 mode (beware double width and combining characters!)
11+
12+* Automatic computation of minimal differences.
13+
14+* Minimizes repaint area, thus virtually eliminating the flicker
15+ problem that plagues ncurses programs.
16+
17+* A pure, compositional interface for efficiently constructing display
18+ images.
19+
20+* Automatically decodes keyboard keys into (key,[modifier]) tuples.
21+
22+* Automatically supports refresh on Ctrl-L.
23+
24+* Automatically supports timeout after 50ms for lone ESC (a barely
25+ noticable delay)
26+
27+* Interface is designed for relatively easy compatible extension.
28+
29+* Supports all ANSI SGR-modes (defined in console_codes(4)) with
30+ a simple type-safe interface.
31+
32+* Properly handles cleanup, leaving the cursor at the bottom of
33+ the screen and erasing the last line.
34+
35+Current disadvantages:
36+
37+* No current support for non-ANSI terminals.
38+
39+* UTF-8 support only works properly on the linux console/xterm,
40+ and even then only if it is not the system default.
41+
42+* Minimal support for special keys on terminals other than the
43+ linux-console. (F1-5 and arrow keys should work, but anything
44+ shifted isn't likely to.)
45+
46+* Uses the TIOCGWINSZ ioctl to find the current window size, which
47+ appears to be limited to Linux and *BSD.
48+
49+darcs get --tag=rel-3.0.0 http://code.haskell.org/vty/
50+
51+To compile the demonstration program: ghc --make Test.hs gwinsz.c
52+
53+The main documentation consists of the haddock-comments and the
54+demonstration program
Setup.lhsnew file+3
@@ -0,0 +1,3 @@
1+#!/usr/bin/env runhaskell
2+> import Distribution.Simple
3+> main = defaultMain
TODOnew file+13
@@ -0,0 +1,13 @@
1+Minor:
2+- input parser uses similar interface to DisplayHandle. Derive instance from terminfo
3+- Improve input handling performance.
4+- xterm cursor foreground handling.
5+ - specific color
6+ - reverse video
7+ - auto
8+- position cursor correctly.
9+
10+
11+Major:
12+- Remove size fields in resize constr
13+
cbits/gwinsz.cnew file+9
@@ -0,0 +1,9 @@
1+#include <sys/ioctl.h>
2+
3+unsigned long c_get_window_size(void) {
4+ struct winsize w;
5+ if (ioctl (0, TIOCGWINSZ, &w) >= 0)
6+ return (w.ws_row << 16) + w.ws_col;
7+ else
8+ return 0x190050;
9+}
cbits/gwinsz.hnew file+1
@@ -0,0 +1 @@
1+unsigned long c_get_window_size(void);
cbits/mk_wcwidth.cnew file+309
@@ -0,0 +1,309 @@
1+/*
2+ * This is an implementation of wcwidth() and wcswidth() (defined in
3+ * IEEE Std 1002.1-2001) for Unicode.
4+ *
5+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
6+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
7+ *
8+ * In fixed-width output devices, Latin characters all occupy a single
9+ * "cell" position of equal width, whereas ideographic CJK characters
10+ * occupy two such cells. Interoperability between terminal-line
11+ * applications and (teletype-style) character terminals using the
12+ * UTF-8 encoding requires agreement on which character should advance
13+ * the cursor by how many cell positions. No established formal
14+ * standards exist at present on which Unicode character shall occupy
15+ * how many cell positions on character terminals. These routines are
16+ * a first attempt of defining such behavior based on simple rules
17+ * applied to data provided by the Unicode Consortium.
18+ *
19+ * For some graphical characters, the Unicode standard explicitly
20+ * defines a character-cell width via the definition of the East Asian
21+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
22+ * In all these cases, there is no ambiguity about which width a
23+ * terminal shall use. For characters in the East Asian Ambiguous (A)
24+ * class, the width choice depends purely on a preference of backward
25+ * compatibility with either historic CJK or Western practice.
26+ * Choosing single-width for these characters is easy to justify as
27+ * the appropriate long-term solution, as the CJK practice of
28+ * displaying these characters as double-width comes from historic
29+ * implementation simplicity (8-bit encoded characters were displayed
30+ * single-width and 16-bit ones double-width, even for Greek,
31+ * Cyrillic, etc.) and not any typographic considerations.
32+ *
33+ * Much less clear is the choice of width for the Not East Asian
34+ * (Neutral) class. Existing practice does not dictate a width for any
35+ * of these characters. It would nevertheless make sense
36+ * typographically to allocate two character cells to characters such
37+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
38+ * represented adequately with a single-width glyph. The following
39+ * routines at present merely assign a single-cell width to all
40+ * neutral characters, in the interest of simplicity. This is not
41+ * entirely satisfactory and should be reconsidered before
42+ * establishing a formal standard in this area. At the moment, the
43+ * decision which Not East Asian (Neutral) characters should be
44+ * represented by double-width glyphs cannot yet be answered by
45+ * applying a simple rule from the Unicode database content. Setting
46+ * up a proper standard for the behavior of UTF-8 character terminals
47+ * will require a careful analysis not only of each Unicode character,
48+ * but also of each presentation form, something the author of these
49+ * routines has avoided to do so far.
50+ *
51+ * http://www.unicode.org/unicode/reports/tr11/
52+ *
53+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
54+ *
55+ * Permission to use, copy, modify, and distribute this software
56+ * for any purpose and without fee is hereby granted. The author
57+ * disclaims all warranties with regard to this software.
58+ *
59+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
60+ */
61+
62+#include <wchar.h>
63+
64+struct interval {
65+ int first;
66+ int last;
67+};
68+
69+/* auxiliary function for binary search in interval table */
70+static int bisearch(wchar_t ucs, const struct interval *table, int max) {
71+ int min = 0;
72+ int mid;
73+
74+ if (ucs < table[0].first || ucs > table[max].last)
75+ return 0;
76+ while (max >= min) {
77+ mid = (min + max) / 2;
78+ if (ucs > table[mid].last)
79+ min = mid + 1;
80+ else if (ucs < table[mid].first)
81+ max = mid - 1;
82+ else
83+ return 1;
84+ }
85+
86+ return 0;
87+}
88+
89+
90+/* The following two functions define the column width of an ISO 10646
91+ * character as follows:
92+ *
93+ * - The null character (U+0000) has a column width of 0.
94+ *
95+ * - Other C0/C1 control characters and DEL will lead to a return
96+ * value of -1.
97+ *
98+ * - Non-spacing and enclosing combining characters (general
99+ * category code Mn or Me in the Unicode database) have a
100+ * column width of 0.
101+ *
102+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
103+ *
104+ * - Other format characters (general category code Cf in the Unicode
105+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
106+ *
107+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
108+ * have a column width of 0.
109+ *
110+ * - Spacing characters in the East Asian Wide (W) or East Asian
111+ * Full-width (F) category as defined in Unicode Technical
112+ * Report #11 have a column width of 2.
113+ *
114+ * - All remaining characters (including all printable
115+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
116+ * etc.) have a column width of 1.
117+ *
118+ * This implementation assumes that wchar_t characters are encoded
119+ * in ISO 10646.
120+ */
121+
122+int mk_wcwidth(wchar_t ucs)
123+{
124+ /* sorted list of non-overlapping intervals of non-spacing characters */
125+ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
126+ static const struct interval combining[] = {
127+ { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
128+ { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
129+ { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
130+ { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
131+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
132+ { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
133+ { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
134+ { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
135+ { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
136+ { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
137+ { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
138+ { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
139+ { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
140+ { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
141+ { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
142+ { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
143+ { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
144+ { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
145+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
146+ { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
147+ { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
148+ { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
149+ { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
150+ { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
151+ { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
152+ { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
153+ { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
154+ { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
155+ { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
156+ { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
157+ { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
158+ { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
159+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
160+ { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
161+ { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
162+ { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
163+ { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
164+ { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
165+ { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
166+ { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
167+ { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
168+ { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
169+ { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
170+ { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
171+ { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
172+ { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
173+ { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
174+ { 0xE0100, 0xE01EF }
175+ };
176+
177+ /* test for 8-bit control characters */
178+ if (ucs == 0)
179+ return 0;
180+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
181+ return -1;
182+
183+ /* binary search in table of non-spacing characters */
184+ if (bisearch(ucs, combining,
185+ sizeof(combining) / sizeof(struct interval) - 1))
186+ return 0;
187+
188+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
189+
190+ return 1 +
191+ (ucs >= 0x1100 &&
192+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
193+ ucs == 0x2329 || ucs == 0x232a ||
194+ (ucs >= 0x2e80 && ucs <= 0xa4cf &&
195+ ucs != 0x303f) || /* CJK ... Yi */
196+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
197+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
198+ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
199+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
200+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
201+ (ucs >= 0xffe0 && ucs <= 0xffe6) ||
202+ (ucs >= 0x20000 && ucs <= 0x2fffd) ||
203+ (ucs >= 0x30000 && ucs <= 0x3fffd)));
204+}
205+
206+
207+int mk_wcswidth(const wchar_t *pwcs, size_t n)
208+{
209+ int w, width = 0;
210+
211+ for (;*pwcs && n-- > 0; pwcs++)
212+ if ((w = mk_wcwidth(*pwcs)) < 0)
213+ return -1;
214+ else
215+ width += w;
216+
217+ return width;
218+}
219+
220+
221+/*
222+ * The following functions are the same as mk_wcwidth() and
223+ * mk_wcswidth(), except that spacing characters in the East Asian
224+ * Ambiguous (A) category as defined in Unicode Technical Report #11
225+ * have a column width of 2. This variant might be useful for users of
226+ * CJK legacy encodings who want to migrate to UCS without changing
227+ * the traditional terminal character-width behaviour. It is not
228+ * otherwise recommended for general use.
229+ */
230+int mk_wcwidth_cjk(wchar_t ucs)
231+{
232+ /* sorted list of non-overlapping intervals of East Asian Ambiguous
233+ * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
234+ static const struct interval ambiguous[] = {
235+ { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
236+ { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
237+ { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
238+ { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
239+ { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
240+ { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
241+ { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
242+ { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
243+ { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
244+ { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
245+ { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
246+ { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
247+ { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
248+ { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
249+ { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
250+ { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
251+ { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
252+ { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
253+ { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
254+ { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
255+ { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
256+ { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
257+ { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
258+ { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
259+ { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
260+ { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
261+ { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
262+ { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
263+ { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
264+ { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
265+ { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
266+ { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
267+ { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
268+ { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
269+ { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
270+ { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
271+ { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
272+ { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
273+ { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
274+ { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
275+ { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
276+ { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
277+ { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
278+ { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
279+ { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
280+ { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
281+ { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
282+ { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
283+ { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
284+ { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
285+ { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
286+ { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
287+ };
288+
289+ /* binary search in table of non-spacing characters */
290+ if (bisearch(ucs, ambiguous,
291+ sizeof(ambiguous) / sizeof(struct interval) - 1))
292+ return 2;
293+
294+ return mk_wcwidth(ucs);
295+}
296+
297+
298+int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n)
299+{
300+ int w, width = 0;
301+
302+ for (;*pwcs && n-- > 0; pwcs++)
303+ if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
304+ return -1;
305+ else
306+ width += w;
307+
308+ return width;
309+}
cbits/set_term_timing.cnew file+14
@@ -0,0 +1,14 @@
1+#include <termios.h>
2+#include <stdio.h>
3+#include <unistd.h>
4+#include <stdlib.h>
5+
6+void set_term_timing(void)
7+{
8+ struct termios trm;
9+ tcgetattr(STDIN_FILENO, &trm);
10+ trm.c_cc[VMIN] = 0;
11+ trm.c_cc[VTIME] = 0;
12+ tcsetattr(STDIN_FILENO, TCSANOW, &trm);
13+}
14+
src/Codec/Binary/UTF8/Debug.hsnew file+17
@@ -0,0 +1,17 @@
1+-- Copyright 2009 Corey O'Connor
2+module Codec.Binary.UTF8.Debug
3+ where
4+
5+import Codec.Binary.UTF8.String ( encode )
6+
7+import Data.Word
8+
9+import Numeric
10+
11+-- Converts an array of ISO-10464 characters (Char type) to an array of Word8 bytes that is the
12+-- corresponding UTF8 byte sequence
13+utf8_from_iso :: Integral i => [i] -> [Word8]
14+utf8_from_iso = encode . map toEnum
15+
16+pp_utf8 = print . map (\f -> f "") . map showHex . utf8_from_iso
17+
src/Codec/Binary/UTF8/Width.hsnew file+32
@@ -0,0 +1,32 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# OPTIONS_GHC -D_XOPEN_SOURCE -fno-cse #-}
3+{-# LANGUAGE ForeignFunctionInterface #-}
4+{-# INCLUDE <wchar.h> #-}
5+module Codec.Binary.UTF8.Width ( wcwidth
6+ , wcswidth
7+ )
8+ where
9+
10+import Foreign.C.Types
11+import Foreign.C.String
12+import Foreign.Storable
13+
14+import Numeric ( showHex )
15+
16+import System.IO.Unsafe
17+
18+wcwidth :: Char -> Int
19+wcwidth c = unsafePerformIO (withCWString [c] $ \ws -> do
20+ wc <- peek ws
21+ putStr $ "wcwidth(0x" ++ showHex (fromEnum wc) "" ++ ")"
22+ w <- wcwidth' wc >>= return . fromIntegral
23+ putStrLn $ " -> " ++ show w
24+ return w
25+ )
26+{-# NOINLINE wcwidth #-}
27+
28+foreign import ccall "mk_wcwidth" wcwidth' :: CWchar -> IO CInt
29+
30+wcswidth :: String -> Int
31+wcswidth str = sum $ map wcwidth str
32+
src/Data/Marshalling.hsnew file+29
@@ -0,0 +1,29 @@
1+-- Copyright 2009 Corey O'Connor
2+module Data.Marshalling ( module Data.Marshalling
3+ , module Data.Word
4+ , module Foreign.Ptr
5+ , module Foreign.ForeignPtr
6+ , module Foreign.Marshal
7+ )
8+ where
9+
10+import Control.Monad ( foldM )
11+import Control.Monad.Trans
12+
13+import Data.Word
14+
15+import Foreign.Ptr
16+import Foreign.ForeignPtr
17+import Foreign.Marshal
18+import Foreign.Storable
19+
20+type OutputBuffer = Ptr Word8
21+
22+string_to_bytes :: String -> [Word8]
23+string_to_bytes str = map (toEnum . fromEnum) str
24+
25+serialize_bytes :: MonadIO m => [Word8] -> OutputBuffer -> m OutputBuffer
26+serialize_bytes bytes out_ptr = foldM ( \ptr b -> liftIO (poke ptr b) >> return ( ptr `plusPtr` 1 ) )
27+ out_ptr
28+ bytes
29+
src/Data/Sequence/Extra.hsnew file+12
@@ -0,0 +1,12 @@
1+-- Copyright 2009 Corey O'Connor
2+module Data.Sequence.Extra where
3+
4+import Data.Sequence
5+
6+import Control.Parallel.Strategies
7+
8+instance NFData a => NFData (Seq a) where
9+ rnf = \v -> rnf' (viewl v)
10+ where
11+ rnf' EmptyL = ()
12+ rnf' (a :< r) = rnf a >| rnf' (viewl r)
src/Data/Terminfo/Eval.hsnew file+159
@@ -0,0 +1,159 @@
1+{-# LANGUAGE RecordWildCards #-}
2+{-# LANGUAGE TypeFamilies #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE NoMonomorphismRestriction #-}
5+{-# LANGUAGE NamedFieldPuns #-}
6+{- Evaluates the paramaterized terminfo string capability with the given parameters.
7+ -
8+ - todo: This can be greatly simplified.
9+ -}
10+module Data.Terminfo.Eval ( cap_expression_required_bytes
11+ , serialize_cap_expression
12+ , bytes_for_range
13+ )
14+ where
15+
16+import Data.Marshalling
17+import Data.Terminfo.Parse
18+
19+import Control.Monad.Reader
20+import Control.Monad.State.Strict
21+
22+import Data.Array.Unboxed
23+import Data.Bits ( (.|.) )
24+import Data.List
25+import Data.Word
26+
27+type Eval a = ReaderT (CapExpression,[CapParam]) (State [CapParam]) a
28+type EvalIO a = ReaderT (CapExpression,[CapParam]) (StateT [CapParam] IO) a
29+
30+pop :: (StateType m ~ [CapParam], MonadState m) => m CapParam
31+pop = do
32+ v : stack <- get
33+ put stack
34+ return v
35+
36+read_param :: (EnvType m ~ (CapExpression, [CapParam]), MonadReader m) => Word -> m CapParam
37+read_param pn = do
38+ (_,params) <- ask
39+ return $ genericIndex params pn
40+
41+push :: (StateType m ~ [CapParam], MonadState m) => CapParam -> m ()
42+push v = do
43+ stack <-get
44+ put (v : stack)
45+
46+apply_param_ops :: CapExpression -> [CapParam] -> [CapParam]
47+apply_param_ops cap params = foldl apply_param_op params (param_ops cap)
48+
49+apply_param_op :: [CapParam] -> ParamOp -> [CapParam]
50+apply_param_op params IncFirstTwo = map (+ 1) params
51+
52+-- | range is 0-based offset into cap_bytes and count
53+--
54+-- todo: The returned list is not assured to have a length st. length == count
55+bytes_for_range :: CapExpression -> (Word8, Word8) -> [Word8]
56+bytes_for_range cap (offset, count)
57+ = take (fromEnum count)
58+ $ drop (fromEnum offset)
59+ $ elems
60+ $ cap_bytes cap
61+
62+cap_expression_required_bytes :: CapExpression -> [CapParam] -> Word
63+cap_expression_required_bytes cap params =
64+ let params' = apply_param_ops cap params
65+ in fst $ runState (runReaderT (cap_ops_required_bytes $ cap_ops cap) (cap, params)) []
66+
67+cap_ops_required_bytes :: CapOps -> Eval Word
68+cap_ops_required_bytes ops = do
69+ counts <- mapM cap_op_required_bytes ops
70+ return $ sum counts
71+
72+cap_op_required_bytes :: CapOp -> Eval Word
73+cap_op_required_bytes (Bytes (_, c)) = return $ toEnum $ fromEnum c
74+cap_op_required_bytes DecOut = do
75+ p <- pop
76+ return $ toEnum $ length $ show p
77+cap_op_required_bytes (PushParam pn) = do
78+ read_param pn >>= push
79+ return 0
80+cap_op_required_bytes (Conditional expr parts) = do
81+ c_expr <- cap_ops_required_bytes expr
82+ c_parts <- foldM cond_parts_required_bytes 0 parts
83+ return $ c_expr + c_parts
84+ where
85+ cond_parts_required_bytes in_c (true_ops, false_ops) = do
86+ -- (man 5 terminfo)
87+ -- Usually the %? expr part pushes a value onto the stack, and %t pops it from the
88+ -- stack, testing if it is nonzero (true). If it is zero (false), control
89+ -- passes to the %e (else) part.
90+ v <- pop
91+ c_branch <- if v /= 0
92+ then cap_ops_required_bytes true_ops
93+ else cap_ops_required_bytes false_ops
94+ return $ in_c + c_branch
95+cap_op_required_bytes BitwiseOr = do
96+ v_0 <- pop
97+ v_1 <- pop
98+ push $ v_0 .|. v_1
99+ return 0
100+cap_op_required_bytes CompareEq = do
101+ v_0 <- pop
102+ v_1 <- pop
103+ push $ if v_0 == v_1 then 1 else 0
104+ return 0
105+cap_op_required_bytes (LiteralInt i) = do
106+ return $ toEnum $ length $ show i
107+
108+serialize_cap_expression :: CapExpression -> [CapParam] -> OutputBuffer -> IO OutputBuffer
109+serialize_cap_expression cap params out_ptr = do
110+ let params' = apply_param_ops cap params
111+ (out_ptr', _) <- runStateT (runReaderT (serialize_cap_ops out_ptr (cap_ops cap)) (cap, params')) []
112+ return out_ptr'
113+
114+serialize_cap_ops :: OutputBuffer -> CapOps -> EvalIO OutputBuffer
115+serialize_cap_ops out_ptr ops = foldM serialize_cap_op out_ptr ops
116+
117+serialize_cap_op :: OutputBuffer -> CapOp -> EvalIO OutputBuffer
118+serialize_cap_op out_ptr (Bytes (offset, c)) = do
119+ (cap, _) <- ask
120+ let out_bytes = bytes_for_range cap (offset, c)
121+ serialize_bytes out_bytes out_ptr
122+serialize_cap_op out_ptr DecOut = do
123+ p <- pop
124+ let out_str = show p
125+ out_bytes = string_to_bytes out_str
126+ serialize_bytes out_bytes out_ptr
127+serialize_cap_op out_ptr (PushParam pn) = do
128+ read_param pn >>= push
129+ return out_ptr
130+serialize_cap_op out_ptr (Conditional expr parts) = do
131+ out_ptr' <- serialize_cap_ops out_ptr expr
132+ out_ptr'' <- foldM serialize_cond_parts out_ptr' parts
133+ return out_ptr''
134+ where
135+ serialize_cond_parts ptr (true_ops, false_ops) = do
136+ -- (man 5 terminfo)
137+ -- Usually the %? expr part pushes a value onto the stack, and %t pops it from the
138+ -- stack, testing if it is nonzero (true). If it is zero (false), control
139+ -- passes to the %e (else) part.
140+ v <- pop
141+ ptr' <- if v /= 0
142+ then serialize_cap_ops ptr true_ops
143+ else serialize_cap_ops ptr false_ops
144+ return ptr'
145+serialize_cap_op out_ptr BitwiseOr = do
146+ v_0 <- pop
147+ v_1 <- pop
148+ push $ v_0 .|. v_1
149+ return out_ptr
150+serialize_cap_op out_ptr CompareEq = do
151+ v_0 <- pop
152+ v_1 <- pop
153+ push $ if v_0 == v_1 then 1 else 0
154+ return out_ptr
155+serialize_cap_op out_ptr (LiteralInt i) = do
156+ let out_str = show i
157+ out_bytes = string_to_bytes out_str
158+ serialize_bytes out_bytes out_ptr
159+
src/Data/Terminfo/Parse.hsnew file+215
@@ -0,0 +1,215 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE NoMonomorphismRestriction #-}
3+{-# LANGUAGE NamedFieldPuns #-}
4+module Data.Terminfo.Parse ( module Data.Terminfo.Parse
5+ , Text.Parsec.ParseError
6+ )
7+ where
8+
9+import Control.Monad ( liftM )
10+import Control.Monad.ST.Strict
11+
12+import Data.Array.Unboxed
13+import Data.Array.ST
14+import Data.Monoid
15+import Data.Word
16+
17+import Numeric
18+
19+import Text.Parsec
20+
21+type BytesLength = Word8
22+type BytesOffset = Word8
23+type CapBytes = UArray Word8 Word8
24+
25+data CapExpression = CapExpression
26+ { cap_ops :: CapOps
27+ , cap_bytes :: CapBytes
28+ , source_string :: String
29+ , param_count :: Word
30+ , param_ops :: ParamOps
31+ }
32+
33+type CapParam = Word
34+
35+type CapOps = [CapOp]
36+data CapOp =
37+ Bytes (BytesOffset, BytesLength)
38+ | DecOut
39+ -- This stores a 0-based index to the parameter. However the operation that implies this op is
40+ -- 1-based
41+ | PushParam !Word
42+ -- The conditional parts are the sequence of (%t expression, %e expression) pairs.
43+ -- The %e expression may be NOP
44+ | Conditional
45+ { conditional_expr :: CapOps
46+ , conditional_parts :: [(CapOps, CapOps)]
47+ }
48+ | BitwiseOr
49+ | LiteralInt Int
50+ deriving ( Show )
51+
52+type ParamOps = [ParamOp]
53+data ParamOp =
54+ IncFirstTwo
55+ deriving ( Show )
56+
57+parse_cap_expression :: String -> Either ParseError CapExpression
58+parse_cap_expression cap_string = runST $ do
59+ v <- runParserT cap_expression_parser
60+ initial_build_state
61+ "terminfo cap"
62+ cap_string
63+ case v of
64+ Left error -> return $ Left error
65+ Right build_results ->
66+ return $ Right $ CapExpression
67+ { cap_ops = out_cap_ops build_results
68+ -- The cap bytes are the lower 8 bits of the input string's characters.
69+ -- \todo Verify the input string actually contains an 8bit byte per character.
70+ , cap_bytes = listArray (0, toEnum $ length cap_string - 1)
71+ $ map (toEnum . fromEnum) cap_string
72+ , source_string = cap_string
73+ , param_count = out_param_count build_results
74+ , param_ops = out_param_ops build_results
75+ }
76+
77+cap_expression_parser = do
78+ rs <- many $ param_escape_parser <|> bytes_op_parser
79+ return $ mconcat rs
80+
81+param_escape_parser = do
82+ char '%'
83+ inc_offset 1
84+ literal_percent_parser <|> param_op_parser
85+
86+literal_percent_parser = do
87+ char '%'
88+ start_offset <- getState >>= return . next_offset
89+ inc_offset 1
90+ return $ BuildResults 0 [Bytes (start_offset, 1)] []
91+
92+param_op_parser
93+ = increment_op_parser
94+ <|> push_op_parser
95+ <|> dec_out_parser
96+ <|> conditional_op_parser
97+ <|> bitwise_op_parser
98+ <|> literal_int_op_parser
99+
100+increment_op_parser = do
101+ char 'i'
102+ inc_offset 1
103+ return $ BuildResults 0 [] [ IncFirstTwo ]
104+
105+push_op_parser = do
106+ char 'p'
107+ param_n <- digit >>= return . (\d -> read [d])
108+ inc_offset 2
109+ return $ BuildResults param_n [ PushParam $ param_n - 1 ] []
110+
111+dec_out_parser = do
112+ char 'd'
113+ inc_offset 1
114+ return $ BuildResults 0 [ DecOut ] []
115+
116+conditional_op_parser = do
117+ char '?'
118+ inc_offset 1
119+ cond_part <- many_expr conditional_true_parser
120+ parts <- many_p
121+ ( do
122+ true_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser
123+ , conditional_false_parser
124+ ]
125+ false_part <- many_expr $ lookAhead conditional_end_parser
126+ return ( true_part, false_part )
127+ )
128+ conditional_end_parser
129+
130+ let true_parts = map fst parts
131+ false_parts = map snd parts
132+ BuildResults n cond cond_param_ops = cond_part
133+
134+ let n' = maximum $ n : map out_param_count true_parts
135+ n'' = maximum $ n' : map out_param_count false_parts
136+
137+ let true_ops = map out_cap_ops true_parts
138+ false_ops = map out_cap_ops false_parts
139+ cond_parts = zip true_ops false_ops
140+
141+ let true_param_ops = mconcat $ map out_param_ops true_parts
142+ false_param_ops = mconcat $ map out_param_ops false_parts
143+ param_ops = mconcat [cond_param_ops, true_param_ops, false_param_ops]
144+
145+ return $ BuildResults n'' [ Conditional cond cond_parts ] param_ops
146+
147+ where
148+ many_p p end = choice
149+ [ try end >> return []
150+ , do v <- p
151+ vs <- many_p p end
152+ return $ v : vs
153+ ]
154+ many_expr end = liftM mconcat $ many_p ( param_escape_parser <|> bytes_op_parser ) end
155+
156+conditional_true_parser = do
157+ string "%t"
158+ inc_offset 2
159+
160+conditional_false_parser = do
161+ string "%e"
162+ inc_offset 2
163+
164+conditional_end_parser = do
165+ string "%;"
166+ inc_offset 2
167+
168+bitwise_op_parser = do
169+ bitwise_or_parser
170+
171+bitwise_or_parser = do
172+ char '|'
173+ inc_offset 1
174+ return $ BuildResults 0 [ BitwiseOr ] [ ]
175+
176+literal_int_op_parser = do
177+ char '{'
178+ inc_offset 1
179+ n_str <- many1 digit
180+ inc_offset $ toEnum $ length n_str
181+ let n = read n_str
182+ char '}'
183+ inc_offset 1
184+ return $ BuildResults 0 [ LiteralInt n ] [ ]
185+
186+bytes_op_parser = do
187+ bytes <- many1 $ satisfy (/= '%')
188+ start_offset <- getState >>= return . next_offset
189+ let !count = toEnum $ length bytes
190+ modifyState $ \s -> s { next_offset = start_offset + count }
191+ return $ BuildResults 0 [Bytes (start_offset, count)] []
192+
193+data BuildState = BuildState
194+ { next_offset :: Word8
195+ }
196+
197+inc_offset n = modifyState $ \s -> s { next_offset = next_offset s + n }
198+
199+initial_build_state = BuildState 0
200+
201+data BuildResults = BuildResults
202+ { out_param_count :: Word
203+ , out_cap_ops :: CapOps
204+ , out_param_ops :: ParamOps
205+ }
206+
207+instance Monoid BuildResults where
208+ mempty = BuildResults 0 [] []
209+ v0 `mappend` v1
210+ = BuildResults
211+ { out_param_count = (out_param_count v0) `max` (out_param_count v1)
212+ , out_cap_ops = (out_cap_ops v0) `mappend` (out_cap_ops v1)
213+ , out_param_ops = (out_param_ops v0) `mappend` (out_param_ops v1)
214+ }
215+
src/Graphics/Vty.hsnew file+131
@@ -0,0 +1,131 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# LANGUAGE ForeignFunctionInterface, BangPatterns, UnboxedTuples #-}
3+{-# CFILES gwinsz.c #-}
4+
5+-- Good sources of documentation for terminal programming are:
6+-- vt100 control sequences: http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.3
7+-- Xterm control sequences: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
8+
9+module Graphics.Vty ( Vty(..)
10+ , mkVty
11+ , mkVtyEscDelay
12+ , module Graphics.Vty.Terminal
13+ , module Graphics.Vty.Picture
14+ , module Graphics.Vty.DisplayRegion
15+ , Key(..)
16+ , Modifier(..)
17+ , Button(..)
18+ , Event(..)
19+ )
20+ where
21+
22+
23+import Graphics.Vty.Terminal
24+import Graphics.Vty.Picture
25+import Graphics.Vty.DisplayRegion
26+import Graphics.Vty.LLInput
27+
28+import Data.IORef
29+import Control.Concurrent
30+
31+import Data.Maybe ( maybe )
32+
33+import System.Console.Terminfo
34+import System.IO
35+
36+-- | The main object. At most one should be created.
37+-- An alternative is to use unsafePerformIO to automatically create a singleton Vty instance when
38+-- required.
39+--
40+-- This has the same thread safety as `output_picture`.
41+--
42+-- todo: Once the Terminal interface encompasses input this interface will be deprecated.
43+-- Currently, just using the Terminal interface there is no support for input events.
44+data Vty = Vty
45+ { -- | Outputs the given Picture. Equivalent to output_picture.
46+ update :: Picture -> IO ()
47+ -- | Get one Event object, blocking if necessary.
48+ , next_event :: IO Event
49+ -- | Handle to the terminal interface. See `Terminal`
50+ --
51+ -- todo: provide a similar abstraction for input. Use haskeline's input backend for
52+ -- implementation.
53+ --
54+ -- The use of Vty typically follows this process:
55+ --
56+ -- 0. initialize vty
57+ --
58+ -- 1. use the update equation of Vty to display a picture
59+ --
60+ -- 2. repeat
61+ --
62+ -- 3. shutdown vty. todo: remove? Automate release of resources as much as possible.
63+ --
64+ -- This version currently supports the same interface.
65+ , terminal :: TerminalHandle
66+ -- | Refresh the display. Normally the library takes care of refreshing.
67+ -- Nonetheless, some other program might output to the terminal and mess the display.
68+ -- In that case the user might want to force a refresh.
69+ , refresh :: IO ()
70+ -- | Clean up after vty.
71+ , shutdown :: IO ()
72+ }
73+
74+-- | Set up the state object for using vty. At most one state object should be
75+-- created at a time.
76+mkVty :: IO Vty
77+mkVty = mkVtyEscDelay 0
78+
79+mkVtyEscDelay :: Int -> IO Vty
80+mkVtyEscDelay escDelay = do
81+ term_info <- setupTermFromEnv
82+ t <- terminal_handle
83+ reserve_display t
84+ (kvar, endi) <- initTermInput escDelay term_info
85+ intMkVty kvar ( endi >> release_display t >> release_terminal t ) t
86+
87+intMkVty :: IO Event -> IO () -> TerminalHandle -> IO Vty
88+intMkVty kvar fend t = do
89+ last_pic_ref <- newIORef undefined
90+ last_update_ref <- newIORef Nothing
91+
92+ let inner_update in_pic = do
93+ b <- display_bounds t
94+ mlast_update <- readIORef last_update_ref
95+ update_data <- case mlast_update of
96+ Nothing -> do
97+ d <- display_context t b
98+ output_picture d in_pic
99+ return (b, d)
100+ Just (last_bounds, last_context) -> do
101+ if b /= last_bounds
102+ then do
103+ d <- display_context t b
104+ output_picture d in_pic
105+ return (b, d)
106+ else do
107+ output_picture last_context in_pic
108+ return (b, last_context)
109+ writeIORef last_update_ref $ Just update_data
110+ writeIORef last_pic_ref $ Just in_pic
111+
112+ let inner_refresh
113+ = readIORef last_pic_ref
114+ >>= maybe ( return () ) ( \pic -> inner_update pic )
115+
116+ let gkey = do k <- kvar
117+ case k of
118+ (EvResize _ _) -> inner_refresh
119+ >> display_bounds t
120+ >>= return . ( \(DisplayRegion w h)
121+ -> EvResize (fromEnum w) (fromEnum h)
122+ )
123+ _ -> return k
124+
125+ return $ Vty { update = inner_update
126+ , next_event = gkey
127+ , terminal = t
128+ , refresh = inner_refresh
129+ , shutdown = fend
130+ }
131+
src/Graphics/Vty/Attributes.hsnew file+192
@@ -0,0 +1,192 @@
1+-- Copyright 2009 Corey O'Connor
2+-- Display attributes
3+{-# LANGUAGE MagicHash #-}
4+module Graphics.Vty.Attributes
5+ where
6+
7+import Data.Bits
8+
9+import Data.Word
10+
11+import Numeric
12+
13+-- | A display attribute defines the color and style of all the characters rendered after the
14+-- attribute is applied.
15+-- 6 possible style attributes:
16+-- standout
17+-- underline
18+-- reverse
19+-- blink
20+-- dim
21+-- bold/bright
22+-- ( invisible, protect, and altcharset are not supported)
23+-- At most 240 + 16 colors for the background and foreground. The 240 colors and 16 colors are
24+-- points in different palettes. Which is why it's not simply "at most 256 colors"
25+--
26+-- For efficiency this can be, in the future, encoded into a single 32 bit word. The 32 bit word is
27+-- first divided into 4 groups of 8 bits where:
28+-- The first group codes what action should be taken with regards to the other groups.
29+-- XXYYZZ__
30+-- XX - style action
31+-- 00 => reset to default
32+-- 01 => unchanged
33+-- 10 => set
34+-- YY - foreground color action
35+-- 00 => reset to default
36+-- 01 => unchanged
37+-- 10 => set
38+-- ZZ - background color action
39+-- 00 => reset to default
40+-- 01 => unchanged
41+-- 10 => set
42+-- __ - unused
43+--
44+-- Next is the style flags
45+-- SURBDO__
46+-- S - standout
47+-- U - underline
48+-- R - reverse video
49+-- B - blink
50+-- D - dim
51+-- O - bold
52+-- __ - unused
53+--
54+-- Then the foreground color encoded into 8 bits.
55+-- Then the background color encoded into 8 bits.
56+--
57+data Attr = Attr
58+ { style :: !(MaybeDefault Style)
59+ , fore_color :: !(MaybeDefault Color)
60+ , back_color :: !(MaybeDefault Color)
61+ } deriving ( Eq, Show )
62+
63+-- | Specifies the display attributes such that the final style and color values do not depend on
64+-- the previously applied display attribute. The display attributes can still depend on the
65+-- terminal's default attributes unfortunately.
66+data FixedAttr = FixedAttr
67+ { fixed_style :: !Style
68+ , fixed_fore_color :: !(Maybe Color)
69+ , fixed_back_color :: !(Maybe Color)
70+ } deriving ( Eq, Show )
71+
72+data Eq v => MaybeDefault v = Default | KeepCurrent | SetTo !v
73+ deriving ( Eq, Show )
74+
75+-- | Abstract data type representing a color.
76+--
77+-- Currently the foreground and background color are specified as points in either a:
78+-- * 16 color palette. Where the first 8 colors are equal to the 8 colors of the ISO 6429 (ANSI) 8
79+-- color palette and the second 8 colors are bright/vivid versions of the first 8 colors.
80+-- * 240 color palette. This palette is a regular sampling of the full RGB colorspace.
81+--
82+-- The 8 ISO 6429 (ANSI) colors are as follows:
83+-- 0. black
84+-- 1. red
85+-- 2. green
86+-- 3. yellow
87+-- 4. blue
88+-- 5. magenta
89+-- 6. cyan
90+-- 7. white
91+--
92+-- The mapping from points in the 240 color palette to colors actually displayable by the terminal
93+-- depends on the number of colors the terminal claims to support. Which is usually determined by
94+-- the terminfo "colors" property. If this property is not being accurately reported then the color
95+-- reproduction will be incorrect.
96+--
97+-- If the terminal reports <= 16 colors then the 240 color palette points are only mapped to the 8
98+-- color pallete. I'm not sure of the RGB points for the "bright" colors which is why they are not
99+-- addressable via the 240 color palette.
100+--
101+-- If the terminal reports > 16 colors then the 240 color palette points are mapped to the nearest
102+-- points in a ("color count" - 16) subsampling of the 240 color palette.
103+--
104+-- All of this assumes the terminals are behaving similarly to xterm and rxvt when handling colors.
105+-- And that the individual colors have not been remapped by the user. There may be a way to verify
106+-- this through terminfo but I don't know it.
107+--
108+-- Seriously, terminal color support is INSANE.
109+data Color = ISOColor !Word8 | Color240 !Word8
110+ deriving ( Eq, Show )
111+
112+-- | Standard 8-color ANSI terminal color codes.
113+black, red, green, yellow, blue, magenta, cyan, white :: Color
114+black = ISOColor 0
115+red = ISOColor 1
116+green = ISOColor 2
117+yellow = ISOColor 3
118+blue = ISOColor 4
119+magenta= ISOColor 5
120+cyan = ISOColor 6
121+white = ISOColor 7
122+
123+-- | Bright/Vivid variants of the standard 8-color ANSI
124+bright_black, bright_red, bright_green, bright_yellow :: Color
125+
126+-- | Bright/Vivid variants of the standard 8-color ANSI
127+bright_blue, bright_magenta, bright_cyan, bright_white :: Color
128+bright_black = ISOColor 8
129+bright_red = ISOColor 9
130+bright_green = ISOColor 10
131+bright_yellow = ISOColor 11
132+bright_blue = ISOColor 12
133+bright_magenta= ISOColor 13
134+bright_cyan = ISOColor 14
135+bright_white = ISOColor 15
136+
137+type Style = Word8
138+
139+-- | 6 possible style attributes:
140+-- standout
141+-- underline
142+-- reverse_video
143+-- blink
144+-- dim
145+-- bold/bright
146+-- ( invisible, protect, and altcharset are not supported)
147+standout, underline, reverse_video, blink, dim, bold :: Style
148+standout = 0x01
149+underline = 0x02
150+reverse_video = 0x04
151+blink = 0x08
152+dim = 0x10
153+bold = 0x20
154+
155+default_style_mask :: Style
156+default_style_mask = 0x00
157+
158+style_mask :: Attr -> Word8
159+style_mask attr
160+ = case style attr of
161+ Default -> 0
162+ KeepCurrent -> 0
163+ SetTo v -> v
164+
165+has_style :: Style -> Style -> Bool
166+has_style style bit_mask = ( style .&. bit_mask ) /= 0
167+
168+-- | Set the foreground color of an `Attr'.
169+with_fore_color :: Attr -> Color -> Attr
170+with_fore_color attr c = attr { fore_color = SetTo c }
171+
172+-- | Set the background color of an `Attr'.
173+with_back_color :: Attr -> Color -> Attr
174+with_back_color attr c = attr { back_color = SetTo c }
175+
176+-- | Add the given style attribute
177+with_style :: Attr -> Style -> Attr
178+with_style attr style_flag = attr { style = SetTo $ style_mask attr .|. style_flag }
179+
180+-- | Sets the style, background color and foreground color to the default values for the terminal.
181+-- There is no easy way to determine what the default background and foreground colors are.
182+def_attr :: Attr
183+def_attr = Attr Default Default Default
184+
185+-- | Keeps the style, background color and foreground color that was previously set. Used to
186+-- override some part of the previous style.
187+-- EG: current_style `with_fore_color` bright_magenta
188+-- Would be the currently applied style (be it underline, bold, etc) but with the foreground color
189+-- set to bright_magenta.
190+current_attr :: Attr
191+current_attr = Attr KeepCurrent KeepCurrent KeepCurrent
192+
src/Graphics/Vty/Debug.hsnew file+81
@@ -0,0 +1,81 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# LANGUAGE ScopedTypeVariables #-}
3+module Graphics.Vty.Debug ( module Graphics.Vty.Debug
4+ )
5+where
6+
7+import Graphics.Vty.Attributes
8+import Graphics.Vty.Image
9+import Graphics.Vty.Image.Debug
10+import Graphics.Vty.Picture
11+import Graphics.Vty.Span
12+import Graphics.Vty.WinRegion
13+
14+import Control.Monad
15+import Data.Array
16+import Data.Word
17+
18+instance Show SpanOpSequence where
19+ show (SpanOpSequence _ row_ops)
20+ = concat $ ["{ "] ++ map (\ops -> show ops ++ "; " ) (elems row_ops) ++ [" }"]
21+
22+instance Show SpanOp where
23+ show (AttributeChange attr) = show attr
24+ show (TextSpan width _) = "TextSpan " ++ show width
25+
26+row_ops_effected_columns :: SpanOpSequence -> [Word]
27+row_ops_effected_columns spans
28+ = map span_ops_effected_columns (elems $ row_ops spans)
29+
30+all_spans_have_width spans expected
31+ = all (== expected) ( map span_ops_effected_columns (elems $ row_ops spans) )
32+
33+span_ops_effected_rows :: SpanOpSequence -> Word
34+span_ops_effected_rows (SpanOpSequence _ row_ops)
35+ = toEnum $ length (filter (not . null) (elems row_ops))
36+
37+type SpanConstructLog = [SpanConstructEvent]
38+data SpanConstructEvent = SpanSetAttr Attr
39+
40+is_set_attr :: Attr -> SpanConstructEvent -> Bool
41+is_set_attr expected_attr (SpanSetAttr in_attr)
42+ | in_attr == expected_attr = True
43+is_set_attr _attr _event = False
44+
45+data DebugWindow = DebugWindow Word Word
46+ deriving (Show, Eq)
47+
48+type TestWindow = DebugWindow
49+
50+instance WinRegion DebugWindow where
51+ win_width (DebugWindow w _) = w
52+ win_height (DebugWindow _ h) = h
53+
54+type ImageConstructLog = [ImageConstructEvent]
55+data ImageConstructEvent = ImageConstructEvent
56+ deriving ( Show, Eq )
57+
58+forward_image_ops = map forward_transform debug_image_ops
59+
60+forward_transform, reverse_transform :: ImageOp -> (Image -> Image)
61+
62+forward_transform (ImageOp f _) = f
63+reverse_transform (ImageOp _ r) = r
64+
65+data ImageOp = ImageOp ImageEndo ImageEndo
66+type ImageEndo = Image -> Image
67+
68+debug_image_ops =
69+ [ id_image_op
70+ -- , render_single_column_char_op
71+ -- , render_double_column_char_op
72+ ]
73+
74+id_image_op :: ImageOp
75+id_image_op = ImageOp id id
76+
77+-- render_char_op :: ImageOp
78+-- render_char_op = ImageOp id id
79+
80+instance Show Picture where
81+ show (Picture _ image _ ) = "Picture ?? " ++ show image ++ " ??"
src/Graphics/Vty/DisplayRegion.hsnew file+11
@@ -0,0 +1,11 @@
1+-- Copyright 2009 Corey O'Connor
2+module Graphics.Vty.DisplayRegion
3+ where
4+
5+import Data.Word
6+
7+data DisplayRegion = DisplayRegion
8+ { region_width :: !Word
9+ , region_height :: !Word
10+ } deriving ( Show, Eq )
11+
src/Graphics/Vty/Image.hsnew file+301
@@ -0,0 +1,301 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE NamedFieldPuns #-}
4+{-# LANGUAGE DisambiguateRecordFields #-}
5+{-# LANGUAGE MagicHash, UnboxedTuples #-}
6+{-# OPTIONS_GHC -funbox-strict-fields #-}
7+{- A picutre in VTY defines
8+ - * properties required to display the image. These are properties that effect the output image
9+ - but are independent of position
10+ - * A set of position-dependent text and attribute regions. The possible regions are
11+ - * a point
12+ - * a horizontal line of characters with a single attribute
13+ - * a vertical line of a single character
14+ - * a horizontal line of a single character.
15+ - * a rectangle of a single character
16+ -
17+ -
18+ -
19+ - todo: Move to UTF-8 encoded bytestring streams?
20+ -}
21+
22+module Graphics.Vty.Image ( Image(..)
23+ , image_width
24+ , image_height
25+ , (<|>)
26+ , (<->)
27+ , horiz_cat
28+ , vert_cat
29+ , horzcat
30+ , vertcat
31+ , background_fill
32+ , char
33+ , string
34+ , iso_10464_string
35+ , utf8_string
36+ , utf8_bytestring
37+ , char_fill
38+ -- | The possible display attributes used in constructing an `Image`.
39+ , module Graphics.Vty.Attributes
40+ )
41+ where
42+
43+import Graphics.Vty.Attributes
44+
45+import Codec.Binary.UTF8.Width
46+
47+import Codec.Binary.UTF8.String ( decode )
48+
49+import qualified Data.ByteString as BS
50+import qualified Data.Sequence as Seq
51+import qualified Data.String.UTF8 as UTF8
52+import Data.Word
53+
54+infixr 5 <|>
55+infixr 4 <->
56+
57+type StringSeq = Seq.Seq Char
58+
59+data Image =
60+ -- | A horizontal text span is always >= 1 column and has a row height of 1.
61+ HorizText
62+ { attr :: !Attr
63+ -- | All character data is stored as Char sequences with the ISO-10646 encoding.
64+ , text :: StringSeq
65+ -- | in columns
66+ , output_width :: !Word -- >= 0
67+ , char_width :: !Word -- >= 1
68+ }
69+ -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
70+ -- required to be between two images of equal height. The horiz_join constructor adds blanks to
71+ -- the provided images that assure this is true for the HorizJoin value produced.
72+ | HorizJoin
73+ { part_left :: Image
74+ , part_right :: Image
75+ , output_width :: !Word -- >= 1
76+ , output_height :: !Word -- >= 1
77+ }
78+ -- | A veritical join can be constructed between any two images. However a VertJoin instance is
79+ -- required to be between two images of equal width. The horiz_join constructor adds blanks to
80+ -- the provides images that assure this is true for the HorizJoin value produced.
81+ | VertJoin
82+ { part_top :: Image
83+ , part_bottom :: Image
84+ , output_width :: !Word -- >= 1
85+ , output_height :: !Word -- >= 1
86+ }
87+ -- | A background fill will be filled with the background pattern. The background pattern is
88+ -- defined as a property of the Picture this Image is used to form.
89+ | BGFill
90+ { output_width :: !Word -- >= 1
91+ , output_height :: !Word -- >= 1
92+ }
93+ -- Any image of zero size equals the id image
94+ | IdImage
95+ deriving Eq
96+
97+instance Show Image where
98+ show ( HorizText { output_width = ow, text = txt } )
99+ = "HorizText [" ++ show ow ++ "] (" ++ show txt ++ ")"
100+ show ( BGFill { output_width = c, output_height = r } )
101+ = "BGFill (" ++ show c ++ "," ++ show r ++ ")"
102+ show ( HorizJoin { part_left = l, part_right = r, output_width = c } )
103+ = "HorizJoin " ++ show c ++ " ( " ++ show l ++ " <|> " ++ show r ++ " )"
104+ show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } )
105+ = "VertJoin (" ++ show c ++ ", " ++ show r ++ ") ( " ++ show t ++ " ) <-> ( " ++ show b ++ " )"
106+ show ( IdImage ) = "IdImage"
107+
108+-- A horizontal text image of 0 characters in width simplifies to the IdImage
109+horiz_text :: Attr -> StringSeq -> Word -> Image
110+horiz_text a txt ow
111+ | ow == 0 = IdImage
112+ | otherwise = HorizText a txt (toEnum $ Seq.length txt) ow
113+
114+horiz_join :: Image -> Image -> Word -> Word -> Image
115+horiz_join i_0 i_1 w h
116+ -- A horiz join of two 0 width images simplifies to the IdImage
117+ | w == 0 = IdImage
118+ -- A horizontal join where either part is 0 columns in width simplifies to the other part.
119+ -- This covers the case where one part is the IdImage.
120+ | image_width i_0 == 0 = i_1
121+ | image_width i_1 == 0 = i_0
122+ -- If the images are of the same height then no BG padding is required
123+ | image_height i_0 == image_height i_1 = HorizJoin i_0 i_1 w h
124+ -- otherwise one of the imagess needs to be padded to the right size.
125+ | image_height i_0 < image_height i_1 -- Pad i_0
126+ = let pad_amount = image_height i_1 - image_height i_0
127+ in horiz_join ( vert_join i_0
128+ ( BGFill ( image_width i_0 ) pad_amount )
129+ ( image_width i_0 )
130+ ( image_height i_1 )
131+ )
132+ i_1
133+ w h
134+ | image_height i_0 > image_height i_1 -- Pad i_1
135+ = let pad_amount = image_height i_0 - image_height i_1
136+ in horiz_join i_0
137+ ( vert_join i_1
138+ ( BGFill ( image_width i_1 ) pad_amount )
139+ ( image_width i_1 )
140+ ( image_height i_0 )
141+ )
142+ w h
143+horiz_join _ _ _ _ = error "horiz_join applied to undefined values."
144+
145+vert_join :: Image -> Image -> Word -> Word -> Image
146+vert_join i_0 i_1 w h
147+ -- A vertical join of two 0 height images simplifies to the IdImage
148+ | h == 0 = IdImage
149+ -- A vertical join where either part is 0 rows in height simplifies to the other part.
150+ -- This covers the case where one part is the IdImage
151+ | image_height i_0 == 0 = i_1
152+ | image_height i_1 == 0 = i_0
153+ -- If the images are of the same height then no background padding is required
154+ | image_width i_0 == image_width i_1 = VertJoin i_0 i_1 w h
155+ -- Otherwise one of the images needs to be padded to the size of the other image.
156+ | image_width i_0 < image_width i_1
157+ = let pad_amount = image_width i_1 - image_width i_0
158+ in vert_join ( horiz_join i_0
159+ ( BGFill pad_amount ( image_height i_0 ) )
160+ ( image_width i_1 )
161+ ( image_height i_0 )
162+ )
163+ i_1
164+ w h
165+ | image_width i_0 > image_width i_1
166+ = let pad_amount = image_width i_0 - image_width i_1
167+ in vert_join i_0
168+ ( horiz_join i_1
169+ ( BGFill pad_amount ( image_height i_1 ) )
170+ ( image_width i_0 )
171+ ( image_height i_1 )
172+ )
173+ w h
174+vert_join _ _ _ _ = error "vert_join applied to undefined values."
175+
176+-- | An area of the picture's bacground (See `Background`) of w columns and h rows.
177+background_fill :: Word -> Word -> Image
178+background_fill w h
179+ | w == 0 = IdImage
180+ | h == 0 = IdImage
181+ | otherwise = BGFill w h
182+
183+-- | Access the width of an Image.
184+image_width :: Image -> Word
185+image_width HorizText { output_width = w } = w
186+image_width HorizJoin { output_width = w } = w
187+image_width VertJoin { output_width = w } = w
188+image_width BGFill { output_width = w } = w
189+image_width IdImage = 0
190+
191+-- | Access the height of an Image.
192+image_height :: Image -> Word
193+image_height HorizText {} = 1
194+image_height HorizJoin { output_height = r } = r
195+image_height VertJoin { output_height = r } = r
196+image_height BGFill { output_height = r } = r
197+image_height IdImage = 0
198+
199+-- | Combines two images side by side.
200+-- The result image will have a width equal to the sum of the two images width.
201+-- The height will equal the largest height of the two images
202+-- The area not defined in one image due to a height missmatch will be filled with the background
203+-- pattern.
204+(<|>) :: Image -> Image -> Image
205+
206+-- Two horizontal text spans with the same attributes can be merged.
207+h0@(HorizText attr_0 text_0 ow_0 _) <|> h1@(HorizText attr_1 text_1 ow_1 _)
208+ | attr_0 == attr_1 = horiz_text attr_0 (text_0 Seq.>< text_1) (ow_0 + ow_1)
209+ | otherwise = horiz_join h0 h1 (ow_0 + ow_1) 1
210+
211+-- Anything placed to the right of a join wil be joined to the right sub image.
212+-- The total columns for the join is the sum of the two arguments columns
213+h0@( HorizJoin {} ) <|> h1
214+ = horiz_join ( part_left h0 )
215+ ( part_right h0 <|> h1 )
216+ ( image_width h0 + image_width h1 )
217+ ( max (image_height h0) (image_height h1) )
218+
219+-- Anything but a join placed to the left of a join wil be joined to the left sub image.
220+-- The total columns for the join is the sum of the two arguments columns
221+h0 <|> h1@( HorizJoin {} )
222+ = horiz_join ( h0 <|> part_left h1 )
223+ ( part_right h1 )
224+ ( image_width h0 + image_width h1 )
225+ ( max (image_height h0) (image_height h1) )
226+
227+h0 <|> h1
228+ = horiz_join h0
229+ h1
230+ ( image_width h0 + image_width h1 )
231+ ( max (image_height h0) (image_height h1) )
232+
233+-- | Combines two images vertically.
234+-- The result image will have a height equal to the sum of the heights of both images.
235+-- The width will equal the largest width of the two images.
236+-- The area not defined in one image due to a width missmatch will be filled with the background
237+-- pattern.
238+(<->) :: Image -> Image -> Image
239+im_t <-> im_b
240+ = vert_join im_t
241+ im_b
242+ ( max (image_width im_t) (image_width im_b) )
243+ ( image_height im_t + image_height im_b )
244+
245+-- | Compose any number of images horizontally.
246+horiz_cat :: [Image] -> Image
247+horiz_cat = foldr (<|>) IdImage
248+
249+horzcat :: [Image] -> Image
250+horzcat = horiz_cat
251+
252+-- | Compose any number of images vertically.
253+vert_cat :: [Image] -> Image
254+vert_cat = foldr (<->) IdImage
255+
256+vertcat :: [Image] -> Image
257+vertcat = vert_cat
258+
259+-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
260+-- the ISO-10464 encoding.
261+char :: Attr -> Char -> Image
262+char !a !c = HorizText a (Seq.singleton c) (safe_wcwidth c) 1
263+
264+-- | A string of characters layed out on a single row. The string is assumed to be a sequence of
265+-- ISO-10464 characters. Note: depending on how the Haskell compiler represents string literals a
266+-- string literal in a UTF-8 encoded source file, for example, may be represented as a ISO-10464
267+-- string. That is, I think, the case with GHC 6.10.
268+iso_10464_string :: Attr -> String -> Image
269+iso_10464_string !a !str = horiz_text a (Seq.fromList str) (safe_wcswidth str)
270+
271+-- | Alias for iso_10646_string. Since the usual case is that a literal string like "foo" is
272+-- represented internally as a list of ISO 10646 31 bit characters.
273+-- Note: Keep in mind that GHC will compile source encoded as UTF-8 but the literal strings, while
274+-- UTF-8 encoded in the source, will be transcoded to a ISO 10646 31 bit characters runtime
275+-- representation.
276+string :: Attr -> String -> Image
277+string = iso_10464_string
278+
279+-- | A string of characters layed out on a single row. The string is assumed to be a sequence of
280+-- UTF-8 characters.
281+utf8_string :: Attr -> [Word8] -> Image
282+utf8_string !a !str = string a ( decode str )
283+
284+safe_wcwidth :: Char -> Word
285+safe_wcwidth c = case wcwidth c of
286+ i | i < 0 -> error "negative wcwidth"
287+ | otherwise -> toEnum i
288+
289+safe_wcswidth :: String -> Word
290+safe_wcswidth str = case wcswidth str of
291+ i | i < 0 -> error "negative wcswidth"
292+ | otherwise -> toEnum i
293+
294+-- | Renders a UTF-8 encoded bytestring.
295+utf8_bytestring :: Attr -> BS.ByteString -> Image
296+utf8_bytestring !a !bs = string a (UTF8.toString $ UTF8.fromRep bs)
297+
298+char_fill :: Enum d => Attr -> Char -> d -> d -> Image
299+char_fill !a !c w h =
300+ vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) $ char a c
301+
src/Graphics/Vty/LLInput.hsnew file+197
@@ -0,0 +1,197 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# OPTIONS_GHC -Wall #-}
3+{-# LANGUAGE ForeignFunctionInterface #-}
4+module Graphics.Vty.LLInput ( Key(..)
5+ , Modifier(..)
6+ , Button(..)
7+ , Event(..)
8+ , initTermInput
9+ )
10+ where
11+
12+import Data.Char
13+import Data.Maybe ( mapMaybe
14+ )
15+import Data.List( inits )
16+import Data.Word
17+import qualified Data.Map as M( fromList, lookup )
18+import qualified Data.Set as S( fromList, member )
19+
20+import Codec.Binary.UTF8.Generic (decode)
21+
22+import Control.Monad (when)
23+import Control.Concurrent
24+import Control.Exception
25+
26+import System.Console.Terminfo
27+
28+import System.Posix.Signals.Exts
29+import System.Posix.Signals
30+import System.Posix.Terminal
31+import System.Posix.IO ( stdInput
32+ ,fdRead
33+ ,setFdOption
34+ ,FdOption(..)
35+ )
36+
37+-- |Representations of non-modifier keys.
38+data Key = KEsc | KFun Int | KPrtScr | KPause | KASCII Char | KBS | KIns
39+ | KHome | KPageUp | KDel | KEnd | KPageDown | KNP5 | KUp | KMenu
40+ | KLeft | KDown | KRight | KEnter
41+ deriving (Eq,Show,Ord)
42+
43+-- |Modifier keys. Key codes are interpreted such that users are more likely to
44+-- have Meta than Alt; for instance on the PC Linux console, 'MMeta' will
45+-- generally correspond to the physical Alt key.
46+data Modifier = MShift | MCtrl | MMeta | MAlt
47+ deriving (Eq,Show,Ord)
48+
49+-- |Mouse buttons. Not yet used.
50+data Button = BLeft | BMiddle | BRight
51+ deriving (Eq,Show,Ord)
52+
53+-- |Generic events.
54+data Event = EvKey Key [Modifier] | EvMouse Int Int Button [Modifier]
55+ | EvResize Int Int
56+ deriving (Eq,Show,Ord)
57+
58+data KClass = Valid Key [Modifier] | Invalid | Prefix | MisPfx Key [Modifier] [Char]
59+ deriving(Show)
60+
61+-- | Set up the terminal for input. Returns a function which reads key
62+-- events, and a function for shutting down the terminal access.
63+initTermInput :: Int -> Terminal -> IO (IO Event, IO ())
64+initTermInput escDelay terminal = do
65+ eventChannel <- newChan
66+ inputChannel <- newChan
67+ hadInput <- newEmptyMVar
68+ oattr <- getTerminalAttributes stdInput
69+ let nattr = foldl withoutMode oattr [StartStopOutput, KeyboardInterrupts,
70+ EnableEcho, ProcessInput]
71+ setTerminalAttributes stdInput nattr Immediately
72+ set_term_timing
73+ let inputToEventThread :: IO ()
74+ inputToEventThread = loop []
75+ where loop kb = case (classify kb) of
76+ Prefix -> do c <- readChan inputChannel
77+ loop (kb ++ [c])
78+ Invalid -> loop ""
79+ MisPfx k m s -> writeChan eventChannel (EvKey k m) >> loop s
80+ Valid k m -> writeChan eventChannel (EvKey k m) >> loop ""
81+
82+ finishAtomicInput = writeChan inputChannel '\xFFFE'
83+
84+ inputThread :: IO ()
85+ inputThread = loop
86+ where
87+ loop = do
88+ setFdOption stdInput NonBlockingRead False
89+ threadWaitRead stdInput
90+ setFdOption stdInput NonBlockingRead True
91+ try readAll
92+ when (escDelay == 0) finishAtomicInput
93+ loop
94+ readAll = do
95+ (bytes, bytes_read) <- fdRead stdInput 1
96+ when (bytes_read > 0) $ do
97+ tryPutMVar hadInput () -- signal input
98+ writeChan inputChannel (head bytes)
99+ readAll
100+ -- | If there is no input for some time, this thread puts '\xFFFE' in the
101+ -- inputChannel.
102+ noInputThread :: IO ()
103+ noInputThread = when (escDelay > 0) loop
104+ where loop = do
105+ takeMVar hadInput -- wait for some input
106+ threadDelay escDelay -- microseconds
107+ hadNoInput <- isEmptyMVar hadInput -- no input yet?
108+ when hadNoInput $ do
109+ finishAtomicInput
110+ loop
111+
112+
113+ compile :: [[([Char],(Key,[Modifier]))]] -> [Char] -> KClass
114+ compile lst = cl' where
115+ lst' = concat lst
116+ pfx = S.fromList $ concatMap (init . inits . fst) $ lst'
117+ mlst = M.fromList lst'
118+ cl' str = case S.member str pfx of
119+ True -> Prefix
120+ False -> case M.lookup str mlst of
121+ Just (k,m) -> Valid k m
122+ Nothing -> case head $ mapMaybe (\s -> (,) s `fmap` M.lookup s mlst) $ init $ inits str of
123+ (s,(k,m)) -> MisPfx k m (drop (length s) str)
124+
125+ -- ANSI specific bits
126+
127+
128+ classify, classifyTab :: [Char] -> KClass
129+
130+ -- As soon as
131+ classify "\xFFFE" = Invalid
132+ classify s@(c:_) | ord c >= 0xC2 =
133+ if utf8Length (ord c) > length s then Prefix else classifyUtf8 s -- beginning of an utf8 sequence
134+ classify other = classifyTab other
135+
136+ classifyUtf8 s = case decode ((map (fromIntegral . ord) s) :: [Word8]) of
137+ Just (unicodeChar, _) -> Valid (KASCII unicodeChar) []
138+ _ -> Invalid -- something bad happened; just ignore and continue.
139+
140+ classifyTab = compile (caps_classify_table : ansi_classify_table)
141+
142+ caps_tabls = [("khome", (KHome, [])),
143+ ("kend", (KEnd, [])),
144+
145+ ("kcud1", (KDown, [])),
146+ ("kcuu1", (KUp, [])),
147+ ("kcuf1", (KRight, [])),
148+ ("kcub1", (KLeft, []))
149+ ]
150+
151+ caps_classify_table = [(x,y) | (Just x,y) <- map (first (getCapability terminal . tiGetStr)) $ caps_tabls]
152+
153+ ansi_classify_table :: [[([Char], (Key, [Modifier]))]]
154+ ansi_classify_table =
155+ [ let k c s = ("\ESC["++c,(s,[])) in
156+ [ k "G" KNP5, k "P" KPause ],
157+ let k n s = ("\ESC["++show n++"~",(s,[])) in zipWith k [2::Int,3,5,6] [KIns,KDel,KPageUp,KPageDown],
158+ [ (x:[],(KASCII x,[])) | x <- map toEnum [0..255] ],
159+ [ ("\ESC[["++[toEnum(64+i)],(KFun i,[])) | i <- [1..5] ],
160+ let f ff nrs m = [ ("\ESC["++show n++"~",(KFun (n-(nrs!!0)+ff), m)) | n <- nrs ] in
161+ concat [ f 6 [17..21] [], f 11 [23,24] [], f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ],
162+ [ ('\ESC':[x],(KASCII x,[MMeta])) | x <- '\ESC':'\t':[' ' .. '\DEL'] ],
163+ [ ([toEnum x],(KASCII y,[MCtrl]))
164+ | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_']),
165+ y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
166+ ],
167+ [ ('\ESC':[toEnum x],(KASCII y,[MMeta,MCtrl])) | (x,y) <- zip [0..31] ('@':['a'..'z']++['['..'_']) ],
168+ [ ("\ESC",(KEsc,[])) , ("\ESC\ESC",(KEsc,[MMeta])) , ("\DEL",(KBS,[])), ("\ESC\DEL",(KBS,[MMeta])),
169+ ("\ESC\^J",(KEnter,[MMeta])), ("\^J",(KEnter,[])) ] ]
170+
171+ eventThreadId <- forkIO $ inputToEventThread
172+ inputThreadId <- forkIO $ inputThread
173+ noInputThreadId <- forkIO $ noInputThread
174+ let pokeIO = (Catch $ do let e = error "(getsize in input layer)"
175+ setTerminalAttributes stdInput nattr Immediately
176+ writeChan eventChannel (EvResize e e))
177+ installHandler windowChange pokeIO Nothing
178+ installHandler continueProcess pokeIO Nothing
179+ let uninit = do killThread eventThreadId
180+ killThread inputThreadId
181+ killThread noInputThreadId
182+ installHandler windowChange Ignore Nothing
183+ installHandler continueProcess Ignore Nothing
184+ setTerminalAttributes stdInput oattr Immediately
185+ return (readChan eventChannel, uninit)
186+
187+first f (x,y) = (f x, y)
188+
189+
190+utf8Length c
191+ | c < 0x80 = 1
192+ | c < 0xE0 = 2
193+ | c < 0xF0 = 3
194+ | otherwise = 4
195+
196+foreign import ccall "set_term_timing" set_term_timing :: IO ()
197+
src/Graphics/Vty/Picture.hsnew file+72
@@ -0,0 +1,72 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# LANGUAGE NamedFieldPuns #-}
3+module Graphics.Vty.Picture ( module Graphics.Vty.Picture
4+ , image_width
5+ , image_height
6+ , (<|>)
7+ , (<->)
8+ , horiz_cat
9+ , vert_cat
10+ , horzcat
11+ , vertcat
12+ , background_fill
13+ , char
14+ , string
15+ , iso_10464_string
16+ , utf8_string
17+ , utf8_bytestring
18+ , char_fill
19+ -- | The possible display attributes used in constructing an `Image`.
20+ , module Graphics.Vty.Attributes
21+ )
22+ where
23+
24+import Graphics.Vty.Attributes
25+import Graphics.Vty.Image hiding ( attr )
26+
27+import Data.Word
28+
29+-- |The type of images to be displayed using 'update'.
30+-- Can be constructed directly or using `pic_for_image`. Which provides an initial instance with
31+-- reasonable defaults for pic_cursor and pic_background.
32+data Picture = Picture
33+ { pic_cursor :: Cursor
34+ , pic_image :: Image
35+ , pic_background :: Background
36+ }
37+
38+-- | Create a picture instance from the given image. The picture will not have a displayed cursor
39+-- and the background display attribute will be `current_attr`.
40+pic_for_image :: Image -> Picture
41+pic_for_image i = Picture
42+ { pic_cursor = NoCursor
43+ , pic_image = i
44+ , pic_background = Background ' ' current_attr
45+ }
46+
47+-- | A picture can be configured either to not show the cursor or show the cursor at the specified
48+-- character position.
49+-- There is not a 1 to 1 map from character positions to a row and column on the screen due to
50+-- characters that take more than 1 column.
51+-- todo?: The Cursor can be given a (character,row) offset outside of the visible bounds of the output
52+-- region. In this case the cursor will not be shown.
53+data Cursor =
54+ NoCursor
55+ | Cursor Word Word
56+
57+-- | Unspecified regions are filled with the picture's background pattern.
58+-- The background pattern can specify a character and a display attribute. If the display attribute
59+-- used previously should be used for a background fill then use `current_attr` for the background
60+-- attribute. This is the default background display attribute.
61+--
62+-- The current attribute is always set to the default attributes on the start of updating the screen
63+-- to a picture.
64+--
65+-- todo: The background character *must* occupy a single column and no more.
66+-- The spans for a picture do not specify character data beyond the bounds of the region provided.
67+-- A text run may be cropped or padded to fit within the region.
68+data Background = Background
69+ { background_char :: Char
70+ , background_attr :: Attr
71+ }
72+
src/Graphics/Vty/PictureToSpans.hsnew file+7
@@ -0,0 +1,7 @@
1+-- Copyright 2009 Corey O'Connor
2+module Graphics.Vty.PictureToSpans
3+ where
4+
5+import Graphics.Vty.Picture
6+import Graphics.Vty.Span
7+
src/Graphics/Vty/Span.hsnew file+239
@@ -0,0 +1,239 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE ExistentialQuantification #-}
4+{-# LANGUAGE NamedFieldPuns #-}
5+{-# LANGUAGE GADTs #-}
6+{-# LANGUAGE ExistentialQuantification #-}
7+{-# LANGUAGE ScopedTypeVariables #-}
8+-- The ops to define the content for an output region.
9+module Graphics.Vty.Span
10+ where
11+
12+import Codec.Binary.UTF8.Width ( wcwidth )
13+
14+import Graphics.Vty.Image
15+import Graphics.Vty.Picture
16+import Graphics.Vty.DisplayRegion
17+
18+import Codec.Binary.UTF8.String ( encode )
19+
20+import Control.Monad ( forM_ )
21+import Control.Monad.ST.Strict
22+
23+import Data.Array.ST
24+import qualified Data.ByteString as B
25+import qualified Data.ByteString.Internal as BInt
26+import qualified Data.Foldable as Foldable
27+import Data.Function
28+import Data.List
29+import Data.Word
30+import qualified Data.ByteString.UTF8 as BSUTF8
31+import qualified Data.String.UTF8 as UTF8
32+
33+import Foreign.Storable ( pokeByteOff )
34+
35+import GHC.Arr
36+
37+{- | A picture is translated into a sequences of state changes and character spans.
38+ - State changes are currently limited to new attribute values. The attribute is applied to all
39+ - following spans. Including spans of the next row. The nth element of the sequence represents the
40+ - nth row (from top to bottom) of the picture to render.
41+ -
42+ - A span op sequence will be defined for all rows and columns (and no more) of the region provided
43+ - with the picture to spans_for_pic.
44+ -
45+ -
46+ - todo: Partition attribute changes into multiple categories according to the serialized
47+ - representation of the various attributes.
48+ -}
49+data SpanOpSequence = SpanOpSequence
50+ { effected_region :: DisplayRegion
51+ , row_ops :: RowOps
52+ }
53+
54+type RowOps = Array Word SpanOps
55+type SpanOps = [SpanOp]
56+
57+span_ops_effected_columns :: [SpanOp] -> Word
58+span_ops_effected_columns in_ops = span_ops_effected_columns' 0 in_ops
59+ where
60+ span_ops_effected_columns' t [] = t
61+ span_ops_effected_columns' t (TextSpan w _ _ : r) = span_ops_effected_columns' (t + w) r
62+ span_ops_effected_columns' t (_ : r) = span_ops_effected_columns' t r
63+
64+-- |
65+--
66+-- todo: This type may need to be restructured to increase sharing in the bytestring
67+--
68+-- todo: Make foldable
69+data SpanOp =
70+ AttributeChange !Attr
71+ -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
72+ -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
73+ -- TextSpan [output width in columns] [number of characters] [data]
74+ | TextSpan !Word !Word (UTF8.UTF8 B.ByteString)
75+ deriving Eq
76+
77+span_op_has_width :: SpanOp -> Maybe (Word, Word)
78+span_op_has_width (TextSpan ow cw _) = Just (cw, ow)
79+span_op_has_width _ = Nothing
80+
81+columns_to_char_offset :: Word -> SpanOp -> Word
82+columns_to_char_offset cx (TextSpan _ _ utf8_str) =
83+ let str = UTF8.toString utf8_str
84+ in toEnum $ sum $ map wcwidth $ take (fromEnum cx) str
85+columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
86+
87+-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
88+-- specified region.
89+spans_for_pic :: Picture -> DisplayRegion -> SpanOpSequence
90+spans_for_pic pic r = SpanOpSequence r $ runSTArray (build_spans pic r)
91+
92+build_spans :: Picture -> DisplayRegion -> ST s (STArray s Word [SpanOp])
93+build_spans pic region = do
94+ -- m for mutable
95+ mrow_ops <- newSTArray (0, region_height region - 1) []
96+ -- I think this can be better optimized if the build produced span operations in display order.
97+ -- However, I got stuck trying to implement an algorithm that did this. This will be considered
98+ -- as a possible future optimization.
99+ --
100+ -- A depth first traversal of the image is performed.
101+ -- ordered according to the column range defined by the image from least to greatest.
102+ -- The output row ops will at least have the region of the image specified. Iterate over all
103+ -- output rows and output background fills for all unspecified columns.
104+ if region_height region > 0
105+ then do
106+ -- The ops builder recursively descends the image and outputs span ops that would
107+ -- display that image. The number of columns remaining in this row before exceeding the
108+ -- bounds is also provided. This is used to clip the span ops produced.
109+ ops_for_row mrow_ops (pic_background pic) region (pic_image pic) 0 (region_width region)
110+ -- Fill in any unspecified columns with the background pattern.
111+ forM_ [0 .. region_height region - 1] $ \row -> do
112+ end_x <- readSTArray mrow_ops row >>= return . span_ops_effected_columns
113+ if end_x < region_width region
114+ then snoc_bg_fill mrow_ops (pic_background pic) (region_width region - end_x) row
115+ else return ()
116+ else return ()
117+ return mrow_ops
118+
119+type MRowOps s = STArray s Word SpanOps
120+
121+ops_for_row :: MRowOps s -> Background -> DisplayRegion -> Image -> Word -> Word -> ST s ()
122+ops_for_row mrow_ops bg region image y remaining_columns
123+ | remaining_columns == 0 = return ()
124+ | y >= region_height region = return ()
125+ | otherwise = case image of
126+ IdImage -> return ()
127+ -- The width provided is the number of columns this text span will occupy when displayed.
128+ -- if this is greater than the number of remaining columsn the output has to be produced a
129+ -- character at a time.
130+ HorizText a text_str ow cw -> do
131+ snoc_text_span a text_str ow cw mrow_ops y remaining_columns
132+ VertJoin t b _ _ -> do
133+ ops_for_row mrow_ops bg region t y remaining_columns
134+ ops_for_row mrow_ops bg region b (y + image_height t) remaining_columns
135+ HorizJoin l r _ _ -> do
136+ ops_for_row mrow_ops bg region l y remaining_columns
137+ -- Don't output the right part unless there is at least a single column left after
138+ -- outputting the left part.
139+ if image_width l < remaining_columns
140+ then ops_for_row mrow_ops bg region r y (remaining_columns - image_width l)
141+ else return ()
142+ BGFill width height -> do
143+ let actual_height = if y + height > region_height region
144+ then region_height region - y
145+ else height
146+ actual_width = if width > remaining_columns
147+ then remaining_columns
148+ else width
149+ forM_ [y .. y + actual_height - 1] $ \y' -> snoc_bg_fill mrow_ops bg actual_width y'
150+
151+snoc_text_span :: (Foldable.Foldable t)
152+ => Attr
153+ -> t Char
154+ -> Word -- width of text span in output display columns
155+ -> Word -- width of text span in characters
156+ -> MRowOps s
157+ -> Word
158+ -> Word
159+ -> ST s ()
160+snoc_text_span a text_str ow cw mrow_ops y remaining_columns = do
161+ if ow > remaining_columns
162+ then do
163+ snoc_op mrow_ops y $ AttributeChange a
164+ let (ow', cw', txt) = Foldable.foldl'
165+ build_cropped_txt
166+ ( 0, 0, B.empty )
167+ text_str
168+ snoc_op mrow_ops y $ TextSpan ow' cw' (UTF8.fromRep txt)
169+ else do
170+ snoc_op mrow_ops y $ AttributeChange a
171+ let utf8_bs = ({-# SCC "BSUTF8.fromString" #-} BSUTF8.fromString) $ Foldable.foldMap (\c -> [c]) text_str
172+ snoc_op mrow_ops y $ TextSpan ow cw (UTF8.fromRep utf8_bs)
173+ where
174+ build_cropped_txt (ow', char_count', b0) c = {-# SCC "build_cropped_txt" #-}
175+ let w = wcwidth c
176+ -- Characters with unknown widths occupy 1 column.
177+ --
178+ -- todo: Not sure if this is actually correct.
179+ -- I presume there is a replacement character that is output by the terminal instead of
180+ -- the character. If so then this replacement process may need to be implemented
181+ -- manually for consistent behavior across terminals.
182+ w' = toEnum $ if w < 0 then 1 else w
183+ in if (w' + ow') > remaining_columns
184+ then ( ow', char_count', b0 )
185+ else ( ow' + w', char_count' + 1, B.append b0 $ B.pack $ encode [c] )
186+
187+snoc_bg_fill :: MRowOps s -> Background -> Word -> Word -> ST s ()
188+snoc_bg_fill _row_ops _bg 0 _row
189+ = return ()
190+snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
191+ = do
192+ snoc_op mrow_ops row $ AttributeChange back_attr
193+ -- By all likelyhood the background character will be an ASCII character. Which is a single
194+ -- byte in utf8. Optimize for this special case.
195+ utf8_bs <- if c <= (toEnum 255 :: Char)
196+ then
197+ let !(c_byte :: Word8) = BInt.c2w c
198+ in unsafeIOToST $ do
199+ BInt.create ( fromEnum fill_length )
200+ $ \ptr -> mapM_ (\i -> pokeByteOff ptr i c_byte)
201+ [0 .. fromEnum (fill_length - 1)]
202+ else
203+ let !(c_bytes :: [Word8]) = encode [c]
204+ in unsafeIOToST $ do
205+ BInt.create (fromEnum fill_length * length c_bytes)
206+ $ \ptr -> mapM_ (\(i,b) -> pokeByteOff ptr i b)
207+ $ zip [0 .. fromEnum (fill_length - 1)] (cycle c_bytes)
208+ snoc_op mrow_ops row $ TextSpan fill_length fill_length (UTF8.fromRep utf8_bs)
209+
210+snoc_op :: MRowOps s -> Word -> SpanOp -> ST s ()
211+snoc_op !mrow_ops !row !op = do
212+ ops <- readSTArray mrow_ops row
213+ let ops' = ops ++ [op]
214+ writeSTArray mrow_ops row ops'
215+ return ()
216+
217+data StateTransition =
218+ NextSpan BuildState
219+ | NextRow BuildState
220+ deriving Show
221+
222+data BuildState = BuildState
223+ { current_column :: !Word
224+ , current_row_build_ops :: [BuildOp]
225+ , next_row_build_ops :: [BuildOp]
226+ }
227+ deriving Show
228+
229+-- A build op is a (column, row) offset into an image and the image.
230+data BuildOp = BuildOp !Word !Word Image
231+ deriving Show
232+
233+initial_state :: Image -> BuildState
234+initial_state i = BuildState 0 [BuildOp 0 0 i] []
235+
236+-- The first op for each row is to the set the current attribute. Which will either be
237+-- 0. the background attribute if the span starts out with the background or is undefined.
238+-- 1. the attributes of the first text row.
239+
src/Graphics/Vty/Terminal.hsnew file+107
@@ -0,0 +1,107 @@
1+-- Copyright 2009 Corey O'Connor
2+-- * Graphics.Vty.Terminal: This instantiates an abtract interface to the terminal interface
3+-- based on the TERM and COLORTERM environment variables.
4+-- * Graphics.Vty.Terminal.Generic: Defines the generic interface all terminals need to implement.
5+-- * Graphics.Vty.Terminal.TerminfoBased: Defines a terminal instance that uses terminfo for all
6+-- control strings.
7+-- - No attempt is made to change the character set to UTF-8 for these terminals. I don't
8+-- know a way to reliably determine if that is required or how to do so.
9+-- * Graphics.Vty.Terminal.XTermColor: This module contains an interface suitable for
10+-- xterm-like terminals. These are the terminals where TERM == xterm. This does use terminfo
11+-- for as many control codes as possible. H: This should derive functionality from the
12+-- TerminfoBased terminal.
13+--
14+-- Selection of a terminal is done as follows:
15+-- If TERM == xterm
16+--
17+-- todo: Use XTermColor with the variant parameter
18+-- StandardXTerm if COLORTERM is not set
19+-- GnomeTerminal if COLORTERM is gnome-terminal
20+-- - Default attribute is different ?How?
21+-- - No blink support
22+-- otherwise TerminfoBased is used.
23+--
24+{-# LANGUAGE ScopedTypeVariables #-}
25+{-# LANGUAGE GADTs #-}
26+module Graphics.Vty.Terminal ( module Graphics.Vty.Terminal
27+ , Terminal(..)
28+ , TerminalHandle(..)
29+ , DisplayHandle(..)
30+ , output_picture
31+ , display_context
32+ )
33+ where
34+
35+
36+import Graphics.Vty.Terminal.Generic
37+import Graphics.Vty.Terminal.MacOSX as MacOSX
38+import Graphics.Vty.Terminal.XTermColor as XTermColor
39+import Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
40+
41+import Control.Exception ( SomeException, try )
42+import Data.Either
43+import Data.List ( isPrefixOf )
44+import Data.Word
45+
46+import System.Environment
47+
48+-- | The terminal has to be determined dynamically at runtime. To satisfy this requirement all
49+-- terminals instances are lifted into an abstract terminal handle via existential qualification.
50+-- This implies that the only equations that can used are those in the terminal class.
51+terminal_handle :: IO TerminalHandle
52+terminal_handle = do
53+ term_type <- getEnv "TERM"
54+ t <- if "xterm" `isPrefixOf` term_type
55+ then do
56+ -- Mac OS X terminals define TERM_PROGRAM. Course, an xterm started by Terminal or iTerm
57+ -- *also* has TERM_PROGRAM defined. However Terminal or iTerm will never have
58+ -- XTERM_VERSION defined. whew
59+ maybe_terminal_app <- get_env "TERM_PROGRAM"
60+ case maybe_terminal_app of
61+ -- No TERM_PROGRAM then assume actual xterm
62+ Nothing
63+ -> XTermColor.terminal_instance term_type >>= new_terminal_handle
64+ Just v | v == "Apple_Terminal" || v == "iTerm.app"
65+ -> do
66+ maybe_xterm <- get_env "XTERM_VERSION"
67+ case maybe_xterm of
68+ Nothing -> MacOSX.terminal_instance >>= new_terminal_handle
69+ Just _ -> XTermColor.terminal_instance term_type >>= new_terminal_handle
70+ -- Assume any other terminal that sets TERM_PROGRAM to not be an OS X terminal.app
71+ -- like terminal?
72+ _ -> XTermColor.terminal_instance term_type >>= new_terminal_handle
73+ -- Not an xterm-like terminal. try for generic terminfo.
74+ else TerminfoBased.terminal_instance term_type >>= new_terminal_handle
75+ return t
76+ where
77+ get_env var = do
78+ mv <- try $ getEnv var
79+ case mv of
80+ Left (_e :: SomeException) -> return $ Nothing
81+ Right v -> return $ Just v
82+
83+-- | Sets the cursor position to the given output column and row.
84+--
85+-- This is not necessarially the same as the character position with the same coordinates.
86+-- Characters can be a variable number of columns in width.
87+--
88+-- Currently, the only way to set the cursor position to a given character coordinate is to specify
89+-- the coordinate in the `Picture` instance provided to `output_picture`.
90+set_cursor_pos :: TerminalHandle -> Word -> Word -> IO ()
91+set_cursor_pos t x y = do
92+ bounds <- display_bounds t
93+ d <- display_context t bounds
94+ marshall_to_terminal t (move_cursor_required_bytes d x y) (serialize_move_cursor d x y)
95+
96+hide_cursor :: TerminalHandle -> IO ()
97+hide_cursor t = do
98+ bounds <- display_bounds t
99+ d <- display_context t bounds
100+ marshall_to_terminal t (hide_cursor_required_bytes d) (serialize_hide_cursor d)
101+
102+show_cursor :: TerminalHandle -> IO ()
103+show_cursor t = do
104+ bounds <- display_bounds t
105+ d <- display_context t bounds
106+ marshall_to_terminal t (show_cursor_required_bytes d) (serialize_show_cursor d)
107+
src/Graphics/Vty/Terminal/Debug.hsnew file+115
@@ -0,0 +1,115 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE TypeFamilies #-}
4+module Graphics.Vty.Terminal.Debug ( DebugTerminal(..)
5+ , DebugDisplay(..)
6+ , terminal_instance
7+ , dehandle
8+ )
9+ where
10+
11+import Graphics.Vty.WinRegion
12+import Graphics.Vty.Terminal.Generic
13+
14+import Control.Monad.State.Strict
15+
16+import qualified Data.ByteString.UTF8 as BS
17+import qualified Data.ByteString as BSCore
18+import Data.IORef
19+import qualified Data.Sequence as Seq
20+import qualified Data.String.UTF8 as UTF8
21+import Data.Word
22+
23+import Foreign.Marshal.Array ( peekArray )
24+import Foreign.Ptr ( plusPtr )
25+import Foreign.Storable ( poke )
26+
27+import Unsafe.Coerce
28+
29+-- | The debug display terminal produces a string representation of the requested picture. There is
30+-- *not* an isomorphism between the string representation and the picture. The string
31+-- representation is a simplification of the picture that is only useful in debugging VTY without
32+-- considering terminal specific issues.
33+--
34+-- The debug implementation is useful in manually determining if the sequence of terminal operations
35+-- matches the expected sequence. So requirement of the produced representation is simplicity in
36+-- parsing the text representation and determining how the picture was mapped to terminal
37+-- operations.
38+--
39+-- All terminals support the operations specified in the Terminal class defined in
40+-- Graphics.Vty.Terminal. As an instance of the Terminal class is also an instance of the Monad
41+-- class there exists a monoid that defines it's algebra. The string representation is a sequence of
42+-- identifiers where each identifier is the name of an operation in the algebra.
43+
44+data DebugTerminal = DebugTerminal
45+ { debug_terminal_last_output :: IORef (UTF8.UTF8 BS.ByteString)
46+ , debug_terminal_bounds :: DisplayBounds
47+ }
48+
49+instance Terminal DebugTerminal where
50+ terminal_ID _t = "debug_terminal"
51+ release_terminal _t = return ()
52+ reserve_display _t = return ()
53+ release_display _t = return ()
54+ display_bounds t = return $ debug_terminal_bounds t
55+ display_context t bounds = return $ DisplayHandle (DebugDisplay bounds) (TerminalHandle t)
56+ output_byte_buffer t out_buffer buffer_size
57+ = do
58+ putStrLn $ "output_byte_buffer ?? " ++ show buffer_size
59+ peekArray (fromEnum buffer_size) out_buffer
60+ >>= return . UTF8.fromRep . BSCore.pack
61+ >>= writeIORef (debug_terminal_last_output t)
62+
63+data DebugDisplay = DebugDisplay
64+ { debug_display_bounds :: DisplayBounds
65+ }
66+
67+terminal_instance :: DisplayBounds -> IO TerminalHandle
68+terminal_instance bounds = do
69+ output_ref <- newIORef undefined
70+ return $ TerminalHandle $ DebugTerminal output_ref bounds
71+
72+dehandle :: TerminalHandle -> DebugTerminal
73+dehandle (TerminalHandle t) = unsafeCoerce t
74+
75+instance DisplayTerminal DebugDisplay where
76+ -- | Provide the current bounds of the output terminal.
77+ context_bounds d = debug_display_bounds d
78+
79+ -- | A cursor move is always visualized as the single character 'M'
80+ move_cursor_required_bytes _d _x _y = 1
81+
82+ -- | A cursor move is always visualized as the single character 'M'
83+ serialize_move_cursor _d _x _y ptr = do
84+ poke ptr (toEnum $ fromEnum 'M')
85+ return $ ptr `plusPtr` 1
86+
87+ -- | Show cursor is always visualized as the single character 'S'
88+ show_cursor_required_bytes _d = 1
89+
90+ -- | Show cursor is always visualized as the single character 'S'
91+ serialize_show_cursor _d ptr = do
92+ poke ptr (toEnum $ fromEnum 'S')
93+ return $ ptr `plusPtr` 1
94+
95+ -- | Hide cursor is always visualized as the single character 'H'
96+ hide_cursor_required_bytes _d = 1
97+
98+ -- | Hide cursor is always visualized as the single character 'H'
99+ serialize_hide_cursor _d ptr = do
100+ poke ptr (toEnum $ fromEnum 'H')
101+ return $ ptr `plusPtr` 1
102+
103+ -- | An attr change is always visualized as the single character 'A'
104+ attr_required_bytes _d _fattr _attr = 1
105+
106+ -- | An attr change is always visualized as the single character 'A'
107+ serialize_set_attr _d _fattr _attr ptr = do
108+ poke ptr (toEnum $ fromEnum 'A')
109+ return $ ptr `plusPtr` 1
110+
111+ default_attr_required_bytes _d = 1
112+ serialize_default_attr _d ptr = do
113+ poke ptr (toEnum $ fromEnum 'D')
114+ return $ ptr `plusPtr` 1
115+
src/Graphics/Vty/Terminal/Generic.hsnew file+376
@@ -0,0 +1,376 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+{-# LANGUAGE Rank2Types #-}
3+{-# LANGUAGE ExistentialQuantification #-}
4+-- Copyright 2009 Corey O'Connor
5+{-# LANGUAGE GADTs #-}
6+{-# LANGUAGE FlexibleContexts #-}
7+module Graphics.Vty.Terminal.Generic
8+ where
9+
10+import Data.Marshalling
11+
12+import Graphics.Vty.Picture
13+import Graphics.Vty.Span
14+import Graphics.Vty.DisplayRegion
15+
16+import Control.Monad ( liftM )
17+
18+import Data.Array
19+import qualified Data.ByteString.Internal as BSCore
20+import Data.Foldable
21+import Data.IORef
22+import Data.Word
23+import Data.String.UTF8 hiding ( foldl )
24+
25+import System.IO
26+
27+data TerminalHandle where
28+ TerminalHandle :: Terminal t => t -> TerminalState -> TerminalHandle
29+
30+terminal_state :: TerminalHandle -> TerminalState
31+terminal_state (TerminalHandle _ s) = s
32+
33+new_terminal_handle :: forall t. Terminal t => t -> IO TerminalHandle
34+new_terminal_handle t = liftM (TerminalHandle t) initial_terminal_state
35+
36+data TerminalState = TerminalState
37+
38+initial_terminal_state :: IO TerminalState
39+initial_terminal_state = return $ TerminalState
40+
41+class Terminal t where
42+ -- | Text identifier for the terminal. Used for debugging.
43+ terminal_ID :: t -> String
44+ -- |
45+ release_terminal :: t -> IO ()
46+ -- | Clear the display and initialize the terminal to some initial display state.
47+ --
48+ -- The expectation of a program is that the display starts in some initial state.
49+ -- The initial state would consist of fixed values
50+ -- - cursor at top left
51+ -- - UTF-8 character encoding
52+ -- - drawing characteristics are the default
53+ -- The abstract operation I think all these behaviors are instances of is reserving exclusive
54+ -- access to a display such that:
55+ -- - The previous state cannot be determined
56+ -- - When exclusive access to a display is release the display returns to the previous state.
57+ --
58+ reserve_display :: t -> IO ()
59+
60+ -- | Return the display to the state before reserve_display
61+ -- If no previous state then set the display state to the initial state.
62+ release_display :: t -> IO ()
63+
64+ -- | Returns the current display bounds.
65+ display_bounds :: t -> IO DisplayRegion
66+
67+ -- Internal method used to provide the DisplayTerminal instance to the DisplayHandle
68+ -- constructor.
69+ display_terminal_instance :: t
70+ -> DisplayRegion
71+ -> (forall d. DisplayTerminal d => d -> DisplayHandle)
72+ -> IO DisplayHandle
73+
74+ -- | Output the byte buffer of the specified size to the terminal device. The size is equal to
75+ -- end_ptr - start_ptr
76+ output_byte_buffer :: t -> OutputBuffer -> Word -> IO ()
77+
78+instance Terminal TerminalHandle where
79+ terminal_ID (TerminalHandle t _) = terminal_ID t
80+ release_terminal (TerminalHandle t _) = release_terminal t
81+ reserve_display (TerminalHandle t _) = reserve_display t
82+ release_display (TerminalHandle t _) = release_display t
83+ display_bounds (TerminalHandle t _) = display_bounds t
84+ display_terminal_instance (TerminalHandle t _) = display_terminal_instance t
85+ output_byte_buffer (TerminalHandle t _) = output_byte_buffer t
86+
87+data DisplayHandle where
88+ DisplayHandle :: DisplayTerminal d => d -> TerminalHandle -> DisplayState -> DisplayHandle
89+
90+-- | Acquire display access to the given region of the display.
91+-- Currently all regions have the upper left corner of (0,0) and the lower right corner at
92+-- (max display_width provided_width, max display_height provided_height)
93+display_context :: TerminalHandle -> DisplayRegion -> IO DisplayHandle
94+display_context t b = do
95+ s <- initial_display_state
96+ let c d = DisplayHandle d t s
97+ display_terminal_instance t b c
98+
99+data DisplayState = DisplayState
100+ { previous_output_ref :: IORef (Maybe SpanOpSequence)
101+ }
102+
103+initial_display_state :: IO DisplayState
104+initial_display_state = liftM DisplayState $ newIORef Nothing
105+
106+class DisplayTerminal d where
107+ -- | Provide the bounds of the display context.
108+ context_region :: d -> DisplayRegion
109+
110+ -- | sets the output position to the specified row and column. Where the number of bytes
111+ -- required for the control codes can be specified seperate from the actual byte sequence.
112+ move_cursor_required_bytes :: d -> Word -> Word -> Word
113+ serialize_move_cursor :: d -> Word -> Word -> OutputBuffer -> IO OutputBuffer
114+
115+ show_cursor_required_bytes :: d -> Word
116+ serialize_show_cursor :: d -> OutputBuffer -> IO OutputBuffer
117+
118+ hide_cursor_required_bytes :: d -> Word
119+ serialize_hide_cursor :: d -> OutputBuffer -> IO OutputBuffer
120+
121+ -- | Assure the specified output attributes will be applied to all the following text until the
122+ -- next output attribute change. Where the number of bytes required for the control codes can
123+ -- be specified seperate from the actual byte sequence. The required number of bytes must be
124+ -- at least the maximum number of bytes required by any attribute changes. The serialization
125+ -- equations must provide the ptr to the next byte to be specified in the output buffer.
126+ --
127+ -- The currently applied display attributes are provided as well. The Attr data type can
128+ -- specify the style or color should not be changed from the currently applied display
129+ -- attributes. In order to support this the currently applied display attributes are required.
130+ -- In addition it may be possible to optimize the state changes based off the currently applied
131+ -- display attributes.
132+ attr_required_bytes :: d -> FixedAttr -> Attr -> Word
133+ serialize_set_attr :: d -> FixedAttr -> Attr -> OutputBuffer -> IO OutputBuffer
134+
135+ -- | Reset the display attributes to the default display attributes
136+ default_attr_required_bytes :: d -> Word
137+ serialize_default_attr :: d -> OutputBuffer -> IO OutputBuffer
138+
139+
140+instance DisplayTerminal DisplayHandle where
141+ context_region (DisplayHandle d _ _) = context_region d
142+ move_cursor_required_bytes (DisplayHandle d _ _) = move_cursor_required_bytes d
143+ serialize_move_cursor (DisplayHandle d _ _) = serialize_move_cursor d
144+ show_cursor_required_bytes (DisplayHandle d _ _) = show_cursor_required_bytes d
145+ serialize_show_cursor (DisplayHandle d _ _) = serialize_show_cursor d
146+ hide_cursor_required_bytes (DisplayHandle d _ _) = hide_cursor_required_bytes d
147+ serialize_hide_cursor (DisplayHandle d _ _) = serialize_hide_cursor d
148+ attr_required_bytes (DisplayHandle d _ _) = attr_required_bytes d
149+ serialize_set_attr (DisplayHandle d _ _) = serialize_set_attr d
150+ default_attr_required_bytes (DisplayHandle d _ _) = default_attr_required_bytes d
151+ serialize_default_attr (DisplayHandle d _ _) = serialize_default_attr d
152+
153+-- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.
154+utf8_text_required_bytes :: UTF8 BSCore.ByteString -> Word
155+utf8_text_required_bytes str =
156+ let (_, _, src_bytes_length) = BSCore.toForeignPtr (toRep str)
157+ in toEnum src_bytes_length
158+
159+-- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.
160+serialize_utf8_text :: UTF8 BSCore.ByteString -> OutputBuffer -> IO OutputBuffer
161+serialize_utf8_text str dest_ptr =
162+ let (src_fptr, src_ptr_offset, src_bytes_length) = BSCore.toForeignPtr (toRep str)
163+ in withForeignPtr src_fptr $ \src_ptr -> do
164+ let src_ptr' = src_ptr `plusPtr` src_ptr_offset
165+ BSCore.memcpy dest_ptr src_ptr' (toEnum src_bytes_length)
166+ return (dest_ptr `plusPtr` src_bytes_length)
167+
168+
169+-- | Displays the given `Picture`.
170+--
171+-- 0. The image is cropped to the display size.
172+--
173+-- 1. Converted into a sequence of attribute changes and text spans.
174+--
175+-- 2. The cursor is hidden.
176+--
177+-- 3. Serialized to the display.
178+--
179+-- 4. The cursor is then shown and positioned or kept hidden.
180+--
181+--
182+-- todo: specify possible IO exceptions.
183+-- abstract from IO monad to a MonadIO instance.
184+output_picture :: DisplayHandle -> Picture -> IO ()
185+output_picture (DisplayHandle d t s) pic = do
186+ let !r = context_region d
187+ let !ops = spans_for_pic pic r
188+ let !initial_attr = FixedAttr default_style_mask Nothing Nothing
189+ -- Diff the previous output against the requested output. Differences are currently on a per-row
190+ -- basis.
191+ diffs :: [Bool]
192+ <- readIORef (previous_output_ref s)
193+ >>= \mprevious_ops -> case mprevious_ops of
194+ Nothing
195+ -> return $ replicate ( fromEnum $ region_height $ effected_region ops )
196+ True
197+ Just previous_ops
198+ -> if effected_region previous_ops /= effected_region ops
199+ then return $ replicate ( fromEnum $ region_height $ effected_region ops )
200+ True
201+ else return $ zipWith (/=) ( elems $ row_ops previous_ops )
202+ ( elems $ row_ops ops )
203+
204+ -- determine the number of bytes required to completely serialize the output ops.
205+ let total = hide_cursor_required_bytes d
206+ + default_attr_required_bytes d
207+ + required_bytes d initial_attr diffs ops
208+ + case pic_cursor pic of
209+ NoCursor -> 0
210+ Cursor x y -> show_cursor_required_bytes d
211+ + move_cursor_required_bytes d x y
212+
213+ -- ... then serialize
214+ allocaBytes (fromEnum total) $ \start_ptr -> do
215+ ptr <- serialize_hide_cursor d start_ptr
216+ ptr' <- serialize_default_attr d ptr
217+ ptr'' <- serialize_output_ops d ptr' initial_attr diffs ops
218+ end_ptr <- case pic_cursor pic of
219+ NoCursor -> return ptr''
220+ Cursor x y -> do
221+ let m = cursor_output_map ops $ pic_cursor pic
222+ (ox, oy) = char_to_output_pos m (x,y)
223+ serialize_show_cursor d ptr'' >>= serialize_move_cursor d ox oy
224+ -- todo: How to handle exceptions?
225+ case end_ptr `minusPtr` start_ptr of
226+ count | count < 0 -> fail "End pointer before start of buffer."
227+ | toEnum count > total -> fail $ "End pointer past end of buffer by " ++ show (toEnum count - total)
228+ | otherwise -> output_byte_buffer t start_ptr (toEnum count)
229+ -- Cache the output spans.
230+ writeIORef (previous_output_ref s) (Just ops)
231+ return ()
232+
233+required_bytes :: DisplayTerminal d => d -> FixedAttr -> [Bool] -> SpanOpSequence -> Word
234+required_bytes d in_fattr diffs ops =
235+ let (_, n, _, _) = foldl' required_bytes' (0, 0, in_fattr, diffs) ( row_ops ops )
236+ in n
237+ where
238+ required_bytes' (y, current_sum, fattr, True : diffs') span_ops
239+ = let (s, fattr') = span_ops_required_bytes d y fattr span_ops
240+ in ( y + 1, s + current_sum, fattr', diffs' )
241+ required_bytes' (y, current_sum, fattr, False : diffs') _span_ops
242+ = ( y + 1, current_sum, fattr, diffs' )
243+ required_bytes' (_y, _current_sum, _fattr, [] ) _span_ops
244+ = error "shouldn't be possible"
245+
246+span_ops_required_bytes :: DisplayTerminal d => d -> Word -> FixedAttr -> SpanOps -> (Word, FixedAttr)
247+span_ops_required_bytes d y in_fattr span_ops =
248+ -- The first operation is to set the cursor to the start of the row
249+ let header_required_bytes = move_cursor_required_bytes d 0 y
250+ -- then the span ops are serialized in the order specified
251+ in foldl' ( \(current_sum, fattr) op -> let (c, fattr') = span_op_required_bytes d fattr op
252+ in (c + current_sum, fattr')
253+ )
254+ (header_required_bytes, in_fattr)
255+ span_ops
256+
257+span_op_required_bytes :: DisplayTerminal d => d -> FixedAttr -> SpanOp -> (Word, FixedAttr)
258+span_op_required_bytes d fattr (AttributeChange attr) =
259+ let c = attr_required_bytes d fattr attr
260+ fattr' = fix_display_attr fattr attr
261+ in (c, fattr')
262+span_op_required_bytes _d fattr (TextSpan _ _ str) = (utf8_text_required_bytes str, fattr)
263+
264+serialize_output_ops :: DisplayTerminal d
265+ => d
266+ -> OutputBuffer
267+ -> FixedAttr
268+ -> [Bool]
269+ -> SpanOpSequence
270+ -> IO OutputBuffer
271+serialize_output_ops d start_ptr in_fattr diffs ops = do
272+ (_, end_ptr, _, _) <- foldlM serialize_output_ops'
273+ ( 0, start_ptr, in_fattr, diffs )
274+ ( row_ops ops )
275+ return end_ptr
276+ where
277+ serialize_output_ops' ( y, out_ptr, fattr, True : diffs' ) span_ops
278+ = serialize_span_ops d y out_ptr fattr span_ops
279+ >>= return . ( \(out_ptr', fattr') -> ( y + 1, out_ptr', fattr', diffs' ) )
280+ serialize_output_ops' ( y, out_ptr, fattr, False : diffs' ) _span_ops
281+ = return ( y + 1, out_ptr, fattr, diffs' )
282+ serialize_output_ops' (_y, _out_ptr, _fattr, [] ) _span_ops
283+ = error "shouldn't be possible"
284+
285+serialize_span_ops :: DisplayTerminal d
286+ => d
287+ -> Word
288+ -> OutputBuffer
289+ -> FixedAttr
290+ -> SpanOps
291+ -> IO (OutputBuffer, FixedAttr)
292+serialize_span_ops d y out_ptr in_fattr span_ops = do
293+ -- The first operation is to set the cursor to the start of the row
294+ out_ptr' <- serialize_move_cursor d 0 y out_ptr
295+ -- then the span ops are serialized in the order specified
296+ foldlM ( \(out_ptr'', fattr) op -> serialize_span_op d op out_ptr'' fattr )
297+ (out_ptr', in_fattr)
298+ span_ops
299+
300+serialize_span_op :: DisplayTerminal d
301+ => d
302+ -> SpanOp
303+ -> OutputBuffer
304+ -> FixedAttr
305+ -> IO (OutputBuffer, FixedAttr)
306+serialize_span_op d (AttributeChange attr) out_ptr fattr = do
307+ out_ptr' <- serialize_set_attr d fattr attr out_ptr
308+ let fattr' = fix_display_attr fattr attr
309+ return (out_ptr', fattr')
310+serialize_span_op _d (TextSpan _ _ str) out_ptr fattr = do
311+ out_ptr' <- serialize_utf8_text str out_ptr
312+ return (out_ptr', fattr)
313+
314+marshall_to_terminal :: Terminal t => t -> Word -> (Ptr Word8 -> IO (Ptr Word8)) -> IO ()
315+marshall_to_terminal t c f = do
316+ start_ptr <- mallocBytes (fromEnum c)
317+ --
318+ -- todo: capture exceptions?
319+ end_ptr <- f start_ptr
320+ case end_ptr `minusPtr` start_ptr of
321+ count | count < 0 -> fail "End pointer before start pointer."
322+ | otherwise -> output_byte_buffer t start_ptr (toEnum count)
323+ free start_ptr
324+ return ()
325+
326+
327+-- | Given the previously applied display attributes as a FixedAttr and the current display
328+-- attributes as an Attr produces a FixedAttr that represents the current display attributes. This
329+-- is done by using the previously applied display attributes to remove the "KeepCurrent"
330+-- abstraction.
331+fix_display_attr :: FixedAttr -> Attr -> FixedAttr
332+fix_display_attr fattr attr
333+ = FixedAttr ( fix_style (fixed_style fattr) (style attr) )
334+ ( fix_color (fixed_fore_color fattr) (fore_color attr) )
335+ ( fix_color (fixed_back_color fattr) (back_color attr) )
336+ where
337+ fix_style _s Default = default_style_mask
338+ fix_style s KeepCurrent = s
339+ fix_style _s (SetTo new_style) = new_style
340+ fix_color _c Default = Nothing
341+ fix_color c KeepCurrent = c
342+ fix_color _c (SetTo c) = Just c
343+
344+data CursorOutputMap = CursorOutputMap
345+ { char_to_output_pos :: (Word, Word) -> (Word, Word)
346+ }
347+
348+cursor_output_map :: SpanOpSequence -> Cursor -> CursorOutputMap
349+cursor_output_map span_ops _cursor = CursorOutputMap
350+ { char_to_output_pos = \(cx, cy) -> (cursor_column_offset span_ops cx cy, cy)
351+ }
352+
353+cursor_column_offset :: SpanOpSequence -> Word -> Word -> Word
354+cursor_column_offset span_ops cx cy =
355+ let cursor_row_ops = row_ops span_ops ! cy
356+ (out_offset, _, _)
357+ = foldl' ( \(d, current_cx, done) op ->
358+ if done then (d, current_cx, done) else case span_op_has_width op of
359+ Nothing -> (d, current_cx, False)
360+ Just (cw, ow) -> case compare cx (current_cx + cw) of
361+ GT -> ( d + ow
362+ , current_cx + cw
363+ , False
364+ )
365+ EQ -> ( d + ow
366+ , current_cx + cw
367+ , True
368+ )
369+ LT -> ( d + columns_to_char_offset (cx - current_cx) op
370+ , current_cx + cw
371+ , True
372+ )
373+ )
374+ (0, 0, False)
375+ cursor_row_ops
376+ in out_offset
src/Graphics/Vty/Terminal/MacOSX.hsnew file+76
@@ -0,0 +1,76 @@
1+-- Copyright 2009 Corey O'Connor
2+-- The standard Mac OS X terminals Terminal.app and iTerm both declare themselves to be
3+-- "xterm-color" by default. However the terminfo database for xterm-color included with OS X is
4+-- incomplete.
5+--
6+-- This terminal implementation modifies the standard terminfo terminal as required for complete OS
7+-- X support.
8+{-# LANGUAGE ForeignFunctionInterface #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE MagicHash #-}
11+{-# LANGUAGE FlexibleInstances #-}
12+{-# LANGUAGE TypeFamilies #-}
13+module Graphics.Vty.Terminal.MacOSX ( terminal_instance
14+ )
15+ where
16+
17+import Graphics.Vty.Terminal.Generic
18+import qualified Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
19+
20+import System.IO
21+
22+data Term = Term
23+ { super_term :: TerminalHandle
24+ }
25+
26+-- Base this off the xterm terminfo database regardless of what the TERM environment variable is.
27+terminal_instance :: IO Term
28+terminal_instance = do
29+ t <- TerminfoBased.terminal_instance "xterm" >>= new_terminal_handle
30+ return $ Term t
31+
32+flushed_put :: String -> IO ()
33+flushed_put str = do
34+ hPutStr stdout str
35+ hFlush stdout
36+
37+instance Terminal Term where
38+ terminal_ID t = "Terminal.app :: MacOSX"
39+
40+ release_terminal t = do
41+ release_terminal $ super_term t
42+
43+ reserve_display t = reserve_display (super_term t)
44+
45+ release_display t = release_display (super_term t)
46+
47+ display_terminal_instance t b c = do
48+ d <- display_context (super_term t) b
49+ return $ c (DisplayContext d)
50+
51+ display_bounds t = display_bounds (super_term t)
52+
53+ output_byte_buffer t = output_byte_buffer (super_term t)
54+
55+data DisplayContext = DisplayContext
56+ { super_display :: DisplayHandle
57+ }
58+
59+instance DisplayTerminal DisplayContext where
60+ context_region d = context_region (super_display d)
61+
62+ move_cursor_required_bytes d = move_cursor_required_bytes (super_display d)
63+ serialize_move_cursor d = serialize_move_cursor (super_display d)
64+
65+ show_cursor_required_bytes d = show_cursor_required_bytes (super_display d)
66+ serialize_show_cursor d = serialize_show_cursor (super_display d)
67+
68+ hide_cursor_required_bytes d = hide_cursor_required_bytes (super_display d)
69+ serialize_hide_cursor d = serialize_hide_cursor (super_display d)
70+
71+ attr_required_bytes d = attr_required_bytes (super_display d)
72+ serialize_set_attr d = serialize_set_attr (super_display d)
73+
74+ default_attr_required_bytes d = default_attr_required_bytes (super_display d)
75+ serialize_default_attr d = serialize_default_attr (super_display d)
76+
src/Graphics/Vty/Terminal/TerminfoBased.hsnew file+200
@@ -0,0 +1,200 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# LANGUAGE ForeignFunctionInterface #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE MagicHash #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE TypeFamilies #-}
7+module Graphics.Vty.Terminal.TerminfoBased ( terminal_instance
8+ )
9+ where
10+
11+import Data.Terminfo.Parse
12+import Data.Terminfo.Eval
13+
14+import Graphics.Vty.Attributes
15+import Graphics.Vty.Terminal.Generic
16+import Graphics.Vty.DisplayRegion
17+
18+import Data.Word
19+
20+import Foreign.C.Types ( CLong )
21+
22+import qualified System.Console.Terminfo as Terminfo
23+import System.IO
24+
25+data Term = Term
26+ { term_info_ID :: String
27+ , smcup :: CapExpression
28+ , rmcup :: CapExpression
29+ , cup :: CapExpression
30+ , cnorm :: CapExpression
31+ , civis :: CapExpression
32+ , set_fore_color :: CapExpression
33+ , set_back_color :: CapExpression
34+ , set_default_attr :: CapExpression
35+ , set_attr :: CapExpression
36+ }
37+
38+marshall_cap_to_terminal :: Term -> (Term -> CapExpression) -> [CapParam] -> IO ()
39+marshall_cap_to_terminal t cap_selector cap_params = do
40+ marshall_to_terminal t (cap_expression_required_bytes (cap_selector t) cap_params)
41+ (serialize_cap_expression (cap_selector t) cap_params)
42+ return ()
43+
44+{- | Uses terminfo for all control codes. While this should provide the most compatible terminal
45+ - terminfo does not support some features that would increase efficiency and improve compatibility:
46+ - * determine the character encoding supported by the terminal. Should this be taken from the LANG
47+ - environment variable?
48+ - * Provide independent string capabilities for all display attributes.
49+ -
50+ -
51+ - todo: Some display attributes like underline and bold have independent string capabilities that
52+ - should be used instead of the generic "sgr" string capability.
53+ -}
54+terminal_instance :: String -> IO Term
55+terminal_instance in_ID = do
56+ term_info <- Terminfo.setupTerm in_ID
57+ let str_cap str = case Terminfo.getCapability term_info (Terminfo.tiGetStr str) of
58+ Nothing -> fail $ "Terminal does not define required capability \"" ++ str ++ "\""
59+ Just cap_str -> case parse_cap_expression cap_str of
60+ Left e -> fail $ show e
61+ Right cap -> return cap
62+ smcup_cap <- str_cap "smcup"
63+ rmcup_cap <- str_cap "rmcup"
64+ cup_cap <- str_cap "cup"
65+ cnorm_cap <- str_cap "cnorm"
66+ civis_cap <- str_cap "civis"
67+ setaf_cap <- str_cap "setaf"
68+ setab_cap <- str_cap "setab"
69+ sgr0_cap <- str_cap "sgr0"
70+ sgr_cap <- str_cap "sgr"
71+ return $ Term in_ID
72+ smcup_cap
73+ rmcup_cap
74+ cup_cap
75+ cnorm_cap
76+ civis_cap
77+ setaf_cap
78+ setab_cap
79+ sgr0_cap
80+ sgr_cap
81+
82+instance Terminal Term where
83+ terminal_ID t = term_info_ID t ++ " :: TerminfoBased"
84+
85+ release_terminal _t = do
86+ return ()
87+
88+ reserve_display t = do
89+ marshall_cap_to_terminal t smcup []
90+ marshall_cap_to_terminal t cup [0, 0]
91+ return ()
92+
93+ release_display t = do
94+ marshall_cap_to_terminal t rmcup []
95+ marshall_cap_to_terminal t cnorm []
96+ return ()
97+
98+ display_terminal_instance t b c = do
99+ return $ c (DisplayContext b t)
100+
101+ display_bounds _t = do
102+ raw_size <- get_window_size
103+ case raw_size of
104+ ( w, h ) | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show raw_size
105+ | otherwise -> return $ DisplayRegion (toEnum w) (toEnum h)
106+
107+ -- Output the byte buffer of the specified size to the terminal device.
108+ output_byte_buffer _t out_ptr out_byte_count = do
109+ hPutBuf stdout out_ptr (fromEnum out_byte_count)
110+ hFlush stdout
111+
112+foreign import ccall "gwinsz.h c_get_window_size" c_get_window_size :: IO CLong
113+
114+get_window_size :: IO (Int,Int)
115+get_window_size = do
116+ (a,b) <- (`divMod` 65536) `fmap` c_get_window_size
117+ return (fromIntegral b, fromIntegral a)
118+
119+data DisplayContext = DisplayContext
120+ { bounds :: DisplayRegion
121+ , term :: Term
122+ }
123+
124+instance DisplayTerminal DisplayContext where
125+ context_region d = bounds d
126+
127+ move_cursor_required_bytes d x y
128+ = cap_expression_required_bytes (cup $ term d) [y, x]
129+ serialize_move_cursor d x y out_ptr
130+ = serialize_cap_expression (cup $ term d) [y, x] out_ptr
131+
132+ show_cursor_required_bytes d
133+ = cap_expression_required_bytes (cnorm $ term d) []
134+ serialize_show_cursor d out_ptr
135+ = serialize_cap_expression (cnorm $ term d) [] out_ptr
136+
137+ hide_cursor_required_bytes d
138+ = cap_expression_required_bytes (civis $ term d) []
139+ serialize_hide_cursor d out_ptr
140+ = serialize_cap_expression (civis $ term d) [] out_ptr
141+
142+ -- 0. set the style attributes. This resets the fore and back color.
143+ -- 1, If a foreground color is to be set then set the foreground color
144+ -- 2. likewise with the background color
145+ --
146+ --
147+ -- todo: This process is accurate but inefficient. Optimize later.
148+ attr_required_bytes d prev_attr req_attr =
149+ let attr = fix_display_attr prev_attr req_attr
150+ in cap_expression_required_bytes ( set_attr $ term d )
151+ ( sgr_args_for_style (fixed_style attr) )
152+ + case fixed_fore_color attr of
153+ Just c -> cap_expression_required_bytes ( set_fore_color $ term d )
154+ [ ansi_color_index c ]
155+ Nothing -> 0
156+ + case fixed_back_color attr of
157+ Just c -> cap_expression_required_bytes ( set_back_color $ term d )
158+ [ ansi_color_index c ]
159+ Nothing -> 0
160+
161+ serialize_set_attr d prev_attr req_attr out_ptr = do
162+ let attr = fix_display_attr prev_attr req_attr
163+ out_ptr' <- serialize_cap_expression ( set_attr $ term d )
164+ ( sgr_args_for_style (fixed_style attr) )
165+ out_ptr
166+ out_ptr'' <- case fixed_fore_color attr of
167+ Just c -> serialize_cap_expression ( set_fore_color $ term d )
168+ [ ansi_color_index c ]
169+ out_ptr'
170+ Nothing -> return out_ptr'
171+ out_ptr''' <- case fixed_back_color attr of
172+ Just c -> serialize_cap_expression ( set_back_color $ term d )
173+ [ ansi_color_index c ]
174+ out_ptr''
175+ Nothing -> return out_ptr''
176+ return out_ptr'''
177+
178+ default_attr_required_bytes d
179+ = cap_expression_required_bytes (set_default_attr $ term d) []
180+ serialize_default_attr d out_ptr = do
181+ out_ptr' <- serialize_cap_expression (set_default_attr $ term d) [] out_ptr
182+ return out_ptr'
183+
184+ansi_color_index :: Color -> Word
185+ansi_color_index (ISOColor v) = toEnum $ fromEnum v
186+ansi_color_index (Color240 v) = 16 + ( toEnum $ fromEnum v )
187+
188+sgr_args_for_style :: Style -> [CapParam]
189+sgr_args_for_style s = map ( \b -> if b then 1 else 0 )
190+ [ s `has_style` standout
191+ , s `has_style` underline
192+ , s `has_style` reverse_video
193+ , s `has_style` blink
194+ , s `has_style` dim
195+ , s `has_style` bold
196+ , False -- invis
197+ , False -- protect
198+ , False -- alt char set
199+ ]
200+
src/Graphics/Vty/Terminal/XTermColor.hsnew file+79
@@ -0,0 +1,79 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# LANGUAGE ForeignFunctionInterface #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE MagicHash #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE TypeFamilies #-}
7+module Graphics.Vty.Terminal.XTermColor ( terminal_instance
8+ )
9+ where
10+
11+import Graphics.Vty.Terminal.Generic
12+import qualified Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
13+
14+import System.IO
15+
16+data XTermColor = XTermColor
17+ { xterm_variant :: String
18+ , super_term :: TerminalHandle
19+ }
20+
21+-- Initialize the display to UTF-8
22+-- Regardless of what is output the text encoding is assumed to be UTF-8
23+terminal_instance :: String -> IO XTermColor
24+terminal_instance variant = do
25+ flushed_put set_utf8_char_set
26+ t <- TerminfoBased.terminal_instance variant >>= new_terminal_handle
27+ return $ XTermColor variant t
28+
29+flushed_put :: String -> IO ()
30+flushed_put str = do
31+ hPutStr stdout str
32+ hFlush stdout
33+
34+-- Since I don't know of a terminfo string cap that produces these strings these are hardcoded.
35+set_utf8_char_set, set_default_char_set :: String
36+set_utf8_char_set = "\ESC%G"
37+set_default_char_set = "\ESC%@"
38+
39+instance Terminal XTermColor where
40+ terminal_ID t = (show $ xterm_variant t) ++ " :: XTermColor"
41+
42+ release_terminal t = do
43+ flushed_put set_default_char_set
44+ release_terminal $ super_term t
45+
46+ reserve_display t = reserve_display (super_term t)
47+
48+ release_display t = release_display (super_term t)
49+
50+ display_terminal_instance t b c = do
51+ d <- display_context (super_term t) b
52+ return $ c (DisplayContext d)
53+
54+ display_bounds t = display_bounds (super_term t)
55+
56+ output_byte_buffer t = output_byte_buffer (super_term t)
57+
58+data DisplayContext = DisplayContext
59+ { super_display :: DisplayHandle
60+ }
61+
62+instance DisplayTerminal DisplayContext where
63+ context_region d = context_region (super_display d)
64+
65+ move_cursor_required_bytes d = move_cursor_required_bytes (super_display d)
66+ serialize_move_cursor d = serialize_move_cursor (super_display d)
67+
68+ show_cursor_required_bytes d = show_cursor_required_bytes (super_display d)
69+ serialize_show_cursor d = serialize_show_cursor (super_display d)
70+
71+ hide_cursor_required_bytes d = hide_cursor_required_bytes (super_display d)
72+ serialize_hide_cursor d = serialize_hide_cursor (super_display d)
73+
74+ attr_required_bytes d = attr_required_bytes (super_display d)
75+ serialize_set_attr d = serialize_set_attr (super_display d)
76+
77+ default_attr_required_bytes d = default_attr_required_bytes (super_display d)
78+ serialize_default_attr d = serialize_default_attr (super_display d)
79+
test/Bench.hsnew file+60
@@ -0,0 +1,60 @@
1+import Graphics.Vty
2+
3+import Control.Concurrent( threadDelay )
4+import Control.Monad( liftM2 )
5+
6+import Data.List
7+import Data.Word
8+
9+import System.Environment( getArgs )
10+import System.IO
11+import System.Random
12+
13+
14+main = do
15+ let fixed_gen = mkStdGen 0
16+ setStdGen fixed_gen
17+ args <- getArgs
18+ case args of
19+ [] -> mkVty >>= liftM2 (>>) (run $ Just 0) shutdown
20+ ["--delay"] -> mkVty >>= liftM2 (>>) (run $ Just 1000000) shutdown
21+ ["--slow"] -> mkVty >>= liftM2 (>>) (run Nothing ) shutdown
22+ _ -> fail "usage: ./Bench [--slow|--delay]"
23+
24+run (Just delay) vt = mapM_ (\p -> update vt p >> threadDelay delay) . benchgen =<< display_bounds (terminal vt)
25+run Nothing vt = mapM_ (update vt) (benchgen $ DisplayRegion 200 100)
26+
27+-- Currently, we just do scrolling.
28+takem :: (a -> Word) -> Word -> [a] -> ([a],[a])
29+takem len n [] = ([],[])
30+takem len n (x:xs) | lx > n = ([], x:xs)
31+ | True = let (tk,dp) = takem len (n - lx) xs in (x:tk,dp)
32+ where lx = len x
33+
34+fold :: (a -> Word) -> [Word] -> [a] -> [[a]]
35+fold len [] xs = []
36+fold len (ll:lls) xs = let (tk,dp) = takem len ll xs in tk : fold len lls dp
37+
38+lengths :: Word -> StdGen -> [Word]
39+lengths ml g =
40+ let (x,g2) = randomR (0,fromEnum ml) g
41+ (y,g3) = randomR (0,x) g2
42+ in (toEnum y) : lengths ml g3
43+
44+nums :: StdGen -> [(Attr, String)]
45+nums g = let (x,g2) = (random g :: (Int, StdGen))
46+ (c,g3) = random g2
47+ in ( if c then def_attr `with_fore_color` red else def_attr
48+ , shows x " "
49+ ) : nums g3
50+
51+pad :: Word -> Image -> Image
52+pad ml img = img <|> char_fill def_attr ' ' (ml - image_width img) 1
53+
54+clines :: StdGen -> Word -> [Image]
55+clines g maxll = map (pad maxll . horiz_cat . map (uncurry string)) $ fold (toEnum . length . snd) (lengths maxll g1) (nums g2)
56+ where (g1,g2) = split g
57+
58+benchgen :: DisplayRegion -> [Picture]
59+benchgen (DisplayRegion w h) = take 500 $ map ((\i -> pic_for_image i) . vert_cat . take (fromEnum h)) $ tails $ clines (mkStdGen 42) w
60+
test/Bench2.hsnew file+30
@@ -0,0 +1,30 @@
1+{-# LANGUAGE BangPatterns #-}
2+import Graphics.Vty
3+import System.IO
4+import System.Environment( getArgs )
5+import Control.Concurrent( threadDelay )
6+import System.Random
7+import Data.List
8+import Control.Monad( liftM2 )
9+
10+import qualified Data.ByteString.Char8 as B
11+
12+main = do
13+ let fixed_gen = mkStdGen 0
14+ setStdGen fixed_gen
15+ args <- getArgs
16+ vt <- mkVty
17+ (w, h) <- getSize vt
18+ let !image_0 = renderFill attr 'X' w h
19+ let !image_1 = renderFill attr '0' w h
20+ run vt image_0 image_1
21+ shutdown vt
22+
23+run vt image_0 image_1 = run' vt 300 image_0 image_1
24+
25+run' vt 0 image_0 image_1 = return ()
26+run' vt n image_0 image_1 = do
27+ let p = pic { pImage = image_0 }
28+ update vt p
29+ run' vt (n - 1) image_1 image_0
30+
test/BenchRenderChar.hsnew file+32
@@ -0,0 +1,32 @@
1+{- benchmarks composing images using the renderChar operation.
2+ - This is what Yi uses in Yi.UI.Vty.drawText. Ideally a sequence of renderChar images horizontally
3+ - composed should provide no worse performance than a fill render op.
4+ -}
5+import Graphics.Vty
6+
7+import Control.Monad ( forM_ )
8+
9+import System.Time
10+
11+main = do
12+ vty <- mkVty
13+ (w, h) <- getSize vty
14+ let test_chars = take 500 $ cycle $ [ c | c <- ['a'..'z']]
15+ start_time_0 <- getClockTime
16+ forM_ test_chars $ \test_char -> do
17+ let test_image = test_image_using_renderChar test_char w h
18+ out_pic = pic { pImage = test_image }
19+ update vty out_pic
20+ end_time_0 <- getClockTime
21+ let start_time_1 = end_time_0
22+ forM_ test_chars $ \test_char -> do
23+ let test_image = renderFill attr test_char w h
24+ out_pic = pic { pImage = test_image }
25+ update vty out_pic
26+ end_time_1 <- getClockTime
27+ shutdown vty
28+ putStrLn $ timeDiffToString $ diffClockTimes end_time_0 start_time_0
29+ putStrLn $ timeDiffToString $ diffClockTimes end_time_1 start_time_1
30+
31+test_image_using_renderChar c w h = vertcat $ replicate h $ horzcat $ map (renderChar attr) (replicate w c)
32+
test/ControlTable.hsnew file+14
@@ -0,0 +1,14 @@
1+module Main where
2+
3+import Graphics.Vty.ControlStrings
4+
5+import System.Console.Terminfo
6+
7+main = do
8+ terminal <- setupTermFromEnv
9+ control_table <- init_control_table terminal
10+ putStrLn $ "ANSI terminal show cursor string: " ++ show cvis
11+ putStrLn $ "Current terminal show cursor string: " ++ show (show_cursor_str control_table)
12+ putStrLn $ "ANSI terminal hide cursor string: " ++ show civis
13+ putStrLn $ "Current terminal hide cursor string: " ++ show (hide_cursor_str control_table)
14+
test/HereDoc.hsnew file+14
@@ -0,0 +1,14 @@
1+{-# LANGUAGE QuasiQuotes #-}
2+{-# LANGUAGE TemplateHaskell #-}
3+{- From http://www.reddit.com/r/haskell/comments/8ereh/a_here_document_syntax/
4+ - copyright unknown?
5+ -}
6+module HereDoc (heredoc) where
7+
8+import Language.Haskell.TH.Quote
9+import Language.Haskell.TH.Syntax
10+import Language.Haskell.TH.Lib
11+
12+heredoc :: QuasiQuoter
13+heredoc = QuasiQuoter (litE . stringL) (litP . stringL)
14+
test/Makefilenew file+44
@@ -0,0 +1,44 @@
1+VERIF_TESTS := \
2+verify_attribute_ops \
3+verify_control_strings \
4+verify_utf8_width \
5+verify_image_ops \
6+verify_image_trans \
7+verify_picture_ops \
8+verify_span_ops \
9+verify_debug_terminal
10+
11+TESTS :=\
12+Bench \
13+Bench2 \
14+BenchRenderChar \
15+Test \
16+Test2 \
17+yi_issue_264 \
18+vty_issue_18 \
19+$(VERIF_TESTS)
20+
21+# TODO: Tests should also be buildable referencing the currently installed vty
22+GHC_ARGS=--make -i../src -hide-package mtl -hide-package monads-fd -package QuickCheck-2.1.0.1 -ignore-package vty ../cbits/gwinsz.c ../cbits/set_term_timing.c ../cbits/mk_wcwidth.c -O -funbox-strict-fields -Wall -threaded -fno-full-laziness -fspec-constr -odir objects -hidir objects
23+
24+GHC_PROF_ARGS=-prof -auto-all $(GHC_ARGS)
25+
26+SOURCE := $(shell find ../src ../cbits -name '*.hs' -print -or -name '*.c' -print -or -name '*.h' -print)
27+
28+.PHONY: all
29+all : $(VERIF_TESTS)
30+
31+.PHONY: $(TESTS)
32+.SECONDEXPANSION:
33+$(TESTS) :
34+ @echo running test $@
35+ @mkdir -p results/$@
36+ ( ghc $(GHC_PROF_ARGS) $@ \
37+ && time --output results/$@/time_report ./$@ +RTS -p -sresults/$@/mem_report \
38+ && cp $@.prof results/$@/profile \
39+ )
40+
41+.PHONY: interactive_terminal_test
42+interactive_terminal_test :
43+ ghc $(GHC_ARGS) $@ && ./$@
44+
test/Test.hsnew file+42
@@ -0,0 +1,42 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+import Graphics.Vty
3+
4+import qualified Data.ByteString as B
5+import Data.Word
6+
7+import System.IO
8+
9+
10+main = do
11+ vt <- mkVty
12+ DisplayRegion w h <- display_bounds $ terminal vt
13+ putStrLn $ show $ DisplayRegion w h
14+ play vt 0 1 w h ""
15+
16+pieceA = def_attr `with_fore_color` red
17+dumpA = def_attr `with_style` reverse_video
18+
19+play :: Vty -> Word -> Word -> Word -> Word -> String -> IO ()
20+play vt x y sx sy btl = do update vt (current_pic x y sx sy btl)
21+ k <- next_event vt
22+ case k of EvKey (KASCII 'r') [MCtrl] -> refresh vt >> play vt x y sx sy btl
23+ EvKey KLeft [] | x /= 0 -> play vt (x-1) y sx sy btl
24+ EvKey KRight [] | x /= (sx-1) -> play vt (x+1) y sx sy btl
25+ EvKey KUp [] | y /= 1 -> play vt x (y-1) sx sy btl
26+ EvKey KDown [] | y /= (sy-2) -> play vt x (y+1) sx sy btl
27+ EvKey KEsc [] -> shutdown vt >> return ()
28+ EvResize nx ny -> play vt (min x (toEnum nx - 1))
29+ (min y (toEnum ny - 2))
30+ (toEnum nx)
31+ (toEnum ny)
32+ btl
33+ _ -> play vt x y sx sy (take (fromEnum sx) (show k ++ btl))
34+
35+
36+current_pic :: Word -> Word -> Word -> Word -> String -> Picture
37+current_pic x y sx sy btl = pic_for_image i
38+ where i = string def_attr "Move the @ character around with the arrow keys. Escape exits."
39+ <-> char_fill pieceA ' ' sx (y - 1)
40+ <-> char_fill pieceA ' ' x 1 <|> char pieceA '@' <|> char_fill pieceA ' ' (sx - x - 1) 1
41+ <-> char_fill pieceA ' ' sx (sy - y - 2)
42+ <-> iso_10464_string dumpA btl
test/Test2.hsnew file+12
@@ -0,0 +1,12 @@
1+module Main where
2+import Graphics.Vty
3+
4+main = do
5+ vty <- mkVty
6+ (sx,sy) <- getSize vty
7+ update vty (pic { pImage = renderFill (setBG red attr) 'X' sx sy })
8+ refresh vty
9+ shutdown vty
10+ putStrLn "Done!"
11+ return ()
12+
test/Verify.hsnew file+75
@@ -0,0 +1,75 @@
1+{-# LANGUAGE DisambiguateRecordFields #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+module Verify ( module Verify
4+ , module Test.QuickCheck
5+ , succeeded
6+ , failed
7+ , Result(..)
8+ , monadicIO
9+ , liftIO
10+ , liftIOResult
11+ , liftResult
12+ , liftBool
13+ )
14+ where
15+
16+import Test.QuickCheck hiding ( Result(..) )
17+import qualified Test.QuickCheck as QC
18+import Test.QuickCheck.Property
19+import Test.QuickCheck.Monadic ( monadicIO )
20+
21+import qualified Codec.Binary.UTF8.String as UTF8
22+
23+import Control.Monad.State.Strict
24+import Control.Monad.Trans ( liftIO )
25+
26+import Data.IORef
27+import Data.Word
28+
29+import Numeric ( showHex )
30+
31+import System.IO
32+
33+type Test v = StateT TestState IO v
34+
35+data TestState = TestState
36+ { results_ref :: IORef [QC.Result]
37+ }
38+
39+run_test :: Test () -> IO ()
40+run_test t = do
41+ s <- newIORef [] >>= return . TestState
42+ s' <- runStateT t s >>= return . snd
43+ results <- readIORef $ results_ref s'
44+ let fail_results = [ fail_result | fail_result@(QC.Failure {}) <- results ]
45+ case fail_results of
46+ [] -> putStrLn "PASS"
47+ _ -> putStrLn "FAIL"
48+
49+verify :: Testable prop => String -> prop -> Test QC.Result
50+verify prop_name prop = do
51+ liftIO $ putStrLn $ "verify " ++ prop_name
52+ get >>= \s -> do
53+ r <- liftIO $ quickCheckResult prop
54+ liftIO $ modifyIORef (results_ref s) (\rs -> r : rs)
55+ return r
56+
57+data SingleColumnChar = SingleColumnChar Char
58+ deriving (Show, Eq)
59+
60+instance Arbitrary SingleColumnChar where
61+ arbitrary = elements $ map SingleColumnChar [toEnum 0x21 .. toEnum 0x7E]
62+
63+data DoubleColumnChar = DoubleColumnChar Char
64+ deriving (Eq)
65+
66+instance Show DoubleColumnChar where
67+ show (DoubleColumnChar c) = "(0x" ++ showHex (fromEnum c) "" ++ ") ->" ++ UTF8.encodeString [c]
68+
69+instance Arbitrary DoubleColumnChar where
70+ arbitrary = elements $ map DoubleColumnChar $
71+ [ toEnum 0x3040 .. toEnum 0x3098 ]
72+ ++ [ toEnum 0x309B .. toEnum 0xA4CF]
73+
74+instance Arbitrary Word where
75+ arbitrary = choose (0, 1024) >>= return . toEnum
test/Verify/Data/Terminfo/Parse.hsnew file+114
@@ -0,0 +1,114 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+module Verify.Data.Terminfo.Parse ( module Verify.Data.Terminfo.Parse
3+ , module Data.Terminfo.Parse
4+ )
5+ where
6+
7+import Data.Terminfo.Parse
8+import Data.Terminfo.Eval
9+import Verify
10+
11+import Data.Array.Unboxed
12+import Data.Word
13+
14+import Numeric
15+
16+instance Show CapExpression where
17+ show c
18+ = "CapExpression { " ++ show (cap_ops c) ++ " }"
19+ ++ " <- [" ++ hex_dump (cap_bytes c) ++ "]"
20+ ++ " <= " ++ show (source_string c)
21+
22+hex_dump :: CapBytes -> String
23+hex_dump bytes = foldr (\b s -> showHex b s) "" $ elems bytes
24+
25+data NonParamCapString = NonParamCapString String
26+ deriving Show
27+
28+instance Arbitrary NonParamCapString where
29+ arbitrary
30+ = ( do
31+ s <- listOf1 $ (choose (0, 255) >>= return . toEnum) `suchThat` (/= '%')
32+ return $ NonParamCapString s
33+ ) `suchThat` ( \(NonParamCapString str) -> length str < 255 )
34+
35+data LiteralPercentCap = LiteralPercentCap String [Word8]
36+ deriving ( Show )
37+
38+instance Arbitrary LiteralPercentCap where
39+ arbitrary
40+ = ( do
41+ NonParamCapString s <- arbitrary
42+ (s', bytes) <- insert_escape_op "%" [toEnum $ fromEnum '%'] s
43+ return $ LiteralPercentCap s' bytes
44+ ) `suchThat` ( \(LiteralPercentCap str _) -> length str < 255 )
45+
46+data IncFirstTwoCap = IncFirstTwoCap String [Word8]
47+ deriving ( Show )
48+
49+instance Arbitrary IncFirstTwoCap where
50+ arbitrary
51+ = ( do
52+ NonParamCapString s <- arbitrary
53+ (s', bytes) <- insert_escape_op "i" [] s
54+ return $ IncFirstTwoCap s' bytes
55+ ) `suchThat` ( \(IncFirstTwoCap str _) -> length str < 255 )
56+
57+data PushParamCap = PushParamCap String Word [Word8]
58+ deriving ( Show )
59+
60+instance Arbitrary PushParamCap where
61+ arbitrary
62+ = ( do
63+ NonParamCapString s <- arbitrary
64+ n :: Word <- choose (1,9) >>= return . toEnum
65+ (s', bytes) <- insert_escape_op ("p" ++ show n) [] s
66+ return $ PushParamCap s' n bytes
67+ ) `suchThat` ( \(PushParamCap str _ _) -> length str < 255 )
68+
69+data DecPrintCap = DecPrintCap String Word [Word8]
70+ deriving ( Show )
71+
72+instance Arbitrary DecPrintCap where
73+ arbitrary
74+ = ( do
75+ NonParamCapString s <- arbitrary
76+ n :: Word <- choose (1,9) >>= return . toEnum
77+ (s', bytes) <- insert_escape_op ("p" ++ show n ++ "%d") [] s
78+ return $ DecPrintCap s' n bytes
79+ ) `suchThat` ( \(DecPrintCap str _ _) -> length str < 255 )
80+
81+insert_escape_op op_str repl_bytes s = do
82+ insert_points <- listOf1 $ elements [0 .. length s - 1]
83+ let s' = f s ('%' : op_str)
84+ remaining_bytes = f (map (toEnum . fromEnum) s) repl_bytes
85+ f in_vs out_v = concat [ vs
86+ | vi <- zip in_vs [0 .. length s - 1]
87+ , let vs = fst vi : ( if snd vi `elem` insert_points
88+ then out_v
89+ else []
90+ )
91+ ]
92+ return (s', remaining_bytes)
93+
94+is_bytes_op :: CapOp -> Bool
95+is_bytes_op (Bytes {}) = True
96+-- is_bytes_op _ = False
97+
98+collect_bytes :: CapExpression -> [Word8]
99+collect_bytes e = concat [ bytes
100+ | Bytes byte_range <- cap_ops e
101+ , let bytes = bytes_for_range e byte_range
102+ ]
103+
104+verify_bytes_equal out_bytes expected_bytes
105+ = if out_bytes == expected_bytes
106+ then liftResult succeeded
107+ else liftResult $ failed
108+ { reason = "out_bytes ["
109+ ++ hex_dump ( listArray (0, toEnum $ length out_bytes - 1) out_bytes )
110+ ++ "] /= expected_bytes ["
111+ ++ hex_dump ( listArray (0, toEnum $ length expected_bytes - 1) expected_bytes )
112+ ++ "]"
113+ }
114+
test/Verify/Graphics/Vty/Attributes.hsnew file+40
@@ -0,0 +1,40 @@
1+module Verify.Graphics.Vty.Attributes ( module Verify.Graphics.Vty.Attributes
2+ , module Graphics.Vty.Attributes
3+ )
4+ where
5+
6+import Graphics.Vty.Attributes
7+import Verify
8+
9+import Data.List ( delete )
10+
11+-- Limit the possible attributes to just a few for now.
12+possible_attr_mods :: [ AttrOp ]
13+possible_attr_mods =
14+ [ set_bold_op
15+ , id_op
16+ ]
17+
18+instance Arbitrary Attr where
19+ arbitrary = elements possible_attr_mods >>= return . flip apply_op def_attr
20+
21+data DiffAttr = DiffAttr Attr Attr
22+
23+instance Arbitrary DiffAttr where
24+ arbitrary = do
25+ op0 <- elements possible_attr_mods
26+ let possible_attr_mods' = delete op0 possible_attr_mods
27+ op1 <- elements possible_attr_mods'
28+ return $ DiffAttr (apply_op op0 def_attr) (apply_op op1 def_attr)
29+
30+data AttrOp = AttrOp String (Attr -> Attr)
31+
32+instance Eq AttrOp where
33+ AttrOp n0 _ == AttrOp n1 _ = n0 == n1
34+
35+set_bold_op = AttrOp "set_bold" (flip with_style bold)
36+id_op = AttrOp "id" id
37+
38+apply_op :: AttrOp -> Attr -> Attr
39+apply_op (AttrOp _ f) a = f a
40+
test/Verify/Graphics/Vty/Image.hsnew file+69
@@ -0,0 +1,69 @@
1+module Verify.Graphics.Vty.Image ( module Verify.Graphics.Vty.Image
2+ , module Graphics.Vty.Image
3+ )
4+ where
5+
6+import Graphics.Vty.Debug
7+import Graphics.Vty.Image
8+import Graphics.Vty.WinRegion
9+
10+import Verify
11+import Verify.Graphics.Vty.Attributes
12+
13+import Data.Word
14+
15+data UnitImage = UnitImage Char Image
16+
17+instance Arbitrary UnitImage where
18+ arbitrary = do
19+ SingleColumnChar c <- arbitrary
20+ return $ UnitImage c (char def_attr c)
21+
22+instance Show UnitImage where
23+ show (UnitImage c _) = "UnitImage " ++ show c
24+
25+data DefaultImage = DefaultImage Image ImageConstructLog
26+
27+instance Show DefaultImage where
28+ show (DefaultImage i image_log)
29+ = "DefaultImage (" ++ show i ++ ") " ++ show (image_width i, image_height i) ++ " " ++ show image_log
30+
31+instance Arbitrary DefaultImage where
32+ arbitrary = do
33+ i <- return $ char def_attr 'X' -- elements forward_image_ops >>= return . (\op -> op empty_image)
34+ return $ DefaultImage i []
35+
36+data SingleRowSingleAttrImage = SingleRowSingleAttrImage Attr Word Image
37+
38+instance Show SingleRowSingleAttrImage where
39+ show (SingleRowSingleAttrImage attr columns image)
40+ = "SingleRowSingleAttrImage (" ++ show attr ++ ") " ++ show columns ++ " ( " ++ show image ++ " )"
41+
42+instance Arbitrary SingleRowSingleAttrImage where
43+ arbitrary = do
44+ single_column_row_text <- arbitrary
45+ attr <- arbitrary
46+ return $ SingleRowSingleAttrImage
47+ attr
48+ ( fromIntegral $ length single_column_row_text )
49+ ( horiz_cat $ [ char attr c | SingleColumnChar c <- single_column_row_text ] )
50+
51+
52+data SingleAttrSingleSpanStack = SingleAttrSingleSpanStack
53+ { stack_image :: Image
54+ , stack_source_images :: [SingleRowSingleAttrImage]
55+ , stack_width :: Word
56+ , stack_height :: Word
57+ }
58+ deriving Show
59+
60+instance Arbitrary SingleAttrSingleSpanStack where
61+ arbitrary = do
62+ NonEmpty image_list <- arbitrary
63+ let image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- image_list ]
64+ return $ SingleAttrSingleSpanStack
65+ image
66+ image_list
67+ ( maximum $ map expected_columns image_list )
68+ ( toEnum $ length image_list )
69+
test/Verify/Graphics/Vty/Picture.hsnew file+50
@@ -0,0 +1,50 @@
1+{-# LANGUAGE DisambiguateRecordFields #-}
2+module Verify.Graphics.Vty.Picture ( module Verify.Graphics.Vty.Picture
3+ , module Graphics.Vty.Picture
4+ )
5+ where
6+
7+import Graphics.Vty.Picture
8+import Graphics.Vty.Debug
9+
10+import Verify.Graphics.Vty.Attributes
11+import Verify.Graphics.Vty.Image
12+import Verify.Graphics.Vty.WinRegion
13+
14+import Verify
15+
16+data DefaultPic = DefaultPic
17+ { default_pic :: Picture
18+ , default_win :: DebugWindow
19+ , default_construct_log :: ImageConstructLog
20+ }
21+
22+instance Show DefaultPic where
23+ show (DefaultPic pic win image_log)
24+ = "DefaultPic\n\t( " ++ show pic ++ ")\n\t" ++ show win ++ "\n\t" ++ show image_log ++ "\n"
25+
26+instance Arbitrary DefaultPic where
27+ arbitrary = do
28+ DefaultImage image image_construct_events <- arbitrary
29+ let win = DebugWindow (image_width image) (image_height image)
30+ return $ DefaultPic (pic_for_image image)
31+ win
32+ image_construct_events
33+
34+data PicWithBGAttr = PicWithBGAttr
35+ { with_attr_pic :: Picture
36+ , with_attr_win :: DebugWindow
37+ , with_attr_construct_log :: ImageConstructLog
38+ , with_attr_specified_attr :: Attr
39+ } deriving ( Show )
40+
41+instance Arbitrary PicWithBGAttr where
42+ arbitrary = do
43+ DefaultImage image image_construct_events <- arbitrary
44+ let win = DebugWindow (image_width image) (image_height image)
45+ attr <- arbitrary
46+ return $ PicWithBGAttr (pic_for_image image)
47+ win
48+ image_construct_events
49+ attr
50+
test/Verify/Graphics/Vty/Span.hsnew file+29
@@ -0,0 +1,29 @@
1+{-# LANGUAGE FlexibleInstances #-}
2+module Verify.Graphics.Vty.Span ( module Verify.Graphics.Vty.Span
3+ , module Graphics.Vty.Span
4+ )
5+ where
6+
7+import Graphics.Vty.Debug
8+import Graphics.Vty.Span
9+
10+import Verify.Graphics.Vty.Picture
11+
12+import Data.Word
13+
14+import Verify
15+
16+is_attr_span_op :: SpanOp -> Bool
17+is_attr_span_op AttributeChange {} = True
18+is_attr_span_op _ = False
19+
20+verify_all_spans_have_width i spans w
21+ = case all_spans_have_width spans w of
22+ True -> succeeded
23+ False -> failed { reason = "Not all spans contained operations defining exactly "
24+ ++ show w
25+ ++ " columns of output -\n"
26+ ++ show i
27+ ++ "\n->\n"
28+ ++ show spans
29+ }
test/Verify/Graphics/Vty/WinRegion.hsnew file+27
@@ -0,0 +1,27 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+module Verify.Graphics.Vty.WinRegion ( module Verify.Graphics.Vty.WinRegion
3+ , module Graphics.Vty.WinRegion
4+ )
5+ where
6+
7+import Graphics.Vty.Debug
8+import Graphics.Vty.WinRegion
9+
10+import Verify
11+
12+import Data.Word
13+
14+data EmptyWindow = EmptyWindow DebugWindow
15+
16+instance Arbitrary EmptyWindow where
17+ arbitrary = return $ EmptyWindow (DebugWindow (0 :: Word) (0 :: Word))
18+
19+instance Show EmptyWindow where
20+ show (EmptyWindow _) = "EmptyWindow"
21+
22+instance Arbitrary DebugWindow where
23+ arbitrary = do
24+ w <- arbitrary
25+ h <- arbitrary
26+ return $ DebugWindow w h
27+
test/interactive_terminal_test.hsnew file+847
@@ -0,0 +1,847 @@
1+{-# LANGUAGE QuasiQuotes #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+module Main where
4+
5+import Graphics.Vty.Attributes
6+import Graphics.Vty.Image
7+import Graphics.Vty.Picture
8+import Graphics.Vty.Terminal
9+import Graphics.Vty.WinRegion
10+
11+import Control.Monad
12+
13+import Data.List ( lookup )
14+import Data.Maybe ( isJust, fromJust )
15+import Data.Monoid
16+import Data.Word
17+
18+import Foreign.Marshal.Array
19+import HereDoc
20+
21+import qualified System.Environment as Env
22+
23+import System.IO ( hFlush, hPutStr, hPutBuf, stdout )
24+
25+main = do
26+ print_intro
27+
28+output_file_path = "test_results.list"
29+
30+print_intro = do
31+ putStr $ [$heredoc|
32+This is an interactive verification program for the terminal input and output
33+support of the VTY library. This will ask a series of questions about what you
34+see onscreen. The goal is to verify that VTY's output and input support
35+performs as expected with your terminal.
36+
37+This program produces a file named
38+ |] ++ output_file_path ++ [$heredoc|
39+in the current directory that contains the results for each test assertion. This
40+can be emailed to coreyoconnor@gmail.com and used by the VTY authors to improve
41+support for your terminal. No personal information is contained in the report.
42+
43+Each test follows, more or less, the following format:
44+ 0. A description of the test is printed which will include a detailed
45+ description of what VTY is going to try and what the expected results are.
46+ Press return to move on.
47+ 1. The program will produce some output or ask for you to press a key.
48+ 2. You will then be asked to confirm if the behavior matched the provided
49+ description. Just pressing enter implies the default response that
50+ everything was as expected.
51+
52+All the tests assume the following about the terminal display:
53+ 0. The terminal display will not be resized during a test and is at least 80
54+ characters in width.
55+ 1. The terminal display is using a monospaced font for both single width and
56+ double width characters.
57+ 2. A double width character is displayed with exactly twice the width of a
58+ single column character. This may require adjusting the font used by the
59+ terminal. At least, that is the case using xterm.
60+ 3. Fonts are installed, and usable by the terminal, that define glyphs for
61+ a good range of the unicode characters. Each test involving unicode display
62+ describes the expected appearance of each glyph.
63+
64+Thanks for the help! :-D
65+|]
66+ wait_for_return
67+ results <- do_test_menu 1
68+ env_attributes <- mapM ( \env_name -> catch ( Env.getEnv env_name >>= return . (,) env_name )
69+ ( const $ return (env_name, "") )
70+ )
71+ [ "TERM", "COLORTERM", "LANG" ]
72+ let results_txt = show env_attributes ++ "\n" ++ show results ++ "\n"
73+ writeFile output_file_path results_txt
74+
75+wait_for_return = do
76+ putStr "\n(press return to continue)"
77+ hFlush stdout
78+ getLine
79+
80+test_menu :: [(String, Test)]
81+test_menu = zip (map show [1..]) all_tests
82+
83+do_test_menu :: Int -> IO [(String, Bool)]
84+do_test_menu next_ID
85+ | next_ID > length all_tests = do
86+ putStrLn $ "Done! Please email the " ++ output_file_path ++ " file to coreyoconnor@gmail.com"
87+ return []
88+ | otherwise = do
89+ display_test_menu
90+ putStrLn $ "Press return to start with #" ++ show next_ID ++ "."
91+ putStrLn "Enter a test number to perform only that test."
92+ putStrLn "q (or control-C) to quit."
93+ putStr "> "
94+ hFlush stdout
95+ s <- getLine >>= return . filter (/= '\n')
96+ case s of
97+ "q" -> return mempty
98+ "" -> do
99+ r <- run_test $ show next_ID
100+ rs <- do_test_menu ( next_ID + 1 )
101+ return $ r : rs
102+ i | isJust ( lookup i test_menu ) -> do
103+ r <- run_test i
104+ rs <- do_test_menu ( read i + 1 )
105+ return $ r : rs
106+ where
107+ display_test_menu
108+ = mapM_ display_test_menu' test_menu
109+ display_test_menu' ( i, t )
110+ = putStrLn $ ( if i == show next_ID
111+ then "> "
112+ else " "
113+ ) ++ i ++ ". " ++ test_name t
114+
115+run_test :: String -> IO (String, Bool)
116+run_test i = do
117+ let t = fromJust $ lookup i test_menu
118+ print_summary t
119+ wait_for_return
120+ test_action t
121+ r <- confirm_results t
122+ return (test_ID t, r)
123+
124+default_success_confirm_results = do
125+ putStr "\n"
126+ putStr "[Y/n] "
127+ hFlush stdout
128+ r <- getLine
129+ case r of
130+ "" -> return True
131+ "y" -> return True
132+ "Y" -> return True
133+ "n" -> return False
134+
135+data Test = Test
136+ { test_name :: String
137+ , test_ID :: String
138+ , test_action :: IO ()
139+ , print_summary :: IO ()
140+ , confirm_results :: IO Bool
141+ }
142+
143+all_tests
144+ = [ reserve_output_test
145+ , display_bounds_test_0
146+ , display_bounds_test_1
147+ , display_bounds_test_2
148+ , display_bounds_test_3
149+ , unicode_single_width_0
150+ , unicode_single_width_1
151+ , unicode_double_width_0
152+ , unicode_double_width_1
153+ , attributes_test_0
154+ , attributes_test_1
155+ , attributes_test_2
156+ , attributes_test_3
157+ , attributes_test_4
158+ , attributes_test_5
159+ ]
160+
161+reserve_output_test = Test
162+ { test_name = "Initialize and reserve terminal output then restore previous state."
163+ , test_ID = "reserve_output_test"
164+ , test_action = do
165+ t <- terminal_handle
166+ reserve_display t
167+ putStrLn "Line 1"
168+ putStrLn "Line 2"
169+ putStrLn "Line 3"
170+ putStrLn "Line 4 (press return)"
171+ hFlush stdout
172+ getLine
173+ release_display t
174+ release_terminal t
175+ return ()
176+ , print_summary = do
177+ putStr $ [$heredoc|
178+Once return is pressed:
179+The screen will be cleared.
180+The cursor should be visible and at the top left corner.
181+Four lines of text should be visible.
182+
183+After enter is pressed for the second time this test then:
184+ * The screen containing the test summary should be restored;
185+ * The cursor is visible.
186+|]
187+ , confirm_results = do
188+ putStr $ [$heredoc|
189+Did the test output match the description?
190+|]
191+ default_success_confirm_results
192+ }
193+
194+display_bounds_test_0 = Test
195+ { test_name = "Verify display bounds are correct test 0: Using spaces."
196+ , test_ID = "display_bounds_test_0"
197+ , test_action = do
198+ t <- terminal_handle
199+ reserve_display t
200+ DisplayBounds w h <- display_bounds t
201+ let row_0 = replicate (fromEnum w) 'X' ++ "\n"
202+ row_h = replicate (fromEnum w - 1) 'X'
203+ row_n = "X" ++ replicate (fromEnum w - 2) ' ' ++ "X\n"
204+ image = row_0 ++ (concat $ replicate (fromEnum h - 2) row_n) ++ row_h
205+ putStr image
206+ hFlush stdout
207+ getLine
208+ release_display t
209+ release_terminal t
210+ return ()
211+ , print_summary = display_bounds_test_summary True
212+ , confirm_results = generic_output_match_confirm
213+ }
214+
215+display_bounds_test_1 = Test
216+ { test_name = "Verify display bounds are correct test 0: Using cursor movement."
217+ , test_ID = "display_bounds_test_1"
218+ , test_action = do
219+ t <- terminal_handle
220+ reserve_display t
221+ DisplayBounds w h <- display_bounds t
222+ set_cursor_pos t 0 0
223+ let row_0 = replicate (fromEnum w) 'X' ++ "\n"
224+ putStr row_0
225+ forM_ [1 .. h - 2] $ \y -> do
226+ set_cursor_pos t 0 y
227+ putStr "X"
228+ set_cursor_pos t (w - 1) y
229+ putStr "X"
230+ set_cursor_pos t 0 (h - 1)
231+ let row_h = replicate (fromEnum w - 1) 'X'
232+ putStr row_h
233+ hFlush stdout
234+ getLine
235+ release_display t
236+ release_terminal t
237+ return ()
238+ , print_summary = display_bounds_test_summary True
239+ , confirm_results = generic_output_match_confirm
240+ }
241+
242+display_bounds_test_2 = Test
243+ { test_name = "Verify display bounds are correct test 0: Using Image ops."
244+ , test_ID = "display_bounds_test_2"
245+ , test_action = do
246+ t <- terminal_handle
247+ reserve_display t
248+ bounds@(DisplayBounds w h) <- display_bounds t
249+ let first_row = horiz_cat $ replicate (fromEnum w) (char def_attr 'X')
250+ middle_rows = vert_cat $ replicate (fromEnum h - 2) middle_row
251+ middle_row = (char def_attr 'X') <|> background_fill (w - 2) 1 <|> (char def_attr 'X')
252+ end_row = first_row
253+ image = first_row <-> middle_rows <-> end_row
254+ pic = (pic_for_image image) { pic_cursor = Cursor (w - 1) (h - 1) }
255+ d <- display_context t bounds
256+ output_picture d pic
257+ getLine
258+ release_display t
259+ release_terminal t
260+ return ()
261+ , print_summary = display_bounds_test_summary True
262+ , confirm_results = generic_output_match_confirm
263+ }
264+
265+display_bounds_test_3 = Test
266+ { test_name = "Verify display bounds are correct test 0: Hide cursor; Set cursor pos."
267+ , test_ID = "display_bounds_test_3"
268+ , test_action = do
269+ t <- terminal_handle
270+ reserve_display t
271+ DisplayBounds w h <- display_bounds t
272+ hide_cursor t
273+ set_cursor_pos t 0 0
274+ let row_0 = replicate (fromEnum w) 'X'
275+ putStrLn row_0
276+ forM_ [1 .. h - 2] $ \y -> do
277+ set_cursor_pos t 0 y
278+ putStr "X"
279+ set_cursor_pos t (w - 1) y
280+ putStr "X"
281+ set_cursor_pos t 0 (h - 1)
282+ let row_h = row_0
283+ putStr row_h
284+ hFlush stdout
285+ getLine
286+ show_cursor t
287+ release_display t
288+ release_terminal t
289+ return ()
290+ , print_summary = display_bounds_test_summary False
291+ , confirm_results = generic_output_match_confirm
292+ }
293+
294+display_bounds_test_summary has_cursor = do
295+ putStr $ [$heredoc|
296+Once return is pressed:
297+ 0. The screen will be cleared.
298+|]
299+ if has_cursor
300+ then putStr " 1. The cursor will be visible."
301+ else putStr " 1. The cursor will NOT be visible."
302+ putStr [$heredoc|
303+ 2. The border of the display will be outlined in Xs.
304+ So if - and | represented the edge of the terminal window:
305+ |-------------|
306+ |XXXXXXXXXXXXX|
307+ |X X||]
308+
309+ if has_cursor
310+ then putStr $ [$heredoc|
311+ |XXXXXXXXXXXXC| |]
312+ else putStr $ [$heredoc|
313+ |XXXXXXXXXXXXX| |]
314+
315+ putStr $ [$heredoc|
316+ |-------------|
317+
318+ ( Where C is the final position of the cursor. There may be an X drawn
319+ under the cursor. )
320+ 3. The display will remain in this state until return is pressed again.
321+
322+After return is pressed for the second time:
323+ 0. The screen containing the test summary should be restored.
324+ 1. The cursor should be visible.
325+|]
326+
327+generic_output_match_confirm = do
328+ putStr $ [$heredoc|
329+Did the test output match the description?
330+|]
331+ default_success_confirm_results
332+
333+-- Explicitely definethe bytes that encode each example text.
334+-- This avoids any issues with how the compiler represents string literals.
335+--
336+-- This document is UTF-8 encoded so the UTF-8 string is still included for
337+-- reference
338+--
339+-- It's assumed the compiler will at least not barf on UTF-8 encoded text in
340+-- comments ;-)
341+--
342+-- txt_0 = ↑↑↓↓←→←→BA
343+
344+utf8_txt_0 :: [[Word8]]
345+utf8_txt_0 = [ [ 0xe2 , 0x86 , 0x91 ]
346+ , [ 0xe2 , 0x86 , 0x91 ]
347+ , [ 0xe2 , 0x86 , 0x93 ]
348+ , [ 0xe2 , 0x86 , 0x93 ]
349+ , [ 0xe2 , 0x86 , 0x90 ]
350+ , [ 0xe2 , 0x86 , 0x92 ]
351+ , [ 0xe2 , 0x86 , 0x90 ]
352+ , [ 0xe2 , 0x86 , 0x92 ]
353+ , [ 0x42 ]
354+ , [ 0x41 ]
355+ ]
356+
357+iso_10464_txt_0 :: String
358+iso_10464_txt_0 = map toEnum
359+ [ 8593
360+ , 8593
361+ , 8595
362+ , 8595
363+ , 8592
364+ , 8594
365+ , 8592
366+ , 8594
367+ , 66
368+ , 65
369+ ]
370+
371+unicode_single_width_0 = Test
372+ { test_name = "Verify terminal can display unicode single-width characters. (Direct UTF-8)"
373+ , test_ID = "unicode_single_width_0"
374+ , test_action = do
375+ t <- terminal_handle
376+ reserve_display t
377+ withArrayLen (concat utf8_txt_0) (flip $ hPutBuf stdout)
378+ hPutStr stdout "\n"
379+ hPutStr stdout "0123456789\n"
380+ hFlush stdout
381+ getLine
382+ release_display t
383+ release_terminal t
384+ return ()
385+ , print_summary = unicode_single_width_summary
386+ , confirm_results = generic_output_match_confirm
387+ }
388+
389+unicode_single_width_1 = Test
390+ { test_name = "Verify terminal can display unicode single-width characters. (Image ops)"
391+ , test_ID = "unicode_single_width_1"
392+ , test_action = do
393+ t <- terminal_handle
394+ reserve_display t
395+ let pic = pic_for_image image
396+ image = line_0 <-> line_1
397+ line_0 = iso_10464_string def_attr iso_10464_txt_0
398+ line_1 = string def_attr "0123456789"
399+ d <- display_bounds t >>= display_context t
400+ output_picture d pic
401+ getLine
402+ release_display t
403+ release_terminal t
404+ return ()
405+ , print_summary = unicode_single_width_summary
406+ , confirm_results = generic_output_match_confirm
407+ }
408+
409+unicode_single_width_summary = putStr [$heredoc|
410+Once return is pressed:
411+ 0. The screen will be cleared.
412+ 1. The cursor will be hidden.
413+ 2. Two horizontal lines of text will be displayed:
414+ a. The first will be a sequence of glyphs in UTF-8 encoding. Each glyph
415+ will occupy one column of space. The order and appearance of the glyphs
416+ will be:
417+ | column | appearance |
418+ ==========================
419+ | 0 | up arrow |
420+ | 1 | up arrow |
421+ | 2 | down arrow |
422+ | 3 | down arrow |
423+ | 4 | left arrow |
424+ | 5 | right arrow |
425+ | 6 | left arrow |
426+ | 7 | right arrow |
427+ | 8 | B |
428+ | 9 | A |
429+ ( see: http://en.wikipedia.org/wiki/Arrow_(symbol) )
430+ b. The second will be: 0123456789.
431+
432+Verify:
433+ * The far right extent of the glyphs on both lines are equal;
434+ * The glyphs are as described.
435+
436+After return is pressed for the second time:
437+ 0. The screen containing the test summary should be restored.
438+ 1. The cursor should be visible.
439+|]
440+
441+-- The second example is a unicode string containing double-width glyphs
442+-- 你好吗
443+utf8_txt_1 :: [[Word8]]
444+utf8_txt_1 = [ [0xe4,0xbd,0xa0]
445+ , [0xe5,0xa5,0xbd]
446+ , [0xe5,0x90,0x97]
447+ ]
448+
449+iso_10464_txt_1 :: String
450+iso_10464_txt_1 = map toEnum [20320,22909,21527]
451+
452+unicode_double_width_0 = Test
453+ { test_name = "Verify terminal can display unicode double-width characters. (Direct UTF-8)"
454+ , test_ID = "unicode_double_width_0"
455+ , test_action = do
456+ t <- terminal_handle
457+ reserve_display t
458+ withArrayLen (concat utf8_txt_1) (flip $ hPutBuf stdout)
459+ hPutStr stdout "\n"
460+ hPutStr stdout "012345\n"
461+ hFlush stdout
462+ getLine
463+ release_display t
464+ release_terminal t
465+ return ()
466+ , print_summary = unicode_double_width_summary
467+ , confirm_results = generic_output_match_confirm
468+ }
469+
470+unicode_double_width_1 = Test
471+ { test_name = "Verify terminal can display unicode double-width characters. (Image ops)"
472+ , test_ID = "unicode_double_width_1"
473+ , test_action = do
474+ t <- terminal_handle
475+ reserve_display t
476+ let pic = pic_for_image image
477+ image = line_0 <-> line_1
478+ line_0 = iso_10464_string def_attr iso_10464_txt_1
479+ line_1 = string def_attr "012345"
480+ d <- display_bounds t >>= display_context t
481+ output_picture d pic
482+ getLine
483+ release_display t
484+ release_terminal t
485+ return ()
486+ , print_summary = unicode_double_width_summary
487+ , confirm_results = generic_output_match_confirm
488+ }
489+
490+unicode_double_width_summary = putStr [$heredoc|
491+Once return is pressed:
492+ 0. The screen will be cleared.
493+ 1. The cursor will be hidden.
494+ 2. Two horizontal lines of text will be displayed:
495+ a. The first will be a sequence of glyphs in UTF-8 encoding. Each glyph
496+ will occupy two columns of space. The order and appearance of the glyphs
497+ will be:
498+ | column | appearance |
499+ ======================================
500+ | 0 | first half of ni3 |
501+ | 1 | second half of ni3 |
502+ | 2 | first half of hao3 |
503+ | 3 | second half of hao3 |
504+ | 4 | first half of ma |
505+ | 5 | second half of ma |
506+ b. The second will be: 012345.
507+
508+Verify:
509+ * The far right extent of the glyphs on both lines are equal;
510+ * The glyphs are as described.
511+
512+After return is pressed for the second time:
513+ 0. The screen containing the test summary should be restored.
514+ 1. The cursor should be visible.
515+|]
516+
517+all_colors = zip [ black, red, green, yellow, blue, magenta, cyan, white, def ]
518+ [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "default" ]
519+
520+attributes_test_0 = Test
521+ { test_name = "Character attributes: foreground colors."
522+ , test_ID = "attributes_test_0"
523+ , test_action = do
524+ t <- terminal_handle
525+ reserve_display t
526+ let pic = pic_for_image image
527+ image = border <|> column_0 <|> border <|> column_1 <|> border
528+ column_0 = vert_cat $ map line_with_color all_colors
529+ border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
530+ column_1 = vert_cat $ map (string def_attr . snd) all_colors
531+ line_with_color (c_value, c_name) = string (setFG c_value def_attr) c_name
532+ d <- display_bounds t >>= display_context t
533+ output_picture d pic
534+ getLine
535+ release_display t
536+ release_terminal t
537+ return ()
538+ , print_summary = do
539+ putStr $ [$heredoc|
540+Once return is pressed:
541+ 0. The screen will be cleared.
542+ 1. The cursor will be hidden.
543+ 2. 9 lines of text in two columns will be drawn. The first column will be a
544+ name of a standard color (for an 8 color terminal) rendered in that color.
545+ For instance, one line will be the word "magenta" and that word should be
546+ rendered in the magenta color. The second column will be the name of a
547+ standard color rendered with the default attributes.
548+
549+Verify:
550+ * In the first column: The foreground color matches the named color.
551+ * The second column: All text is rendered with the default attributes.
552+ * The vertical bars used in each line to mark the border of a column are
553+ lined up.
554+
555+After return is pressed for the second time:
556+ 0. The screen containing the test summary should be restored.
557+ 1. The cursor should be visible.
558+|]
559+ , confirm_results = do
560+ putStr $ [$heredoc|
561+Did the test output match the description?
562+|]
563+ default_success_confirm_results
564+ }
565+
566+attributes_test_1 = Test
567+ { test_name = "Character attributes: background colors."
568+ , test_ID = "attributes_test_1"
569+ , test_action = do
570+ t <- terminal_handle
571+ reserve_display t
572+ let pic = pic_for_image image
573+ image = border <|> column_0 <|> border <|> column_1 <|> border
574+ column_0 = vert_cat $ map line_with_color all_colors
575+ border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
576+ column_1 = vert_cat $ map (string def_attr . snd) all_colors
577+ line_with_color (c_value, c_name) = string (setBG c_value def_attr) c_name
578+ d <- display_bounds t >>= display_context t
579+ output_picture d pic
580+ getLine
581+ release_display t
582+ release_terminal t
583+ return ()
584+ , print_summary = do
585+ putStr $ [$heredoc|
586+Once return is pressed:
587+ 0. The screen will be cleared.
588+ 1. The cursor will be hidden.
589+ 2. 9 lines of text in two columns will be drawn. The first column will
590+ contain be a name of a standard color for an 8 color terminal rendered with
591+ the default foreground color with a background the named color. For
592+ instance, one line will contain be the word "magenta" and that words should
593+ be rendered in the default foreground color over a magenta background. The
594+ second column will be the name of a standard color rendered with the default
595+ attributes.
596+
597+Verify:
598+ * The first column: The background color matches the named color.
599+ * The second column: All text is rendered with the default attributes.
600+ * The vertical bars used in each line to mark the border of a column are
601+ lined up.
602+
603+Note: I haven't decided if, in this case, the background color should extend to
604+fills added for alignment. Right now the selected background color is only
605+applied to the background where the word is actually rendered. Since each word
606+is not of the same length VTY adds background fills to make the width of each
607+row effectively the same. These added fills are all currently rendered with the
608+default background pattern.
609+
610+After return is pressed for the second time:
611+ 0. The screen containing the test summary should be restored.
612+ 1. The cursor should be visible.
613+|]
614+ , confirm_results = do
615+ putStr $ [$heredoc|
616+Did the test output match the description?
617+|]
618+ default_success_confirm_results
619+ }
620+
621+attributes_test_2 = Test
622+ { test_name = "Character attributes: Vivid foreground colors."
623+ , test_ID = "attributes_test_2"
624+ , test_action = do
625+ t <- terminal_handle
626+ reserve_display t
627+ let pic = pic_for_image image
628+ image = horiz_cat [border, column_0, border, column_1, border, column_2, border]
629+ border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
630+ column_0 = vert_cat $ map line_with_color_0 all_colors
631+ column_1 = vert_cat $ map line_with_color_1 all_colors
632+ column_2 = vert_cat $ map (string def_attr . snd) all_colors
633+ line_with_color_0 (c_value, c_name) = string (setFG c_value def_attr) c_name
634+ line_with_color_1 (c_value, c_name) = string (setFGVivid c_value def_attr) c_name
635+ d <- display_bounds t >>= display_context t
636+ output_picture d pic
637+ getLine
638+ release_display t
639+ release_terminal t
640+ return ()
641+ , print_summary = do
642+ putStr $ [$heredoc|
643+Once return is pressed:
644+ 0. The screen will be cleared.
645+ 1. The cursor will be hidden.
646+ 2. 9 lines of text in three columns will be drawn:
647+ a. The first column will be a name of a standard color (for an 8 color
648+ terminal) rendered with that color as the foreground color.
649+ b. The next column will be also be the name of a standard color rendered
650+ with that color as the foreground color but the shade used should be
651+ more vivid than the shade used in the first column.
652+ c. The final column will be the name of a color rendered with the
653+ default attributes.
654+
655+For instance, one line will be the word "magenta" and that word should be
656+rendered in the magenta color.
657+
658+I'm not actually sure exactly what "vivid" means in this context. For xterm the
659+vivid colors are brighter.
660+
661+Verify:
662+ * The first column: The foreground color matches the named color.
663+ * The second column: The foreground color matches the named color but is
664+ more vivid than the color used in the first column.
665+ * The third column: All text is rendered with the default attributes.
666+ * The vertical bars used in each line to mark the border of a column are
667+ lined up.
668+
669+After return is pressed for the second time:
670+ 0. The screen containing the test summary should be restored.
671+ 1. The cursor should be visible.
672+|]
673+ , confirm_results = do
674+ putStr $ [$heredoc|
675+Did the test output match the description?
676+|]
677+ default_success_confirm_results
678+ }
679+
680+attributes_test_3 = Test
681+ { test_name = "Character attributes: vivid background colors."
682+ , test_ID = "attributes_test_3"
683+ , test_action = do
684+ t <- terminal_handle
685+ reserve_display t
686+ let pic = pic_for_image image
687+ image = horiz_cat [border, column_0, border, column_1, border, column_2, border]
688+ border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
689+ column_0 = vert_cat $ map line_with_color_0 all_colors
690+ column_1 = vert_cat $ map line_with_color_1 all_colors
691+ column_2 = vert_cat $ map (string def_attr . snd) all_colors
692+ line_with_color_0 (c_value, c_name) = string (setBG c_value def_attr) c_name
693+ line_with_color_1 (c_value, c_name) = string (setBGVivid c_value def_attr) c_name
694+ d <- display_bounds t >>= display_context t
695+ output_picture d pic
696+ getLine
697+ release_display t
698+ release_terminal t
699+ return ()
700+ , print_summary = do
701+ putStr $ [$heredoc|
702+Once return is pressed:
703+ 0. The screen will be cleared.
704+ 1. The cursor will be hidden.
705+ 2. 9 lines of text in three columns will be drawn:
706+ a. The first column will contain be a name of a standard color for an 8
707+ color terminal rendered with the default foreground color with a
708+ background the named color.
709+ b. The first column will contain be a name of a standard color for an 8
710+ color terminal rendered with the default foreground color with the
711+ background a vivid version of the named color.
712+ c. The third column will be the name of a standard color rendered with
713+ the default attributes.
714+
715+For instance, one line will contain be the word "magenta" and that words should
716+be rendered in the default foreground color over a magenta background.
717+
718+I'm not actually sure exactly what "vivid" means in this context. For xterm the
719+vivid colors are brighter.
720+
721+Verify:
722+ * The first column: The background color matches the named color.
723+ * The second column: The background color matches the named color and is
724+ more vivid than the color used in the first column.
725+ * The third column column: All text is rendered with the default attributes.
726+ * The vertical bars used in each line to mark the border of a column are
727+ lined up.
728+
729+Note: I haven't decided if, in this case, the background color should extend to
730+fills added for alignment. Right now the selected background color is only
731+applied to the background where the word is actually rendered. Since each word
732+is not of the same length VTY adds background fills to make the width of each
733+row effectively the same. These added fills are all currently rendered with the
734+default background pattern.
735+
736+After return is pressed for the second time:
737+ 0. The screen containing the test summary should be restored.
738+ 1. The cursor should be visible.
739+|]
740+ , confirm_results = do
741+ putStr $ [$heredoc|
742+Did the test output match the description?
743+|]
744+ default_success_confirm_results
745+ }
746+
747+attr_combos =
748+ [ ("default", id)
749+ , ("bold", setBold)
750+ , ("blink", setBlink)
751+ , ("underline", setUnderline)
752+ , ("bold + blink", setBlink . setBold)
753+ , ("bold + underline", setUnderline . setBold)
754+ , ("underline + blink", setBlink . setUnderline)
755+ , ("bold + blink + underline", setUnderline . setBlink . setBold)
756+ ]
757+
758+attributes_test_4 = Test
759+ { test_name = "Character attributes: Bold; Blink; Underline."
760+ , test_ID = "attributes_test_4"
761+ , test_action = do
762+ t <- terminal_handle
763+ reserve_display t
764+ let pic = pic_for_image image
765+ image = horiz_cat [border, column_0, border, column_1, border]
766+ border = vert_cat $ replicate (length attr_combos) $ string def_attr " | "
767+ column_0 = vert_cat $ map line_with_attrs attr_combos
768+ column_1 = vert_cat $ map (string def_attr . fst) attr_combos
769+ line_with_attrs (desc, attr_f) = string (attr_f def_attr) desc
770+ d <- display_bounds t >>= display_context t
771+ output_picture d pic
772+ getLine
773+ release_display t
774+ release_terminal t
775+ return ()
776+ , print_summary = do
777+ putStr $ [$heredoc|
778+Once return is pressed:
779+ 0. The screen will be cleared.
780+ 1. The cursor will be hidden.
781+ 2. 8 rows of text in two columns.
782+ The rows will contain the following text:
783+ default
784+ bold
785+ blink
786+ underline
787+ bold + blink
788+ bold + underline
789+ underline + blink
790+ bold + blink + underline
791+ The first column will be rendered with the described attributes. The second
792+ column will be rendered with the default attributes.
793+
794+Verify:
795+ * The vertical bars used in each line to mark the border of a column are
796+ lined up.
797+ * The text in the first column is rendered as described.
798+
799+After return is pressed for the second time:
800+ 0. The screen containing the test summary should be restored.
801+ 1. The cursor should be visible.
802+|]
803+ , confirm_results = do
804+ putStr $ [$heredoc|
805+Did the test output match the description?
806+|]
807+ default_success_confirm_results
808+ }
809+
810+attributes_test_5 = Test
811+ { test_name = "Character attributes: 240 color palette"
812+ , test_ID = "attributes_test_5"
813+ , test_action = do
814+ t <- terminal_handle
815+ reserve_display t
816+ let pic = pic_for_image image
817+ image = vert_cat $ map horiz_cat $ split_color_images color_images
818+ color_images = map (\i -> string (current_attr `with_back_color` Color240 i) " ") [0..239]
819+ split_color_images [] = []
820+ split_color_images is = (take 20 is ++ [string def_attr " "]) : (split_color_images (drop 20 is))
821+ d <- display_bounds t >>= display_context t
822+ output_picture d pic
823+ getLine
824+ release_display t
825+ release_terminal t
826+ return ()
827+ , print_summary = do
828+ putStr $ [$heredoc|
829+Once return is pressed:
830+ 0. The screen will be cleared.
831+ 1. The cursor will be hidden.
832+ 2. A 20 character wide and 12 row high block of color squares. This should look like a palette
833+ of some sort. I'm not exactly sure if all color terminals use the same palette. I doubt it...
834+
835+Verify:
836+
837+After return is pressed for the second time:
838+ 0. The screen containing the test summary should be restored.
839+ 1. The cursor should be visible.
840+|]
841+ , confirm_results = do
842+ putStr $ [$heredoc|
843+Did the test output match the description?
844+|]
845+ default_success_confirm_results
846+ }
847+
test/make_tests.shnew file+31
@@ -0,0 +1,31 @@
1+#!/bin/bash
2+set -e
3+GHC_ARGS='--make -i../src -ignore-package vty ../cbits/gwinsz.c ../cbits/set_term_timing.c -funbox-strict-fields -Wall -threaded -fno-full-laziness -fspec-constr'
4+
5+rm -f Bench.o Bench.hi Bench
6+ghc $GHC_ARGS '-prof' '-auto-all' Bench.hs
7+
8+rm -f Bench2.o Bench2.hi Bench2
9+ghc $GHC_ARGS '-prof' '-auto-all' Bench2.hs
10+
11+rm -f BenchRenderChar.o BenchRenderChar.hi BenchRenderChar
12+ghc $GHC_ARGS '-prof' '-auto-all' BenchRenderChar
13+
14+rm -f Test.o Test.hi Test
15+ghc $GHC_ARGS Test
16+
17+rm -f Test2.o Test2.hi Test2
18+ghc $GHC_ARGS Test2
19+
20+rm -f BenchmarkImageOps.hi BenchmarkImageOps.o BenchmarkImageOps
21+ghc $GHC_ARGS BenchmarkImageOps
22+
23+rm -f ControlTable.hi ControlTable.o ControlTable
24+ghc $GHC_ARGS ControlTable
25+
26+rm -f yi_issue_264.hi yi_issue_264.o yi_issue_264
27+ghc $GHC_ARGS yi_issue_264
28+
29+rm -f vty_issue_18.hi vty_issue_18.o vty_issue_18
30+ghc $GHC_ARGS vty_issue_18
31+
test/verify_attribute_ops.hsnew file+8
@@ -0,0 +1,8 @@
1+module Main where
2+
3+import Verify.Graphics.Vty.Attributes
4+
5+import Verify
6+
7+main = run_test $ do
8+ return ()
test/verify_control_strings.hsnew file+102
@@ -0,0 +1,102 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+module Main where
4+
5+import Data.Terminfo.Eval ( bytes_for_range )
6+
7+import qualified System.Console.Terminfo as Terminfo
8+
9+import Verify.Data.Terminfo.Parse
10+import Verify
11+
12+import Control.Monad ( mapM_ )
13+
14+import Data.Array.Unboxed
15+import Data.Maybe ( fromJust )
16+import Data.Word
17+
18+import Numeric
19+
20+caps =
21+ [ "cup"
22+ , "sc"
23+ , "rc"
24+ , "setf"
25+ , "setb"
26+ , "setaf"
27+ , "setab"
28+ , "op"
29+ , "cnorm"
30+ , "civis"
31+ , "smcup"
32+ , "rmcup"
33+ , "clear"
34+ , "hpa"
35+ , "vpa"
36+ , "sgr"
37+ , "sgr0"
38+ ]
39+
40+from_capname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
41+
42+main = do
43+ ti <- Terminfo.setupTermFromEnv
44+ mapM_ ( print_cap ti) caps
45+ run_test $ do
46+ mapM_ ( \cap -> let cap_str = from_capname ti cap
47+ in verify ( "parse cap " ++ show cap ++ " -> " ++ show cap_str )
48+ ( verify_parse_cap cap_str $ const (liftResult succeeded) )
49+ ) caps
50+ verify "parse_non_paramaterized_caps" non_paramaterized_caps
51+ verify "parse cap string with literal %" literal_percent_caps
52+ verify "parse cap string with %i op" inc_first_two_caps
53+ verify "parse cap string with %pN op" push_param_caps
54+ return ()
55+ return ()
56+
57+verify_parse_cap cap_string on_parse = do
58+ case parse_cap_expression cap_string of
59+ Left error -> liftResult $ failed { reason = "parse error " ++ show error }
60+ Right e -> on_parse e
61+
62+non_paramaterized_caps (NonParamCapString cap) = do
63+ verify_parse_cap cap $ \e ->
64+ let expected_count :: Word8 = toEnum $ length cap
65+ expected_bytes = map (toEnum . fromEnum) cap
66+ out_bytes = bytes_for_range e (0, expected_count)
67+ in verify_bytes_equal out_bytes expected_bytes
68+
69+literal_percent_caps (LiteralPercentCap cap_string expected_bytes) = do
70+ verify_parse_cap cap_string $ \e ->
71+ let expected_count :: Word8 = toEnum $ length expected_bytes
72+ out_bytes = collect_bytes e
73+ in verify_bytes_equal out_bytes expected_bytes
74+
75+inc_first_two_caps (IncFirstTwoCap cap_string expected_bytes) = do
76+ verify_parse_cap cap_string $ \e ->
77+ let expected_count :: Word8 = toEnum $ length expected_bytes
78+ out_bytes = collect_bytes e
79+ in verify_bytes_equal out_bytes expected_bytes
80+
81+push_param_caps (PushParamCap cap_string expected_param_count expected_bytes) = do
82+ verify_parse_cap cap_string $ \e ->
83+ let expected_count :: Word8 = toEnum $ length expected_bytes
84+ out_bytes = collect_bytes e
85+ out_param_count = param_count e
86+ in verify_bytes_equal out_bytes expected_bytes
87+ .&. out_param_count == expected_param_count
88+
89+dec_print_param_caps (DecPrintCap cap_string expected_param_count expected_bytes) = do
90+ verify_parse_cap cap_string $ \e ->
91+ let expected_count :: Word8 = toEnum $ length expected_bytes
92+ out_bytes = collect_bytes e
93+ out_param_count = param_count e
94+ in verify_bytes_equal out_bytes expected_bytes
95+ .&. out_param_count == expected_param_count
96+
97+print_cap ti cap_name = do
98+ putStrLn $ cap_name ++ ": " ++ show (from_capname ti cap_name)
99+
100+print_expression ti cap_name = do
101+ putStrLn $ cap_name ++ ": " ++ show (parse_cap_expression $ from_capname ti cap_name)
102+
test/verify_debug_terminal.hsnew file+115
@@ -0,0 +1,115 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+module Main where
3+
4+import Verify.Graphics.Vty.WinRegion
5+import Verify.Graphics.Vty.Picture
6+import Verify.Graphics.Vty.Image
7+import Verify.Graphics.Vty.Span
8+import Graphics.Vty.Terminal
9+import Graphics.Vty.Terminal.Debug
10+
11+import Graphics.Vty.Debug
12+
13+import Verify
14+
15+import qualified Data.ByteString as BS
16+import Data.IORef
17+import qualified Data.String.UTF8 as UTF8
18+
19+import System.IO
20+
21+unit_image_unit_bounds :: UnitImage -> Property
22+unit_image_unit_bounds (UnitImage _ i) = liftIOResult $ do
23+ t <- terminal_instance (DisplayBounds 1 1)
24+ d <- display_bounds t >>= display_context t
25+ let pic = pic_for_image i
26+ output_picture d pic
27+ return succeeded
28+
29+unit_image_arb_bounds :: UnitImage -> DebugWindow -> Property
30+unit_image_arb_bounds (UnitImage _ i) (DebugWindow w h) = liftIOResult $ do
31+ t <- terminal_instance (DisplayBounds w h)
32+ d <- display_bounds t >>= display_context t
33+ let pic = pic_for_image i
34+ output_picture d pic
35+ return succeeded
36+
37+single_T_row :: DebugWindow -> Property
38+single_T_row (DebugWindow w h) = liftIOResult $ do
39+ t <- terminal_instance (DisplayBounds w h)
40+ d <- display_bounds t >>= display_context t
41+ -- create an image that contains just the character T repeated for a single row
42+ let i = horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
43+ pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
44+ output_picture d pic
45+ out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep
46+ -- The UTF8 string that represents the output bytes a single line containing the T string:
47+ let expected = "HD" ++ "MA" ++ replicate (fromEnum w) 'T'
48+ -- Followed by h - 1 lines of a change to the background attribute and then the background
49+ -- character
50+ ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')
51+ expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
52+ if out_bytes /= expected_bytes
53+ then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
54+ else return succeeded
55+
56+many_T_rows :: DebugWindow -> Property
57+many_T_rows (DebugWindow w h) = liftIOResult $ do
58+ t <- terminal_instance (DisplayBounds w h)
59+ d <- display_bounds t >>= display_context t
60+ -- create an image that contains the character 'T' repeated for all the rows
61+ let i = vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
62+ pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
63+ output_picture d pic
64+ out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep
65+ -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
66+ -- attribute change. 'A', followed by w 'T's
67+ let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
68+ expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
69+ if out_bytes /= expected_bytes
70+ then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
71+ else return succeeded
72+
73+many_T_rows_cropped_width :: DebugWindow -> Property
74+many_T_rows_cropped_width (DebugWindow w h) = liftIOResult $ do
75+ t <- terminal_instance (DisplayBounds w h)
76+ d <- display_bounds t >>= display_context t
77+ -- create an image that contains the character 'T' repeated for all the rows
78+ let i = vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w * 2) (char def_attr 'T')
79+ pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
80+ output_picture d pic
81+ out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep
82+ -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
83+ -- attribute change. 'A', followed by w 'T's
84+ let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
85+ expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
86+ if out_bytes /= expected_bytes
87+ then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
88+ else return succeeded
89+
90+many_T_rows_cropped_height :: DebugWindow -> Property
91+many_T_rows_cropped_height (DebugWindow w h) = liftIOResult $ do
92+ t <- terminal_instance (DisplayBounds w h)
93+ d <- display_bounds t >>= display_context t
94+ -- create an image that contains the character 'T' repeated for all the rows
95+ let i = vert_cat $ replicate (fromEnum h * 2) $ horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
96+ pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
97+ output_picture d pic
98+ out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep
99+ -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
100+ -- attribute change. 'A', followed by w count 'T's
101+ let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
102+ expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
103+ if out_bytes /= expected_bytes
104+ then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
105+ else return succeeded
106+
107+main = run_test $ do
108+ verify "unit_image_unit_bounds" unit_image_unit_bounds
109+ verify "unit_image_arb_bounds" unit_image_arb_bounds
110+ verify "single_T_row" single_T_row
111+ verify "many_T_rows" many_T_rows
112+ verify "many_T_rows_cropped_width" many_T_rows_cropped_width
113+ verify "many_T_rows_cropped_height" many_T_rows_cropped_height
114+ return ()
115+
test/verify_image_ops.hsnew file+57
@@ -0,0 +1,57 @@
1+module Main where
2+import Graphics.Vty.Attributes
3+import Graphics.Vty.Image
4+
5+import Verify
6+
7+two_sw_horiz_concat :: SingleColumnChar -> SingleColumnChar -> Bool
8+two_sw_horiz_concat (SingleColumnChar c1) (SingleColumnChar c2) =
9+ image_width (char def_attr c1 <|> char def_attr c2) == 2
10+
11+many_sw_horiz_concat :: [SingleColumnChar] -> Bool
12+many_sw_horiz_concat cs =
13+ let chars = [ char | SingleColumnChar char <- cs ]
14+ l = fromIntegral $ length cs
15+ in image_width ( horiz_cat $ map (char def_attr) chars ) == l
16+
17+two_sw_vert_concat :: SingleColumnChar -> SingleColumnChar -> Bool
18+two_sw_vert_concat (SingleColumnChar c1) (SingleColumnChar c2) =
19+ image_height (char def_attr c1 <-> char def_attr c2) == 2
20+
21+horiz_concat_sw_assoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool
22+horiz_concat_sw_assoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) =
23+ (char def_attr c0 <|> char def_attr c1) <|> char def_attr c2
24+ ==
25+ char def_attr c0 <|> (char def_attr c1 <|> char def_attr c2)
26+
27+two_dw_horiz_concat :: DoubleColumnChar -> DoubleColumnChar -> Bool
28+two_dw_horiz_concat (DoubleColumnChar c1) (DoubleColumnChar c2) =
29+ image_width (char def_attr c1 <|> char def_attr c2) == 4
30+
31+many_dw_horiz_concat :: [DoubleColumnChar] -> Bool
32+many_dw_horiz_concat cs =
33+ let chars = [ char | DoubleColumnChar char <- cs ]
34+ l = fromIntegral $ length cs
35+ in image_width ( horiz_cat $ map (char def_attr) chars ) == l * 2
36+
37+two_dw_vert_concat :: DoubleColumnChar -> DoubleColumnChar -> Bool
38+two_dw_vert_concat (DoubleColumnChar c1) (DoubleColumnChar c2) =
39+ image_height (char def_attr c1 <-> char def_attr c2) == 2
40+
41+horiz_concat_dw_assoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool
42+horiz_concat_dw_assoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) =
43+ (char def_attr c0 <|> char def_attr c1) <|> char def_attr c2
44+ ==
45+ char def_attr c0 <|> (char def_attr c1 <|> char def_attr c2)
46+
47+main = run_test $ do
48+ verify "two_sw_horiz_concat" two_sw_horiz_concat
49+ verify "many_sw_horiz_concat" many_sw_horiz_concat
50+ verify "two_sw_vert_concat" two_sw_vert_concat
51+ verify "horiz_concat_sw_assoc" horiz_concat_sw_assoc
52+ verify "many_dw_horiz_concat" many_dw_horiz_concat
53+ verify "two_dw_horiz_concat" two_dw_horiz_concat
54+ verify "two_dw_vert_concat" two_dw_vert_concat
55+ verify "horiz_concat_dw_assoc" horiz_concat_dw_assoc
56+ return ()
57+
test/verify_image_trans.hsnew file+30
@@ -0,0 +1,30 @@
1+{-# LANGUAGE NamedFieldPuns #-}
2+module Main where
3+
4+import Verify.Graphics.Vty.Image
5+import Graphics.Vty.Debug
6+
7+import Verify
8+
9+import Data.Word
10+
11+is_horiz_text_of_columns :: Image -> Word -> Bool
12+is_horiz_text_of_columns (HorizText { columns = in_w }) expected_w = in_w == expected_w
13+is_horiz_text_of_columns (HorizBlank { columns = in_w }) expected_w = in_w == expected_w
14+is_horiz_text_of_columns _image _expected_w = False
15+
16+verify_horiz_contat_wo_attr_change_simplifies :: SingleRowSingleAttrImage -> Bool
17+verify_horiz_contat_wo_attr_change_simplifies (SingleRowSingleAttrImage _attr char_count image) =
18+ is_horiz_text_of_columns image char_count
19+
20+verify_horiz_contat_w_attr_change_simplifies :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Bool
21+verify_horiz_contat_w_attr_change_simplifies (SingleRowSingleAttrImage attr0 char_count0 image0)
22+ (SingleRowSingleAttrImage attr1 char_count1 image1)
23+ | char_count0 == 0 || char_count1 == 0 || attr0 == attr1 = is_horiz_text_of_columns i (char_count0 + char_count1)
24+ | otherwise = False == is_horiz_text_of_columns i (char_count0 + char_count1)
25+ where i = image0 <|> image1
26+
27+main = run_test $ do
28+ verify "verify_horiz_contat_wo_attr_change_simplifies" verify_horiz_contat_wo_attr_change_simplifies
29+ verify "verify_horiz_contat_w_attr_change_simplifies" verify_horiz_contat_w_attr_change_simplifies
30+ return ()
test/verify_picture_ops.hsnew file+9
@@ -0,0 +1,9 @@
1+module Main where
2+
3+import Graphics.Vty.Picture
4+
5+import Verify
6+
7+main = run_test $ do
8+ return ()
9+
test/verify_picture_to_span.hsnew file+10
@@ -0,0 +1,10 @@
1+module Main where
2+
3+import Graphics.Vty.Picture
4+import Graphics.Vty.Span
5+import Graphics.Vty.PictureToSpans
6+
7+import Verify
8+
9+main = run_test $ do
10+ return ()
test/verify_span_ops.hsnew file+179
@@ -0,0 +1,179 @@
1+{-# LANGUAGE DisambiguateRecordFields #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+module Main where
4+
5+import Verify.Graphics.Vty.Picture
6+import Verify.Graphics.Vty.Image
7+import Verify.Graphics.Vty.Span
8+import Verify.Graphics.Vty.WinRegion
9+
10+import Graphics.Vty.Debug
11+
12+import Verify
13+
14+import Data.Array
15+
16+unit_image_and_zero_window_0 :: UnitImage -> EmptyWindow -> Bool
17+unit_image_and_zero_window_0 (UnitImage _ i) (EmptyWindow w) =
18+ let p = pic_for_image i
19+ spans = spans_for_pic p w
20+ in win_width spans == 0 && win_height spans == 0
21+
22+unit_image_and_zero_window_1 :: UnitImage -> EmptyWindow -> Bool
23+unit_image_and_zero_window_1 (UnitImage _ i) (EmptyWindow w) =
24+ let p = pic_for_image i
25+ spans = spans_for_pic p w
26+ in ( span_ops_effected_rows spans == 0 ) && ( all_spans_have_width spans 0 )
27+
28+horiz_span_image_and_zero_window_0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
29+horiz_span_image_and_zero_window_0 (SingleRowSingleAttrImage { row_image = i }) (EmptyWindow w) =
30+ let p = pic_for_image i
31+ spans = spans_for_pic p w
32+ in win_width spans == 0 && win_height spans == 0
33+
34+horiz_span_image_and_zero_window_1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
35+horiz_span_image_and_zero_window_1 (SingleRowSingleAttrImage { row_image = i }) (EmptyWindow w) =
36+ let p = pic_for_image i
37+ spans = spans_for_pic p w
38+ in ( span_ops_effected_rows spans == 0 ) && ( all_spans_have_width spans 0 )
39+
40+horiz_span_image_and_equal_window_0 :: SingleRowSingleAttrImage -> Result
41+horiz_span_image_and_equal_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
42+ let p = pic_for_image i
43+ w = DebugWindow c 1
44+ spans = spans_for_pic p w
45+ in verify_all_spans_have_width i spans c
46+
47+horiz_span_image_and_equal_window_1 :: SingleRowSingleAttrImage -> Bool
48+horiz_span_image_and_equal_window_1 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
49+ let p = pic_for_image i
50+ w = DebugWindow c 1
51+ spans = spans_for_pic p w
52+ in span_ops_effected_rows spans == 1
53+
54+horiz_span_image_and_lesser_window_0 :: SingleRowSingleAttrImage -> Result
55+horiz_span_image_and_lesser_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
56+ let p = pic_for_image i
57+ lesser_width = c `div` 2
58+ w = DebugWindow lesser_width 1
59+ spans = spans_for_pic p w
60+ in verify_all_spans_have_width i spans lesser_width
61+
62+single_attr_single_span_stack_cropped_0 :: SingleAttrSingleSpanStack -> Result
63+single_attr_single_span_stack_cropped_0 stack =
64+ let p = pic_for_image (stack_image stack)
65+ w = DebugWindow (stack_width stack `div` 2) (stack_height stack)
66+ spans = spans_for_pic p w
67+ in verify_all_spans_have_width (stack_image stack) spans (stack_width stack `div` 2)
68+
69+single_attr_single_span_stack_cropped_1 :: SingleAttrSingleSpanStack -> Bool
70+single_attr_single_span_stack_cropped_1 stack =
71+ let p = pic_for_image (stack_image stack)
72+ expected_row_count = stack_height stack `div` 2
73+ w = DebugWindow (stack_width stack) expected_row_count
74+ spans = spans_for_pic p w
75+ actual_row_count = span_ops_effected_rows spans
76+ in expected_row_count == actual_row_count
77+
78+single_attr_single_span_stack_cropped_2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
79+single_attr_single_span_stack_cropped_2 stack_0 stack_1 =
80+ let p = pic_for_image (stack_image stack_0 <|> stack_image stack_1)
81+ w = DebugWindow (stack_width stack_0) (image_height (pic_image p))
82+ spans = spans_for_pic p w
83+ in verify_all_spans_have_width (pic_image p) spans (stack_width stack_0)
84+
85+single_attr_single_span_stack_cropped_3 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
86+single_attr_single_span_stack_cropped_3 stack_0 stack_1 =
87+ let p = pic_for_image (stack_image stack_0 <|> stack_image stack_1)
88+ w = DebugWindow (image_width (pic_image p)) expected_row_count
89+ spans = spans_for_pic p w
90+ expected_row_count = image_height (pic_image p) `div` 2
91+ actual_row_count = span_ops_effected_rows spans
92+ in expected_row_count == actual_row_count
93+
94+single_attr_single_span_stack_cropped_4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
95+single_attr_single_span_stack_cropped_4 stack_0 stack_1 =
96+ let p = pic_for_image (stack_image stack_0 <-> stack_image stack_1)
97+ w = DebugWindow expected_width (image_height (pic_image p))
98+ spans = spans_for_pic p w
99+ expected_width = image_width (pic_image p) `div` 2
100+ in verify_all_spans_have_width (pic_image p) spans expected_width
101+
102+single_attr_single_span_stack_cropped_5 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
103+single_attr_single_span_stack_cropped_5 stack_0 stack_1 =
104+ let p = pic_for_image (stack_image stack_0 <-> stack_image stack_1)
105+ w = DebugWindow (image_width (pic_image p)) (stack_height stack_0)
106+ spans = spans_for_pic p w
107+ expected_row_count = stack_height stack_0
108+ actual_row_count = span_ops_effected_rows spans
109+ in expected_row_count == actual_row_count
110+
111+horiz_span_image_and_greater_window_0 :: SingleRowSingleAttrImage -> Result
112+horiz_span_image_and_greater_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
113+ let p = pic_for_image i
114+ -- SingleRowSingleAttrImage always has width >= 1
115+ greater_width = c * 2
116+ w = DebugWindow greater_width 1
117+ spans = spans_for_pic p w
118+ in verify_all_spans_have_width i spans greater_width
119+
120+arb_image_is_cropped :: DefaultImage -> DebugWindow -> Bool
121+arb_image_is_cropped (DefaultImage image _) win@(DebugWindow w h) =
122+ let pic = pic_for_image image
123+ spans = spans_for_pic pic win
124+ in ( span_ops_effected_rows spans == h ) && ( all_spans_have_width spans w )
125+
126+span_ops_actually_fill_rows :: DefaultPic -> Bool
127+span_ops_actually_fill_rows (DefaultPic pic win _) =
128+ let spans = spans_for_pic pic win
129+ expected_row_count = win_height win
130+ actual_row_count = span_ops_effected_rows spans
131+ in expected_row_count == actual_row_count
132+
133+span_ops_actually_fill_columns :: DefaultPic -> Bool
134+span_ops_actually_fill_columns (DefaultPic pic win _) =
135+ let spans = spans_for_pic pic win
136+ expected_column_count = win_width win
137+ in all_spans_have_width spans expected_column_count
138+
139+first_span_op_sets_attr :: DefaultPic -> Bool
140+first_span_op_sets_attr DefaultPic { default_pic = pic, default_win = win } =
141+ let spans = spans_for_pic pic win
142+ in all ( is_attr_span_op . head ) ( elems $ row_ops spans )
143+
144+single_attr_single_span_stack_op_coverage :: SingleAttrSingleSpanStack -> Result
145+single_attr_single_span_stack_op_coverage stack =
146+ let p = pic_for_image (stack_image stack)
147+ w = DebugWindow (stack_width stack) (stack_height stack)
148+ spans = spans_for_pic p w
149+ in verify_all_spans_have_width (stack_image stack) spans (stack_width stack)
150+
151+main :: IO ()
152+main = run_test $ do
153+ verify "unit image is cropped when window size == (0,0) [0]" unit_image_and_zero_window_0
154+ verify "unit image is cropped when window size == (0,0) [1]" unit_image_and_zero_window_1
155+ verify "horiz span image is cropped when window size == (0,0) [0]" horiz_span_image_and_zero_window_0
156+ verify "horiz span image is cropped when window size == (0,0) [1]" horiz_span_image_and_zero_window_1
157+ verify "horiz span image is not cropped when window size == size of image [width]" horiz_span_image_and_equal_window_0
158+ verify "horiz span image is not cropped when window size == size of image [height]" horiz_span_image_and_equal_window_1
159+ verify "horiz span image is not cropped when window size < size of image [width]" horiz_span_image_and_lesser_window_0
160+ verify "horiz span image is not cropped when window size > size of image [width]" horiz_span_image_and_greater_window_0
161+ verify "arbitrary image is padded or cropped" arb_image_is_cropped
162+ verify "The span ops actually define content for all the rows in the output region" span_ops_actually_fill_rows
163+ verify "The span ops actually define content for all the columns in the output region" span_ops_actually_fill_columns
164+ verify "first span op is always to set the text attribute" first_span_op_sets_attr
165+ verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"
166+ single_attr_single_span_stack_op_coverage
167+ verify "a single attr text span is cropped when window size < size of stack image [width]"
168+ single_attr_single_span_stack_cropped_0
169+ verify "a single attr text span is cropped when window size < size of stack image [height]"
170+ single_attr_single_span_stack_cropped_1
171+ verify "single attr text span <|> single attr text span cropped. [width]"
172+ single_attr_single_span_stack_cropped_2
173+ verify "single attr text span <|> single attr text span cropped. [height]"
174+ single_attr_single_span_stack_cropped_3
175+ verify "single attr text span <-> single attr text span cropped. [width]"
176+ single_attr_single_span_stack_cropped_4
177+ verify "single attr text span <-> single attr text span cropped. [height]"
178+ single_attr_single_span_stack_cropped_5
179+ return ()
test/verify_utf8_width.hsnew file+17
@@ -0,0 +1,17 @@
1+module Main where
2+import Verify
3+
4+import Graphics.Vty.Attributes
5+import Graphics.Vty.Picture
6+
7+sw_is_1_column :: SingleColumnChar -> Bool
8+sw_is_1_column (SingleColumnChar c) = image_width (char def_attr c) == 1
9+
10+dw_is_2_column :: DoubleColumnChar -> Bool
11+dw_is_2_column (DoubleColumnChar c) = image_width (char def_attr c) == 2
12+
13+main = run_test $ do
14+ verify "sw_is_1_column" sw_is_1_column
15+ verify "dw_is_2_column" dw_is_2_column
16+ return ()
17+
test/vty_issue_18.hsnew file+43
@@ -0,0 +1,43 @@
1+module Main
2+ where
3+
4+import Graphics.Vty
5+import Graphics.Vty.Debug
6+
7+import System.IO
8+
9+main :: IO ()
10+main = do
11+ vty <- mkVty
12+ (sx, sy) <- getSize vty
13+ play vty sx sy
14+
15+play :: Vty -> Int -> Int -> IO ()
16+play vty sx sy =
17+ let
18+ testScreen = pic {
19+ pCursor = NoCursor
20+ , pImage = box 10 10 }
21+ in do
22+ update vty testScreen
23+ getEvent vty
24+ shutdown vty
25+ return ()
26+ {-
27+ k <- getEvent vty
28+ case k of
29+ EvKey KEsc [] -> shutdown vty >> return ()
30+ EvResize nx ny -> play vty nx ny
31+ _ -> shutdown vty
32+ -}
33+
34+box :: Int -> Int -> Image
35+box w h =
36+ let
37+ corner = renderChar attr '+'
38+ vertLine = renderFill attr '|' 1 (h - 2)
39+ horizLine = corner <|> renderHFill attr '-' (w - 2) <|> corner
40+ centerArea = vertLine <|> renderFill attr 'X' (w - 2) (h - 2) <|> vertLine
41+ in
42+ horizLine <-> centerArea <-> horizLine
43+
vty.cabalnew file+63
@@ -0,0 +1,63 @@
1+Name: vty
2+Version: 4.0.0
3+License: BSD3
4+License-file: LICENSE
5+Author: Stefan O'Rear, Corey O'Connor
6+Maintainer: Corey O'Connor (coreyoconnor@gmail.com)
7+Category: User Interfaces
8+Synopsis: A simple terminal access library
9+Description:
10+ vty is terminal GUI library in the niche of ncurses. It is intended to be easy to use, have no
11+ confusing corner cases, and good support for common terminal types.
12+ .
13+ Included in the source distribution is a program test/interactive_terminal_test.hs that
14+ demonstrates the various features.
15+ .
16+ If your terminal is not behaving as expected the results of the test/interactive_terminal_test.hs
17+ program should be sent to the Vty maintainter to aid in debugging the issue.
18+ .
19+ Notable infelicities: Sometimes poor efficiency; Assumes UTF-8 character encoding support by the
20+ terminal;
21+ .
22+ You can 'darcs get' it from <http://code.haskell.org/vty/>
23+ &#169; 2006-2007 Stefan O'Rear; BSD3 license.
24+ &#169; 2008-2009 Corey O'Connor; BSD3 license.
25+
26+Build-Depends: base >= 4 && < 5, bytestring, containers, unix, uvector
27+Build-Depends: terminfo >= 0.2 && < 0.3
28+Build-Depends: utf8-string >= 0.3 && < 0.4
29+Build-Depends: transformers, monads-tf
30+Build-Depends: ghc-prim
31+Build-Depends: array
32+Build-Depends: parsec >= 3 && < 4
33+Build-Type: Simple
34+Data-Files: README, TODO
35+Extra-Source-Files: test/Test.hs, cbits/gwinsz.h, test/Bench.hs
36+
37+Exposed-Modules: Graphics.Vty
38+ Graphics.Vty.Terminal
39+ Graphics.Vty.LLInput
40+ Graphics.Vty.Attributes
41+ Graphics.Vty.Picture
42+ Graphics.Vty.DisplayRegion
43+
44+other-modules: Codec.Binary.UTF8.Width
45+ Data.Marshalling
46+ Data.Terminfo.Parse
47+ Data.Terminfo.Eval
48+ Graphics.Vty.Image
49+ Graphics.Vty.Span
50+ Graphics.Vty.Terminal.Generic
51+ Graphics.Vty.Terminal.XTermColor
52+ Graphics.Vty.Terminal.TerminfoBased
53+
54+C-Sources: cbits/gwinsz.c
55+ cbits/set_term_timing.c
56+ cbits/mk_wcwidth.c
57+Include-Dirs: cbits
58+hs-source-dirs: src
59+Install-Includes: gwinsz.h
60+
61+ghc-options: -funbox-strict-fields -Wall -threaded
62+ghc-prof-options: -funbox-strict-fields -auto-all -Wall -threaded
63+