on master help sign in
.gitignore+7
since splitsnapshots
unifiedsplit
@@ -4,5 +4,12 @@
4*.prof
4*.prof
5Setup
5Setup
6dist
6dist
7+dist-newstyle
8+cabal.project.local
9+.ghc.environment.*
7test/benchmark
10test/benchmark
8.hpc
11.hpc
12+.cabal-sandbox
13+cabal.sandbox.config
14+test/cabal.sandbox.config
15+.stack-work
.travis.yml+109−3
@@ -1,3 +1,109 @@
1+# This Travis job script has been generated by a script via
2+#
3+# runghc make_travis_yml_2.hs 'vty.cabal'
4+#
5+# For more information, see https://github.com/haskell-CI/haskell-ci
6+#
1−language: haskell
7+language: c
2−notifications:
8+sudo: false
3− email: false
9+
10+git:
11+ submodules: false # whether to recursively clone submodules
12+
13+cache:
14+ directories:
15+ - $HOME/.cabal/packages
16+ - $HOME/.cabal/store
17+
18+before_cache:
19+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
20+ # remove files that are regenerated by 'cabal update'
21+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*
22+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json
23+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache
24+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar
25+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx
26+
27+ - rm -rfv $HOME/.cabal/packages/head.hackage
28+
29+matrix:
30+ include:
31+ - compiler: "ghc-8.4.3"
32+ # env: TEST=--disable-tests BENCH=--disable-benchmarks
33+ addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.3], sources: [hvr-ghc]}}
34+ - compiler: "ghc-8.2.2"
35+ # env: TEST=--disable-tests BENCH=--disable-benchmarks
36+ addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.2.2], sources: [hvr-ghc]}}
37+ - compiler: "ghc-8.0.2"
38+ # env: TEST=--disable-tests BENCH=--disable-benchmarks
39+ addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.0.2], sources: [hvr-ghc]}}
40+ - compiler: "ghc-7.10.3"
41+ # env: TEST=--disable-tests BENCH=--disable-benchmarks
42+ addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.3], sources: [hvr-ghc]}}
43+
44+before_install:
45+ - HC=${CC}
46+ - HCPKG=${HC/ghc/ghc-pkg}
47+ - unset CC
48+ - ROOTDIR=$(pwd)
49+ - mkdir -p $HOME/.local/bin
50+ - "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"
51+ - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
52+ - echo $HCNUMVER
53+
54+install:
55+ - cabal --version
56+ - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
57+ - BENCH=${BENCH---enable-benchmarks}
58+ - TEST=${TEST---enable-tests}
59+ - HADDOCK=${HADDOCK-true}
60+ - UNCONSTRAINED=${UNCONSTRAINED-true}
61+ - NOINSTALLEDCONSTRAINTS=${NOINSTALLEDCONSTRAINTS-false}
62+ - GHCHEAD=${GHCHEAD-false}
63+ - travis_retry cabal update -v
64+ - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
65+ - rm -fv cabal.project cabal.project.local
66+ - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
67+ - "printf 'packages: \".\"\\n' > cabal.project"
68+ - touch cabal.project.local
69+ - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg | grep -vw -- vty | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
70+ - cat cabal.project || true
71+ - cat cabal.project.local || true
72+ - if [ -f "./configure.ac" ]; then
73+ (cd "." && autoreconf -i);
74+ fi
75+ - rm -f cabal.project.freeze
76+ - cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dep -j2 all
77+ - cabal new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project" --dep -j2 all
78+ - rm -rf .ghc.environment.* "."/dist
79+ - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
80+
81+# Here starts the actual work to be performed for the package under test;
82+# any command which exits with a non-zero exit code causes the build to fail.
83+script:
84+ # test that source-distributions can be generated
85+ - (cd "." && cabal sdist)
86+ - mv "."/dist/vty-*.tar.gz ${DISTDIR}/
87+ - cd ${DISTDIR} || false
88+ - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
89+ - "printf 'packages: vty-*/*.cabal\\n' > cabal.project"
90+ - touch cabal.project.local
91+ - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg | grep -vw -- vty | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
92+ - cat cabal.project || true
93+ - cat cabal.project.local || true
94+ # this builds all libraries and executables (without tests/benchmarks)
95+ - cabal new-build -w ${HC} --disable-tests --disable-benchmarks all
96+
97+ # build & run tests, build benchmarks
98+ - cabal new-build -w ${HC} ${TEST} ${BENCH} all
99+ - if [ "x$TEST" = "x--enable-tests" ]; then cabal new-test -w ${HC} ${TEST} ${BENCH} all; fi
100+
101+ # haddock
102+ - rm -rf ./dist-newstyle
103+ - if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
104+
105+ # Build without installed constraints for packages in global-db
106+ - if $UNCONSTRAINED; then rm -f cabal.project.local; echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks all; else echo "Not building without installed constraints"; fi
107+
108+# REGENDATA ["vty.cabal"]
109+# EOF
AUTHORS+6−1
@@ -1,4 +1,6 @@
1−The following people should be thanked for contributing to the vty library:
1+The following people should be thanked for contributing to the vty
2+library:
3+
2 * Andrea Vezzosi
4 * Andrea Vezzosi
3 * Corey O'Connor
5 * Corey O'Connor
4 * Emily Backes
6 * Emily Backes
@@ -15,3 +17,6 @@ The following people should be thanked for contributing to the vty library:
15 * Mikolaj Konarski
17 * Mikolaj Konarski
16 * Eyal Lotem
18 * Eyal Lotem
17 * Yoshikuni Jujo
19 * Yoshikuni Jujo
20+ * Dmitry Ivanov
21+
22+Plus others.. Check the git log and CHANGELOG.md for a full list.
CHANGELOGdeleted−131
@@ -1,131 +0,0 @@
1−5.0.0
2− * removed the typeclass based terminal and display context interface in favor of a data
3− structure of properties interface.
4− * The default picture for an image now uses the "clear" background. This background fills
5− background spans with spaces or just ends the line.
6− * Previously the background defaulted to the space character. This causes issues copying
7− text from a text editor. The text would end up with many extra spaces at the end of the
8− line.
9− * Layer support
10− * Each layer is an image.
11− * The layers for a picture are a list of images.
12− * The first image is the top-most layer.
13− * The final rendering is as if the bottom layer (last image) was rendered, then the next
14− (second to last image) etc.
15− * The transparent areas for a layer are the background image components.
16− * If the background is clear there is no background layer.
17− * If there is a background character then the bottom layer is the background layer.
18− * compatibility improvements:
19− * terminfo based terminals with no cursor support are silently accepted. The cursor
20− visibility changes in the Picture will have no effect.
21− * alternate (setf/setb) color maps supported. Though colors beyond the first 8 are just a
22− guess.
23− * added "rgb_color" for easy support of RGB specified colors.
24− * input handling changes
25− * tests exist.
26− * the default amount of time between ESC and a subsequent character that causes VTY to
27− interpret the input as "ESC" then the character, versus some special key, has been
28− increased to 4000 microseconds; 4 milliseconds.
29− * examples optionally installed, using the --with-examples, with library:
30− * vty-interactive-terminal-test
31− * interactive test. Useful for building a bug report for vty's author.
32− * tests/interactive_terminal_test.hs
33− * vty-event-echo
34− * view a input event log for vty. Small example of interacting with user.
35− * tests/EventEcho.hs
36− * vty-rouge
37− * A bad rouge-like game. Go from the entrance to exit. Ooooh. Small example of layers
38− and image build operations.
39− * tests/Rouge.hs
40− * vty-benchmark
41− * benchmarks vty. A series of tests that push random pictures to the terminal. The
42− random pictures are generated using QuickCheck. The same generators used in the
43− automated tests.
44− * tests/benchmark.hs
45−
46−4.7.0.0
47− * API changes:
48− * Added Graphics.Vty.Image.crop: Ensure an image is no larger than the specified size.
49− * Added Graphics.Vty.Image.pad: Ensure an image is no smaller than the specified size.
50− * Added Graphics.Vty.Image.translate: Offset an image.
51− * Thanks Ben Boeckel <MathStuf@gmail.com> for these features.
52−
53−4.3.0.0
54−
55−4.2.1.0
56− * API changes:
57− * Attr record accessor fore_color changed to attr_fore_color
58− * Attr record accessor back_color changed to attr_back_color
59− * Attr record accessor style changed to attr_style
60− * Added an "inline" display attribute changing DSL:
61− * put_attr_change applies a display attribute change immediately to a terminal
62− * For instance, can be used to change the display attrbiutes of text output via putStrLn
63− and putStr. EX: "put_attr_change $ back_color red" will set the
64− background color to red.
65− * Changes do not apply to a Picture output via output_picture.
66− * See Graphics.Vty.Inline
67− * Moved all IO actions into any monad an instance of MonadIO
68−
69−4.0.0.1
70− * binding for mk_wcswidth was incorrect. Most platforms just magically worked due to
71− coincidence.
72−
73−4.0.0
74− * API changes:
75− * "getSize" has been removed. Use "terminal vty >>= display_bounds" where "vty" is an
76− instance of the Vty data structure.
77− * added a "terminal" field to the Vty data structure. Accesses the TerminalHandle associated
78− with the Vty instance.
79− * Graphics.Vty.Types has undergone a number of changes. Summary:
80− * Partitioned into Graphics.Vty.Attributes for display attributes. Graphics.Vty.Image for
81− image combinators. Graphics.Vty.Picture for final picture construction.
82− * Graphics.Vty.Attributes:
83− * "setFG" and "setBG" are now "with_fore_color" and "with_back_color"
84− * All other "set.." equations similarly replaced.
85− * "attr" is now "def_attr", short for "default display attributes" Also added a
86− "current_attr" for "currently applied display attributes"
87− * Graphics.Vty.Image:
88− * "horzcat" is now "horiz_cat"
89− * "vertcat" is now "vert_cat"
90− * "renderBS" is now "utf8_bytestring"
91− * "renderChar" is now "char"
92− * "renderFill" is now "char_fill"
93− * added a "utf8_string" and "string" (AKA "iso_10464_string") for UTF-8 encoded Strings
94− and ISO-10464 encoded Strings. String literals in GHC have an ISO-10464 runtime
95− representation.
96− * Graphics.Vty.Picture:
97− * exports Graphics.Vty.Image
98− * "pic" is now "pic_for_image"
99− * added API for setting background fill pattern.
100− * Completely rewritten output backend.
101− * Efficient, scanline style output span generator. Has not been fully optimized, but good
102− enough.
103− * The details required to display the desired picture on a terminal are well encapsulated.
104− * Terminfo based display terminal implementation. With specialized derivitives for xterm,
105− Terminal.app, and iTerm.app.
106− * Attempts to robustly handle even terminals that don't support all display attributes.
107− * I've tested the following terminals with success: iTerm.app, Terminal.app, xterm,
108− rxvt, mlterm, Eterm, gnome-terminal, konsole, screen, linux vty. Hopefully you will be
109− as successfull.
110− * Improved unicode support. Double wide characters will display as expected.
111− * 256 color support. See Graphics.Vty.Attributes.Color240. The actual output color is adjusted
112− according to the number of colors the terminal supports.
113− * The Graphics.Vty.Image combinators no longer require matching dimensions to arguments.
114− Unspecified areas are filled in with a user-customizable background pattern. See
115− Graphics.Vty.Picture.
116− * output images are always cropped to display size.
117− * Significant code coverage by QuickCheck tests. An interactive test for those final properties
118− that couldn't be automatically verified.
119−
120− issues resolved:
121− * "gnome terminal displays non-basic attributes as strikethrough"
122− * http://trac.haskell.org/vty/ticket/14
123− * "Multi-byte characters are not displayed correctly on update"
124− * http://trac.haskell.org/vty/ticket/10
125− * "Redraw does not handle rendering a line that extends beyond screen width characters"
126− * http://trac.haskell.org/vty/ticket/13
127− * "The <|> and <-> combinators should be more forgiving of mismatched dimensions"
128− * http://trac.haskell.org/vty/ticket/9
129− * "256-color support"
130− * http://trac.haskell.org/vty/ticket/19
131−
CHANGELOG.mdnew file+861
@@ -0,0 +1,861 @@
1+
2+5.35.1
3+------
4+
5+Bug fixes:
6+ * Fixed a build issue with a test program.
7+
8+5.35
9+----
10+
11+New features:
12+ * Add support for 24-bit color (thanks @u-quark). This change
13+ updates Vty to look at the `COLORTERM` environment variable that is
14+ conventionally used to advertise support for truecolor escape
15+ sequences. The change also updates the Vty demo to demonstrate
16+ 24-bit colors. This change also adds a new data type, `ColorMode`,
17+ to represent the color mode in use, as well as an `Output` interface
18+ field, `outputColorMode`, to track the active color mode and use it
19+ to clamp emitted color escape sequences to the active color range.
20+
21+API changes:
22+ * All types in `Graphics.Vty.Input.Events` now have strict constructor
23+ fields.
24+ * Internal events are now wrapped in a new `InternalEvent` type to
25+ improve how signal handling is done. This change modifies the `Input`
26+ type's event channel API to produce `InternalEvents`, not `Events`.
27+ The new `InternalEvent` either wraps `Event` with the `InputEvent`
28+ constructor (the previous behavior) or indicates that Vty resumed
29+ after handling a signal using the `ResumeAfterSignal` constructor.
30+ This change avoids the previous use of `EvResize` with lazy exception
31+ arguments as a sentinel value for `ResumeAfterSignal`.
32+
33+Other enhancements:
34+ * Bracketed paste parsing performance has been greatly improved thanks
35+ to benchmarking and optimization work by @iphydf. As part of that
36+ work, Vty now uses bytestrings rather than Strings internally when
37+ parsing input to look for events.
38+ * The `\b` value is now interpreted as `KBS` (thanks @vglfr)
39+
40+5.34
41+----
42+
43+API changes:
44+ * Added an `NFData` instance for `Event` (thanks Mario Lang)
45+ * Removed `Monoid` and `Semigroup` instances for `Attr` and
46+ `MaybeDefault`. This change removed the instances because they were
47+ misbehaved; merging `Attr` and `MaybeDefault` values with these
48+ instances resulted in field value losses. For example, before this
49+ change,
50+```
51+(defAttr `withForeColor` blue) <> (defAttr `withBackColor` green)
52+```
53+ would result in just
54+```
55+ (defAttr `withBackColor` green)
56+```
57+ because the instances were designed to favor the right-hand
58+ arguments' fields even if they had not been explicitly set
59+ (a consequence of the `MaybeDefault` `Semigroup` instance).
60+ While that behavior was sensible specifically in the context of
61+ `Graphics.Vty.Inline`, it wasn't a useful user-facing API and it made
62+ for surprising instance behavior. Since there is actually no good way
63+ to handle this in a `Semigroup` instance for `Attr` -- some choices
64+ have to be made about how to merge two attributes' foreground colors,
65+ and that won't be much better than what we had -- the instance was
66+ just removed.
67+
68+
69+5.33
70+----
71+
72+API changes:
73+* The `Cursor` type got a new `PositionOnly` constructor for cursor
74+ placement without visibility.
75+
76+Package changes:
77+* Relaxed upper bound for `random`
78+* Updated `microlens` bounds to allow 0.4.12
79+
80+Other improvements:
81+* Various hlint-driven improvements (thanks Willem Van Onsem)
82+* The implementation of `color240` was improved (thanks (Willem Van
83+ Onsem)
84+
85+5.32
86+----
87+
88+New features:
89+ * Meta-PageUp and Meta-PageDown are now supported (#193)
90+ * Added `supportsItalics` and `supportsStrikethrough` functions to
91+ check for feature support in terminfo
92+
93+Bug fixes:
94+ * Detect utf-8 mode in `LANG` regardless of case (thanks Emeka
95+ Nkurumeh)
96+
97+5.31
98+----
99+
100+New features and API changes:
101+ * Added support for strikethrough mode. This change adds a new
102+ `strikethrough` `Style` value and uses the `smxx` and `rmxx`
103+ Terminfo capabilities to activate and deactivate strikethrough mode,
104+ respectively. If the terminfo does not report those capabilities,
105+ this style is ignored.
106+ * `Output`: added the `setDisplayBounds` field to set the output
107+ dimensions of the output handle; added an implementation of this for
108+ the `TerminfoBased` backend.
109+
110+Other changes:
111+ * The C prototype for `vty_c_get_window_size` in `gwinsz.h` was fixed.
112+
113+5.30
114+----
115+
116+New features:
117+ * Added `Graphics.Vty.setWindowTitle` to emit an escape
118+ sequence to set the window title, provide the terminal emulator
119+ accepts Xterm-style title sequences. For details, see:
120+ https://tldp.org/HOWTO/Xterm-Title-3.html
121+
122+5.29
123+----
124+
125+API changes:
126+ * The Input type got a new field, 'restoreInputState'. This field
127+ allows the end user to have direct access to the logic needed to
128+ restore the terminal's input state flags. Prior to having this field,
129+ this state restoration logic could only be invoked as part of calling
130+ 'shutdownInput', but since that function does other things (like
131+ killing threads) it is not advisable to call it repeatedly (which is
132+ necessary in the use case this change is intended to support). This
133+ can be called directly to restore the input state flags as needed,
134+ although this is not required if 'shutdown' (or 'shutdownInput') is
135+ called.
136+
137+Other changes:
138+ * attributeControl: explicitly enable the ICRNL terminal mode flag (see
139+ #187 and c572ad).
140+
141+5.28.2
142+------
143+
144+Bug fixes:
145+ * Added a package dependency on `semigroups` for the
146+ `vty-build-width-table` tool on older GHCs (#185)
147+
148+5.28.1
149+------
150+
151+Bug fixes:
152+ * `installUnicodeWidthTable`: use `throwIO`, not `throw`
153+
154+5.28
155+----
156+
157+This release improves Vty's support for multi-column Unicode characters
158+and provides greater compatibility with a wider array of terminal
159+emulators. The following sections summarize the relevant changes, but an
160+overview of the new functionality is motivated and detailed in the new
161+"Multi-Column Character Support" README section. For
162+historical context, please also consider reading over
163+[#175](https://github.com/jtdaugherty/vty/issues/175).
164+
165+API changes:
166+ * New modules were added:
167+ * `Graphics.Vty.UnicodeWidthTable.Types`
168+ * `Graphics.Vty.UnicodeWidthTable.IO`
169+ * `Graphics.Vty.UnicodeWidthTable.Query`
170+ * `Graphics.Vty.UnicodeWidthTable.Install`
171+ * The `Config` type got a new field, `allowCustomUnicodeWidthTables`,
172+ that controls whether `mkVty` will attempt to load a Unicode width
173+ table if specified in the configuration.
174+
175+Configuration file changes:
176+ * A new syntax was added to support specifying Unicode width tables on
177+ a per-`TERM` basis. The syntax is `widthMap <TERM> <PATH>`. See the
178+ documentation for `Graphics.Vty.Config` for details. Since prior
179+ versions of this library will silently ignore any configuration file
180+ lines they cannot parse, this change to user configuration files is
181+ at least non-breaking for older versions of Vty.
182+
183+Other changes:
184+ * The `mkVty` function now automatically attempts to load a custom
185+ Unicode width table if one is specified in the configuration,
186+ provided `allowCustomUnicodeWidthTables` is not set to `Just False`.
187+ See the documentation for `Graphics.Vty.mkVty` for details.
188+ * Vty now includes a command line tool, `vty-build-width-table`, that
189+ queries the terminal emulator to construct a custom Unicode width
190+ table and optionally update the Vty configuration file to use it.
191+ Programs that want to use that tool's functionality may also do so
192+ via the API exposed in the various modules listed above.
193+
194+5.27
195+----
196+
197+* Added `Graphics.Vty.Config.getTtyEraseChar` to support querying the
198+ kernel for the current terminal's settings to obtain the character
199+ assigned by the `stty erase` command. That can then be added to the
200+ Vty configuration's input map to map to `KBS` (backspace) if desired.
201+
202+5.26
203+----
204+
205+* Resolved various import warnings (thanks @glguy)
206+* Removed the `MonadIO` constraint from the Output type's fields and
207+ removed `MonadFail` uses (PR #177, thanks @glguy)
208+* Clarified documentation for ANSI colors (thanks Colby Jenn)
209+* `Graphics.Vty.Attributes` no longer re-exports
210+ `Graphics.Vty.Attributes.Color`
211+* The `Graphics.Vty.Attributes.Color` module is now exposed (thanks
212+ Colby Jenn)
213+* Raised upper bound for `microlens` to 0.4.12 (thanks Artyom Kazak)
214+* Changed from using `System.Posix.Env.getEnv` to
215+ `System.Environment.lookupEnv` (thanks Jonathan Osser)
216+* Added `Graphics.Vty.Image` functions for dealing with character width
217+ computations on `Text` values instead of `Strings`:
218+ * `safeWctwidth`
219+ * `safeWctlwidth`
220+ * `wctwidth`
221+ * `wctlwidth`
222+
223+5.25.1
224+------
225+
226+* Avoided a conflict with a Microlens 0.4.10 operator and added an
227+ upper bound on Microlens of 0.4.11.
228+
229+5.25
230+----
231+
232+* The Vty type got a new field, isShutdown, that returns whether the
233+ Vty handle has had its 'shutdown' function called (thanks Ian
234+ Jeffries)
235+* Vty's shutdown function is now thread-safe.
236+
237+5.24.1
238+------
239+
240+* The "shutdown" method of Vty handles is now idempotent (#159)
241+
242+5.24
243+----
244+
245+* Add Generic and NFData instances for some types
246+* Image: remove custom Show instance, add derived Show and Read
247+ instances
248+* Updated Travis build settings (thanks Eric Mertens)
249+
250+5.23.1
251+------
252+
253+* Fixed a bug where italics did not combine properly with other display
254+ modes (#155, thanks Eric Mertens)
255+
256+5.23
257+----
258+
259+* Added support for italicized output when terminfo supports it. This
260+ takes the form of a new Style, "italic". Note that most terminfo
261+ descriptors do not report capabilities for italics, so support for
262+ this will be very spotty.
263+* Updateed text/string function documentation to indicate that escapes
264+ are not permitted in their inputs.
265+
266+5.22
267+----
268+
269+* Added Graphics.Vty.Attributes.Color240.color240CodeToRGB function
270+ (thanks Brent Carmer)
271+* Added nextEventNonblocking function (field) to Vty type (#87)
272+
273+5.21
274+----
275+
276+* Picture and Background now provide Eq instances (thanks Jaro Reinders)
277+* #145: vty builds with microlens 0.4.9 (thanks Daniel Wagner)
278+* #142: note requirement of threaded RTS
279+
280+5.20
281+----
282+
283+API changes:
284+* Split up Monoid instances into Monoid and Semigroup for newer GHCs
285+ (thanks Ryan Scott)
286+
287+5.19.1
288+------
289+
290+API changes:
291+* Cursor now provides an Eq instance (thanks Jaro Reinders)
292+
293+5.19
294+----
295+
296+API changes:
297+* URL hyperlinking (via 'withURL') is now optional and disabled by
298+ default due to poor support on some common terminals. A new 'Mode'
299+ constructor, 'Hyperlink', has been added to enable this feature. To
300+ change the hyperlinking mode, use 'setMode' on the 'outputIface' of a
301+ Vty handle.
302+
303+5.18.1
304+------
305+
306+Bug fixes:
307+* Reset the hyperlink state on line endings to avoid run-on hyperlinks
308+
309+5.18
310+----
311+
312+API changes:
313+* Added support for hyperlinking attributes (thanks Getty Ritter). This
314+ change adds a new Attr field for containing the hyperlink to apply,
315+ as per https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
316+
317+5.17.1
318+------
319+
320+* withStyle now ignores zero arguments, leaving attribute styles
321+ untouched if the input style is the null style
322+
323+5.17
324+----
325+
326+API changes:
327+* Add support for terminal focus events. This change adds a new mode
328+ usable with setMode, Focus, that requests that the terminal send
329+ events on focus lose/gain. This change also adds two new Event
330+ constructors, EvLostFocus and EvGainedFocus.
331+* No longer enable UTF8 mouse event encoding. This encoding was not
332+ working properly with Terminal.app, and using the other modes (SGR,
333+ etc.) work.
334+* Graphics.Vty.Attributes: escape backticks in Haddock comment (fixes
335+ #131)
336+
337+5.16
338+----
339+
340+API changes:
341+* Added support for mouse wheel events while in mouse mode. The Button
342+ type got two new constructors as a result: BScrollUp and BScrollDown.
343+ Thanks to doublescale@tutanota.com for this contribution!
344+
345+Bug fixes:
346+* charFill now clamps negative arguments to zero (thanks Eric Mertens!)
347+
348+5.15.1
349+------
350+
351+Package changes:
352+* Documentation files are now marked accordingly (thanks Michal
353+ Suchánek)
354+
355+Bug fixes:
356+* translateX/Y: fix negative translations
357+
358+5.15
359+----
360+
361+Package changes:
362+* Discontinued support for GHC versions prior to 7.10.1.
363+* Removed instructions and configuration for Stack builds since they
364+ are no longer supported.
365+* Clarified README mention of (lack of) Windows support (contributors
366+ wanted, though!)
367+* Removed dependency on data-default (see below).
368+
369+API changes:
370+* Moved color definitions from Attributes to Color module.
371+* In lieu of data-default (Default) instances for Attr and Config, use
372+ 'defAttr' and the new 'defaultConfig' (or 'mempty') instead of 'def'.
373+* Graphics.Vty.Output no longer re-exports
374+ Graphics.Vty.Output.Interface.
375+* Removed Graphics.Vty.Prelude module and moved DisplayRegion and its
376+ accessors to Graphics.Vty.Image.
377+* Graphics.Vty.Image no longer re-exports Graphics.Vty.Attributes.
378+* Graphics.Vty.Picture no longer re-exports Graphics.Vty.Image.
379+
380+5.14
381+----
382+
383+* addMaybeClippedJoin: instead of raising an exception when the join
384+ is totally clipped, just reduce the clip amount and continue
385+* addMaybeClipped: skip blit of joins when their primary dimension is
386+ zero
387+* 'string' and related text functions no longer treat an empty string
388+ as an empty image (thanks Chris Penner). This means that now it is
389+ possible to use 'str ""' as a non-empty image with height 1.
390+
391+5.13
392+----
393+
394+* Reverted changes in 5.12 due to disagreements between terminal
395+ emulators and utf8proc; for more details, please see the ticket
396+ discussion at
397+ https://github.com/coreyoconnor/vty/issues/115
398+
399+5.12
400+----
401+
402+* Replaced 'wcwidth' with a call to the utf8proc library's character
403+ width function, which is much more up to date (by several Unicode
404+ versions) and returns the right width for a much larger set of
405+ characters.
406+* Added a bundled version of the utf8proc C library.
407+
408+5.11.3
409+------
410+
411+* Fix mouse event offsets in mouse-up events
412+
413+5.11.2
414+------
415+
416+* Mouse events were modified so that the upper-left corner of the window
417+ is (0,0) rather than (1,1).
418+
419+5.11.1
420+------
421+
422+* Add Generic instance for Image
423+* nextEvent: stop trying to refresh on a resize event (fixes segfault
424+ on refresh with normal cursor positioning mode)
425+* Remove redundant clause from clipForCharWidth (thanks Eric Mertens)
426+* Update maintainer
427+
428+5.11
429+----
430+
431+* Vty now raises a VtyConfigurationError exception when the TERM
432+ evironment variable is missing (thanks Eric Mertens)
433+* Graphics.Vty.Config got an explicit export list to avoid accidentally
434+ exporting internal types (thanks Eric Mertens)
435+
436+5.10
437+----
438+
439+* Add absolute cursor positioning mode AbsoluteCursor to Cursor. This
440+ mode provides greater control over cursor positioning by bypassing
441+ the logical positioning provided by default. Rather than positioning
442+ the cursor by looking at the widths of characters involved, this
443+ constructor lets you provide a physical row and column instead. This
444+ is useful in more sophisticated programs. (thanks Eric Mertens)
445+* Added a new Generic-derived config parser (thanks Eric Mertens)
446+* Fixed the MShift case in the configuration file parser (thanks Eric
447+ Mertens)
448+* Fixed wcwidth import and matched safeWcswidth to its documented
449+ behavior. Previously vty_mk_wcwidth was being imported with the
450+ wrong type causing the -1 return value to be mapped to the wrong Int
451+ value. Additionally safeWcswidth was using the unsafe character width
452+ function and only ensuring that the final result was non-negative.
453+ (thanks Eric Mertens)
454+
455+5.9.1
456+-----
457+
458+* Vty now only emits UTF8 charset sequences in terminals without a
459+ preexisting UTF8 declaration to avoid emitting garbage sequences
460+ (fixes #89)
461+
462+5.9
463+---
464+
465+* Added new Output methods supportsBell and ringTerminalBell to find out
466+ whether the output device has an audio bell and to ring it (see #102)
467+
468+5.8.1
469+-----
470+
471+* Fixed "refresh" to work as advertised (see #104)
472+
473+5.8
474+---
475+
476+* API change: EvPaste input event now provides paste data as a raw
477+ ByteString rather than a String to allow the application to decode how
478+ best to decode it
479+
480+5.7.1
481+-----
482+
483+* ModeDemo: added an explicit Control.Applicative import for older GHCs
484+
485+5.7
486+---
487+
488+* Mouse and paste modes are now off by default.
489+* The Config type got new fields: mouseMode and bracketedPasteMode.
490+ These determine whether these modes are enabled initially (for
491+ terminals that support them).
492+* Added a Mode type for modal terminal features (mouse events,
493+ bracketed paste mode) that is used with new Output interface
494+ functions:
495+ * supportsMode :: Mode -> Bool tells whether the device supports a
496+ mode
497+ * setMode :: Mode -> Bool -> IO () turns a mode on or off
498+ * getModeStatus :: Mode -> IO Bool tells you whether a mode is on or
499+ off
500+* Added a new demo program, ModeDemo.hs, to demonstrate usage of modes
501+
502+5.6
503+---
504+
505+* Added support for normal and extended mouse modes in Xterm-like
506+ terminals via the MouseDown and MouseUp Event constructors
507+* Added support for bracketed paste mode in Xterm-like terminals via
508+ the EvPaste event constructor
509+* Added derived Show instances for Event and Button (thanks Felix
510+ Hirn)
511+* Now TERM values containing "screen" will automatically use the
512+ XtermColor driver rather than just TerminfoBased
513+
514+5.5.0
515+-----
516+
517+* Replaced lens dependency with microlens, microlens-mtl, microlens-th
518+ dependencies. Issue #90
519+ * Thanks Jonathan Daugherty
520+* Cabal corrections.
521+ * Thanks Lennart Spitzner
522+
523+5.4.0
524+-----
525+
526+* Changed eventChannel of Graphics.Vty.Input from Chan to
527+ TChan. This enables clients to query if there are no pending
528+ events. The Graphics.Vty interface nextEvent is unchanged.
529+ Clients that use eventChannel directly will require updating.
530+ https://github.com/coreyoconnor/vty/issues/60
531+
532+5.3.1
533+-----
534+
535+* Reverted cabal file to depend on Cabal >= 1.18 instead of 1.20 due to
536+ possibly breaking this on reasonable GHC versions
537+
538+5.3
539+---
540+
541+* Upgraded QuickCheck dependency to 2.7
542+* The standard IO Config (standardIOConfig) was overriding any provided
543+ application config. In addition, the inputFd and outputFd could not be
544+ changed if mkVty was used. Fixed.
545+* Correct handling of display attributes at end of line. The output
546+ attributes are set to default at the end of content for the line and
547+ at the start of a new line. Previously the current attribute would
548+ extend to the next start of content. This was odd to reason about and
549+ was the cause of https://github.com/coreyoconnor/vty/issues/76 IIRC Yi
550+ requires the old behavior to display the selection region correctly.
551+* shutdown of the input thread is now performed using killThread and
552+ synchronization on an MVar. For correct handling of the terminal read
553+ vmin and vtime the read must be a blocking read on an OS thread.
554+ This places a threadWaitRead, which will be interrupted by the
555+ killThread, prior to the uninterruptable read. An alternative would be
556+ to re-import the read foreign call as interruptable.
557+
558+5.2.11
559+------
560+
561+* deepseq bounds increased for tests.
562+* Clean up warnings when compiling on 7.10
563+ * Thanks Eric Mertens
564+* Avoid discarding input bytes after multi-byte encoded codepoint
565+ * Thanks Eric Mertens
566+
567+5.2.10
568+------
569+
570+* "str" now returns EmptyImage for empty strings to match behavior of
571+ other string-like Image constructors (fixes #74)
572+ * Thanks Jonathan Daugherty
573+
574+5.2.9
575+-----
576+
577+* dependency version bumps
578+ * https://github.com/coreyoconnor/vty/pull/71
579+ * https://github.com/coreyoconnor/vty/pull/70
580+* Correct/Simplify the example code
581+ * Thanks glguy
582+ * https://github.com/coreyoconnor/vty/pull/69
583+
584+5.2.8
585+-----
586+
587+* blaze-builder, lens, utf8-string version constraint bump
588+ * Thanks glguy
589+ * https://github.com/coreyoconnor/vty/pull/67
590+* Do not differentiate based on TERM_PROGRAM
591+ * https://github.com/coreyoconnor/vty/issues/68
592+
593+5.2.7
594+-----
595+
596+* lens and deepseq constraint bump + misc
597+ * Thanks ethercrow
598+ * https://github.com/coreyoconnor/vty/pull/66
599+
600+5.2.6
601+-----
602+
603+* lens constraint bump
604+ * Thanks alexander-b!
605+ * https://github.com/coreyoconnor/vty/pull/64
606+
607+5.2.5
608+-----
609+
610+* lens and random version constraint bump.
611+ * Thanks RyanGlScott!
612+ * https://github.com/coreyoconnor/vty/pull/62
613+
614+5.2.4
615+-----
616+
617+* removed -fpic from cc-options. No longer required.
618+ * https://github.com/coreyoconnor/vty/issues/61
619+ * https://ghc.haskell.org/trac/ghc/ticket/9657
620+ * Thanks Fuuzetsu!
621+
622+5.2.3
623+-----
624+
625+* evaluate/compile the input parsing table once instead of each
626+ keystroke.
627+ * https://github.com/coreyoconnor/vty/pull/59
628+ * Thanks ethercrow!
629+
630+5.2.2
631+-----
632+
633+* When looking at input for an event, don't look too deep.
634+ * https://github.com/coreyoconnor/vty/pull/57
635+ * Thanks ethercrow!
636+
637+5.2.1
638+-----
639+
640+* Bump upper version bound for lens to 4.5. Thanks markus1189!
641+
642+5.2.0
643+-----
644+
645+* Config structure now specifies file descriptor to use. The default
646+ is stdInput and stdOutput file descriptors. Previously Vty used
647+ stdInput for input and the follow code for output:
648+ * hDuplicate stdout >>= handleToFd >>= (`hSetBuffering`
649+ NoBuffering)
650+ * the difference was required by Vty.Inline. Now, Vty.Inline uses
651+ the Config structure options to acheive the same effect.
652+* removed: derivedVtime, derivedVmin, inputForCurrentTerminal,
653+ inputForNameAndIO, outputForCurrentTerminal, outputForNameAndIO
654+* added: inputForConfig, outputForConfig
655+* updates to vty-rogue from jtdaugherty. Thanks!
656+* the oldest version of GHC tested to support vty is 7.6.2.
657+* the oldest version of GHC that vty compiles under is 7.4.2
658+
659+5.1.4
660+-----
661+
662+* merged https://github.com/coreyoconnor/vty/pull/51 thanks trofi!
663+
664+5.1.1
665+-----
666+
667+* merged https://github.com/coreyoconnor/vty/pull/48 thanks sjmielke!
668+* jtdaugherty resolved a number of compiler warnings. Thanks!
669+
670+5.1.0
671+-----
672+
673+* vmin and vtime can be specified however the application requires.
674+ See Graphics.Vty.Config.
675+* fixed the processing of input when vmin is set > 1.
676+
677+5.0.0
678+-----
679+
680+* The naming convention now matches:
681+ * http://www.haskell.org/haskellwiki/Programming_guidelines#Naming_Conventions
682+* all projects using vty for input must be compiled with -threaded.
683+ Please notify vty author if this is not acceptable.
684+* mkVtyEscDelay has been removed. Use "mkVty def". Which initialized
685+ vty with the default configuration.
686+* input handling changes
687+ * KASCII is now KChar
688+ * KPN5 is now KCenter
689+ * tests exist.
690+ * Applications can add to the input tables by setting inputMap of
691+ the Config. See Graphics.Vty.Config
692+ * Users can define input table extensions that will apply to all vty
693+ applications. See Graphics.Vty.Config
694+ * terminal timing is now handled by selecting an appropriate VTIME.
695+ Previously this was implemented within Vty itself. This reduced
696+ complexity in vty but provides a different meta key behavior and
697+ implies a requirement on -threaded.
698+ * The time vty will wait to verify an ESC byte means a single ESC
699+ key is the singleEscPeriod of the Input Config structure.
700+* removed the typeclass based terminal and display context interface
701+ in favor of a data structure of properties interface.
702+* renamed the Terminal interface to Output
703+* The default picture for an image now uses the "clear" background.
704+ This background fills background spans with spaces or just ends the
705+ line.
706+ * Previously the background defaulted to the space character. This
707+ causes issues copying text from a text editor. The text would end
708+ up with extra spaces at the end of the line.
709+* Layer support
710+ * Each layer is an image.
711+ * The layers for a picture are a list of images.
712+ * The first image is the top-most layer. The images are ordered from
713+ top to bottom.
714+ * The transparent areas for a layer are the backgroundFill areas.
715+ backgroundFill is added to pad images when images of different
716+ sizes are joined.
717+ * If the background is clear there is no background layer.
718+ * If there is a background character then the bottom layer is the
719+ background layer.
720+ * emptyPicture is a Picture with no layers and no cursor
721+ * addToTop and addToBottom add a layer to the top and bottom of the
722+ given Picture.
723+* compatibility improvements:
724+ * terminfo based terminals with no cursor support are silently
725+ accepted. The cursor visibility changes in the Picture will have
726+ no effect.
727+ * alternate (setf/setb) color maps supported. Though colors beyond
728+ the first 8 are just a guess.
729+ * added "rgbColor" for easy support of RGB specified colors.
730+ * Both applications and users can add to the mapping used to
731+ translate from input bytes to events.
732+* Additional information about input and output process can be
733+ appended to a debug log
734+ * Set environment variable VTY_DEBUG_LOG to path of debug log
735+ * Or use "debugLog <path>" config directive
736+ * Or set 'debugLog' property of the Config provided to mkVty.
737+* examples moved to vty-examples package. See test directory for cabal
738+ file.
739+ * vty-interactive-terminal-test
740+ * interactive test. Useful for building a bug report for vty's
741+ author.
742+ * test/interactive_terminal_test.hs
743+ * vty-event-echo
744+ * view a input event log for vty. Example of interacting with
745+ user.
746+ * test/EventEcho.hs
747+ * vty-rogue
748+ * The start of a rogue-like game. Example of layers and image
749+ build operations.
750+ * test/Rogue.hs
751+ * vty-benchmark
752+ * benchmarks vty. A series of tests that push random pictures to
753+ the terminal. The random pictures are generated using
754+ QuickCheck. The same generators used in the automated tests.
755+ * test/benchmark.hs
756+
757+4.7.0.0
758+-------
759+
760+API changes:
761+* Added Graphics.Vty.Image.crop: Ensure an image is no larger
762+ than the specified size.
763+* Added Graphics.Vty.Image.pad: Ensure an image is no smaller
764+ than the specified size.
765+* Added Graphics.Vty.Image.translate: Offset an image.
766+* Thanks Ben Boeckel <MathStuf@gmail.com> for these features.
767+
768+4.2.1.0
769+-------
770+
771+API changes:
772+* Attr record accessor fore_color changed to attr_fore_color
773+* Attr record accessor back_color changed to attr_back_color
774+* Attr record accessor style changed to attr_style
775+* Added an "inline" display attribute changing DSL:
776+ * put_attr_change applies a display attribute change
777+ immediately to a terminal
778+ * For instance, can be used to change the display attrbiutes
779+ of text output via putStrLn and putStr. EX:
780+ "put_attr_change $ back_color red" will set the background
781+ color to red.
782+ * Changes do not apply to a Picture output via output_picture.
783+ * See Graphics.Vty.Inline
784+* Moved all IO actions into any monad an instance of MonadIO
785+
786+4.0.0.1
787+-------
788+
789+* binding for mk_wcswidth was incorrect. Most platforms just
790+ magically worked due to coincidence.
791+
792+4.0.0
793+-----
794+
795+API changes:
796+* "getSize" has been removed. Use "terminal vty >>= display_bounds"
797+ where "vty" is an instance of the Vty data structure.
798+* added a "terminal" field to the Vty data structure. Accesses the
799+ TerminalHandle associated with the Vty instance.
800+* Graphics.Vty.Types has undergone a number of changes. Summary:
801+ * Partitioned into Graphics.Vty.Attributes for display attributes.
802+ Graphics.Vty.Image for image combinators. Graphics.Vty.Picture
803+ for final picture construction.
804+* Graphics.Vty.Attributes:
805+ * "setFG" and "setBG" are now "with_fore_color" and
806+ "with_back_color"
807+ * All other "set.." equations similarly replaced.
808+ * "attr" is now "def_attr", short for "default display attributes"
809+ Also added a "current_attr" for "currently applied display
810+ attributes"
811+* Graphics.Vty.Image:
812+ * "horzcat" is now "horiz_cat"
813+ * "vertcat" is now "vert_cat"
814+ * "renderBS" is now "utf8_bytestring"
815+ * "renderChar" is now "char"
816+ * "renderFill" is now "char_fill"
817+ * added a "utf8_string" and "string" (AKA "iso_10464_string") for
818+ UTF-8 encoded Strings and ISO-10464 encoded Strings. String
819+ literals in GHC have an ISO-10464 runtime representation.
820+* Graphics.Vty.Picture:
821+ * exports Graphics.Vty.Image
822+ * "pic" is now "pic_for_image"
823+ * added API for setting background fill pattern.
824+* Completely rewritten output backend.
825+ * Efficient, scanline style output span generator. Has not been
826+ fully optimized, but good enough.
827+ * The details required to display the desired picture on a
828+ terminal are well encapsulated.
829+ * Terminfo based display terminal implementation. With specialized
830+ derivitives for xterm, Terminal.app, and iTerm.app.
831+ * Attempts to robustly handle even terminals that don't
832+ support all display attributes.
833+ * I've tested the following terminals with success: iTerm.app,
834+ Terminal.app, xterm, rxvt, mlterm, Eterm, gnome-terminal,
835+ konsole, screen, linux vty. Hopefully you will be as
836+ successfull.
837+ * Improved unicode support. Double wide characters will display as
838+ expected.
839+* 256 color support. See Graphics.Vty.Attributes.Color240. The actual
840+ output color is adjusted according to the number of colors the
841+ terminal supports.
842+* The Graphics.Vty.Image combinators no longer require matching
843+ dimensions to arguments. Unspecified areas are filled in with a
844+ user-customizable background pattern. See Graphics.Vty.Picture.
845+* output images are always cropped to display size.
846+* Significant code coverage by QuickCheck tests. An interactive test
847+ for those final properties that couldn't be automatically verified.
848+
849+Issues resolved:
850+* "gnome terminal displays non-basic attributes as strikethrough"
851+ * http://trac.haskell.org/vty/ticket/14
852+* "Multi-byte characters are not displayed correctly on update"
853+ * http://trac.haskell.org/vty/ticket/10
854+* "Redraw does not handle rendering a line that extends beyond screen
855+ width characters"
856+ * http://trac.haskell.org/vty/ticket/13
857+* "The <|> and <-> combinators should be more forgiving of mismatched
858+ dimensions"
859+ * http://trac.haskell.org/vty/ticket/9
860+* "256-color support"
861+ * http://trac.haskell.org/vty/ticket/19
LICENSE+2
@@ -1,3 +1,5 @@
1+BSD 3-Clause License
2+
1Copyright Stefan O'Rear 2006, Corey O'Connor 2008, Corey O'Connor 2009
3Copyright Stefan O'Rear 2006, Corey O'Connor 2008, Corey O'Connor 2009
2
4
3All rights reserved.
5All rights reserved.
README.md+159−43
@@ -1,85 +1,201 @@
1−vty is a terminal interface library.
1+[![Build Status](https://travis-ci.org/jtdaugherty/vty.png)](https://travis-ci.org/jtdaugherty/vty)
2
2
3−Project is hosted on github.com: https://github.com/coreyoconnor/vty
3+`vty` is a terminal interface library. It provides a high-level
4+interface for doing terminal I/O. Vty is supported on GHC versions
5+7.10.1 and up.
4
6
7+Install via `git` with:
8+
9+```
5−git clone git://github.com/coreyoconnor/vty.git
10+git clone git://github.com/jtdaugherty/vty.git
11+```
12+
13+Install via `cabal` with:
14+
15+```
16+cabal install vty
17+```
6
18
7# Features
19# Features
8
20
9−* Automatic handling of window resizes.
21+* Supports a large number of terminals, i.e., vt100, ansi, hurd, linux,
22+ `screen`, etc., or anything with a sufficient terminfo entry.
10
23
11−* Supports Unicode characters on output, automatically setting and
24+* Automatically handles window resizes.
12− resetting UTF-8 mode for xterm. Other terminals are assumed to support
13
25
14−* Efficient output.
26+* Supports Unicode output on terminals with UTF-8 support.
15
27
16−* Minimizes repaint area, thus virtually eliminating the flicker
28+* Provides an efficient output algorithm. Output buffering and terminal
17− problem that plagues ncurses programs.
29+ state changes are minimized.
18
30
31+* Minimizes repaint area, which virtually eliminates the flicker
32+ problems that plague ncurses programs.
33+
19−* A pure, compositional interface for efficiently constructing display
34+* Provides a pure, compositional interface for efficiently constructing
20− images.
35+ display images.
21
36
22* Automatically decodes keyboard keys into (key,[modifier]) tuples.
37* Automatically decodes keyboard keys into (key,[modifier]) tuples.
23
38
24* Automatically supports refresh on Ctrl-L.
39* Automatically supports refresh on Ctrl-L.
25
40
26−* Automatically supports timeout after 50ms for lone ESC (a barely
41+* Supports a keypress timeout after for lone ESC. The timeout is
27− noticable delay)
42+ customizable.
28
43
29−* Interface is designed for relatively easy compatible extension.
44+* Provides extensible input and output interfaces.
30
45
31−* Supports all ANSI SGR-modes (defined in console_codes(4)) with
46+* Supports ANSI graphics modes (SGR as defined in `console_codes(4)`)
32− a type-safe interface.
47+ with a type-safe interface and graceful fallback for terminals
48+ with limited or nonexistent support for such modes.
33
49
34−* Properly handles cleanup.
50+* Properly handles cleanup (but not due to signals).
35
51
36−# Known Issues
52+* Provides a comprehensive test suite.
37
53
38−* The character encoding of the output terminal is assumed to be UTF-8.
54+* Supports "normal" and "extended" (SGR) mouse modes as described at
55+ http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
39
56
40−* Minimal support for special keys on terminals other than the
57+* Supports bracketed paste mode as described at
41− linux-console. (F1-5 and arrow keys should work, but anything
58+ http://cirw.in/blog/bracketed-paste
42− shifted isn't likely to.)
43
59
44−* Uses the TIOCGWINSZ ioctl to find the current window size, which
60+* Supports multi-column Unicode characters such as emoji characters. In
45− appears to be limited to Linux and *BSD.
61+ cases where Vty and your terminal emulator disagree on character
62+ widths, Vty provides a tool `vty-build-width-table` and library
63+ functionality to build a width table that will work for your terminal
64+ and load it on application startup.
65+
66+# Development Notes
67+
68+Vty uses threads internally, so programs made with Vty need to be
69+compiled with the threaded runtime using the GHC `-threaded` option.
46
70
47# Platform Support
71# Platform Support
48
72
49## Posix Terminals
73## Posix Terminals
50
74
51−Uses terminfo to determine terminal protocol. Some special rules for Mac terminal applications. The
75+For the most part, Vty uses `terminfo` to determine terminal protocol
52−special rules might be invalid on newer Mac OS.
76+with some special rules to handle some omissions from `terminfo`.
53
77
54## Windows
78## Windows
55
79
56−None!
80+Windows is not supported.
81+
82+# Multi-Column Character Support
83+
84+Vty supports rendering of multi-column characters such as two-column
85+Asian characters and Emoji characters. This section details how to
86+take advantage of this feature, since its behavior will depend on the
87+terminal emulator in use.
88+
89+Terminal emulators support Unicode to varying degrees, and each terminal
90+emulator relies on a table of column widths for each supported Unicode
91+character. Vty also needs to rely on such a table to compute the width
92+of Vty images to do image layout. Since those tables can disagree if
93+Vty and the terminal emulator support different versions of Unicode,
94+and since different terminal emulators will support different versions
95+of Unicode, it's likely that for some wide characters, Vty applications
96+will exhibit rendering problems. Those rendering problems arise from Vty
97+and the terminal emulator coming to different conclusions about how wide
98+some characters are.
99+
100+To address this, Vty supports loading custom character width tables
101+that are based on the terminal's behavior in order to eliminate these
102+disagreements. By default, though, Vty will use its built-in Unicode
103+character width table. Since the built-in table is likely to eventually
104+disagree with your terminal, Vty provides an API and a command-line tool
105+to generate and install custom tables.
106+
107+Custom Unicode width tables based on your terminal emulator can be
108+built by running Vty's built-in tool, `vty-build-width-table`. The tool
109+works by querying the current terminal emulator to obtain its width
110+measurements for the entire supported Unicode range. The
111+results are then saved to a disk file. These custom tables
112+can also be generated programmatically by using the API in
113+`Graphics.Vty.UnicodeWidthTable.Query`.
114+
115+Saved width tables can then be loaded in one of two ways:
116+
117+* Via the library API in `Graphics.Vty.UnicodeWidthTable.IO`
118+* By adding a `widthMap` directive to your Vty configuration file and
119+ then invoking `mkVty` to initialize Vty
120+
121+The Vty configuration file supports the `widthMap` directive to allow
122+users to specify which custom width table should be loaded for a given
123+terminal type. This is done by specifying, e.g.,
124+
125+```
126+widthMap "xterm" "/path/to/map.dat"
127+```
128+
129+where the first argument is the value that `TERM` must have in order for
130+the table to be loaded, and the second argument is the path to the table
131+file itself as generated by the two alternatives listed above. If the
132+Vty configuration file contains multiple matching `widthMap` directives
133+for the current value of `TERM`, the last one listed in the file is
134+used.
135+
136+The tables declared in the configuration file are only ever
137+automatically loaded when applications set up Vty by calling
138+`Graphics.Vty.mkVty`.
139+
140+Before a custom table has been loaded, calls to the library's character
141+width functions (e.g. `wcwidth`) will use the default built-in table.
142+Once a custom table has been loaded, the functions will use the new
143+custom table. Only one custom table load can be performed in a Vty
144+program. Once a custom table has been loaded, it cannot be replaced or
145+removed.
146+
147+Without using a custom width table, users of Vty-based applications
148+are likely to eventually experience rendering problems with with wide
149+characters. We recommend that developers of Vty-based applications either:
150+
151+* Provide the `vty-build-width-table` tool and documentation for running
152+ it and updating the Vty configuration, or
153+* Have the application invoke the Vty library's table-building
154+ functionality and load the table at startup without using the Vty
155+ configuration.
156+
157+The best option will depend on a number of factors: the user audience,
158+the amount of risk posed by wide character rendering, the terminal
159+emulators in use, etc.
160+
161+# Contributing
162+
163+If you decide to contribute, that's great! Here are some guidelines you
164+should consider to make submitting patches easier for all concerned:
165+
166+ - If you want to take on big things, talk to me first; let's have a
167+ design/vision discussion before you start coding. Create a GitHub
168+ issue and we can use that as the place to hash things out.
169+ - If you make changes, make them consistent with the syntactic
170+ conventions already used in the codebase.
171+ - Please provide Haddock documentation for any changes you make.
57
172
58−# Development Notes
173+# Known Issues
59
174
60−## Coverage
175+* Terminals have numerous quirks and bugs, so mileage may vary. Please
176+ report issues as you encounter them and provide details on your
177+ terminal emulator, operating system, etc.
61
178
62−Profiling appears to cause issues with coverage when enabled. To evaluate coverage configure as
179+* STOP, TERM and INT signals are not handled.
63−follows:
64
180
65−~~~
181+* The character encoding of the terminal is assumed to be UTF-8 if
66−rm -rf dist ; cabal configure --enable-tests --enable-library-coverage \
182+ unicode is used.
67− --disable-library-profiling \
68− --disable-executable-profiling
69−~~~
70
183
71−## Profiling
184+* Terminfo is assumed to be correct unless there is an override
185+ configured. Some terminals will not have correct special key support
186+ (shifted F10 etc). See `Config` for customizing vty's behavior for a
187+ particular terminal.
72
188
189+* Vty uses the `TIOCGWINSZ` ioctl to find the current window size, which
190+ appears to be limited to Linux and BSD.
73
191
74−~~~
192+# Further Reading
75−rm -rf dist ; cabal configure --enable-tests --disable-library-coverage \
76− --enable-library-profiling \
77− --enable-executable-profiling
78−~~~
79
193
80−# References
194+Good sources of documentation for terminal programming are:
81
195
82* https://github.com/b4winckler/vim/blob/master/src/term.c
196* https://github.com/b4winckler/vim/blob/master/src/term.c
83* http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
197* http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
84* http://ulisse.elettra.trieste.it/services/doc/serial/config.html
198* http://ulisse.elettra.trieste.it/services/doc/serial/config.html
85* http://www.leonerd.org.uk/hacks/hints/xterm-8bit.html
199* http://www.leonerd.org.uk/hacks/hints/xterm-8bit.html
200+* http://www.unixwiz.net/techtips/termios-vmin-vtime.html
201+* http://vt100.net/docs/vt100-ug/chapter3.html
TODOdeleted−15
@@ -1,15 +0,0 @@
1−- Improve input handling
2− - base off of haskeline input system. The haskeline input system appears to be excellent and
3− satisfy all of Vty's input requirements. The current haskeline distribution does not appear to
4− export the required modules. Either:
5− 0. Add the required exports to the haskeline distribution.
6− - fine for development but complicates the UI for production clients. Though, exposing
7− the modules would only complicate the appearance of haskeline's interface.
8− 1. Partition the backend of haskeline into a separate package usable by both vty and
9− haskeline.
10−- use compact-string for character encoding handling
11−- Custom cursor appearance handling?
12− - specific color?
13− - reverse video?
14− - auto?
15−
cbits/get_tty_erase.cnew file+18
@@ -0,0 +1,18 @@
1+#include <termios.h>
2+#include <stdio.h>
3+#include <unistd.h>
4+#include <stdlib.h>
5+
6+// Given a file descriptor for a terminal, get the ERASE character for
7+// the terminal. If the terminal info cannot be obtained, this returns
8+// zero.
9+char vty_get_tty_erase(int fd)
10+{
11+ struct termios trm;
12+
13+ if (0 == tcgetattr(fd, &trm)) {
14+ return (char) trm.c_cc[VERASE];
15+ } else {
16+ return (char) 0;
17+ }
18+}
cbits/gwinsz.c+15−6
@@ -1,9 +1,18 @@
1#include <sys/ioctl.h>
1#include <sys/ioctl.h>
2
2
3−unsigned long vty_c_get_window_size(void) {
3+unsigned long vty_c_get_window_size(int fd) {
4− struct winsize w;
4+ struct winsize w;
5− if (ioctl (0, TIOCGWINSZ, &w) >= 0)
5+ if (ioctl (fd, TIOCGWINSZ, &w) >= 0)
6− return (w.ws_row << 16) + w.ws_col;
6+ return (w.ws_row << 16) + w.ws_col;
7− else
7+ else
8− return 0x190050;
8+ return 0x190050;
9+}
10+
11+void vty_c_set_window_size(int fd, unsigned long val) {
12+ struct winsize w;
13+ if (ioctl(fd, TIOCGWINSZ, &w) >= 0) {
14+ w.ws_row = val >> 16;
15+ w.ws_col = val & 0xFFFF;
16+ ioctl(fd, TIOCSWINSZ, &w);
17+ }
9}
18}
cbits/gwinsz.h+2−1
@@ -1 +1,2 @@
1−unsigned long vty_c_get_window_size(void);
1+unsigned long vty_c_get_window_size(int fd);
2+unsigned long vty_c_set_window_size(int fd, unsigned long val);
cbits/mk_wcwidth.c+126−97
@@ -59,7 +59,29 @@
59 * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
59 * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
60 */
60 */
61
61
62−#include <wchar.h>
62+#include <HsFFI.h>
63+#include <stdlib.h>
64+#include <string.h>
65+
66+// The maximum size for a custom character width table is the total
67+// number of possible characters as dictated by the compiler.
68+#define MAX_CUSTOM_TABLE_SIZE (HS_CHAR_MAX + 1)
69+
70+// The pointer to the start of the custom character width table, if
71+// any. If this is NULL or this is set but the ready flag is false, the
72+// built-in tree search logic is used.
73+static uint8_t* custom_table = NULL;
74+
75+// The size of the custom table, in entries. This should only be set
76+// if custom_table is not NULL. Its value should be the size of the
77+// custom_table array.
78+static uint32_t custom_table_size = 0;
79+
80+// A flag indicating whether the custom table is ready for
81+// use. This should only be set once the table has been
82+// allocated with vty_init_custom_table and initialized with
83+// vty_set_custom_table_range.
84+static uint8_t custom_table_ready = 0;
63
85
64struct interval {
86struct interval {
65 int first;
87 int first;
@@ -67,7 +89,7 @@ struct interval {
67};
89};
68
90
69/* auxiliary function for binary search in interval table */
91/* auxiliary function for binary search in interval table */
70−static int vty_bisearch(wchar_t ucs, const struct interval *table, int max) {
92+static int vty_bisearch(HsChar ucs, const struct interval *table, int max) {
71 int min = 0;
93 int min = 0;
72 int mid;
94 int mid;
73
95
@@ -119,7 +141,7 @@ static int vty_bisearch(wchar_t ucs, const struct interval *table, int max) {
119 * in ISO 10646.
141 * in ISO 10646.
120 */
142 */
121
143
122−int vty_mk_wcwidth(wchar_t ucs)
144+static HsInt builtin_wcwidth(HsChar ucs)
123{
145{
124 /* sorted list of non-overlapping intervals of non-spacing characters */
146 /* 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" */
147 /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
@@ -182,7 +204,7 @@ int vty_mk_wcwidth(wchar_t ucs)
182
204
183 /* binary search in table of non-spacing characters */
205 /* binary search in table of non-spacing characters */
184 if (vty_bisearch(ucs, combining,
206 if (vty_bisearch(ucs, combining,
185− sizeof(combining) / sizeof(struct interval) - 1))
207+ sizeof(combining) / sizeof(struct interval) - 1))
186 return 0;
208 return 0;
187
209
188 /* if we arrive here, ucs is not a combining or C0/C1 control character */
210 /* if we arrive here, ucs is not a combining or C0/C1 control character */
@@ -203,107 +225,114 @@ int vty_mk_wcwidth(wchar_t ucs)
203 (ucs >= 0x30000 && ucs <= 0x3fffd)));
225 (ucs >= 0x30000 && ucs <= 0x3fffd)));
204}
226}
205
227
206−
228+// Return the width, in terminal cells, of the specified character.
207−int vty_mk_wcswidth(const wchar_t *pwcs, size_t n)
229+//
230+// If the global custom width table is present, that table will be
231+// consulted for the character's width. If the character is not in
232+// the table, zero will be returned. If the custom width table is not
233+// present, the built-in width table will be used.
234+HsInt vty_mk_wcwidth(HsChar ch)
208{
235{
209− int w, width = 0;
236+ if (custom_table_ready) {
210−
237+ if ((ch >= 0) && (ch < custom_table_size)) {
211− for (;n-- > 0; pwcs++)
238+ return custom_table[ch];
212− if ((w = vty_mk_wcwidth(*pwcs)) < 0)
239+ } else {
213− return -1;
240+ return -1;
241+ }
214− else
242+ } else {
215− width += w;
243+ return builtin_wcwidth(ch);
216−
244+ }
217− return width;
218}
245}
219
246
220−
247+// Initialize a custom character width table.
221−/*
248+//
222− * The following functions are the same as mk_wcwidth() and
249+// This allocates a new character width table of the specified size
223− * mk_wcswidth(), except that spacing characters in the East Asian
250+// (in characters). If a custom table has already been allocated, this
224− * Ambiguous (A) category as defined in Unicode Technical Report #11
251+// returns 1. Otherwise it allocates a new table, initializes all of its
225− * have a column width of 2. This variant might be useful for users of
252+// entries to 1, and returns zero.
226− * CJK legacy encodings who want to migrate to UCS without changing
253+//
227− * the traditional terminal character-width behaviour. It is not
254+// Note that this does *not* mark the table as ready for use. Until the
228− * otherwise recommended for general use.
255+// table is marked ready, it will not be used by vty_mk_wcwidth. To mark
229− */
256+// the table as ready, call vty_activate_custom_table() after the table
230−int vty_mk_wcwidth_cjk(wchar_t ucs)
257+// has been set up with calls to vty_set_custom_table_range.
258+int vty_init_custom_table(int size)
231{
259{
232− /* sorted list of non-overlapping intervals of East Asian Ambiguous
260+ if (custom_table == NULL) {
233− * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
261+ if (size > 0 && size <= MAX_CUSTOM_TABLE_SIZE) {
234− static const struct interval ambiguous[] = {
262+ custom_table_ready = 0;
235− { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
263+ custom_table = malloc(size);
236− { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
264+ memset(custom_table, 1, size);
237− { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
265+ custom_table_size = size;
238− { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
266+ return 0;
239− { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
267+ } else {
240− { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
268+ return 1;
241− { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
269+ }
242− { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
270+ } else {
243− { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
271+ return 1;
244− { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
272+ }
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 (vty_bisearch(ucs, ambiguous,
291− sizeof(ambiguous) / sizeof(struct interval) - 1))
292− return 2;
293−
294− return vty_mk_wcwidth(ucs);
295}
273}
296
274
275+// Set the specified character range in the custom width table to the
276+// specified width.
277+//
278+// This function sets 'width' as the character width for all entries
279+// in the custom character table starting at the 'start' entry and
280+// including all entries up to and including 'start + size - 1'.
281+//
282+// If this succeeds, it returns zero. If it fails, it returns 1. It
283+// fails if the table is not allocated, marked as ready (i.e. it is in
284+// use and has already been populated), or if the start or size values
285+// are not in bounds for the table.
286+int vty_set_custom_table_range(uint32_t start, uint32_t size, uint8_t width)
287+{
288+ if ((custom_table == NULL) ||
289+ (size >= custom_table_size) ||
290+ (start >= custom_table_size) ||
291+ ((start + 1) >= (custom_table_size - size)) ||
292+ custom_table_ready) {
293+ return 1;
294+ } else {
295+ memset(custom_table + start, width, size);
296+ return 0;
297+ }
298+}
297
299
298−int vty_mk_wcswidth_cjk(const wchar_t *pwcs, size_t n)
300+// Mark the allocated custom character width table as ready for use.
301+//
302+// After this call, further calls to vty_set_custom_table_range will
303+// fail.
304+//
305+// This function returns 0 if it succeeds. If it fails, it returns 1.
306+// It fails if the custom table is already ready or if it has not been
307+// allocated.
308+int vty_activate_custom_table()
299{
309{
300− int w, width = 0;
310+ if (custom_table_ready || (custom_table == NULL)) {
311+ return 1;
312+ } else {
313+ custom_table_ready = 1;
314+ return 0;
315+ }
316+}
301
317
302− for (;n-- > 0; pwcs++)
318+// Returns whether a custom character width table has been marked ready.
303− if ((w = vty_mk_wcwidth_cjk(*pwcs)) < 0)
319+int vty_custom_table_ready()
304− return -1;
320+{
305− else
321+ return custom_table_ready;
306− width += w;
322+}
307
323
308− return width;
324+// Deallocate the custom width table.
325+//
326+// This does nothing if there is no allocated custom width table, or if
327+// there is one but it is in use (marked ready). This is only useful if
328+// an initial allocation succeeds, but range population fails, after
329+// which point the application may want to deallocate the table to avoid
330+// leaving it in an intermediate state.
331+void vty_deallocate_custom_table()
332+{
333+ if ((custom_table != NULL) && (!custom_table_ready)) {
334+ free(custom_table);
335+ custom_table = NULL;
336+ custom_table_size = 0;
337+ }
309}
338}
cbits/set_term_timing.c+5−6
@@ -3,12 +3,11 @@
3#include <unistd.h>
3#include <unistd.h>
4#include <stdlib.h>
4#include <stdlib.h>
5
5
6−void vty_set_term_timing(void)
6+void vty_set_term_timing(int fd, int vmin, int vtime)
7{
7{
8 struct termios trm;
8 struct termios trm;
9− tcgetattr(STDIN_FILENO, &trm);
9+ tcgetattr(fd, &trm);
10− trm.c_cc[VMIN] = 0;
10+ trm.c_cc[VMIN] = vmin;
11− trm.c_cc[VTIME] = 0;
11+ trm.c_cc[VTIME] = vtime;
12− tcsetattr(STDIN_FILENO, TCSANOW, &trm);
12+ tcsetattr(fd, TCSANOW, &trm);
13}
13}
14−
ci/build-and-testnew file+44
@@ -0,0 +1,44 @@
1+#!/usr/bin/env bash
2+set -ex
3+
4+source /etc/profile
5+
6+GHCVER=`ghc --version | cut -d' ' -f8`
7+
8+function init-sandbox() {
9+ cabal sandbox init
10+}
11+
12+function build() {
13+ if [[ "${GHCVER}" > "7.4.2" ]] ; then
14+ cabal install --enable-tests --only-dependencies --force-reinstalls
15+ else
16+ echo "Skipping tests on ${GHCVER}"
17+ cabal install --only-dependencies --force-reinstalls
18+ fi
19+}
20+
21+function rebuild() {
22+ rm -rf dist
23+ build
24+}
25+
26+function rebuild-including-deps() {
27+ rm -rf .cabal-sandbox
28+ rm -rf cabal.sandbox.config
29+ init-sandbox
30+ build
31+}
32+
33+set -ex
34+
35+cd vty
36+if [[ ! -f cabal.sandbox.config ]] ; then
37+ init-sandbox
38+fi
39+
40+build || rebuild || rebuild-including-deps
41+
42+if [[ "${GHCVER}" > "7.4.2" ]] ; then
43+ cabal test
44+fi
ci/travis_ci_installnew file+12
@@ -0,0 +1,12 @@
1+#!/bin/bash
2+set -ex
3+cabal --version
4+
5+GHC_IS_OLD=$(python -c "from distutils.version import StrictVersion as SV; print '$GHCVER' != 'head' and SV('$GHCVER') < SV('7.2.3')")
6+
7+if [[ "False" == "$GHC_IS_OLD" ]] ; then
8+ /opt/cabal/${CABALVER}/bin/cabal install --only-dependencies --enable-tests
9+else
10+ /opt/cabal/${CABALVER}/bin/cabal install --only-dependencies
11+ echo "Skipping test dependency install on ${GHCVER}"
12+fi
ci/travis_ci_scriptnew file+11
@@ -0,0 +1,11 @@
1+#!/bin/bash
2+set -ex
3+
4+GHC_IS_OLD=$(python -c "from distutils.version import StrictVersion as SV; print '$GHCVER' != 'head' and SV('$GHCVER') < SV('7.2.3')")
5+
6+if [[ "False" == "$GHC_IS_OLD" ]] ; then
7+ /opt/cabal/${CABALVER}/bin/cabal configure --enable-tests && /opt/cabal/${CABALVER}/bin/cabal build && /opt/cabal/${CABALVER}/bin/cabal test
8+else
9+ echo "Skipping tests on ${GHCVER}"
10+ /opt/cabal/${CABALVER}/bin/cabal configure && /opt/cabal/${CABALVER}/bin/cabal build
11+fi
demos/Demo.hsnew file+90
@@ -0,0 +1,90 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+module Main where
3+
4+import Graphics.Vty
5+
6+import Control.Applicative hiding ((<|>))
7+import Control.Arrow
8+import Control.Monad.RWS
9+
10+import Data.Sequence (Seq, (<|) )
11+import qualified Data.Sequence as Seq
12+import Data.Foldable
13+
14+eventBufferSize = 1000
15+
16+type App = RWST Vty () (Seq String) IO
17+
18+main = do
19+ vty <- if True -- change to false for emacs-like input processing
20+ then mkVty defaultConfig
21+ else mkVty (defaultConfig { vmin = Just 2, vtime = Just 300 } )
22+ _ <- execRWST (vtyInteract False) vty Seq.empty
23+ shutdown vty
24+
25+vtyInteract :: Bool -> App ()
26+vtyInteract shouldExit = do
27+ updateDisplay
28+ unless shouldExit $ handleNextEvent >>= vtyInteract
29+
30+introText = vertCat $ map (string defAttr)
31+ [ "this line is hidden by the top layer"
32+ , "The vty demo program will echo the events generated by the pressed keys."
33+ , "Below there is a 240 color box."
34+ , "Followed by a description of the 16 color palette."
35+ , "Followed by tones of red using a 24-bit palette."
36+ , "If the 240 color box is not visible then the terminal"
37+ , "claims 240 colors are not supported."
38+ , "Try setting TERM to xterm-256color"
39+ , "This text is on a lower layer than the event list."
40+ , "Which means it'll be hidden soon."
41+ , "Bye!"
42+ , "Great Faith in the ¯\\_(ツ)_/¯"
43+ , "¯\\_(ツ)_/¯ ¯\\_(ツ)_/¯ ¯\\_(ツ)_/¯ ¯\\_(ツ)_/¯"
44+ ]
45+
46+splitColorImages :: [Image] -> [[Image]]
47+splitColorImages [] = []
48+splitColorImages is = (take 20 is ++ [string defAttr " "]) : (splitColorImages (drop 20 is))
49+
50+fullcolorbox :: Image
51+fullcolorbox = vertCat $ map horizCat $ splitColorImages colorImages
52+ where
53+ colorImages = map (\i -> string (currentAttr `withBackColor` linearColor i 0 0) " ") [0..255]
54+
55+colorbox_240 :: Image
56+colorbox_240 = vertCat $ map horizCat $ splitColorImages colorImages
57+ where
58+ colorImages = map (\i -> string (currentAttr `withBackColor` Color240 i) " ") [0..239]
59+
60+colorbox_16 :: Image
61+colorbox_16 = border <|> column0 <|> border <|> column1 <|> border <|> column2 <|> border
62+ where
63+ column0 = vertCat $ map lineWithColor normal
64+ column1 = vertCat $ map lineWithColor bright
65+ border = vertCat $ replicate (length normal) $ string defAttr " | "
66+ column2 = vertCat $ map (string defAttr . snd) normal
67+ lineWithColor (c, cName) = string (defAttr `withForeColor` c) cName
68+ normal = zip [ black, red, green, yellow, blue, magenta, cyan, white ]
69+ [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]
70+ bright = zip [ brightBlack, brightRed, brightGreen, brightYellow, brightBlue
71+ , brightMagenta, brightCyan, brightWhite ]
72+ [ "bright black", "bright red", "bright green", "bright yellow"
73+ , "bright blue", "bright magenta", "bright cyan", "bright white" ]
74+
75+updateDisplay :: App ()
76+updateDisplay = do
77+ let info = string (defAttr `withForeColor` black `withBackColor` green)
78+ "Press ESC to exit. Events for keys below."
79+ eventLog <- foldMap (string defAttr) <$> get
80+ let pic = picForImage (info <-> eventLog)
81+ `addToBottom` (introText <-> colorbox_240 <|> colorbox_16 <|> fullcolorbox)
82+ vty <- ask
83+ liftIO $ update vty pic
84+
85+handleNextEvent = ask >>= liftIO . nextEvent >>= handleEvent
86+ where
87+ handleEvent e = do
88+ modify $ (<|) (show e) >>> Seq.take eventBufferSize
89+ return $ e == EvKey KEsc []
90+
demos/ModeDemo.hsnew file+55
@@ -0,0 +1,55 @@
1+module Main where
2+
3+import Control.Applicative
4+import Data.Monoid
5+import Foreign.C.Types (CInt(..), CChar(..))
6+import System.Posix.Types (Fd(..))
7+
8+import Graphics.Vty
9+
10+mkUI :: (Bool, Bool, Bool, Bool) -> Maybe Event -> Image
11+mkUI (m, ms, p, ps) e =
12+ vertCat [ string defAttr $ "Mouse mode supported: " <> show m
13+ , string defAttr $ "Mouse mode status: " <> show ms
14+ , string defAttr " "
15+ , string defAttr $ "Paste mode supported: " <> show p
16+ , string defAttr $ "Paste mode status: " <> show ps
17+ , string defAttr " "
18+ , string defAttr $ "Last event: " <> show e
19+ , string defAttr " "
20+ , string defAttr "Press 'm' to toggle mouse mode, 'p' to toggle paste mode, and 'q' to quit."
21+ ]
22+
23+main :: IO ()
24+main = do
25+ cfg <- standardIOConfig
26+ vty <- mkVty cfg
27+
28+ let renderUI lastE = do
29+ let output = outputIface vty
30+ info <- (,,,) <$> (pure $ supportsMode output Mouse)
31+ <*> getModeStatus output Mouse
32+ <*> (pure $ supportsMode output BracketedPaste)
33+ <*> getModeStatus output BracketedPaste
34+ return $ picForImage $ mkUI info lastE
35+
36+ let go lastE = do
37+ pic <- renderUI lastE
38+ update vty pic
39+ e <- nextEvent vty
40+ case e of
41+ EvKey (KChar 'q') [] -> return ()
42+ EvKey (KChar 'm') [] -> do
43+ let output = outputIface vty
44+ enabled <- getModeStatus output Mouse
45+ setMode output Mouse (not enabled)
46+ go (Just e)
47+ EvKey (KChar 'p') [] -> do
48+ let output = outputIface vty
49+ enabled <- getModeStatus output BracketedPaste
50+ setMode output BracketedPaste (not enabled)
51+ go (Just e)
52+ _ -> go (Just e)
53+
54+ go Nothing
55+ shutdown vty
DESIGN → docs/DESIGNrenamed

The content is the same.

scripts/256colres.pl+3−3
@@ -59,9 +59,9 @@ import Text.Printf
59--
59--
60-- generated from 256colres.pl which is forked from xterm 256colres.pl
60-- generated from 256colres.pl which is forked from xterm 256colres.pl
61-- todo: all values get clamped high.
61-- todo: all values get clamped high.
62−rgb_color :: Integral i => i -> i -> i -> Color
62+rgbColor :: Integral i => i -> i -> i -> Color
63−rgb_color r g b
63+rgbColor r g b
64− | r < 0 && g < 0 && b < 0 = error "rgb_color with negative color component intensity"
64+ | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"
65EOF
65EOF
66
66
67my $grey_line=" | r == %d && g == %d && b == %d = Color240 %d\n";
67my $grey_line=" | r == %d && g == %d && b == %d = Color240 %d\n";
src/Codec/Binary/UTF8/Debug.hs+8−10
@@ -1,7 +1,6 @@
1−{-# LANGUAGE NoMonomorphismRestriction #-}
1+{-# OPTIONS_HADDOCK hide #-}
2-- Copyright 2009 Corey O'Connor
2-- Copyright 2009 Corey O'Connor
3−module Codec.Binary.UTF8.Debug
3+module Codec.Binary.UTF8.Debug where
4− where
5
4
6import Codec.Binary.UTF8.String ( encode )
5import Codec.Binary.UTF8.String ( encode )
7
6
@@ -9,11 +8,10 @@ import Data.Word
9
8
10import Numeric
9import Numeric
11
10
12−-- | Converts an array of ISO-10646 characters (Char type) to an array of Word8 bytes that is the
11+-- | Converts an array of ISO-10646 characters (Char type) to an array
13−-- corresponding UTF8 byte sequence
12+-- of Word8 bytes that is the corresponding UTF8 byte sequence
14−utf8_from_iso :: [Int] -> [Word8]
13+utf8FromIso :: [Int] -> [Word8]
15−utf8_from_iso = encode . map toEnum
14+utf8FromIso = encode . map toEnum
16−
17−pp_utf8 :: [Int] -> IO ()
18−pp_utf8 = print . map (\f -> f "") . map showHex . utf8_from_iso
19
15
16+ppUtf8 :: [Int] -> IO ()
17+ppUtf8 = print . map (`showHex` "") . utf8FromIso
src/Data/Marshalling.hsdeleted−30
@@ -1,30 +0,0 @@
1−{-# LANGUAGE BangPatterns #-}
2−-- Copyright 2009 Corey O'Connor
3−module Data.Marshalling ( module Data.Marshalling
4− , module Data.Word
5− , module Foreign.Ptr
6− , module Foreign.ForeignPtr
7− , module Foreign.Marshal
8− , module Foreign.Storable
9− )
10− where
11−
12−import Control.Monad.Trans
13−
14−import Data.Word
15−
16−import Foreign.Ptr
17−import Foreign.ForeignPtr
18−import Foreign.Marshal
19−import Foreign.Storable
20−
21−type OutputBuffer = Ptr Word8
22−
23−string_to_bytes :: String -> [Word8]
24−string_to_bytes str = map (toEnum . fromEnum) str
25−
26−serialize_bytes :: MonadIO m => [Word8] -> OutputBuffer -> m OutputBuffer
27−serialize_bytes bytes !out_ptr = do
28− liftIO $! pokeArray out_ptr bytes
29− return $! out_ptr `plusPtr` ( length bytes )
30−
src/Data/Sequence/Extra.hs+3−2
@@ -1,3 +1,4 @@
1+{-# OPTIONS_HADDOCK hide #-}
1-- Copyright 2009 Corey O'Connor
2-- Copyright 2009 Corey O'Connor
2module Data.Sequence.Extra where
3module Data.Sequence.Extra where
3
4
@@ -6,7 +7,7 @@ import Data.Sequence
6import Control.Parallel.Strategies
7import Control.Parallel.Strategies
7
8
8instance NFData a => NFData (Seq a) where
9instance NFData a => NFData (Seq a) where
9− rnf = \v -> rnf' (viewl v)
10+ rnf = rnf' . viewl
10− where
11+ where
11 rnf' EmptyL = ()
12 rnf' EmptyL = ()
12 rnf' (a :< r) = rnf a >| rnf' (viewl r)
13 rnf' (a :< r) = rnf a >| rnf' (viewl r)
src/Data/Terminfo/Eval.hs+97−213
@@ -1,248 +1,132 @@
1−{-# LANGUAGE MagicHash #-}
2{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE RecordWildCards #-}
4−{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE NoMonomorphismRestriction #-}
2{-# LANGUAGE NoMonomorphismRestriction #-}
6−{-# LANGUAGE NamedFieldPuns #-}
7{-# OPTIONS_GHC -funbox-strict-fields #-}
3{-# OPTIONS_GHC -funbox-strict-fields #-}
4+{-# OPTIONS_HADDOCK hide #-}
5+
8−{- Evaluates the paramaterized terminfo string capability with the given parameters.
6+-- | Evaluates the paramaterized terminfo string capability with the
9− -
7+-- given parameters.
10− - todo: This can be greatly simplified.
11− -}
12−module Data.Terminfo.Eval ( cap_expression_required_bytes
8+module Data.Terminfo.Eval
13− , serialize_cap_expression
9+ ( writeCapExpr
14− )
10+ )
15− where
11+where
16
12
17−import Data.ByteString.Internal ( memcpy )
13+import Blaze.ByteString.Builder.Word
18−import Data.Marshalling
14+import Blaze.ByteString.Builder
19import Data.Terminfo.Parse
15import Data.Terminfo.Parse
20
16
21import Control.Monad.Identity
17import Control.Monad.Identity
22import Control.Monad.State.Strict
18import Control.Monad.State.Strict
19+import Control.Monad.Writer
23
20
24−import Data.Bits ( (.|.), (.&.), xor )
21+import Data.Bits ((.|.), (.&.), xor)
25−import Data.List
22+import Data.List
26
23
27−import GHC.Prim
24+import qualified Data.Vector.Unboxed as Vector
28−import GHC.Word
29
25
30-- | capability evaluator state
26-- | capability evaluator state
31data EvalState = EvalState
27data EvalState = EvalState
32− { eval_stack :: ![ CapParam ]
28+ { evalStack :: ![CapParam]
33− , eval_expression :: !CapExpression
29+ , evalExpression :: !CapExpression
34− , eval_params :: ![ CapParam ]
30+ , evalParams :: ![CapParam]
35 }
31 }
36
32
37−type EvalT m a = StateT EvalState m a
33+type Eval a = StateT EvalState (Writer Write) a
38−type Eval a = EvalT Identity a
39
34
40−{-# SPECIALIZE pop :: EvalT IO CapParam #-}
41−pop :: Monad m => EvalT m CapParam
35+pop :: Eval CapParam
42pop = do
36pop = do
43 s <- get
37 s <- get
44− let v : stack' = eval_stack s
38+ let v : stack' = evalStack s
45− s' = s { eval_stack = stack' }
39+ s' = s { evalStack = stack' }
46 put s'
40 put s'
47 return v
41 return v
48
42
49−{-# SPECIALIZE read_param :: Word -> EvalT IO CapParam #-}
43+readParam :: Word -> Eval CapParam
50−read_param :: Monad m => Word -> EvalT m CapParam
44+readParam pn = do
51−read_param pn = do
52− !params <- get >>= return . eval_params
45+ !params <- evalParams <$> get
53 return $! genericIndex params pn
46 return $! genericIndex params pn
54
47
55−{-# SPECIALIZE push :: CapParam -> EvalT IO () #-}
48+push :: CapParam -> Eval ()
56−push :: Monad m => CapParam -> EvalT m ()
57push !v = do
49push !v = do
58 s <- get
50 s <- get
59− let s' = s { eval_stack = v : eval_stack s }
51+ let s' = s { evalStack = v : evalStack s }
60 put s'
52 put s'
61
53
62−apply_param_ops :: CapExpression -> [CapParam] -> [CapParam]
54+applyParamOps :: CapExpression -> [CapParam] -> [CapParam]
63−apply_param_ops cap params = foldl apply_param_op params (param_ops cap)
55+applyParamOps cap params = foldl applyParamOp params (paramOps cap)
64
56
65−apply_param_op :: [CapParam] -> ParamOp -> [CapParam]
57+applyParamOp :: [CapParam] -> ParamOp -> [CapParam]
66−apply_param_op params IncFirstTwo = map (+ 1) params
58+applyParamOp params IncFirstTwo = map (+ 1) params
67
59
68−cap_expression_required_bytes :: CapExpression -> [CapParam] -> Int
60+writeCapExpr :: CapExpression -> [CapParam] -> Write
69−cap_expression_required_bytes cap params =
61+writeCapExpr cap params =
70− let params' = apply_param_ops cap params
62+ let params' = applyParamOps cap params
71− s_0 = EvalState [] cap params'
63+ s0 = EvalState [] cap params'
72− in fromEnum $! fst $! runIdentity $! runStateT ( cap_ops_required_bytes $! cap_ops cap ) s_0
64+ in snd $ runWriter (runStateT (writeCapOps (capOps cap)) s0)
73
65
74−cap_ops_required_bytes :: CapOps -> Eval Word
66+writeCapOps :: CapOps -> Eval ()
75−cap_ops_required_bytes ops = do
67+writeCapOps = mapM_ writeCapOp
76− counts <- mapM cap_op_required_bytes ops
77− return $ sum counts
78
68
79−cap_op_required_bytes :: CapOp -> Eval Word
69+writeCapOp :: CapOp -> Eval ()
80−cap_op_required_bytes (Bytes _ _ c) = return $ toEnum c
70+writeCapOp (Bytes !offset !count) = do
81−cap_op_required_bytes DecOut = do
71+ !cap <- evalExpression <$> get
82− p <- pop
72+ let bytes = Vector.take count $ Vector.drop offset (capBytes cap)
83− return $ toEnum $ length $ show p
73+ Vector.forM_ bytes $ tell.writeWord8
84−cap_op_required_bytes CharOut = do
74+writeCapOp DecOut = do
85− _ <- pop
86− return 1
87−cap_op_required_bytes (PushParam pn) = do
88− read_param pn >>= push
89− return 0
90−cap_op_required_bytes (PushValue v) = do
91− push v
92− return 0
93−cap_op_required_bytes (Conditional expr parts) = do
94− c_expr <- cap_ops_required_bytes expr
95− c_parts <- cond_parts_required_bytes parts
96− return $ c_expr + c_parts
97− where
98− cond_parts_required_bytes [] = return 0
99− cond_parts_required_bytes ( (true_ops, false_ops) : false_parts ) = do
100− -- (man 5 terminfo)
101− -- Usually the %? expr part pushes a value onto the stack, and %t pops it from the
102− -- stack, testing if it is nonzero (true). If it is zero (false), control
103− -- passes to the %e (else) part.
104− v <- pop
105− c_total <- if v /= 0
106− then cap_ops_required_bytes true_ops
107− else do
108− c_false <- cap_ops_required_bytes false_ops
109− c_remain <- cond_parts_required_bytes false_parts
110− return $ c_false + c_remain
111− return c_total
112−cap_op_required_bytes BitwiseOr = do
113− v_1 <- pop
114− v_0 <- pop
115− push $ v_0 .|. v_1
116− return 0
117−cap_op_required_bytes BitwiseAnd = do
118− v_1 <- pop
119− v_0 <- pop
120− push $ v_0 .&. v_1
121− return 0
122−cap_op_required_bytes BitwiseXOr = do
123− v_1 <- pop
124− v_0 <- pop
125− push $ v_0 `xor` v_1
126− return 0
127−cap_op_required_bytes ArithPlus = do
128− v_1 <- pop
129− v_0 <- pop
130− push $ v_0 + v_1
131− return 0
132−cap_op_required_bytes ArithMinus = do
133− v_1 <- pop
134− v_0 <- pop
135− push $ v_0 - v_1
136− return 0
137−cap_op_required_bytes CompareEq = do
138− v_1 <- pop
139− v_0 <- pop
140− push $ if v_0 == v_1 then 1 else 0
141− return 0
142−cap_op_required_bytes CompareLt = do
143− v_1 <- pop
144− v_0 <- pop
145− push $ if v_0 < v_1 then 1 else 0
146− return 0
147−cap_op_required_bytes CompareGt = do
148− v_1 <- pop
149− v_0 <- pop
150− push $ if v_0 > v_1 then 1 else 0
151− return 0
152−
153−serialize_cap_expression :: CapExpression -> [CapParam] -> OutputBuffer -> IO OutputBuffer
154−serialize_cap_expression cap params out_ptr = do
155− let params' = apply_param_ops cap params
156− s_0 = EvalState [] cap params'
157− (!out_ptr', _) <- runStateT ( serialize_cap_ops out_ptr (cap_ops cap) ) s_0
158− return $! out_ptr'
159−
160−serialize_cap_ops :: OutputBuffer -> CapOps -> EvalT IO OutputBuffer
161−serialize_cap_ops out_ptr ops = foldM serialize_cap_op out_ptr ops
162−
163−serialize_cap_op :: OutputBuffer -> CapOp -> EvalT IO OutputBuffer
164−serialize_cap_op !out_ptr (Bytes !offset !byte_count !next_offset) = do
165− !cap <- get >>= return . eval_expression
166− let ( !start_ptr, _ ) = cap_bytes cap
167− !src_ptr = start_ptr `plusPtr` offset
168− !out_ptr' = out_ptr `plusPtr` next_offset
169− liftIO $! memcpy out_ptr src_ptr (fromIntegral byte_count)
170− return $! out_ptr'
171−serialize_cap_op out_ptr DecOut = do
172 p <- pop
75 p <- pop
173− let out_str = show p
76+ forM_ (show p) $ tell.writeWord8.toEnum.fromEnum
174− out_bytes = string_to_bytes out_str
77+writeCapOp CharOut = do
175− serialize_bytes out_bytes out_ptr
78+ pop >>= tell.writeWord8.toEnum.fromEnum
176−serialize_cap_op out_ptr CharOut = do
177− W# p <- pop
178− -- XXX Truncate the character value to a single byte?
179− let !out_byte = W8# (and# p 0xFF##)
180− !out_ptr' = out_ptr `plusPtr` 1
181− liftIO $ poke out_ptr out_byte
182− return out_ptr'
183−serialize_cap_op out_ptr (PushParam pn) = do
79+writeCapOp (PushParam pn) = do
184− read_param pn >>= push
80+ readParam pn >>= push
185− return out_ptr
186−serialize_cap_op out_ptr (PushValue v) = do
81+writeCapOp (PushValue v) = do
187 push v
82 push v
188− return out_ptr
189−serialize_cap_op out_ptr (Conditional expr parts) = do
83+writeCapOp (Conditional expr parts) = do
190− out_ptr' <- serialize_cap_ops out_ptr expr
84+ writeCapOps expr
191− out_ptr'' <- serialize_cond_parts out_ptr' parts
85+ writeContitionalParts parts
192− return out_ptr''
193− where
86+ where
194− serialize_cond_parts ptr [] = return ptr
87+ writeContitionalParts [] = return ()
195− serialize_cond_parts ptr ( (true_ops, false_ops) : false_parts ) = do
88+ writeContitionalParts ((trueOps, falseOps) : falseParts) = do
196 -- (man 5 terminfo)
89 -- (man 5 terminfo)
197− -- Usually the %? expr part pushes a value onto the stack, and %t pops it from the
90+ -- Usually the %? expr part pushes a value onto the stack,
91+ -- and %t pops it from the stack, testing if it is nonzero
198− -- stack, testing if it is nonzero (true). If it is zero (false), control
92+ -- (true). If it is zero (false), control passes to the %e
199− -- passes to the %e (else) part.
93+ -- (else) part.
200 v <- pop
94 v <- pop
201− ptr'' <- if v /= 0
95+ if v /= 0
202− then serialize_cap_ops ptr true_ops
96+ then writeCapOps trueOps
203− else do
97+ else do
204− ptr' <- serialize_cap_ops ptr false_ops
98+ writeCapOps falseOps
205− serialize_cond_parts ptr' false_parts
99+ writeContitionalParts falseParts
206− return ptr''
100+
207−
208−serialize_cap_op out_ptr BitwiseOr = do
101+writeCapOp BitwiseOr = do
209− v_0 <- pop
102+ v0 <- pop
210− v_1 <- pop
103+ v1 <- pop
211− push $ v_0 .|. v_1
104+ push $ v0 .|. v1
212− return out_ptr
213−serialize_cap_op out_ptr BitwiseAnd = do
105+writeCapOp BitwiseAnd = do
214− v_0 <- pop
106+ v0 <- pop
215− v_1 <- pop
107+ v1 <- pop
216− push $ v_0 .&. v_1
108+ push $ v0 .&. v1
217− return out_ptr
218−serialize_cap_op out_ptr BitwiseXOr = do
109+writeCapOp BitwiseXOr = do
219− v_1 <- pop
110+ v1 <- pop
220− v_0 <- pop
111+ v0 <- pop
221− push $ v_0 `xor` v_1
112+ push $ v0 `xor` v1
222− return out_ptr
223−serialize_cap_op out_ptr ArithPlus = do
113+writeCapOp ArithPlus = do
224− v_1 <- pop
114+ v1 <- pop
225− v_0 <- pop
115+ v0 <- pop
226− push $ v_0 + v_1
116+ push $ v0 + v1
227− return out_ptr
228−serialize_cap_op out_ptr ArithMinus = do
117+writeCapOp ArithMinus = do
229− v_1 <- pop
118+ v1 <- pop
230− v_0 <- pop
119+ v0 <- pop
231− push $ v_0 - v_1
120+ push $ v0 - v1
232− return out_ptr
233−serialize_cap_op out_ptr CompareEq = do
121+writeCapOp CompareEq = do
234− v_1 <- pop
122+ v1 <- pop
235− v_0 <- pop
123+ v0 <- pop
236− push $ if v_0 == v_1 then 1 else 0
124+ push $ if v0 == v1 then 1 else 0
237− return out_ptr
238−serialize_cap_op out_ptr CompareLt = do
125+writeCapOp CompareLt = do
239− v_1 <- pop
126+ v1 <- pop
240− v_0 <- pop
127+ v0 <- pop
241− push $ if v_0 < v_1 then 1 else 0
128+ push $ if v0 < v1 then 1 else 0
242− return out_ptr
243−serialize_cap_op out_ptr CompareGt = do
129+writeCapOp CompareGt = do
244− v_1 <- pop
130+ v1 <- pop
245− v_0 <- pop
131+ v0 <- pop
246− push $ if v_0 > v_1 then 1 else 0
132+ push $ if v0 > v1 then 1 else 0
247− return out_ptr
248−
src/Data/Terminfo/Parse.hs+218−221
@@ -1,66 +1,64 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2−{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE CPP #-}
3{-# LANGUAGE NoMonomorphismRestriction #-}
3{-# LANGUAGE NoMonomorphismRestriction #-}
4−{-# LANGUAGE NamedFieldPuns #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5{-# OPTIONS_GHC -funbox-strict-fields -O #-}
5{-# OPTIONS_GHC -funbox-strict-fields -O #-}
6+{-# OPTIONS_HADDOCK hide #-}
7+
6−module Data.Terminfo.Parse ( module Data.Terminfo.Parse
8+module Data.Terminfo.Parse
9+ ( module Data.Terminfo.Parse
7− , Text.ParserCombinators.Parsec.ParseError
10+ , Text.Parsec.ParseError
8− )
11+ )
9− where
12+where
10
13
11−import Control.Applicative ( Applicative(..), pure, (<*>) )
12import Control.Monad ( liftM )
14import Control.Monad ( liftM )
13−import Control.Monad.Trans
14import Control.DeepSeq
15import Control.DeepSeq
15
16
17+#if !(MIN_VERSION_base(4,11,0))
16−import Data.Monoid
18+import Data.Semigroup (Semigroup(..))
19+#endif
17import Data.Word
20import Data.Word
18−
21+import qualified Data.Vector.Unboxed as Vector
19−import Foreign.C.Types
20−import Foreign.Marshal.Array
21−import Foreign.Ptr
22
22
23import Numeric (showHex)
23import Numeric (showHex)
24
24
25−import Text.ParserCombinators.Parsec
25+import Text.Parsec
26−
27−type CapBytes = ( Ptr Word8, CSize )
28
26
29data CapExpression = CapExpression
27data CapExpression = CapExpression
30− { cap_ops :: !CapOps
28+ { capOps :: !CapOps
31− , cap_bytes :: !CapBytes
29+ , capBytes :: !(Vector.Vector Word8)
32− , source_string :: !String
30+ , sourceString :: !String
33− , param_count :: !Word
31+ , paramCount :: !Int
34− , param_ops :: !ParamOps
32+ , paramOps :: !ParamOps
35 } deriving (Eq)
33 } deriving (Eq)
36
34
37instance Show CapExpression where
35instance Show CapExpression where
38 show c
36 show c
39− = "CapExpression { " ++ show (cap_ops c) ++ " }"
37+ = "CapExpression { " ++ show (capOps c) ++ " }"
40− ++ " <- [" ++ hex_dump ( map ( toEnum . fromEnum ) $! source_string c ) ++ "]"
38+ ++ " <- [" ++ hexDump ( map ( toEnum . fromEnum ) $! sourceString c ) ++ "]"
41− ++ " <= " ++ show (source_string c)
39+ ++ " <= " ++ show (sourceString c)
42 where
40 where
43− hex_dump :: [Word8] -> String
41+ hexDump :: [Word8] -> String
44− hex_dump = foldr (\b s -> showHex b s) ""
42+ hexDump = foldr showHex ""
45
43
46instance NFData CapExpression where
44instance NFData CapExpression where
47− rnf (CapExpression ops !_bytes !str !c !p_ops)
45+ rnf (CapExpression ops !_bytes !str !c !pOps)
48− = rnf ops `seq` rnf str `seq` rnf c `seq` rnf p_ops
46+ = rnf ops `seq` rnf str `seq` rnf c `seq` rnf pOps
49
47
50type CapParam = Word
48type CapParam = Word
51
49
52type CapOps = [CapOp]
50type CapOps = [CapOp]
53−data CapOp =
51+data CapOp =
54− Bytes !Int !CSize !Int
52+ Bytes !Int !Int -- offset count
55 | DecOut | CharOut
53 | DecOut | CharOut
56− -- This stores a 0-based index to the parameter. However the operation that implies this op is
54+ -- This stores a 0-based index to the parameter. However the
57− -- 1-based
55+ -- operation that implies this op is 1-based
58 | PushParam !Word | PushValue !Word
56 | PushParam !Word | PushValue !Word
59− -- The conditional parts are the sequence of (%t expression, %e expression) pairs.
57+ -- The conditional parts are the sequence of (%t expression, %e
60− -- The %e expression may be NOP
58+ -- The expression) pairs. %e expression may be NOP
61− | Conditional
59+ | Conditional
62− { conditional_expr :: !CapOps
60+ { conditionalExpr :: !CapOps
63− , conditional_parts :: ![(CapOps, CapOps)]
61+ , conditionalParts :: ![(CapOps, CapOps)]
64 }
62 }
65 | BitwiseOr | BitwiseXOr | BitwiseAnd
63 | BitwiseOr | BitwiseXOr | BitwiseAnd
66 | ArithPlus | ArithMinus
64 | ArithPlus | ArithMinus
@@ -68,10 +66,10 @@ data CapOp =
68 deriving (Show, Eq)
66 deriving (Show, Eq)
69
67
70instance NFData CapOp where
68instance NFData CapOp where
71− rnf (Bytes offset _count next_offset) = rnf offset `seq` rnf next_offset
69+ rnf (Bytes offset byteCount ) = rnf offset `seq` rnf byteCount
72 rnf (PushParam pn) = rnf pn
70 rnf (PushParam pn) = rnf pn
73− rnf (PushValue v) = rnf v
71+ rnf (PushValue v) = rnf v
74− rnf (Conditional c_expr c_parts) = rnf c_expr `seq` rnf c_parts
72+ rnf (Conditional cExpr cParts) = rnf cExpr `seq` rnf cParts
75 rnf BitwiseOr = ()
73 rnf BitwiseOr = ()
76 rnf BitwiseXOr = ()
74 rnf BitwiseXOr = ()
77 rnf BitwiseAnd = ()
75 rnf BitwiseAnd = ()
@@ -91,261 +89,260 @@ data ParamOp =
91instance NFData ParamOp where
89instance NFData ParamOp where
92 rnf IncFirstTwo = ()
90 rnf IncFirstTwo = ()
93
91
94−parse_cap_expression :: ( Applicative m
95− , MonadIO m
96− )
97− => String
98− -> m ( Either ParseError CapExpression )
92+parseCapExpression :: String -> Either ParseError CapExpression
99−parse_cap_expression cap_string =
93+parseCapExpression capString =
100− let v = runParser cap_expression_parser
94+ let v = runParser capExpressionParser
101− initial_build_state
95+ initialBuildState
102− "terminfo cap"
96+ "terminfo cap"
103− cap_string
97+ capString
104 in case v of
98 in case v of
105− Left e -> return $ Left e
99+ Left e -> Left e
106− Right build_results -> pure Right <*> construct_cap_expression cap_string build_results
100+ Right buildResults -> Right $ constructCapExpression capString buildResults
107
101
108−construct_cap_expression :: MonadIO m => [Char] -> BuildResults -> m CapExpression
102+constructCapExpression :: String -> BuildResults -> CapExpression
109−construct_cap_expression cap_string build_results = do
103+constructCapExpression capString buildResults =
110− byte_array <- liftIO $ newArray (map ( toEnum . fromEnum ) cap_string )
111 let expr = CapExpression
104 let expr = CapExpression
112− { cap_ops = out_cap_ops build_results
105+ { capOps = outCapOps buildResults
113− -- The cap bytes are the lower 8 bits of the input string's characters.
106+ -- The cap bytes are the lower 8 bits of the input
114− -- \todo Verify the input string actually contains an 8bit byte per character.
107+ -- string's characters.
115− , cap_bytes = ( byte_array, toEnum $! length cap_string )
108+ , capBytes = Vector.fromList $ map (toEnum.fromEnum) capString
116− , source_string = cap_string
109+ , sourceString = capString
117− , param_count = out_param_count build_results
110+ , paramCount = outParamCount buildResults
118− , param_ops = out_param_ops build_results
111+ , paramOps = outParamOps buildResults
119− }
112+ }
120− return $! rnf expr `seq` expr
113+ in rnf expr `seq` expr
121−
114+
122−type CapParser a = GenParser Char BuildState a
115+type CapParser a = Parsec String BuildState a
123−
116+
124−cap_expression_parser :: CapParser BuildResults
117+capExpressionParser :: CapParser BuildResults
125−cap_expression_parser = do
118+capExpressionParser = do
126− rs <- many $ param_escape_parser <|> bytes_op_parser
119+ rs <- many $ paramEscapeParser <|> bytesOpParser
127 return $ mconcat rs
120 return $ mconcat rs
128
121
129−param_escape_parser :: CapParser BuildResults
122+paramEscapeParser :: CapParser BuildResults
130−param_escape_parser = do
123+paramEscapeParser = do
131 _ <- char '%'
124 _ <- char '%'
132− inc_offset 1
125+ incOffset 1
133− literal_percent_parser <|> param_op_parser
126+ literalPercentParser <|> paramOpParser
134
127
135−literal_percent_parser :: CapParser BuildResults
128+literalPercentParser :: CapParser BuildResults
136−literal_percent_parser = do
129+literalPercentParser = do
137 _ <- char '%'
130 _ <- char '%'
138− start_offset <- getState >>= return . next_offset
131+ startOffset <- nextOffset <$> getState
139− inc_offset 1
132+ incOffset 1
140− return $ BuildResults 0 [Bytes start_offset 1 1] []
133+ return $ BuildResults 0 [Bytes startOffset 1] []
141−
134+
142−param_op_parser :: CapParser BuildResults
135+paramOpParser :: CapParser BuildResults
143−param_op_parser
136+paramOpParser
144− = increment_op_parser
137+ = incrementOpParser
145− <|> push_op_parser
138+ <|> pushOpParser
146− <|> dec_out_parser
139+ <|> decOutParser
147− <|> char_out_parser
140+ <|> charOutParser
148− <|> conditional_op_parser
141+ <|> conditionalOpParser
149− <|> bitwise_op_parser
142+ <|> bitwiseOpParser
150− <|> arith_op_parser
143+ <|> arithOpParser
151− <|> literal_int_op_parser
144+ <|> literalIntOpParser
152− <|> compare_op_parser
145+ <|> compareOpParser
153− <|> char_const_parser
146+ <|> charConstParser
154−
147+
155−increment_op_parser :: CapParser BuildResults
148+incrementOpParser :: CapParser BuildResults
156−increment_op_parser = do
149+incrementOpParser = do
157 _ <- char 'i'
150 _ <- char 'i'
158− inc_offset 1
151+ incOffset 1
159 return $ BuildResults 0 [] [ IncFirstTwo ]
152 return $ BuildResults 0 [] [ IncFirstTwo ]
160
153
161−push_op_parser :: CapParser BuildResults
154+pushOpParser :: CapParser BuildResults
162−push_op_parser = do
155+pushOpParser = do
163 _ <- char 'p'
156 _ <- char 'p'
164− param_n <- digit >>= return . (\d -> read [d])
157+ paramN <- read . pure <$> digit
165− inc_offset 2
158+ incOffset 2
166− return $ BuildResults param_n [ PushParam $ param_n - 1 ] []
159+ return $ BuildResults (fromEnum paramN) [PushParam $ paramN - 1] []
167
160
168−dec_out_parser :: CapParser BuildResults
161+decOutParser :: CapParser BuildResults
169−dec_out_parser = do
162+decOutParser = do
170 _ <- char 'd'
163 _ <- char 'd'
171− inc_offset 1
164+ incOffset 1
172 return $ BuildResults 0 [ DecOut ] []
165 return $ BuildResults 0 [ DecOut ] []
173
166
174−char_out_parser :: CapParser BuildResults
167+charOutParser :: CapParser BuildResults
175−char_out_parser = do
168+charOutParser = do
176 _ <- char 'c'
169 _ <- char 'c'
177− inc_offset 1
170+ incOffset 1
178 return $ BuildResults 0 [ CharOut ] []
171 return $ BuildResults 0 [ CharOut ] []
179
172
180−conditional_op_parser :: CapParser BuildResults
173+conditionalOpParser :: CapParser BuildResults
181−conditional_op_parser = do
174+conditionalOpParser = do
182 _ <- char '?'
175 _ <- char '?'
183− inc_offset 1
176+ incOffset 1
184− cond_part <- many_expr conditional_true_parser
177+ condPart <- manyExpr conditionalTrueParser
185− parts <- many_p
178+ parts <- manyP
186− ( do
179+ ( do
187− true_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser
180+ truePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser
188− , conditional_false_parser
181+ , conditionalFalseParser
189− ]
182+ ]
190− false_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser
183+ falsePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser
191− , conditional_true_parser
184+ , conditionalTrueParser
192− ]
185+ ]
193− return ( true_part, false_part )
186+ return ( truePart, falsePart )
194− )
187+ )
195− conditional_end_parser
188+ conditionalEndParser
196−
189+
197− let true_parts = map fst parts
190+ let trueParts = map fst parts
198− false_parts = map snd parts
191+ falseParts = map snd parts
199− BuildResults n cond cond_param_ops = cond_part
192+ BuildResults n cond condParamOps = condPart
200−
193+
201− let n' = maximum $ n : map out_param_count true_parts
194+ let n' = maximum $ n : map outParamCount trueParts
202− n'' = maximum $ n' : map out_param_count false_parts
195+ n'' = maximum $ n' : map outParamCount falseParts
203−
196+
204− let true_ops = map out_cap_ops true_parts
197+ let trueOps = map outCapOps trueParts
205− false_ops = map out_cap_ops false_parts
198+ falseOps = map outCapOps falseParts
206− cond_parts = zip true_ops false_ops
199+ condParts = zip trueOps falseOps
207−
200+
208− let true_param_ops = mconcat $ map out_param_ops true_parts
201+ let trueParamOps = mconcat $ map outParamOps trueParts
209− false_param_ops = mconcat $ map out_param_ops false_parts
202+ falseParamOps = mconcat $ map outParamOps falseParts
210− p_ops = mconcat [cond_param_ops, true_param_ops, false_param_ops]
203+ pOps = mconcat [condParamOps, trueParamOps, falseParamOps]
211−
204+
212− return $ BuildResults n'' [ Conditional cond cond_parts ] p_ops
205+ return $ BuildResults n'' [ Conditional cond condParts ] pOps
213−
206+
214− where
207+ where
215− many_p !p !end = choice
208+ manyP !p !end = choice
216 [ try end >> return []
209 [ try end >> return []
217− , do !v <- p
210+ , do !v <- p
218− !vs <- many_p p end
211+ !vs <- manyP p end
219 return $! v : vs
212 return $! v : vs
220 ]
213 ]
221− many_expr end = liftM mconcat $ many_p ( param_escape_parser <|> bytes_op_parser ) end
214+ manyExpr end = liftM mconcat $ manyP ( paramEscapeParser <|> bytesOpParser ) end
222
215
223−conditional_true_parser :: CapParser ()
216+conditionalTrueParser :: CapParser ()
224−conditional_true_parser = do
217+conditionalTrueParser = do
225 _ <- string "%t"
218 _ <- string "%t"
226− inc_offset 2
219+ incOffset 2
227
220
228−conditional_false_parser :: CapParser ()
221+conditionalFalseParser :: CapParser ()
229−conditional_false_parser = do
222+conditionalFalseParser = do
230 _ <- string "%e"
223 _ <- string "%e"
231− inc_offset 2
224+ incOffset 2
232
225
233−conditional_end_parser :: CapParser ()
226+conditionalEndParser :: CapParser ()
234−conditional_end_parser = do
227+conditionalEndParser = do
235 _ <- string "%;"
228 _ <- string "%;"
236− inc_offset 2
229+ incOffset 2
237
230
238−bitwise_op_parser :: CapParser BuildResults
231+bitwiseOpParser :: CapParser BuildResults
239−bitwise_op_parser
232+bitwiseOpParser
240− = bitwise_or_parser
233+ = bitwiseOrParser
241− <|> bitwise_and_parser
234+ <|> bitwiseAndParser
242− <|> bitwise_xor_parser
235+ <|> bitwiseXorParser
243
236
244−bitwise_or_parser :: CapParser BuildResults
237+bitwiseOrParser :: CapParser BuildResults
245−bitwise_or_parser = do
238+bitwiseOrParser = do
246 _ <- char '|'
239 _ <- char '|'
247− inc_offset 1
240+ incOffset 1
248 return $ BuildResults 0 [ BitwiseOr ] [ ]
241 return $ BuildResults 0 [ BitwiseOr ] [ ]
249
242
250−bitwise_and_parser :: CapParser BuildResults
243+bitwiseAndParser :: CapParser BuildResults
251−bitwise_and_parser = do
244+bitwiseAndParser = do
252 _ <- char '&'
245 _ <- char '&'
253− inc_offset 1
246+ incOffset 1
254 return $ BuildResults 0 [ BitwiseAnd ] [ ]
247 return $ BuildResults 0 [ BitwiseAnd ] [ ]
255
248
256−bitwise_xor_parser :: CapParser BuildResults
249+bitwiseXorParser :: CapParser BuildResults
257−bitwise_xor_parser = do
250+bitwiseXorParser = do
258 _ <- char '^'
251 _ <- char '^'
259− inc_offset 1
252+ incOffset 1
260 return $ BuildResults 0 [ BitwiseXOr ] [ ]
253 return $ BuildResults 0 [ BitwiseXOr ] [ ]
261
254
262−arith_op_parser :: CapParser BuildResults
255+arithOpParser :: CapParser BuildResults
263−arith_op_parser
256+arithOpParser
264− = plus_op
257+ = plusOp
265− <|> minus_op
258+ <|> minusOp
266 where
259 where
267− plus_op = do
260+ plusOp = do
268 _ <- char '+'
261 _ <- char '+'
269− inc_offset 1
262+ incOffset 1
270 return $ BuildResults 0 [ ArithPlus ] [ ]
263 return $ BuildResults 0 [ ArithPlus ] [ ]
271− minus_op = do
264+ minusOp = do
272 _ <- char '-'
265 _ <- char '-'
273− inc_offset 1
266+ incOffset 1
274 return $ BuildResults 0 [ ArithMinus ] [ ]
267 return $ BuildResults 0 [ ArithMinus ] [ ]
275
268
276−literal_int_op_parser :: CapParser BuildResults
269+literalIntOpParser :: CapParser BuildResults
277−literal_int_op_parser = do
270+literalIntOpParser = do
278 _ <- char '{'
271 _ <- char '{'
279− inc_offset 1
272+ incOffset 1
280− n_str <- many1 digit
273+ nStr <- many1 digit
281− inc_offset $ toEnum $ length n_str
274+ incOffset $ toEnum $ length nStr
282− let n :: Word = read n_str
275+ let n :: Word = read nStr
283 _ <- char '}'
276 _ <- char '}'
284− inc_offset 1
277+ incOffset 1
285 return $ BuildResults 0 [ PushValue n ] [ ]
278 return $ BuildResults 0 [ PushValue n ] [ ]
286
279
287−compare_op_parser :: CapParser BuildResults
280+compareOpParser :: CapParser BuildResults
288−compare_op_parser
281+compareOpParser
289− = compare_eq_op
282+ = compareEqOp
290− <|> compare_lt_op
283+ <|> compareLtOp
291− <|> compare_gt_op
284+ <|> compareGtOp
292 where
285 where
293− compare_eq_op = do
286+ compareEqOp = do
294 _ <- char '='
287 _ <- char '='
295− inc_offset 1
288+ incOffset 1
296 return $ BuildResults 0 [ CompareEq ] [ ]
289 return $ BuildResults 0 [ CompareEq ] [ ]
297− compare_lt_op = do
290+ compareLtOp = do
298 _ <- char '<'
291 _ <- char '<'
299− inc_offset 1
292+ incOffset 1
300 return $ BuildResults 0 [ CompareLt ] [ ]
293 return $ BuildResults 0 [ CompareLt ] [ ]
301− compare_gt_op = do
294+ compareGtOp = do
302 _ <- char '>'
295 _ <- char '>'
303− inc_offset 1
296+ incOffset 1
304 return $ BuildResults 0 [ CompareGt ] [ ]
297 return $ BuildResults 0 [ CompareGt ] [ ]
305
298
306−bytes_op_parser :: CapParser BuildResults
299+bytesOpParser :: CapParser BuildResults
307−bytes_op_parser = do
300+bytesOpParser = do
308 bytes <- many1 $ satisfy (/= '%')
301 bytes <- many1 $ satisfy (/= '%')
309− start_offset <- getState >>= return . next_offset
302+ startOffset <- nextOffset <$> getState
310 let !c = length bytes
303 let !c = length bytes
311 !s <- getState
304 !s <- getState
312− let s' = s { next_offset = start_offset + c }
305+ let s' = s { nextOffset = startOffset + c }
313 setState s'
306 setState s'
314− return $ BuildResults 0 [Bytes start_offset ( toEnum c ) c ] []
307+ return $ BuildResults 0 [Bytes startOffset c] []
315
308
316−char_const_parser :: CapParser BuildResults
309+charConstParser :: CapParser BuildResults
317−char_const_parser = do
310+charConstParser = do
318 _ <- char '\''
311 _ <- char '\''
319− char_value <- liftM (toEnum . fromEnum) anyChar
312+ charValue <- liftM (toEnum . fromEnum) anyChar
320 _ <- char '\''
313 _ <- char '\''
321− inc_offset 3
314+ incOffset 3
322− return $ BuildResults 0 [ PushValue char_value ] [ ]
315+ return $ BuildResults 0 [ PushValue charValue ] [ ]
323
316
324−data BuildState = BuildState
317+data BuildState = BuildState
325− { next_offset :: Int
318+ { nextOffset :: Int
326− }
319+ }
327
320
328−inc_offset :: Int -> CapParser ()
321+incOffset :: Int -> CapParser ()
329−inc_offset n = do
322+incOffset n = do
330 s <- getState
323 s <- getState
331− let s' = s { next_offset = next_offset s + n }
324+ let s' = s { nextOffset = nextOffset s + n }
332 setState s'
325 setState s'
333
326
334−initial_build_state :: BuildState
327+initialBuildState :: BuildState
335−initial_build_state = BuildState 0
328+initialBuildState = BuildState 0
336
329
337data BuildResults = BuildResults
330data BuildResults = BuildResults
338− { out_param_count :: !Word
331+ { outParamCount :: !Int
339− , out_cap_ops :: !CapOps
332+ , outCapOps :: !CapOps
340− , out_param_ops :: !ParamOps
333+ , outParamOps :: !ParamOps
341 }
334 }
342
335
343−instance Monoid BuildResults where
336+instance Semigroup BuildResults where
344− mempty = BuildResults 0 [] []
345− v0 `mappend` v1
337+ v0 <> v1
346 = BuildResults
338 = BuildResults
347− { out_param_count = (out_param_count v0) `max` (out_param_count v1)
339+ { outParamCount = outParamCount v0 `max` outParamCount v1
348− , out_cap_ops = (out_cap_ops v0) `mappend` (out_cap_ops v1)
340+ , outCapOps = outCapOps v0 <> outCapOps v1
349− , out_param_ops = (out_param_ops v0) `mappend` (out_param_ops v1)
341+ , outParamOps = outParamOps v0 <> outParamOps v1
350 }
342 }
351
343
344+instance Monoid BuildResults where
345+ mempty = BuildResults 0 [] []
346+#if !(MIN_VERSION_base(4,11,0))
347+ mappend = (<>)
348+#endif
src/Graphics/Text/Width.hs+56−26
@@ -1,34 +1,64 @@
1-- Copyright 2009 Corey O'Connor
1-- Copyright 2009 Corey O'Connor
2{-# OPTIONS_GHC -D_XOPEN_SOURCE #-}
2{-# OPTIONS_GHC -D_XOPEN_SOURCE #-}
3{-# LANGUAGE ForeignFunctionInterface #-}
3{-# LANGUAGE ForeignFunctionInterface #-}
4+-- | This module provides functions to measure the terminal column width
5+-- of characters and strings.
6+--
7+-- The functions provided in this module all ultimately make calls to
8+-- the C implementation in @cbits/mk_wcwidth.c@. That code manages some
9+-- global state that carries a table of Unicode character widths. For
10+-- more details, see 'Graphics.Vty.UnicodeWidthTable.Install', the C
11+-- code, and the "Multi-Column Character Support" section of the project
12+-- @README@.
4−module Graphics.Text.Width ( wcwidth
13+module Graphics.Text.Width
14+ ( wcwidth
5− , wcswidth
15+ , wcswidth
6− , safe_wcwidth
16+ , wctwidth
7− , safe_wcswidth
17+ , wctlwidth
18+ , safeWcwidth
19+ , safeWcswidth
20+ , safeWctwidth
21+ , safeWctlwidth
8− )
22+ )
9− where
23+where
24+
25+import Data.List (foldl')
26+import qualified Data.Text as T
27+import qualified Data.Text.Lazy as TL
10
28
11foreign import ccall unsafe "vty_mk_wcwidth" wcwidth :: Char -> Int
29foreign import ccall unsafe "vty_mk_wcwidth" wcwidth :: Char -> Int
12
30
13wcswidth :: String -> Int
31wcswidth :: String -> Int
14−wcswidth = sum . map wcwidth
32+wcswidth = foldl' (\l c -> wcwidth c + l) 0
15−
33+{-# INLINE [1] wcswidth #-}
16−-- XXX: Characters with unknown widths occupy 1 column?
34+
17−--
35+wctwidth :: T.Text -> Int
18−-- Not sure if this is actually correct. I presume there is a replacement character that is output
36+wctwidth = T.foldl' (\l c -> wcwidth c + l) 0
19−-- by the terminal instead of the character and this replacement character is 1 column wide. If this
37+
20−-- is not true for all terminals then a per-terminal replacement character width needs to be
38+wctlwidth :: TL.Text -> Int
21−-- implemented.
39+wctlwidth = TL.foldl' (\l c -> wcwidth c + l) 0
22−
40+
41+{-# RULES
42+"wcswidth/unpack" forall x. wcswidth (T.unpack x) = wctwidth x
43+"wcswidth/lazy-unpack" forall x. wcswidth (TL.unpack x) = wctlwidth x
44+ #-}
45+
23−-- | Returns the display width of a character. Assumes all characters with unknown widths are 0 width
46+-- | Returns the display width of a character. Assumes all characters
24−safe_wcwidth :: Char -> Int
47+-- with unknown widths are 0 width.
25−safe_wcwidth c = case wcwidth c of
48+safeWcwidth :: Char -> Int
26− i | i < 0 -> 0
49+safeWcwidth = max 0 . wcwidth
27− | otherwise -> i
50+
28−
29−-- | Returns the display width of a string. Assumes all characters with unknown widths are 0 width
51+-- | Returns the display width of a string. Assumes all characters with
52+-- unknown widths are 0 width.
30−safe_wcswidth :: String -> Int
53+safeWcswidth :: String -> Int
31−safe_wcswidth str = case wcswidth str of
54+safeWcswidth = foldl' (\l c -> safeWcwidth c + l) 0
32− i | i < 0 -> 0
55+
33− | otherwise -> i
56+-- | Returns the display width of a text. Assumes all characters with
57+-- unknown widths are 0 width.
58+safeWctwidth :: T.Text -> Int
59+safeWctwidth = T.foldl' (\l c -> safeWcwidth c + l) 0
34
60
61+-- | Returns the display width of a lazy text. Assumes all characters
62+-- with unknown widths are 0 width.
63+safeWctlwidth :: TL.Text -> Int
64+safeWctlwidth = TL.foldl' (\l c -> safeWcwidth c + l) 0
src/Graphics/Vty.hs+244−139
@@ -1,150 +1,255 @@
1−-- Copyright 2009-2010 Corey O'Connor
1+{-# LANGUAGE CPP #-}
2−{-# LANGUAGE ForeignFunctionInterface, BangPatterns, UnboxedTuples #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3−{-# CFILES gwinsz.c #-}
3+
4−
4+-- | Vty provides interfaces for both terminal input and terminal
5−-- Good sources of documentation for terminal programming are:
5+-- output.
6−-- vt100 control sequences: http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.3
6+--
7−-- Xterm control sequences: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
7+-- - Input to the terminal is provided to the Vty application as a
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(..)
8+-- sequence of 'Event's.
19− )
9+--
10+-- - Output is provided to Vty by the application in the form of a
11+-- 'Picture'. A 'Picture' is one or more layers of 'Image's.
12+-- 'Image' values can be built by the various constructors in
13+-- "Graphics.Vty.Image". Output can be syled using 'Attr' (attribute)
14+-- values in the "Graphics.Vty.Attributes" module.
15+--
16+-- Vty uses threads internally, so programs made with Vty need to be
17+-- compiled with the threaded runtime using the GHC @-threaded@ option.
18+--
19+-- @
20+-- import "Graphics.Vty"
21+--
22+-- main = do
23+-- cfg <- 'standardIOConfig'
24+-- vty <- 'mkVty' cfg
25+-- let line0 = 'string' ('defAttr' ` 'withForeColor' ` 'green') \"first line\"
26+-- line1 = 'string' ('defAttr' ` 'withBackColor' ` 'blue') \"second line\"
27+-- img = line0 '<->' line1
28+-- pic = 'picForImage' img
29+-- 'update' vty pic
30+-- e <- 'nextEvent' vty
31+-- 'shutdown' vty
32+-- 'print' (\"Last event was: \" '++' 'show' e)
33+-- @
34+module Graphics.Vty
35+ ( Vty(..)
36+ , mkVty
37+ , setWindowTitle
38+ , Mode(..)
39+ , module Graphics.Vty.Config
40+ , module Graphics.Vty.Input
41+ , module Graphics.Vty.Output
42+ , module Graphics.Vty.Output.Interface
43+ , module Graphics.Vty.Picture
44+ , module Graphics.Vty.Image
45+ , module Graphics.Vty.Attributes
46+ )
20− where
47+where
21−
48+
22−
23−import Graphics.Vty.Terminal
24−import Graphics.Vty.Picture
49+import Graphics.Vty.Config
25−import Graphics.Vty.DisplayRegion
26import Graphics.Vty.Input
50import Graphics.Vty.Input
51+import Graphics.Vty.Input.Events
52+import Graphics.Vty.Output
53+import Graphics.Vty.Output.Interface
54+import Graphics.Vty.Picture
55+import Graphics.Vty.Image
56+import Graphics.Vty.Attributes
57+import Graphics.Vty.UnicodeWidthTable.IO
58+import Graphics.Vty.UnicodeWidthTable.Install
59+
60+import Data.Char (isPrint, showLitChar)
61+import qualified Data.ByteString.Char8 as BS8
62+
63+import qualified Control.Exception as E
64+import Control.Monad (when)
65+import Control.Concurrent.STM
27
66
28import Data.IORef
67import Data.IORef
68+#if !(MIN_VERSION_base(4,11,0))
69+import Data.Semigroup ((<>))
70+#endif
71+
72+-- | A Vty value represents a handle to the Vty library that the
73+-- application must create in order to use Vty.
74+--
75+-- The use of Vty typically follows this process:
76+--
77+-- 1. Initialize vty with 'mkVty' (this takes control of the terminal).
78+--
79+-- 2. Use 'update' to display a picture.
80+--
81+-- 3. Use 'nextEvent' to get the next input event.
82+--
83+-- 4. Depending on the event, go to 2 or 5.
84+--
85+-- 5. Shutdown vty and restore the terminal state with 'shutdown'. At
86+-- this point the 'Vty' handle cannot be used again.
87+--
88+-- Operations on Vty handles are not thread-safe.
89+data Vty =
90+ Vty { update :: Picture -> IO ()
91+ -- ^ Outputs the given 'Picture'.
92+ , nextEvent :: IO Event
93+ -- ^ Return the next 'Event' or block until one becomes
94+ -- available.
95+ , nextEventNonblocking :: IO (Maybe Event)
96+ -- ^ Non-blocking version of 'nextEvent'.
97+ , inputIface :: Input
98+ -- ^ The input interface. See 'Input'.
99+ , outputIface :: Output
100+ -- ^ The output interface. See 'Output'.
101+ , refresh :: IO ()
102+ -- ^ Refresh the display. If other programs output to the
103+ -- terminal and mess up the display then the application might
104+ -- want to force a refresh using this function.
105+ , shutdown :: IO ()
106+ -- ^ Clean up after vty. A call to this function is necessary to
107+ -- cleanly restore the terminal state before application exit.
108+ -- The above methods will throw an exception if executed after
109+ -- this is executed. Idempotent.
110+ , isShutdown :: IO Bool
111+ }
29
112
30−import qualified System.Console.Terminfo as Terminfo
31−
32−-- | The main object. At most one should be created.
113+-- | Create a Vty handle. At most one handle should be created at a time
33−-- An alternative is to use unsafePerformIO to automatically create a singleton Vty instance when
34−-- required.
35−--
36−-- This does not assure any thread safety. In theory, as long as an update action is not executed
37−-- when another update action is already then it's safe to call this on multiple threads.
38−--
39−-- todo: Once the Terminal interface encompasses input this interface will be deprecated.
40−-- Currently, just using the Terminal interface there is no support for input events.
41−data Vty = Vty
42− { -- | Outputs the given Picture. Equivalent to output_picture applied to a display context
43− -- implicitly managed by Vty.
44− update :: Picture -> IO ()
45− -- | Get one Event object, blocking if necessary.
46− , next_event :: IO Event
47− -- | The terminal device interface. See `Interface`
114+-- for a given terminal device.
48− --
115+--
49− -- The use of Vty typically follows this process:
116+-- The specified configuration is added to the the configuration
50− --
117+-- loaded by 'userConfig' with the 'userConfig' configuration taking
51− -- 0. initialize vty
118+-- precedence. See "Graphics.Vty.Config".
52− --
119+--
53− -- 1. use the update equation of Vty to display a picture
120+-- For most applications @mkVty defaultConfig@ is sufficient.
54− --
121+mkVty :: Config -> IO Vty
55− -- 2. repeat
122+mkVty appConfig = do
56− --
123+ config <- (<> appConfig) <$> userConfig
57− -- 3. shutdown vty.
124+
58− --
125+ when (allowCustomUnicodeWidthTables config /= Just False) $
59− -- todo: provide a similar abstraction to Graphics.Vty.Terminal for input. Use haskeline's
126+ installCustomWidthTable config
60− -- input backend for implementation.
127+
61− --
128+ input <- inputForConfig config
62− -- todo: remove explicit `shutdown` requirement.
129+ out <- outputForConfig config
63− , terminal :: Terminal
130+ internalMkVty input out
64− -- | Refresh the display. Normally the library takes care of refreshing. Nonetheless, some
131+
65− -- other program might output to the terminal and mess the display. In that case the user
132+installCustomWidthTable :: Config -> IO ()
66− -- might want to force a refresh.
133+installCustomWidthTable c = do
67− , refresh :: IO ()
134+ let doLog s = case debugLog c of
68− -- | Clean up after vty.
135+ Nothing -> return ()
69− -- The above methods will throw an exception if executed after this is executed.
136+ Just path -> appendFile path $ "installWidthTable: " <> s <> "\n"
70− , shutdown :: IO ()
137+
71− }
138+ customInstalled <- isCustomTableReady
72−
139+ when (not customInstalled) $ do
73−-- | Set up the state object for using vty. At most one state object should be
140+ mTerm <- currentTerminalName
74−-- created at a time.
141+ case mTerm of
75−mkVty :: IO Vty
142+ Nothing ->
76−mkVty = mkVtyEscDelay defaultEscDelay
143+ doLog "No current terminal name available"
77−
144+ Just currentTerm ->
78−-- | Set up the state object for using vty. At most one state object should be
145+ case lookup currentTerm (termWidthMaps c) of
79−-- created at a time. The delay, in microseconds, specifies the period of time to wait for a key
146+ Nothing ->
80−-- following reading ESC from the terminal before considering the ESC key press as a discrete event.
147+ doLog "Current terminal not found in custom character width mapping list"
81−-- \todo move input init into terminal interface
148+ Just path -> do
82−mkVtyEscDelay :: Int -> IO Vty
149+ tableResult <- E.try $ readUnicodeWidthTable path
83−mkVtyEscDelay escDelay = do
150+ case tableResult of
84− term_info <- Terminfo.setupTermFromEnv
151+ Left (e::E.SomeException) ->
85− t <- current_terminal
152+ doLog $ "Error reading custom character width table " <>
86− reserve_display t
153+ "at " <> show path <> ": " <> show e
87− (kvar, endi) <- initTermInput escDelay term_info
154+ Right (Left msg) ->
88− intMkVty kvar ( endi >> release_display t >> release_terminal t ) t
155+ doLog $ "Error reading custom character width table " <>
89−
156+ "at " <> show path <> ": " <> msg
90−intMkVty :: IO Event -> IO () -> Terminal -> IO Vty
157+ Right (Right table) -> do
91−intMkVty kvar fend t = do
158+ installResult <- E.try $ installUnicodeWidthTable table
92− last_pic_ref <- newIORef Nothing
159+ case installResult of
93− last_update_ref <- newIORef Nothing
160+ Left (e::E.SomeException) ->
94−
161+ doLog $ "Error installing unicode table (" <>
95− let inner_update in_pic = do
162+ show path <> ": " <> show e
96− b <- display_bounds t
163+ Right () ->
97− let DisplayRegion w h = b
164+ doLog $ "Successfully installed Unicode width table " <>
98− cursor = pic_cursor in_pic
165+ " from " <> show path
99− in_pic' = case cursor of
166+
100− Cursor x y ->
167+internalMkVty :: Input -> Output -> IO Vty
101− let
168+internalMkVty input out = do
102− x' = case x of
169+ reserveDisplay out
103− _ | x >= 0x80000000 -> 0
170+
104− | x >= w -> w - 1
171+ shutdownVar <- newTVarIO False
105− | otherwise -> x
172+ let shutdownIo = do
106− y' = case y of
173+ alreadyShutdown <- atomically $ swapTVar shutdownVar True
107− _ | y >= 0x80000000 -> 0
174+ when (not alreadyShutdown) $ do
108− | y >= h -> h - 1
175+ shutdownInput input
109− | otherwise -> y
176+ releaseDisplay out
110− in in_pic { pic_cursor = Cursor x' y' }
177+ releaseTerminal out
111− _ -> in_pic
178+
112− mlast_update <- readIORef last_update_ref
179+ let shutdownStatus = readTVarIO shutdownVar
113− update_data <- case mlast_update of
180+
181+ lastPicRef <- newIORef Nothing
182+ lastUpdateRef <- newIORef Nothing
183+
184+ let innerUpdate inPic = do
185+ b <- displayBounds out
186+ mlastUpdate <- readIORef lastUpdateRef
187+ updateData <- case mlastUpdate of
114 Nothing -> do
188 Nothing -> do
115− dc <- display_context t b
189+ dc <- displayContext out b
116− output_picture dc in_pic'
190+ outputPicture dc inPic
117 return (b, dc)
191 return (b, dc)
118− Just (last_bounds, last_context) -> do
192+ Just (lastBounds, lastContext) -> do
119− if b /= last_bounds
193+ if b /= lastBounds
120 then do
194 then do
121− dc <- display_context t b
195+ dc <- displayContext out b
122− output_picture dc in_pic'
196+ outputPicture dc inPic
123 return (b, dc)
197 return (b, dc)
124 else do
198 else do
125− output_picture last_context in_pic'
199+ outputPicture lastContext inPic
126− return (b, last_context)
200+ return (b, lastContext)
127− writeIORef last_update_ref $ Just update_data
201+ writeIORef lastUpdateRef $ Just updateData
128− writeIORef last_pic_ref $ Just in_pic'
202+ writeIORef lastPicRef $ Just inPic
129−
203+
130− let inner_refresh
204+ let innerRefresh = do
131− = writeIORef last_update_ref Nothing
205+ writeIORef lastUpdateRef Nothing
206+ bounds <- displayBounds out
207+ dc <- displayContext out bounds
208+ writeIORef (assumedStateRef $ contextDevice dc) initialAssumedState
132− >> readIORef last_pic_ref
209+ mPic <- readIORef lastPicRef
133− >>= maybe ( return () ) ( \pic -> inner_update pic )
210+ maybe (return ()) innerUpdate mPic
134−
211+
212+ let mkResize = uncurry EvResize <$> displayBounds out
213+
214+ translateInternalEvent ResumeAfterSignal = mkResize
215+ translateInternalEvent (InputEvent e) = return e
216+
135− let gkey = do k <- kvar
217+ gkey = do
218+ e <- atomically $ readTChan $ _eventChannel input
219+ translateInternalEvent e
220+ gkey' = do
221+ mEv <- atomically $ tryReadTChan $ _eventChannel input
136− case k of
222+ case mEv of
137− (EvResize _ _) -> inner_refresh
223+ Just e -> Just <$> translateInternalEvent e
138− >> display_bounds t
139− >>= return . ( \(DisplayRegion w h)
140− -> EvResize (fromEnum w) (fromEnum h)
141− )
142− _ -> return k
224+ Nothing -> return Nothing
143−
225+
144− return $ Vty { update = inner_update
226+ return $ Vty { update = innerUpdate
145− , next_event = gkey
227+ , nextEvent = gkey
146− , terminal = t
228+ , nextEventNonblocking = gkey'
147− , refresh = inner_refresh
229+ , inputIface = input
230+ , outputIface = out
231+ , refresh = innerRefresh
148− , shutdown = fend
232+ , shutdown = shutdownIo
233+ , isShutdown = shutdownStatus
149 }
234 }
150
235
236+-- | Set the terminal window title string.
237+--
238+-- This function emits an Xterm-compatible escape sequence that we
239+-- anticipate will work for essentially all modern terminal emulators.
240+-- Ideally we'd use a terminal capability for this, but there does not
241+-- seem to exist a termcap for setting window titles. If you find that
242+-- this function does not work for a given terminal emulator, please
243+-- report the issue.
244+--
245+-- For details, see:
246+--
247+-- https://tldp.org/HOWTO/Xterm-Title-3.html
248+setWindowTitle :: Vty -> String -> IO ()
249+setWindowTitle vty title = do
250+ let sanitize :: String -> String
251+ sanitize = concatMap sanitizeChar
252+ sanitizeChar c | not (isPrint c) = showLitChar c ""
253+ | otherwise = [c]
254+ let buf = BS8.pack $ "\ESC]2;" <> sanitize title <> "\007"
255+ outputByteBuffer (outputIface vty) buf
src/Graphics/Vty/Attributes.hs+162−125
@@ -1,11 +1,89 @@
1−{-# LANGUAGE StandaloneDeriving #-}
1+{-# LANGUAGE CPP #-}
2−{-# LANGUAGE GADTs #-}
2+{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE RankNTypes #-}
3+{-# LANGUAGE DeriveAnyClass #-}
4+
4-- | Display attributes
5-- | Display attributes
5--
6--
7+-- Attributes have three components: a foreground color, a background
8+-- color, and a style mask. The simplest attribute is the default
9+-- attribute, or 'defAttr'. Attributes can be modified with
10+-- 'withForeColor', 'withBackColor', and 'withStyle', e.g.,
11+--
12+-- @
13+-- defAttr \`withForeColor\` red
14+-- @
15+--
16+-- 'Image' constructors often require an 'Attr' to indicate the
17+-- attributes used in the image, e.g.,
18+--
19+-- @
20+-- string (defAttr \`withForeColor\` red) "this text will be red"
21+-- @
22+--
23+-- The appearance of 'Image's using 'defAttr' is determined by the The
24+-- terminal, so this is not something VTY can control. The user is free
25+-- to The define the color scheme of the terminal as they see fit.
26+--
27+-- The value 'currentAttr' will keep the attributes of whatever was
28+-- output previously.
29+module Graphics.Vty.Attributes
30+ ( module Graphics.Vty.Attributes.Color
31+
32+ , Attr(..)
33+ , FixedAttr(..)
34+ , MaybeDefault(..)
35+ , defAttr
36+ , currentAttr
37+
38+ -- * Styles
39+ , Style
40+ , withStyle
41+ , standout
42+ , italic
43+ , strikethrough
44+ , underline
45+ , reverseVideo
46+ , blink
47+ , dim
48+ , bold
49+ , defaultStyleMask
50+ , styleMask
51+ , hasStyle
52+
53+ -- * Setting attribute colors
54+ , withForeColor
55+ , withBackColor
56+
57+ -- * Setting hyperlinks
58+ , withURL
59+ )
60+where
61+
62+import Control.DeepSeq
63+import Data.Bits
64+import Data.Text (Text)
65+import Data.Word
66+import GHC.Generics
67+
68+import Graphics.Vty.Attributes.Color
69+
70+-- | A display attribute defines the Color and Style of all the
71+-- characters rendered after the attribute is applied.
72+--
73+-- At most 256 colors, picked from a 240 and 16 color palette, are
74+-- possible for the background and foreground. The 240 colors and
75+-- 16 colors are points in different palettes. See Color for more
76+-- information.
77+data Attr = Attr
78+ { attrStyle :: !(MaybeDefault Style)
79+ , attrForeColor :: !(MaybeDefault Color)
80+ , attrBackColor :: !(MaybeDefault Color)
81+ , attrURL :: !(MaybeDefault Text)
82+ } deriving ( Eq, Show, Read, Generic, NFData )
83+
6−-- For efficiency, this could be encoded into a single 32 bit word. The 32 bit word is first divided
84+-- This could be encoded into a single 32 bit word. The 32 bit word is
7−-- into 4 groups of 8 bits where: The first group codes what action should be taken with regards to
85+-- first divided into 4 groups of 8 bits where: The first group codes
8−-- the other groups.
86+-- what action should be taken with regards to the other groups.
9-- XXYYZZ__
87-- XXYYZZ__
10-- XX - style action
88-- XX - style action
11-- 00 => reset to default
89-- 00 => reset to default
@@ -22,115 +100,53 @@
22-- __ - unused
100-- __ - unused
23--
101--
24-- Next is the style flags
102-- Next is the style flags
25−-- SURBDO__
103+-- SURBDOI_
26-- S - standout
104-- S - standout
27-- U - underline
105-- U - underline
28-- R - reverse video
106-- R - reverse video
29-- B - blink
107-- B - blink
30-- D - dim
108-- D - dim
31-- O - bold
109-- O - bold
110+-- I - italic
32−-- __ - unused
111+-- _ - unused
33--
112--
34-- Then the foreground color encoded into 8 bits.
113-- Then the foreground color encoded into 8 bits.
35-- Then the background color encoded into 8 bits.
114-- Then the background color encoded into 8 bits.
36−--
37−module Graphics.Vty.Attributes ( module Graphics.Vty.Attributes
38− , module Graphics.Vty.Attributes.Color
39− , module Graphics.Vty.Attributes.Color240
40− )
41− where
42−
43−import Data.Bits
44−import Data.Monoid
45−import Data.Word
46
115
47−import Graphics.Vty.Attributes.Color
116+-- | Specifies the display attributes such that the final style and
48−import Graphics.Vty.Attributes.Color240
117+-- color values do not depend on the previously applied display
49−
118+-- attribute. The display attributes can still depend on the terminal's
50−-- | A display attribute defines the Color and Style of all the characters rendered after the
119+-- default colors (unfortunately).
51−-- attribute is applied.
120+data FixedAttr = FixedAttr
52−--
121+ { fixedStyle :: !Style
53−-- At most 256 colors, picked from a 240 and 16 color palette, are possible for the background and
122+ , fixedForeColor :: !(Maybe Color)
54−-- foreground. The 240 colors and 16 colors are points in different palettes. See Color for more
123+ , fixedBackColor :: !(Maybe Color)
55−-- information.
124+ , fixedURL :: !(Maybe Text)
56−data Attr = Attr
57− { attr_style :: !(MaybeDefault Style)
58− , attr_fore_color :: !(MaybeDefault Color)
59− , attr_back_color :: !(MaybeDefault Color)
60 } deriving ( Eq, Show )
125 } deriving ( Eq, Show )
61
126
62−instance Monoid Attr where
127+-- | The style and color attributes can either be the terminal defaults.
63− mempty = Attr mempty mempty mempty
128+-- Or be equivalent to the previously applied style. Or be a specific
64− mappend attr_0 attr_1 =
129+-- value.
65− Attr ( attr_style attr_0 `mappend` attr_style attr_1 )
130+data MaybeDefault v = Default | KeepCurrent | SetTo !v
66− ( attr_fore_color attr_0 `mappend` attr_fore_color attr_1 )
131+ deriving (Eq, Read, Show)
67− ( attr_back_color attr_0 `mappend` attr_back_color attr_1 )
68
132
69−-- | Specifies the display attributes such that the final style and color values do not depend on
133+instance (NFData v) => NFData (MaybeDefault v) where
70−-- the previously applied display attribute. The display attributes can still depend on the
134+ rnf Default = ()
71−-- terminal's default colors (unfortunately).
135+ rnf KeepCurrent = ()
72−data FixedAttr = FixedAttr
136+ rnf (SetTo v) = rnf v
73− { fixed_style :: !Style
74− , fixed_fore_color :: !(Maybe Color)
75− , fixed_back_color :: !(Maybe Color)
76− } deriving ( Eq, Show )
77
137
78−-- | The style and color attributes can either be the terminal defaults. Or be equivalent to the
79−-- previously applied style. Or be a specific value.
80−data MaybeDefault v where
81− Default :: MaybeDefault v
82− KeepCurrent :: MaybeDefault v
83− SetTo :: forall v . ( Eq v, Show v ) => !v -> MaybeDefault v
84−
85−deriving instance Eq v => Eq (MaybeDefault v)
86−deriving instance Eq v => Show (MaybeDefault v)
87−
88−instance Eq v => Monoid ( MaybeDefault v ) where
89− mempty = KeepCurrent
90− mappend Default Default = Default
91− mappend Default KeepCurrent = Default
92− mappend Default ( SetTo v ) = SetTo v
93− mappend KeepCurrent Default = Default
94− mappend KeepCurrent KeepCurrent = KeepCurrent
95− mappend KeepCurrent ( SetTo v ) = SetTo v
96− mappend ( SetTo _v ) Default = Default
97− mappend ( SetTo v ) KeepCurrent = SetTo v
98− mappend ( SetTo _ ) ( SetTo v ) = SetTo v
99−
100−-- | Standard 8-color ANSI terminal color codes.
101−black, red, green, yellow, blue, magenta, cyan, white :: Color
102−black = ISOColor 0
103−red = ISOColor 1
104−green = ISOColor 2
105−yellow = ISOColor 3
106−blue = ISOColor 4
107−magenta= ISOColor 5
108−cyan = ISOColor 6
109−white = ISOColor 7
110−
111−-- | Bright/Vivid variants of the standard 8-color ANSI
112−bright_black, bright_red, bright_green, bright_yellow :: Color
113−bright_blue, bright_magenta, bright_cyan, bright_white :: Color
114−bright_black = ISOColor 8
115−bright_red = ISOColor 9
116−bright_green = ISOColor 10
117−bright_yellow = ISOColor 11
118−bright_blue = ISOColor 12
119−bright_magenta= ISOColor 13
120−bright_cyan = ISOColor 14
121−bright_white = ISOColor 15
122−
123−-- | Styles are represented as an 8 bit word. Each bit in the word is 1 if the style attribute
138+-- | Styles are represented as an 8 bit word. Each bit in the word is 1
139+-- if the style attribute assigned to that bit should be applied and 0
124−-- assigned to that bit should be applied and 0 if the style attribute should not be applied.
140+-- if the style attribute should not be applied.
125type Style = Word8
141type Style = Word8
126
142
127−-- | The 6 possible style attributes:
143+-- | Valid style attributes include:
128--
144--
129-- * standout
145-- * standout
130--
146--
131-- * underline
147-- * underline
132--
148--
133−-- * reverse_video
149+-- * reverseVideo
134--
150--
135-- * blink
151-- * blink
136--
152--
@@ -138,54 +154,75 @@ type Style = Word8
138--
154--
139-- * bold/bright
155-- * bold/bright
140--
156--
157+-- * italic
158+--
159+-- * strikethrough (via the smxx/rmxx terminfo capabilities)
160+--
141−-- ( The invisible, protect, and altcharset display attributes some terminals support are not
161+-- (The invisible, protect, and altcharset display attributes some
142−-- supported via VTY.)
162+-- terminals support are not supported via VTY.)
143−standout, underline, reverse_video, blink, dim, bold :: Style
163+standout, underline, reverseVideo, blink, dim, bold, italic, strikethrough :: Style
144standout = 0x01
164standout = 0x01
145underline = 0x02
165underline = 0x02
146−reverse_video = 0x04
166+reverseVideo = 0x04
147blink = 0x08
167blink = 0x08
148dim = 0x10
168dim = 0x10
149bold = 0x20
169bold = 0x20
170+italic = 0x40
171+strikethrough = 0x80
150
172
151−default_style_mask :: Style
173+defaultStyleMask :: Style
152−default_style_mask = 0x00
174+defaultStyleMask = 0x00
153
175
154−style_mask :: Attr -> Word8
176+styleMask :: Attr -> Word8
155−style_mask attr
177+styleMask attr
156− = case attr_style attr of
178+ = case attrStyle attr of
157 Default -> 0
179 Default -> 0
158 KeepCurrent -> 0
180 KeepCurrent -> 0
159 SetTo v -> v
181 SetTo v -> v
160
182
161-- | true if the given Style value has the specified Style set.
183-- | true if the given Style value has the specified Style set.
162−has_style :: Style -> Style -> Bool
184+hasStyle :: Style -> Style -> Bool
163−has_style s bit_mask = ( s .&. bit_mask ) /= 0
185+hasStyle s bitMask = ( s .&. bitMask ) /= 0
164
186
165-- | Set the foreground color of an `Attr'.
187-- | Set the foreground color of an `Attr'.
166−with_fore_color :: Attr -> Color -> Attr
188+withForeColor :: Attr -> Color -> Attr
167−with_fore_color attr c = attr { attr_fore_color = SetTo c }
189+withForeColor attr c = attr { attrForeColor = SetTo c }
168
190
169-- | Set the background color of an `Attr'.
191-- | Set the background color of an `Attr'.
170−with_back_color :: Attr -> Color -> Attr
192+withBackColor :: Attr -> Color -> Attr
171−with_back_color attr c = attr { attr_back_color = SetTo c }
193+withBackColor attr c = attr { attrBackColor = SetTo c }
172
194
173-- | Add the given style attribute
195-- | Add the given style attribute
174−with_style :: Attr -> Style -> Attr
196+withStyle :: Attr -> Style -> Attr
175−with_style attr style_flag = attr { attr_style = SetTo $ style_mask attr .|. style_flag }
197+withStyle attr 0 = attr
176−
198+withStyle attr styleFlag = attr { attrStyle = SetTo $ styleMask attr .|. styleFlag }
199+
200+-- | Add a hyperlinked URL using the proposed [escape sequences for
201+-- hyperlinked
202+-- URLs](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
203+-- These escape sequences are comparatively new and aren't widely
204+-- supported in terminal emulators yet, but most terminal emulators
205+-- that don't know about these sequences will ignore these sequences,
206+-- and therefore this should fall back sensibly. In some cases they
207+-- won't and this will result in garbage, so this is why hyperlinking is
208+-- disabled by default, in which case this combinator has no observable
209+-- effect. To enable it, enable 'Hyperlink' mode on your Vty output
210+-- interface.
211+withURL :: Attr -> Text -> Attr
212+withURL attr url = attr { attrURL = SetTo url }
213+
177−-- | Sets the style, background color and foreground color to the default values for the terminal.
214+-- | Sets the style, background color and foreground color to the
215+-- default values for the terminal. There is no easy way to determine
178−-- There is no easy way to determine what the default background and foreground colors are.
216+-- what the default background and foreground colors are.
179−def_attr :: Attr
217+defAttr :: Attr
180−def_attr = Attr Default Default Default
218+defAttr = Attr Default Default Default Default
181−
219+
182−-- | Keeps the style, background color and foreground color that was previously set. Used to
220+-- | Keeps the style, background color and foreground color that was
183−-- override some part of the previous style.
221+-- previously set. Used to override some part of the previous style.
184--
222--
185−-- EG: current_style `with_fore_color` bright_magenta
223+-- EG: current_style `withForeColor` brightMagenta
186--
224--
187−-- Would be the currently applied style (be it underline, bold, etc) but with the foreground color
225+-- Would be the currently applied style (be it underline, bold, etc) but
188−-- set to bright_magenta.
226+-- with the foreground color set to brightMagenta.
189−current_attr :: Attr
227+currentAttr :: Attr
190−current_attr = Attr KeepCurrent KeepCurrent KeepCurrent
228+currentAttr = Attr KeepCurrent KeepCurrent KeepCurrent KeepCurrent
191−
src/Graphics/Vty/Attributes/Color.hs+161−21
@@ -1,16 +1,75 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE DeriveAnyClass #-}
3+{-# LANGUAGE MultiWayIf #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+
1−module Graphics.Vty.Attributes.Color where
6+module Graphics.Vty.Attributes.Color
7+ ( Color(..)
8+ , ColorMode(..)
9+
10+ -- * Detecting Terminal Color Support
11+ , detectColorMode
12+
13+ -- ** Fixed Colors
14+ -- | Standard 8-color ANSI terminal color codes.
15+ --
16+ -- Note that these map to colors in the terminal's custom palette. For
17+ -- instance, `white` maps to whatever the terminal color theme uses for
18+ -- white.
19+ --
20+ -- Use these functions if you want to make apps that fit the terminal theme.
21+ -- If you want access to more/stronger colors use `rgbColor`
22+ , black
23+ , red
24+ , green
25+ , yellow
26+ , blue
27+ , magenta
28+ , cyan
29+ , white
30+
31+ -- | Bright/Vivid variants of the standard 8-color ANSI
32+ , brightBlack
33+ , brightRed
34+ , brightGreen
35+ , brightYellow
36+ , brightBlue
37+ , brightMagenta
38+ , brightCyan
39+ , brightWhite
40+ -- ** Creating Colors From RGB
41+ , linearColor
42+ , srgbColor
43+ , rgbColor
44+ , color240
45+ , module Graphics.Vty.Attributes.Color240
46+ )
47+where
48+
2import Data.Word
49import Data.Word
50+import GHC.Generics
51+import Control.DeepSeq
52+import System.Environment (lookupEnv)
53+
54+import qualified System.Console.Terminfo as Terminfo
55+import Control.Exception (catch)
56+import Data.Maybe
57+
58+import Graphics.Vty.Attributes.Color240
3
59
4-- | Abstract data type representing a color.
60-- | Abstract data type representing a color.
5−--
6−-- Currently the foreground and background color are specified as points in either a:
7--
61--
62+-- Currently the foreground and background color are specified as points
63+-- in either a:
64+--
8−-- * 16 color palette. Where the first 8 colors are equal to the 8 colors of the ISO 6429 (ANSI) 8
65+-- * 16 color palette. Where the first 8 colors are equal to the 8
66+-- colors of the ISO 6429 (ANSI) 8 color palette and the second 8
9−-- color palette and the second 8 colors are bright/vivid versions of the first 8 colors.
67+-- colors are bright/vivid versions of the first 8 colors.
68+--
69+-- * 240 color palette. This palette is a regular sampling of the full
70+-- RGB colorspace for the first 224 colors. The remaining 16 colors is
71+-- a greyscale palette.
10--
72--
11−-- * 240 color palette. This palette is a regular sampling of the full RGB colorspace for the first
12−-- 224 colors. The remaining 16 colors is a greyscale palette.
13−--
14-- The 8 ISO 6429 (ANSI) colors are as follows:
73-- The 8 ISO 6429 (ANSI) colors are as follows:
15--
74--
16-- 0. black
75-- 0. black
@@ -29,23 +88,104 @@ import Data.Word
29--
88--
30-- 7. white
89-- 7. white
31--
90--
32−-- The mapping from points in the 240 color palette to colors actually displayable by the terminal
91+-- The mapping from points in the 240 color palette to colors actually
92+-- displayable by the terminal depends on the number of colors the
33−-- depends on the number of colors the terminal claims to support. Which is usually determined by
93+-- terminal claims to support. Which is usually determined by the
34−-- the terminfo "colors" property. If this property is not being accurately reported then the color
94+-- terminfo "colors" property. If this property is not being accurately
35−-- reproduction will be incorrect.
95+-- reported then the color reproduction will be incorrect.
36--
96--
37−-- If the terminal reports <= 16 colors then the 240 color palette points are only mapped to the 8
97+-- If the terminal reports <= 16 colors then the 240 color palette
98+-- points are only mapped to the 8 color palette. I'm not sure of
38−-- color pallete. I'm not sure of the RGB points for the "bright" colors which is why they are not
99+-- the RGB points for the "bright" colors which is why they are not
39−-- addressable via the 240 color palette.
100+-- addressable via the 240 color palette.
40--
101--
41−-- If the terminal reports > 16 colors then the 240 color palette points are mapped to the nearest
102+-- If the terminal reports > 16 colors then the 240 color palette
103+-- points are mapped to the nearest points in a ("color count" - 16)
42−-- points in a ("color count" - 16) subsampling of the 240 color palette.
104+-- subsampling of the 240 color palette.
43--
105--
44−-- All of this assumes the terminals are behaving similarly to xterm and rxvt when handling colors.
106+-- All of this assumes the terminals are behaving similarly to xterm and
107+-- rxvt when handling colors. And that the individual colors have not
45−-- And that the individual colors have not been remapped by the user. There may be a way to verify
108+-- been remapped by the user. There may be a way to verify this through
46−-- this through terminfo but I don't know it.
109+-- terminfo but I don't know it.
47--
110--
48-- Seriously, terminal color support is INSANE.
111-- Seriously, terminal color support is INSANE.
49−data Color = ISOColor !Word8 | Color240 !Word8
112+data Color = ISOColor !Word8 | Color240 !Word8 | RGBColor !Word8 !Word8 !Word8
113+ deriving ( Eq, Show, Read, Generic, NFData )
114+
115+data ColorMode
116+ = NoColor
117+ | ColorMode8
118+ | ColorMode16
119+ | ColorMode240 !Word8
120+ | FullColor
50 deriving ( Eq, Show )
121 deriving ( Eq, Show )
51
122
123+detectColorMode :: String -> IO ColorMode
124+detectColorMode termName' = do
125+ term <- catch (Just <$> Terminfo.setupTerm termName')
126+ (\(_ :: Terminfo.SetupTermError) -> return Nothing)
127+ let getCap cap = term >>= \t -> Terminfo.getCapability t cap
128+ termColors = fromMaybe 0 $ getCap (Terminfo.tiGetNum "colors")
129+ colorterm <- lookupEnv "COLORTERM"
130+ return $ if
131+ | termColors < 8 -> NoColor
132+ | termColors < 16 -> ColorMode8
133+ | termColors == 16 -> ColorMode16
134+ | termColors < 256 -> ColorMode240 (fromIntegral termColors - 16)
135+ | colorterm == Just "truecolor" -> FullColor
136+ | colorterm == Just "24bit" -> FullColor
137+ | otherwise -> ColorMode240 240
138+
139+black, red, green, yellow, blue, magenta, cyan, white :: Color
140+black = ISOColor 0
141+red = ISOColor 1
142+green = ISOColor 2
143+yellow = ISOColor 3
144+blue = ISOColor 4
145+magenta= ISOColor 5
146+cyan = ISOColor 6
147+white = ISOColor 7
148+
149+brightBlack, brightRed, brightGreen, brightYellow :: Color
150+brightBlue, brightMagenta, brightCyan, brightWhite :: Color
151+brightBlack = ISOColor 8
152+brightRed = ISOColor 9
153+brightGreen = ISOColor 10
154+brightYellow = ISOColor 11
155+brightBlue = ISOColor 12
156+brightMagenta= ISOColor 13
157+brightCyan = ISOColor 14
158+brightWhite = ISOColor 15
159+
160+linearColor :: Integral i => i -> i -> i -> Color
161+linearColor r g b = RGBColor r' g' b'
162+ where
163+ r' = fromIntegral (clamp r) :: Word8
164+ g' = fromIntegral (clamp g) :: Word8
165+ b' = fromIntegral (clamp b) :: Word8
166+ clamp = min 255 . max 0
167+
168+srgbColor :: Integral i => i -> i -> i -> Color
169+srgbColor r g b =
170+ -- srgb to rgb transformation as described at
171+ -- https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
172+ --
173+ -- TODO: it may be worth translating this to a lookup table, as with color240
174+ let shrink n = fromIntegral n / 255 :: Double
175+ -- called gamma^-1 in wiki
176+ gamma u
177+ | u <= 0.04045 = u/12.92
178+ | otherwise = ((u + 0.055) / 1.055) ** 2.4
179+ -- TODO: this is a slightly inaccurate conversion. is it worth doing proterly?
180+ expand n = round (255 * n)
181+ convert = expand . gamma . shrink
182+ in RGBColor (convert r) (convert g) (convert b)
183+
184+color240 :: Integral i => i -> i -> i -> Color
185+color240 r g b = Color240 (rgbColorToColor240 r g b)
186+
187+-- | Create a Vty 'Color' (in the 240 color set) from an RGB triple.
188+-- This function is lossy in the sense that we only internally support 240 colors but the
189+-- #RRGGBB format supports 16^3 colors.
190+rgbColor :: Integral i => i -> i -> i -> Color
191+rgbColor = color240
src/Graphics/Vty/Attributes/Color240.hs+292−255
@@ -1,261 +1,298 @@
1−{-
2− - This header file was generated by ./256colres.pl
1+-- This header file was generated by ./256colres.pl
3− -}
4−module Graphics.Vty.Attributes.Color240 where
2+module Graphics.Vty.Attributes.Color240
5−
3+ ( rgbColorToColor240
6−import Graphics.Vty.Attributes.Color
4+ , color240CodeToRGB
5+ )
6+where
7
7
8+import Data.Word (Word8)
8import Text.Printf
9import Text.Printf
9
10
10−-- | RGB color to 240 color palette.
11+-- Note: rgbColor's mapping from RGB to 240 colors was generated from
11−--
12+-- 256colres.pl which is forked from xterm 256colres.pl.
12−-- generated from 256colres.pl which is forked from xterm 256colres.pl
13+
13−-- todo: all values get clamped high.
14+-- | Create a value in the Color240 set from an RGB triple
14−rgb_color :: Integral i => i -> i -> i -> Color
15+rgbColorToColor240 :: Integral i => i -> i -> i -> Word8
15−rgb_color r g b
16+rgbColorToColor240 r g b
16− | r < 0 && g < 0 && b < 0 = error "rgb_color with negative color component intensity"
17+ | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"
17− | r == 8 && g == 8 && b == 8 = Color240 216
18+ | r == 8 && g == 8 && b == 8 = 216
18− | r == 18 && g == 18 && b == 18 = Color240 217
19+ | r == 18 && g == 18 && b == 18 = 217
19− | r == 28 && g == 28 && b == 28 = Color240 218
20+ | r == 28 && g == 28 && b == 28 = 218
20− | r == 38 && g == 38 && b == 38 = Color240 219
21+ | r == 38 && g == 38 && b == 38 = 219
21− | r == 48 && g == 48 && b == 48 = Color240 220
22+ | r == 48 && g == 48 && b == 48 = 220
22− | r == 58 && g == 58 && b == 58 = Color240 221
23+ | r == 58 && g == 58 && b == 58 = 221
23− | r == 68 && g == 68 && b == 68 = Color240 222
24+ | r == 68 && g == 68 && b == 68 = 222
24− | r == 78 && g == 78 && b == 78 = Color240 223
25+ | r == 78 && g == 78 && b == 78 = 223
25− | r == 88 && g == 88 && b == 88 = Color240 224
26+ | r == 88 && g == 88 && b == 88 = 224
26− | r == 98 && g == 98 && b == 98 = Color240 225
27+ | r == 98 && g == 98 && b == 98 = 225
27− | r == 108 && g == 108 && b == 108 = Color240 226
28+ | r == 108 && g == 108 && b == 108 = 226
28− | r == 118 && g == 118 && b == 118 = Color240 227
29+ | r == 118 && g == 118 && b == 118 = 227
29− | r == 128 && g == 128 && b == 128 = Color240 228
30+ | r == 128 && g == 128 && b == 128 = 228
30− | r == 138 && g == 138 && b == 138 = Color240 229
31+ | r == 138 && g == 138 && b == 138 = 229
31− | r == 148 && g == 148 && b == 148 = Color240 230
32+ | r == 148 && g == 148 && b == 148 = 230
32− | r == 158 && g == 158 && b == 158 = Color240 231
33+ | r == 158 && g == 158 && b == 158 = 231
33− | r == 168 && g == 168 && b == 168 = Color240 232
34+ | r == 168 && g == 168 && b == 168 = 232
34− | r == 178 && g == 178 && b == 178 = Color240 233
35+ | r == 178 && g == 178 && b == 178 = 233
35− | r == 188 && g == 188 && b == 188 = Color240 234
36+ | r == 188 && g == 188 && b == 188 = 234
36− | r == 198 && g == 198 && b == 198 = Color240 235
37+ | r == 198 && g == 198 && b == 198 = 235
37− | r == 208 && g == 208 && b == 208 = Color240 236
38+ | r == 208 && g == 208 && b == 208 = 236
38− | r == 218 && g == 218 && b == 218 = Color240 237
39+ | r == 218 && g == 218 && b == 218 = 237
39− | r == 228 && g == 228 && b == 228 = Color240 238
40+ | r == 228 && g == 228 && b == 228 = 238
40− | r == 238 && g == 238 && b == 238 = Color240 239
41+ | r == 238 && g == 238 && b == 238 = 239
41− | r <= 0 && g <= 0 && b <= 0 = Color240 0
42+ | otherwise = 36 * go r + 6 * go g + go b
42− | r <= 0 && g <= 0 && b <= 95 = Color240 1
43+ where go = simpleColor_ (error (printf "RGB color %d %d %d does not map to 240 palette."
43− | r <= 0 && g <= 0 && b <= 135 = Color240 2
44− | r <= 0 && g <= 0 && b <= 175 = Color240 3
45− | r <= 0 && g <= 0 && b <= 215 = Color240 4
46− | r <= 0 && g <= 0 && b <= 255 = Color240 5
47− | r <= 0 && g <= 95 && b <= 0 = Color240 6
48− | r <= 0 && g <= 95 && b <= 95 = Color240 7
49− | r <= 0 && g <= 95 && b <= 135 = Color240 8
50− | r <= 0 && g <= 95 && b <= 175 = Color240 9
51− | r <= 0 && g <= 95 && b <= 215 = Color240 10
52− | r <= 0 && g <= 95 && b <= 255 = Color240 11
53− | r <= 0 && g <= 135 && b <= 0 = Color240 12
54− | r <= 0 && g <= 135 && b <= 95 = Color240 13
55− | r <= 0 && g <= 135 && b <= 135 = Color240 14
56− | r <= 0 && g <= 135 && b <= 175 = Color240 15
57− | r <= 0 && g <= 135 && b <= 215 = Color240 16
58− | r <= 0 && g <= 135 && b <= 255 = Color240 17
59− | r <= 0 && g <= 175 && b <= 0 = Color240 18
60− | r <= 0 && g <= 175 && b <= 95 = Color240 19
61− | r <= 0 && g <= 175 && b <= 135 = Color240 20
62− | r <= 0 && g <= 175 && b <= 175 = Color240 21
63− | r <= 0 && g <= 175 && b <= 215 = Color240 22
64− | r <= 0 && g <= 175 && b <= 255 = Color240 23
65− | r <= 0 && g <= 215 && b <= 0 = Color240 24
66− | r <= 0 && g <= 215 && b <= 95 = Color240 25
67− | r <= 0 && g <= 215 && b <= 135 = Color240 26
68− | r <= 0 && g <= 215 && b <= 175 = Color240 27
69− | r <= 0 && g <= 215 && b <= 215 = Color240 28
70− | r <= 0 && g <= 215 && b <= 255 = Color240 29
71− | r <= 0 && g <= 255 && b <= 0 = Color240 30
72− | r <= 0 && g <= 255 && b <= 95 = Color240 31
73− | r <= 0 && g <= 255 && b <= 135 = Color240 32
74− | r <= 0 && g <= 255 && b <= 175 = Color240 33
75− | r <= 0 && g <= 255 && b <= 215 = Color240 34
76− | r <= 0 && g <= 255 && b <= 255 = Color240 35
77− | r <= 95 && g <= 0 && b <= 0 = Color240 36
78− | r <= 95 && g <= 0 && b <= 95 = Color240 37
79− | r <= 95 && g <= 0 && b <= 135 = Color240 38
80− | r <= 95 && g <= 0 && b <= 175 = Color240 39
81− | r <= 95 && g <= 0 && b <= 215 = Color240 40
82− | r <= 95 && g <= 0 && b <= 255 = Color240 41
83− | r <= 95 && g <= 95 && b <= 0 = Color240 42
84− | r <= 95 && g <= 95 && b <= 95 = Color240 43
85− | r <= 95 && g <= 95 && b <= 135 = Color240 44
86− | r <= 95 && g <= 95 && b <= 175 = Color240 45
87− | r <= 95 && g <= 95 && b <= 215 = Color240 46
88− | r <= 95 && g <= 95 && b <= 255 = Color240 47
89− | r <= 95 && g <= 135 && b <= 0 = Color240 48
90− | r <= 95 && g <= 135 && b <= 95 = Color240 49
91− | r <= 95 && g <= 135 && b <= 135 = Color240 50
92− | r <= 95 && g <= 135 && b <= 175 = Color240 51
93− | r <= 95 && g <= 135 && b <= 215 = Color240 52
94− | r <= 95 && g <= 135 && b <= 255 = Color240 53
95− | r <= 95 && g <= 175 && b <= 0 = Color240 54
96− | r <= 95 && g <= 175 && b <= 95 = Color240 55
97− | r <= 95 && g <= 175 && b <= 135 = Color240 56
98− | r <= 95 && g <= 175 && b <= 175 = Color240 57
99− | r <= 95 && g <= 175 && b <= 215 = Color240 58
100− | r <= 95 && g <= 175 && b <= 255 = Color240 59
101− | r <= 95 && g <= 215 && b <= 0 = Color240 60
102− | r <= 95 && g <= 215 && b <= 95 = Color240 61
103− | r <= 95 && g <= 215 && b <= 135 = Color240 62
104− | r <= 95 && g <= 215 && b <= 175 = Color240 63
105− | r <= 95 && g <= 215 && b <= 215 = Color240 64
106− | r <= 95 && g <= 215 && b <= 255 = Color240 65
107− | r <= 95 && g <= 255 && b <= 0 = Color240 66
108− | r <= 95 && g <= 255 && b <= 95 = Color240 67
109− | r <= 95 && g <= 255 && b <= 135 = Color240 68
110− | r <= 95 && g <= 255 && b <= 175 = Color240 69
111− | r <= 95 && g <= 255 && b <= 215 = Color240 70
112− | r <= 95 && g <= 255 && b <= 255 = Color240 71
113− | r <= 135 && g <= 0 && b <= 0 = Color240 72
114− | r <= 135 && g <= 0 && b <= 95 = Color240 73
115− | r <= 135 && g <= 0 && b <= 135 = Color240 74
116− | r <= 135 && g <= 0 && b <= 175 = Color240 75
117− | r <= 135 && g <= 0 && b <= 215 = Color240 76
118− | r <= 135 && g <= 0 && b <= 255 = Color240 77
119− | r <= 135 && g <= 95 && b <= 0 = Color240 78
120− | r <= 135 && g <= 95 && b <= 95 = Color240 79
121− | r <= 135 && g <= 95 && b <= 135 = Color240 80
122− | r <= 135 && g <= 95 && b <= 175 = Color240 81
123− | r <= 135 && g <= 95 && b <= 215 = Color240 82
124− | r <= 135 && g <= 95 && b <= 255 = Color240 83
125− | r <= 135 && g <= 135 && b <= 0 = Color240 84
126− | r <= 135 && g <= 135 && b <= 95 = Color240 85
127− | r <= 135 && g <= 135 && b <= 135 = Color240 86
128− | r <= 135 && g <= 135 && b <= 175 = Color240 87
129− | r <= 135 && g <= 135 && b <= 215 = Color240 88
130− | r <= 135 && g <= 135 && b <= 255 = Color240 89
131− | r <= 135 && g <= 175 && b <= 0 = Color240 90
132− | r <= 135 && g <= 175 && b <= 95 = Color240 91
133− | r <= 135 && g <= 175 && b <= 135 = Color240 92
134− | r <= 135 && g <= 175 && b <= 175 = Color240 93
135− | r <= 135 && g <= 175 && b <= 215 = Color240 94
136− | r <= 135 && g <= 175 && b <= 255 = Color240 95
137− | r <= 135 && g <= 215 && b <= 0 = Color240 96
138− | r <= 135 && g <= 215 && b <= 95 = Color240 97
139− | r <= 135 && g <= 215 && b <= 135 = Color240 98
140− | r <= 135 && g <= 215 && b <= 175 = Color240 99
141− | r <= 135 && g <= 215 && b <= 215 = Color240 100
142− | r <= 135 && g <= 215 && b <= 255 = Color240 101
143− | r <= 135 && g <= 255 && b <= 0 = Color240 102
144− | r <= 135 && g <= 255 && b <= 95 = Color240 103
145− | r <= 135 && g <= 255 && b <= 135 = Color240 104
146− | r <= 135 && g <= 255 && b <= 175 = Color240 105
147− | r <= 135 && g <= 255 && b <= 215 = Color240 106
148− | r <= 135 && g <= 255 && b <= 255 = Color240 107
149− | r <= 175 && g <= 0 && b <= 0 = Color240 108
150− | r <= 175 && g <= 0 && b <= 95 = Color240 109
151− | r <= 175 && g <= 0 && b <= 135 = Color240 110
152− | r <= 175 && g <= 0 && b <= 175 = Color240 111
153− | r <= 175 && g <= 0 && b <= 215 = Color240 112
154− | r <= 175 && g <= 0 && b <= 255 = Color240 113
155− | r <= 175 && g <= 95 && b <= 0 = Color240 114
156− | r <= 175 && g <= 95 && b <= 95 = Color240 115
157− | r <= 175 && g <= 95 && b <= 135 = Color240 116
158− | r <= 175 && g <= 95 && b <= 175 = Color240 117
159− | r <= 175 && g <= 95 && b <= 215 = Color240 118
160− | r <= 175 && g <= 95 && b <= 255 = Color240 119
161− | r <= 175 && g <= 135 && b <= 0 = Color240 120
162− | r <= 175 && g <= 135 && b <= 95 = Color240 121
163− | r <= 175 && g <= 135 && b <= 135 = Color240 122
164− | r <= 175 && g <= 135 && b <= 175 = Color240 123
165− | r <= 175 && g <= 135 && b <= 215 = Color240 124
166− | r <= 175 && g <= 135 && b <= 255 = Color240 125
167− | r <= 175 && g <= 175 && b <= 0 = Color240 126
168− | r <= 175 && g <= 175 && b <= 95 = Color240 127
169− | r <= 175 && g <= 175 && b <= 135 = Color240 128
170− | r <= 175 && g <= 175 && b <= 175 = Color240 129
171− | r <= 175 && g <= 175 && b <= 215 = Color240 130
172− | r <= 175 && g <= 175 && b <= 255 = Color240 131
173− | r <= 175 && g <= 215 && b <= 0 = Color240 132
174− | r <= 175 && g <= 215 && b <= 95 = Color240 133
175− | r <= 175 && g <= 215 && b <= 135 = Color240 134
176− | r <= 175 && g <= 215 && b <= 175 = Color240 135
177− | r <= 175 && g <= 215 && b <= 215 = Color240 136
178− | r <= 175 && g <= 215 && b <= 255 = Color240 137
179− | r <= 175 && g <= 255 && b <= 0 = Color240 138
180− | r <= 175 && g <= 255 && b <= 95 = Color240 139
181− | r <= 175 && g <= 255 && b <= 135 = Color240 140
182− | r <= 175 && g <= 255 && b <= 175 = Color240 141
183− | r <= 175 && g <= 255 && b <= 215 = Color240 142
184− | r <= 175 && g <= 255 && b <= 255 = Color240 143
185− | r <= 215 && g <= 0 && b <= 0 = Color240 144
186− | r <= 215 && g <= 0 && b <= 95 = Color240 145
187− | r <= 215 && g <= 0 && b <= 135 = Color240 146
188− | r <= 215 && g <= 0 && b <= 175 = Color240 147
189− | r <= 215 && g <= 0 && b <= 215 = Color240 148
190− | r <= 215 && g <= 0 && b <= 255 = Color240 149
191− | r <= 215 && g <= 95 && b <= 0 = Color240 150
192− | r <= 215 && g <= 95 && b <= 95 = Color240 151
193− | r <= 215 && g <= 95 && b <= 135 = Color240 152
194− | r <= 215 && g <= 95 && b <= 175 = Color240 153
195− | r <= 215 && g <= 95 && b <= 215 = Color240 154
196− | r <= 215 && g <= 95 && b <= 255 = Color240 155
197− | r <= 215 && g <= 135 && b <= 0 = Color240 156
198− | r <= 215 && g <= 135 && b <= 95 = Color240 157
199− | r <= 215 && g <= 135 && b <= 135 = Color240 158
200− | r <= 215 && g <= 135 && b <= 175 = Color240 159
201− | r <= 215 && g <= 135 && b <= 215 = Color240 160
202− | r <= 215 && g <= 135 && b <= 255 = Color240 161
203− | r <= 215 && g <= 175 && b <= 0 = Color240 162
204− | r <= 215 && g <= 175 && b <= 95 = Color240 163
205− | r <= 215 && g <= 175 && b <= 135 = Color240 164
206− | r <= 215 && g <= 175 && b <= 175 = Color240 165
207− | r <= 215 && g <= 175 && b <= 215 = Color240 166
208− | r <= 215 && g <= 175 && b <= 255 = Color240 167
209− | r <= 215 && g <= 215 && b <= 0 = Color240 168
210− | r <= 215 && g <= 215 && b <= 95 = Color240 169
211− | r <= 215 && g <= 215 && b <= 135 = Color240 170
212− | r <= 215 && g <= 215 && b <= 175 = Color240 171
213− | r <= 215 && g <= 215 && b <= 215 = Color240 172
214− | r <= 215 && g <= 215 && b <= 255 = Color240 173
215− | r <= 215 && g <= 255 && b <= 0 = Color240 174
216− | r <= 215 && g <= 255 && b <= 95 = Color240 175
217− | r <= 215 && g <= 255 && b <= 135 = Color240 176
218− | r <= 215 && g <= 255 && b <= 175 = Color240 177
219− | r <= 215 && g <= 255 && b <= 215 = Color240 178
220− | r <= 215 && g <= 255 && b <= 255 = Color240 179
221− | r <= 255 && g <= 0 && b <= 0 = Color240 180
222− | r <= 255 && g <= 0 && b <= 95 = Color240 181
223− | r <= 255 && g <= 0 && b <= 135 = Color240 182
224− | r <= 255 && g <= 0 && b <= 175 = Color240 183
225− | r <= 255 && g <= 0 && b <= 215 = Color240 184
226− | r <= 255 && g <= 0 && b <= 255 = Color240 185
227− | r <= 255 && g <= 95 && b <= 0 = Color240 186
228− | r <= 255 && g <= 95 && b <= 95 = Color240 187
229− | r <= 255 && g <= 95 && b <= 135 = Color240 188
230− | r <= 255 && g <= 95 && b <= 175 = Color240 189
231− | r <= 255 && g <= 95 && b <= 215 = Color240 190
232− | r <= 255 && g <= 95 && b <= 255 = Color240 191
233− | r <= 255 && g <= 135 && b <= 0 = Color240 192
234− | r <= 255 && g <= 135 && b <= 95 = Color240 193
235− | r <= 255 && g <= 135 && b <= 135 = Color240 194
236− | r <= 255 && g <= 135 && b <= 175 = Color240 195
237− | r <= 255 && g <= 135 && b <= 215 = Color240 196
238− | r <= 255 && g <= 135 && b <= 255 = Color240 197
239− | r <= 255 && g <= 175 && b <= 0 = Color240 198
240− | r <= 255 && g <= 175 && b <= 95 = Color240 199
241− | r <= 255 && g <= 175 && b <= 135 = Color240 200
242− | r <= 255 && g <= 175 && b <= 175 = Color240 201
243− | r <= 255 && g <= 175 && b <= 215 = Color240 202
244− | r <= 255 && g <= 175 && b <= 255 = Color240 203
245− | r <= 255 && g <= 215 && b <= 0 = Color240 204
246− | r <= 255 && g <= 215 && b <= 95 = Color240 205
247− | r <= 255 && g <= 215 && b <= 135 = Color240 206
248− | r <= 255 && g <= 215 && b <= 175 = Color240 207
249− | r <= 255 && g <= 215 && b <= 215 = Color240 208
250− | r <= 255 && g <= 215 && b <= 255 = Color240 209
251− | r <= 255 && g <= 255 && b <= 0 = Color240 210
252− | r <= 255 && g <= 255 && b <= 95 = Color240 211
253− | r <= 255 && g <= 255 && b <= 135 = Color240 212
254− | r <= 255 && g <= 255 && b <= 175 = Color240 213
255− | r <= 255 && g <= 255 && b <= 215 = Color240 214
256− | r <= 255 && g <= 255 && b <= 255 = Color240 215
257− | otherwise = error (printf "RGB color %d %d %d does not map to 240 palette."
258 (fromIntegral r :: Int)
44 (fromIntegral r :: Int)
259 (fromIntegral g :: Int)
45 (fromIntegral g :: Int)
260− (fromIntegral b :: Int))
46+ (fromIntegral b :: Int)))
47+
48+simpleColor_ :: Integral i => Word8 -> i -> Word8
49+simpleColor_ e c
50+ | c <= 0 = 0
51+ | c <= 95 = 1
52+ | c <= 255 = fromIntegral ((c-16) `div` 40)
53+ | otherwise = e
261
54
55+-- | Create a RGB triple from a value in the Color240 set.
56+color240CodeToRGB :: Word8 -> Maybe (Int, Int, Int)
57+color240CodeToRGB n = case n of
58+ 0 -> Just (0, 0, 0)
59+ 1 -> Just (0, 0, 95)
60+ 2 -> Just (0, 0, 135)
61+ 3 -> Just (0, 0, 175)
62+ 4 -> Just (0, 0, 215)
63+ 5 -> Just (0, 0, 255)
64+ 6 -> Just (0, 95, 0)
65+ 7 -> Just (0, 95, 95)
66+ 8 -> Just (0, 95, 135)
67+ 9 -> Just (0, 95, 175)
68+ 10 -> Just (0, 95, 215)
69+ 11 -> Just (0, 95, 255)
70+ 12 -> Just (0, 135, 0)
71+ 13 -> Just (0, 135, 95)
72+ 14 -> Just (0, 135, 135)
73+ 15 -> Just (0, 135, 175)
74+ 16 -> Just (0, 135, 215)
75+ 17 -> Just (0, 135, 255)
76+ 18 -> Just (0, 175, 0)
77+ 19 -> Just (0, 175, 95)
78+ 20 -> Just (0, 175, 135)
79+ 21 -> Just (0, 175, 175)
80+ 22 -> Just (0, 175, 215)
81+ 23 -> Just (0, 175, 255)
82+ 24 -> Just (0, 215, 0)
83+ 25 -> Just (0, 215, 95)
84+ 26 -> Just (0, 215, 135)
85+ 27 -> Just (0, 215, 175)
86+ 28 -> Just (0, 215, 215)
87+ 29 -> Just (0, 215, 255)
88+ 30 -> Just (0, 255, 0)
89+ 31 -> Just (0, 255, 95)
90+ 32 -> Just (0, 255, 135)
91+ 33 -> Just (0, 255, 175)
92+ 34 -> Just (0, 255, 215)
93+ 35 -> Just (0, 255, 255)
94+ 36 -> Just (95, 0, 0)
95+ 37 -> Just (95, 0, 95)
96+ 38 -> Just (95, 0, 135)
97+ 39 -> Just (95, 0, 175)
98+ 40 -> Just (95, 0, 215)
99+ 41 -> Just (95, 0, 255)
100+ 42 -> Just (95, 95, 0)
101+ 43 -> Just (95, 95, 95)
102+ 44 -> Just (95, 95, 135)
103+ 45 -> Just (95, 95, 175)
104+ 46 -> Just (95, 95, 215)
105+ 47 -> Just (95, 95, 255)
106+ 48 -> Just (95, 135, 0)
107+ 49 -> Just (95, 135, 95)
108+ 50 -> Just (95, 135, 135)
109+ 51 -> Just (95, 135, 175)
110+ 52 -> Just (95, 135, 215)
111+ 53 -> Just (95, 135, 255)
112+ 54 -> Just (95, 175, 0)
113+ 55 -> Just (95, 175, 95)
114+ 56 -> Just (95, 175, 135)
115+ 57 -> Just (95, 175, 175)
116+ 58 -> Just (95, 175, 215)
117+ 59 -> Just (95, 175, 255)
118+ 60 -> Just (95, 215, 0)
119+ 61 -> Just (95, 215, 95)
120+ 62 -> Just (95, 215, 135)
121+ 63 -> Just (95, 215, 175)
122+ 64 -> Just (95, 215, 215)
123+ 65 -> Just (95, 215, 255)
124+ 66 -> Just (95, 255, 0)
125+ 67 -> Just (95, 255, 95)
126+ 68 -> Just (95, 255, 135)
127+ 69 -> Just (95, 255, 175)
128+ 70 -> Just (95, 255, 215)
129+ 71 -> Just (95, 255, 255)
130+ 72 -> Just (135, 0, 0)
131+ 73 -> Just (135, 0, 95)
132+ 74 -> Just (135, 0, 135)
133+ 75 -> Just (135, 0, 175)
134+ 76 -> Just (135, 0, 215)
135+ 77 -> Just (135, 0, 255)
136+ 78 -> Just (135, 95, 0)
137+ 79 -> Just (135, 95, 95)
138+ 80 -> Just (135, 95, 135)
139+ 81 -> Just (135, 95, 175)
140+ 82 -> Just (135, 95, 215)
141+ 83 -> Just (135, 95, 255)
142+ 84 -> Just (135, 135, 0)
143+ 85 -> Just (135, 135, 95)
144+ 86 -> Just (135, 135, 135)
145+ 87 -> Just (135, 135, 175)
146+ 88 -> Just (135, 135, 215)
147+ 89 -> Just (135, 135, 255)
148+ 90 -> Just (135, 175, 0)
149+ 91 -> Just (135, 175, 95)
150+ 92 -> Just (135, 175, 135)
151+ 93 -> Just (135, 175, 175)
152+ 94 -> Just (135, 175, 215)
153+ 95 -> Just (135, 175, 255)
154+ 96 -> Just (135, 215, 0)
155+ 97 -> Just (135, 215, 95)
156+ 98 -> Just (135, 215, 135)
157+ 99 -> Just (135, 215, 175)
158+ 100 -> Just (135, 215, 215)
159+ 101 -> Just (135, 215, 255)
160+ 102 -> Just (135, 255, 0)
161+ 103 -> Just (135, 255, 95)
162+ 104 -> Just (135, 255, 135)
163+ 105 -> Just (135, 255, 175)
164+ 106 -> Just (135, 255, 215)
165+ 107 -> Just (135, 255, 255)
166+ 108 -> Just (175, 0, 0)
167+ 109 -> Just (175, 0, 95)
168+ 110 -> Just (175, 0, 135)
169+ 111 -> Just (175, 0, 175)
170+ 112 -> Just (175, 0, 215)
171+ 113 -> Just (175, 0, 255)
172+ 114 -> Just (175, 95, 0)
173+ 115 -> Just (175, 95, 95)
174+ 116 -> Just (175, 95, 135)
175+ 117 -> Just (175, 95, 175)
176+ 118 -> Just (175, 95, 215)
177+ 119 -> Just (175, 95, 255)
178+ 120 -> Just (175, 135, 0)
179+ 121 -> Just (175, 135, 95)
180+ 122 -> Just (175, 135, 135)
181+ 123 -> Just (175, 135, 175)
182+ 124 -> Just (175, 135, 215)
183+ 125 -> Just (175, 135, 255)
184+ 126 -> Just (175, 175, 0)
185+ 127 -> Just (175, 175, 95)
186+ 128 -> Just (175, 175, 135)
187+ 129 -> Just (175, 175, 175)
188+ 130 -> Just (175, 175, 215)
189+ 131 -> Just (175, 175, 255)
190+ 132 -> Just (175, 215, 0)
191+ 133 -> Just (175, 215, 95)
192+ 134 -> Just (175, 215, 135)
193+ 135 -> Just (175, 215, 175)
194+ 136 -> Just (175, 215, 215)
195+ 137 -> Just (175, 215, 255)
196+ 138 -> Just (175, 255, 0)
197+ 139 -> Just (175, 255, 95)
198+ 140 -> Just (175, 255, 135)
199+ 141 -> Just (175, 255, 175)
200+ 142 -> Just (175, 255, 215)
201+ 143 -> Just (175, 255, 255)
202+ 144 -> Just (215, 0, 0)
203+ 145 -> Just (215, 0, 95)
204+ 146 -> Just (215, 0, 135)
205+ 147 -> Just (215, 0, 175)
206+ 148 -> Just (215, 0, 215)
207+ 149 -> Just (215, 0, 255)
208+ 150 -> Just (215, 95, 0)
209+ 151 -> Just (215, 95, 95)
210+ 152 -> Just (215, 95, 135)
211+ 153 -> Just (215, 95, 175)
212+ 154 -> Just (215, 95, 215)
213+ 155 -> Just (215, 95, 255)
214+ 156 -> Just (215, 135, 0)
215+ 157 -> Just (215, 135, 95)
216+ 158 -> Just (215, 135, 135)
217+ 159 -> Just (215, 135, 175)
218+ 160 -> Just (215, 135, 215)
219+ 161 -> Just (215, 135, 255)
220+ 162 -> Just (215, 175, 0)
221+ 163 -> Just (215, 175, 95)
222+ 164 -> Just (215, 175, 135)
223+ 165 -> Just (215, 175, 175)
224+ 166 -> Just (215, 175, 215)
225+ 167 -> Just (215, 175, 255)
226+ 168 -> Just (215, 215, 0)
227+ 169 -> Just (215, 215, 95)
228+ 170 -> Just (215, 215, 135)
229+ 171 -> Just (215, 215, 175)
230+ 172 -> Just (215, 215, 215)
231+ 173 -> Just (215, 215, 255)
232+ 174 -> Just (215, 255, 0)
233+ 175 -> Just (215, 255, 95)
234+ 176 -> Just (215, 255, 135)
235+ 177 -> Just (215, 255, 175)
236+ 178 -> Just (215, 255, 215)
237+ 179 -> Just (215, 255, 255)
238+ 180 -> Just (255, 0, 0)
239+ 181 -> Just (255, 0, 95)
240+ 182 -> Just (255, 0, 135)
241+ 183 -> Just (255, 0, 175)
242+ 184 -> Just (255, 0, 215)
243+ 185 -> Just (255, 0, 255)
244+ 186 -> Just (255, 95, 0)
245+ 187 -> Just (255, 95, 95)
246+ 188 -> Just (255, 95, 135)
247+ 189 -> Just (255, 95, 175)
248+ 190 -> Just (255, 95, 215)
249+ 191 -> Just (255, 95, 255)
250+ 192 -> Just (255, 135, 0)
251+ 193 -> Just (255, 135, 95)
252+ 194 -> Just (255, 135, 135)
253+ 195 -> Just (255, 135, 175)
254+ 196 -> Just (255, 135, 215)
255+ 197 -> Just (255, 135, 255)
256+ 198 -> Just (255, 175, 0)
257+ 199 -> Just (255, 175, 95)
258+ 200 -> Just (255, 175, 135)
259+ 201 -> Just (255, 175, 175)
260+ 202 -> Just (255, 175, 215)
261+ 203 -> Just (255, 175, 255)
262+ 204 -> Just (255, 215, 0)
263+ 205 -> Just (255, 215, 95)
264+ 206 -> Just (255, 215, 135)
265+ 207 -> Just (255, 215, 175)
266+ 208 -> Just (255, 215, 215)
267+ 209 -> Just (255, 215, 255)
268+ 210 -> Just (255, 255, 0)
269+ 211 -> Just (255, 255, 95)
270+ 212 -> Just (255, 255, 135)
271+ 213 -> Just (255, 255, 175)
272+ 214 -> Just (255, 255, 215)
273+ 215 -> Just (255, 255, 255)
274+ 216 -> Just (8, 8, 8)
275+ 217 -> Just (18, 18, 18)
276+ 218 -> Just (28, 28, 28)
277+ 219 -> Just (38, 38, 38)
278+ 220 -> Just (48, 48, 48)
279+ 221 -> Just (58, 58, 58)
280+ 222 -> Just (68, 68, 68)
281+ 223 -> Just (78, 78, 78)
282+ 224 -> Just (88, 88, 88)
283+ 225 -> Just (98, 98, 98)
284+ 226 -> Just (108, 108, 108)
285+ 227 -> Just (118, 118, 118)
286+ 228 -> Just (128, 128, 128)
287+ 229 -> Just (138, 138, 138)
288+ 230 -> Just (148, 148, 148)
289+ 231 -> Just (158, 158, 158)
290+ 232 -> Just (168, 168, 168)
291+ 233 -> Just (178, 178, 178)
292+ 234 -> Just (188, 188, 188)
293+ 235 -> Just (198, 198, 198)
294+ 236 -> Just (208, 208, 208)
295+ 237 -> Just (218, 218, 218)
296+ 238 -> Just (228, 228, 228)
297+ 239 -> Just (238, 238, 238)
298+ _ -> Nothing
src/Graphics/Vty/Config.hsnew file+488
@@ -0,0 +1,488 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}
4+{-# LANGUAGE TypeOperators #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE LambdaCase, MultiWayIf #-}
7+-- | Vty supports a configuration file format and associated 'Config'
8+-- data type. The 'Config' can be provided to 'mkVty' to customize the
9+-- application's use of Vty.
10+--
11+-- Lines in config files that fail to parse are ignored. Later entries
12+-- take precedence over earlier ones.
13+--
14+-- = Debug
15+--
16+-- == @debugLog@
17+--
18+-- Format:
19+--
20+-- @
21+-- \"debugLog\" string
22+-- @
23+--
24+-- The value of the environment variable @VTY_DEBUG_LOG@ is equivalent
25+-- to a debugLog entry at the end of the last config file.
26+--
27+-- = Input Processing
28+--
29+-- == @map@
30+--
31+-- Format:
32+--
33+-- @
34+-- \"map\" term string key modifier_list
35+-- where
36+-- key := KEsc | KChar Char | KBS ... (same as 'Key')
37+-- modifier_list := \"[\" modifier+ \"]\"
38+-- modifier := MShift | MCtrl | MMeta | MAlt
39+-- term := "_" | string
40+-- @
41+--
42+-- E.g., if the contents are
43+--
44+-- @
45+-- map _ \"\\ESC[B\" KUp []
46+-- map _ \"\\ESC[1;3B\" KDown [MAlt]
47+-- map \"xterm\" \"\\ESC[D\" KLeft []
48+-- @
49+--
50+-- Then the bytes @\"\\ESC[B\"@ will result in the KUp event on all
51+-- terminals. The bytes @\"\\ESC[1;3B\"@ will result in the event KDown
52+-- with the MAlt modifier on all terminals. The bytes @\"\\ESC[D\"@ will
53+-- result in the KLeft event when @TERM@ is @xterm@.
54+--
55+-- If a debug log is requested then vty will output the current input
56+-- table to the log in the above format. A workflow for using this is
57+-- to set @VTY_DEBUG_LOG@. Run the application. Check the debug log for
58+-- incorrect mappings. Add corrected mappings to @$HOME\/.vty\/config@.
59+--
60+-- = Unicode Character Width Maps
61+--
62+-- == @widthMap@
63+--
64+-- Format:
65+--
66+-- @
67+-- \"widthMap\" string string
68+-- @
69+--
70+-- E.g.,
71+--
72+-- @
73+-- widthMap \"xterm\" \"\/home\/user\/.vty\/xterm\_map.dat\"
74+-- @
75+--
76+-- This directive specifies the path to a Unicode character width
77+-- map (the second argument) that should be loaded and used when
78+-- the value of TERM matches the first argument. Unicode character
79+-- width maps can be produced either by running the provided binary
80+-- @vty-build-width-table@ or by calling the library routine
81+-- 'Graphics.Vty.UnicodeWidthTable.Query.buildUnicodeWidthTable'. The
82+-- 'Graphics.Vty.mkVty' function will use these configuration settings
83+-- to attempt to load and install the specified width map. See the
84+-- documentation for 'Graphics.Vty.mkVty' for details.
85+module Graphics.Vty.Config
86+ ( InputMap
87+ , Config(..)
88+ , VtyConfigurationError(..)
89+ , userConfig
90+ , overrideEnvConfig
91+ , standardIOConfig
92+ , runParseConfig
93+ , parseConfigFile
94+ , defaultConfig
95+ , getTtyEraseChar
96+ , currentTerminalName
97+
98+ , vtyConfigPath
99+ , widthTableFilename
100+ , vtyDataDirectory
101+ , terminalWidthTablePath
102+ , vtyConfigFileEnvName
103+
104+ , ConfigUpdateResult(..)
105+ , addConfigWidthMap
106+ )
107+where
108+
109+import Prelude
110+
111+import Control.Applicative hiding (many)
112+
113+import Control.Exception (catch, IOException, Exception(..), throwIO)
114+import Control.Monad (liftM, guard, void)
115+
116+import qualified Data.ByteString as BS
117+#if !(MIN_VERSION_base(4,8,0))
118+import Data.Monoid (Monoid(..))
119+#endif
120+#if !(MIN_VERSION_base(4,11,0))
121+import Data.Semigroup (Semigroup(..))
122+#endif
123+import Data.Typeable (Typeable)
124+
125+import Graphics.Vty.Input.Events
126+import Graphics.Vty.Attributes.Color (ColorMode(..), detectColorMode)
127+
128+import GHC.Generics
129+
130+import System.Directory ( getAppUserDataDirectory, doesFileExist
131+ , createDirectoryIfMissing
132+ )
133+import System.Environment (lookupEnv)
134+import System.FilePath ((</>), takeDirectory)
135+import System.Posix.IO (stdInput, stdOutput)
136+import System.Posix.Types (Fd(..))
137+import Foreign.C.Types (CInt(..), CChar(..))
138+
139+import Text.Parsec hiding ((<|>))
140+import Text.Parsec.Token ( GenLanguageDef(..) )
141+import qualified Text.Parsec.Token as P
142+
143+-- | Type of errors that can be thrown when configuring VTY
144+data VtyConfigurationError =
145+ VtyMissingTermEnvVar
146+ -- ^ TERM environment variable not set
147+ deriving (Show, Eq, Typeable)
148+
149+instance Exception VtyConfigurationError where
150+ displayException VtyMissingTermEnvVar = "TERM environment variable not set"
151+
152+-- | Mappings from input bytes to event in the order specified. Later
153+-- entries take precedence over earlier in the case multiple entries
154+-- have the same byte string.
155+type InputMap = [(Maybe String, String, Event)]
156+
157+-- | A Vty configuration.
158+data Config =
159+ Config { vmin :: Maybe Int
160+ -- ^ The default is 1 character.
161+ , vtime :: Maybe Int
162+ -- ^ The default is 100 milliseconds, 0.1 seconds.
163+ , mouseMode :: Maybe Bool
164+ -- ^ The default is False.
165+ , bracketedPasteMode :: Maybe Bool
166+ -- ^ The default is False.
167+ , debugLog :: Maybe FilePath
168+ -- ^ Debug information is appended to this file if not
169+ -- Nothing.
170+ , inputMap :: InputMap
171+ -- ^ The (input byte, output event) pairs extend the internal
172+ -- input table of VTY and the table from terminfo.
173+ --
174+ -- See "Graphics.Vty.Config" module documentation for
175+ -- documentation of the @map@ directive.
176+ , inputFd :: Maybe Fd
177+ -- ^ The input file descriptor to use. The default is
178+ -- 'System.Posix.IO.stdInput'
179+ , outputFd :: Maybe Fd
180+ -- ^ The output file descriptor to use. The default is
181+ -- 'System.Posix.IO.stdOutput'
182+ , termName :: Maybe String
183+ -- ^ The terminal name used to look up terminfo capabilities.
184+ -- The default is the value of the TERM environment variable.
185+ , termWidthMaps :: [(String, FilePath)]
186+ -- ^ Terminal width map files.
187+ , allowCustomUnicodeWidthTables :: Maybe Bool
188+ -- ^ Whether to permit custom Unicode width table loading by
189+ -- 'Graphics.Vty.mkVty'. @'Just' 'False'@ indicates that
190+ -- table loading should not be performed. Other values permit
191+ -- table loading.
192+ --
193+ -- If a table load is attempted and fails, information
194+ -- about the failure will be logged to the debug log if the
195+ -- configuration specifies one. If no custom table is loaded
196+ -- (or if a load fails), the built-in character width table
197+ -- will be used.
198+ , colorMode :: Maybe ColorMode
199+ -- ^ The color mode used to know how many colors the terminal
200+ -- supports.
201+ }
202+ deriving (Show, Eq)
203+
204+defaultConfig :: Config
205+defaultConfig = mempty
206+
207+instance Semigroup Config where
208+ c0 <> c1 =
209+ -- latter config takes priority for everything but inputMap
210+ Config { vmin = vmin c1 <|> vmin c0
211+ , vtime = vtime c1 <|> vtime c0
212+ , mouseMode = mouseMode c1
213+ , bracketedPasteMode = bracketedPasteMode c1
214+ , debugLog = debugLog c1 <|> debugLog c0
215+ , inputMap = inputMap c0 <> inputMap c1
216+ , inputFd = inputFd c1 <|> inputFd c0
217+ , outputFd = outputFd c1 <|> outputFd c0
218+ , termName = termName c1 <|> termName c0
219+ , termWidthMaps = termWidthMaps c1 <|> termWidthMaps c0
220+ , allowCustomUnicodeWidthTables =
221+ allowCustomUnicodeWidthTables c1 <|> allowCustomUnicodeWidthTables c0
222+ , colorMode = colorMode c1 <|> colorMode c0
223+ }
224+
225+instance Monoid Config where
226+ mempty =
227+ Config { vmin = Nothing
228+ , vtime = Nothing
229+ , mouseMode = Nothing
230+ , bracketedPasteMode = Nothing
231+ , debugLog = mempty
232+ , inputMap = mempty
233+ , inputFd = Nothing
234+ , outputFd = Nothing
235+ , termName = Nothing
236+ , termWidthMaps = []
237+ , allowCustomUnicodeWidthTables = Nothing
238+ , colorMode = Nothing
239+ }
240+#if !(MIN_VERSION_base(4,11,0))
241+ mappend = (<>)
242+#endif
243+
244+vtyDataDirectory :: IO FilePath
245+vtyDataDirectory = getAppUserDataDirectory "vty"
246+
247+vtyConfigPath :: IO FilePath
248+vtyConfigPath = do
249+ dir <- vtyDataDirectory
250+ return $ dir </> "config"
251+
252+vtyConfigFileEnvName :: String
253+vtyConfigFileEnvName = "VTY_CONFIG_FILE"
254+
255+-- | Load a configuration from 'vtyConfigPath' and @$VTY_CONFIG_FILE@.
256+userConfig :: IO Config
257+userConfig = do
258+ configFile <- vtyConfigPath >>= parseConfigFile
259+ overrideConfig <- maybe (return defaultConfig) parseConfigFile =<<
260+ lookupEnv vtyConfigFileEnvName
261+ let base = configFile <> overrideConfig
262+ mappend base <$> overrideEnvConfig
263+
264+widthTableFilename :: String -> String
265+widthTableFilename term = "width_table_" <> term <> ".dat"
266+
267+termVariable :: String
268+termVariable = "TERM"
269+
270+currentTerminalName :: IO (Maybe String)
271+currentTerminalName = lookupEnv termVariable
272+
273+terminalWidthTablePath :: IO (Maybe FilePath)
274+terminalWidthTablePath = do
275+ dataDir <- vtyDataDirectory
276+ result <- lookupEnv termVariable
277+ case result of
278+ Nothing -> return Nothing
279+ Just term -> do
280+ return $ Just $ dataDir </> widthTableFilename term
281+
282+overrideEnvConfig :: IO Config
283+overrideEnvConfig = do
284+ d <- lookupEnv "VTY_DEBUG_LOG"
285+ return $ defaultConfig { debugLog = d }
286+
287+-- | Configures VTY using defaults suitable for terminals. This function
288+-- can raise 'VtyConfigurationError'.
289+standardIOConfig :: IO Config
290+standardIOConfig = do
291+ mb <- lookupEnv termVariable
292+ case mb of
293+ Nothing -> throwIO VtyMissingTermEnvVar
294+ Just t -> do
295+ mcolorMode <- detectColorMode t
296+ return defaultConfig
297+ { vmin = Just 1
298+ , mouseMode = Just False
299+ , bracketedPasteMode = Just False
300+ , vtime = Just 100
301+ , inputFd = Just stdInput
302+ , outputFd = Just stdOutput
303+ , termName = Just t
304+ , colorMode = Just mcolorMode
305+ }
306+
307+parseConfigFile :: FilePath -> IO Config
308+parseConfigFile path = do
309+ catch (runParseConfig path <$> BS.readFile path)
310+ (\(_ :: IOException) -> return defaultConfig)
311+
312+runParseConfig :: String -> BS.ByteString -> Config
313+runParseConfig name cfgTxt =
314+ case runParser parseConfig () name cfgTxt of
315+ Right cfg -> cfg
316+ Left{} -> defaultConfig
317+
318+------------------------------------------------------------------------
319+
320+type Parser = Parsec BS.ByteString ()
321+
322+configLanguage :: Monad m => P.GenLanguageDef BS.ByteString () m
323+configLanguage = LanguageDef
324+ { commentStart = "{-"
325+ , commentEnd = "-}"
326+ , commentLine = "--"
327+ , nestedComments = True
328+ , identStart = letter <|> char '_'
329+ , identLetter = alphaNum <|> oneOf "_'"
330+ , opStart = opLetter configLanguage
331+ , opLetter = oneOf ":!#$%&*+./<=>?@\\^|-~"
332+ , reservedOpNames = []
333+ , reservedNames = []
334+ , caseSensitive = True
335+ }
336+
337+configLexer :: Monad m => P.GenTokenParser BS.ByteString () m
338+configLexer = P.makeTokenParser configLanguage
339+
340+mapDecl :: Parser Config
341+mapDecl = do
342+ "map" <- P.identifier configLexer
343+ termIdent <- (char '_' >> P.whiteSpace configLexer >> return Nothing)
344+ <|> (Just <$> P.stringLiteral configLexer)
345+ bytes <- P.stringLiteral configLexer
346+ key <- parseValue
347+ modifiers <- parseValue
348+ return defaultConfig { inputMap = [(termIdent, bytes, EvKey key modifiers)] }
349+
350+debugLogDecl :: Parser Config
351+debugLogDecl = do
352+ "debugLog" <- P.identifier configLexer
353+ path <- P.stringLiteral configLexer
354+ return defaultConfig { debugLog = Just path }
355+
356+widthMapDecl :: Parser Config
357+widthMapDecl = do
358+ "widthMap" <- P.identifier configLexer
359+ tName <- P.stringLiteral configLexer
360+ path <- P.stringLiteral configLexer
361+ return defaultConfig { termWidthMaps = [(tName, path)] }
362+
363+ignoreLine :: Parser ()
364+ignoreLine = void $ manyTill anyChar newline
365+
366+parseConfig :: Parser Config
367+parseConfig = liftM mconcat $ many $ do
368+ P.whiteSpace configLexer
369+ let directives = [try mapDecl, try debugLogDecl, try widthMapDecl]
370+ choice directives <|> (ignoreLine >> return defaultConfig)
371+
372+class Parse a where parseValue :: Parser a
373+instance Parse Char where parseValue = P.charLiteral configLexer
374+instance Parse Int where parseValue = fromInteger <$> P.natural configLexer
375+instance Parse Key where parseValue = genericParse
376+instance Parse Modifier where parseValue = genericParse
377+instance Parse a => Parse [a] where
378+ parseValue = P.brackets configLexer
379+ (parseValue `sepBy` P.symbol configLexer ",")
380+
381+------------------------------------------------------------------------
382+-- Derived parser for ADTs via generics
383+------------------------------------------------------------------------
384+
385+genericParse :: (Generic a, GParse (Rep a)) => Parser a
386+genericParse = to <$> gparse
387+
388+class GParse f where gparse :: Parser (f a)
389+instance GParse f => GParse (M1 S i f) where gparse = M1 <$> gparse
390+instance GParse U1 where gparse = return U1
391+instance Parse a => GParse (K1 i a) where gparse = K1 <$> parseValue
392+
393+instance (GParse f, GParse g) => GParse (f :*: g) where
394+ gparse = (:*:) <$> gparse <*> gparse
395+
396+instance GParseAlts f => GParse (M1 D i f) where
397+ gparse =
398+ do con <- P.identifier configLexer
399+ M1 <$> gparseAlts con
400+
401+------------------------------------------------------------------------
402+
403+class GParseAlts f where
404+ gparseAlts :: String -> Parser (f a)
405+
406+instance (Constructor i, GParse f) => GParseAlts (M1 C i f) where
407+ gparseAlts con =
408+ do guard (con == conName (M1 Nothing :: C1 i Maybe a))
409+ M1 <$> gparse
410+
411+instance (GParseAlts f, GParseAlts g) => GParseAlts (f :+: g) where
412+ gparseAlts con = L1 <$> gparseAlts con <|> R1 <$> gparseAlts con
413+
414+instance GParseAlts V1 where gparseAlts _ = fail "GParse: V1"
415+
416+foreign import ccall "vty_get_tty_erase" cGetTtyErase :: Fd -> IO CChar
417+
418+-- | Get the "erase" character for the terminal attached to the
419+-- specified file descriptor. This is the character configured by 'stty
420+-- erase'. If the call to 'tcgetattr' fails, this will return 'Nothing'.
421+-- Otherwise it will return the character that has been configured to
422+-- indicate the canonical mode ERASE behavior. That character can then
423+-- be added to the table of strings that we interpret to mean Backspace.
424+--
425+-- For more details, see:
426+--
427+-- * https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
428+-- * https://www.gsp.com/cgi-bin/man.cgi?section=1&topic=stty
429+-- * https://github.com/matterhorn-chat/matterhorn/issues/565
430+getTtyEraseChar :: Fd -> IO (Maybe Char)
431+getTtyEraseChar fd = do
432+ c <- cGetTtyErase fd
433+ if c /= 0
434+ then return $ Just $ toEnum $ fromEnum c
435+ else return Nothing
436+
437+data ConfigUpdateResult =
438+ ConfigurationCreated
439+ | ConfigurationModified
440+ | ConfigurationConflict String
441+ | ConfigurationRedundant
442+ deriving (Eq, Show)
443+
444+-- | Add a @widthMap@ directive to the Vty configuration file at the
445+-- specified path.
446+--
447+-- If the configuration path refers to a configuration that already
448+-- contains the directive for the specified map and terminal type, the
449+-- configuration file will not be modified. If the file does not contain
450+-- the directive, it will be appended to the file.
451+--
452+-- If the configuration path does not exist, a new configuration file
453+-- will be created and any directories in the path will also be created.
454+--
455+-- This returns @True@ if the configuration was created or modified and
456+-- @False@ otherwise. This does not handle exceptions raised by file or
457+-- directory permissions issues.
458+addConfigWidthMap :: FilePath
459+ -- ^ The configuration file path of the configuration
460+ -- to modify or create.
461+ -> String
462+ -- ^ The @TERM@ value for the @widthMap@ directive.
463+ -> FilePath
464+ -- ^ The width table file path for the directive.
465+ -> IO ConfigUpdateResult
466+addConfigWidthMap configPath term tablePath = do
467+ configEx <- doesFileExist configPath
468+ if configEx
469+ then updateConfig
470+ else createConfig >> return ConfigurationCreated
471+
472+ where
473+ directive = "widthMap " <> show term <> " " <> show tablePath <> "\n"
474+
475+ createConfig = do
476+ let dir = takeDirectory configPath
477+ createDirectoryIfMissing True dir
478+ writeFile configPath directive
479+
480+ updateConfig = do
481+ config <- parseConfigFile configPath
482+ if (term, tablePath) `elem` termWidthMaps config
483+ then return ConfigurationRedundant
484+ else case lookup term (termWidthMaps config) of
485+ Just other -> return $ ConfigurationConflict other
486+ Nothing -> do
487+ appendFile configPath directive
488+ return ConfigurationModified
src/Graphics/Vty/Debug.hs+21−21
@@ -1,39 +1,39 @@
1-- Copyright 2009-2010 Corey O'Connor
1-- Copyright 2009-2010 Corey O'Connor
2−module Graphics.Vty.Debug ( module Graphics.Vty.Debug
2+module Graphics.Vty.Debug
3+ ( module Graphics.Vty.Debug
3− , module Graphics.Vty.Debug.Image
4+ , module Graphics.Vty.Debug.Image
4− )
5+ )
5where
6where
6
7
7import Graphics.Vty.Attributes
8import Graphics.Vty.Attributes
9+import Graphics.Vty.Image (DisplayRegion)
8import Graphics.Vty.Debug.Image
10import Graphics.Vty.Debug.Image
9import Graphics.Vty.Span
11import Graphics.Vty.Span
10−import Graphics.Vty.DisplayRegion
11
12
12−import qualified Data.Vector as Vector
13+import qualified Data.Vector as Vector
13
14
14−row_ops_effected_columns :: DisplayOps -> [Int]
15+rowOpsAffectedColumns :: DisplayOps -> [Int]
15−row_ops_effected_columns ops
16+rowOpsAffectedColumns ops
16− = Vector.toList $ Vector.map span_ops_effected_columns ops
17+ = Vector.toList $ Vector.map spanOpsAffectedColumns ops
17
18
18−all_spans_have_width :: DisplayOps -> Int -> Bool
19+allSpansHaveWidth :: DisplayOps -> Int -> Bool
19−all_spans_have_width ops expected
20+allSpansHaveWidth ops expected
20− = all (== expected) $ Vector.toList $ Vector.map span_ops_effected_columns ops
21+ = all (== expected) $ Vector.toList $ Vector.map spanOpsAffectedColumns ops
21
22
22−span_ops_effected_rows :: DisplayOps -> Int
23+spanOpsAffectedRows :: DisplayOps -> Int
23−span_ops_effected_rows ops
24+spanOpsAffectedRows ops
24 = toEnum $ length (filter (not . null . Vector.toList) (Vector.toList ops))
25 = toEnum $ length (filter (not . null . Vector.toList) (Vector.toList ops))
25−
26+
26type SpanConstructLog = [SpanConstructEvent]
27type SpanConstructLog = [SpanConstructEvent]
27data SpanConstructEvent = SpanSetAttr Attr
28data SpanConstructEvent = SpanSetAttr Attr
28
29
29−is_set_attr :: Attr -> SpanConstructEvent -> Bool
30+isSetAttr :: Attr -> SpanConstructEvent -> Bool
30−is_set_attr expected_attr (SpanSetAttr in_attr)
31+isSetAttr expectedAttr (SpanSetAttr inAttr)
31− | in_attr == expected_attr = True
32+ | inAttr == expectedAttr = True
32−is_set_attr _attr _event = False
33+isSetAttr _attr _event = False
33
34
34data MockWindow = MockWindow Int Int
35data MockWindow = MockWindow Int Int
35 deriving (Show, Eq)
36 deriving (Show, Eq)
36
37
37−region_for_window :: MockWindow -> DisplayRegion
38+regionForWindow :: MockWindow -> DisplayRegion
38−region_for_window (MockWindow w h) = DisplayRegion w h
39+regionForWindow (MockWindow w h) = (w,h)
39−
src/Graphics/Vty/Debug/Image.hs+15−14
@@ -1,3 +1,4 @@
1+{-# OPTIONS_HADDOCK hide #-}
1module Graphics.Vty.Debug.Image where
2module Graphics.Vty.Debug.Image where
2
3
3import Graphics.Vty.Image
4import Graphics.Vty.Image
@@ -6,26 +7,26 @@ type ImageConstructLog = [ImageConstructEvent]
6data ImageConstructEvent = ImageConstructEvent
7data ImageConstructEvent = ImageConstructEvent
7 deriving ( Show, Eq )
8 deriving ( Show, Eq )
8
9
9−forward_image_ops :: [Image -> Image]
10+forwardImageOps :: [Image -> Image]
10−forward_image_ops = map forward_transform debug_image_ops
11+forwardImageOps = map forwardTransform debugImageOps
11
12
12−forward_transform, reverse_transform :: ImageOp -> (Image -> Image)
13+forwardTransform, reverseTransform :: ImageOp -> (Image -> Image)
13
14
14−forward_transform (ImageOp f _) = f
15+forwardTransform (ImageOp f _) = f
15−reverse_transform (ImageOp _ r) = r
16+reverseTransform (ImageOp _ r) = r
16
17
17data ImageOp = ImageOp ImageEndo ImageEndo
18data ImageOp = ImageOp ImageEndo ImageEndo
18type ImageEndo = Image -> Image
19type ImageEndo = Image -> Image
19
20
20−debug_image_ops :: [ImageOp]
21+debugImageOps :: [ImageOp]
21−debug_image_ops =
22+debugImageOps =
22− [ id_image_op
23+ [ idImageOp
23− -- , render_single_column_char_op
24+ -- , renderSingleColumnCharOp
24− -- , render_double_column_char_op
25+ -- , renderDoubleColumnCharOp
25 ]
26 ]
26
27
27−id_image_op :: ImageOp
28+idImageOp :: ImageOp
28−id_image_op = ImageOp id id
29+idImageOp = ImageOp id id
29
30
30−-- render_char_op :: ImageOp
31+-- renderCharOp :: ImageOp
31−-- render_char_op = ImageOp id id
32+-- renderCharOp = ImageOp id id
src/Graphics/Vty/DisplayAttributes.hs+122−83
@@ -1,78 +1,103 @@
1-- Copyright 2009-2010 Corey O'Connor
1-- Copyright 2009-2010 Corey O'Connor
2{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE CPP #-}
4+
3−module Graphics.Vty.DisplayAttributes
5+module Graphics.Vty.DisplayAttributes where
4− where
5
6
6import Graphics.Vty.Attributes
7import Graphics.Vty.Attributes
7
8
8−import Data.Bits ( (.&.) )
9+import Data.Bits ((.&.))
9−import Data.Monoid ( Monoid(..), mconcat )
10+import Data.ByteString (ByteString)
11+#if !(MIN_VERSION_base(4,11,0))
12+import Data.Semigroup (Semigroup(..))
13+#endif
14+import Data.Text (Text)
15+import Data.Text.Encoding (encodeUtf8)
10
16
11−-- | Given the previously applied display attributes as a FixedAttr and the current display
17+-- | Given the previously applied display attributes as a FixedAttr and
18+-- the current display attributes as an Attr produces a FixedAttr that
12−-- attributes as an Attr produces a FixedAttr that represents the current display attributes. This
19+-- represents the current display attributes. This is done by using the
13−-- is done by using the previously applied display attributes to remove the "KeepCurrent"
20+-- previously applied display attributes to remove the "KeepCurrent"
14-- abstraction.
21-- abstraction.
15−fix_display_attr :: FixedAttr -> Attr -> FixedAttr
22+fixDisplayAttr :: FixedAttr -> Attr -> FixedAttr
16−fix_display_attr fattr attr
23+fixDisplayAttr fattr attr
17− = FixedAttr ( fix_style (fixed_style fattr) (attr_style attr) )
24+ = FixedAttr (fixStyle (fixedStyle fattr) (attrStyle attr))
18− ( fix_color (fixed_fore_color fattr) (attr_fore_color attr) )
25+ (fixColor (fixedForeColor fattr) (attrForeColor attr))
19− ( fix_color (fixed_back_color fattr) (attr_back_color attr) )
26+ (fixColor (fixedBackColor fattr) (attrBackColor attr))
27+ (fixURL (fixedURL fattr) (attrURL attr))
20 where
28 where
21− fix_style _s Default = default_style_mask
29+ fixStyle _s Default = defaultStyleMask
22− fix_style s KeepCurrent = s
30+ fixStyle s KeepCurrent = s
23− fix_style _s (SetTo new_style) = new_style
31+ fixStyle _s (SetTo newStyle) = newStyle
24− fix_color _c Default = Nothing
32+ fixColor _c Default = Nothing
25− fix_color c KeepCurrent = c
33+ fixColor c KeepCurrent = c
26− fix_color _c (SetTo c) = Just c
34+ fixColor _c (SetTo c) = Just c
27−
35+ fixURL c KeepCurrent = c
36+ fixURL _c (SetTo n) = Just n
37+ fixURL _c Default = Nothing
38+
28−-- | difference between two display attributes. Used in the calculation of the operations required
39+-- | difference between two display attributes. Used in the calculation
29−-- to go from one display attribute to the next.
40+-- of the operations required to go from one display attribute to the
41+-- next.
30--
42--
31−-- Previously, vty would reset display attributes to default then apply the new display attributes.
43+-- Previously, vty would reset display attributes to default then apply
44+-- the new display attributes. This turned out to be very expensive: A
32−-- This turned out to be very expensive: A *lot* more data would be sent to the terminal than
45+-- *lot* more data would be sent to the terminal than required.
33−-- required.
34data DisplayAttrDiff = DisplayAttrDiff
46data DisplayAttrDiff = DisplayAttrDiff
35− { style_diffs :: [ StyleStateChange ]
47+ { styleDiffs :: [StyleStateChange]
36− , fore_color_diff :: DisplayColorDiff
48+ , foreColorDiff :: DisplayColorDiff
37− , back_color_diff :: DisplayColorDiff
49+ , backColorDiff :: DisplayColorDiff
50+ , urlDiff :: URLDiff
38 }
51 }
39− deriving ( Show )
52+ deriving (Show)
53+
54+instance Semigroup DisplayAttrDiff where
55+ d0 <> d1 =
56+ let ds = simplifyStyleDiffs (styleDiffs d0) (styleDiffs d1)
57+ fcd = simplifyColorDiffs (foreColorDiff d0) (foreColorDiff d1)
58+ bcd = simplifyColorDiffs (backColorDiff d0) (backColorDiff d1)
59+ ud = simplifyUrlDiffs (urlDiff d0) (urlDiff d1)
60+ in DisplayAttrDiff ds fcd bcd ud
40
61
41instance Monoid DisplayAttrDiff where
62instance Monoid DisplayAttrDiff where
42− mempty = DisplayAttrDiff [] NoColorChange NoColorChange
63+ mempty = DisplayAttrDiff [] NoColorChange NoColorChange NoLinkChange
64+#if !(MIN_VERSION_base(4,11,0))
43− mappend d_0 d_1 =
65+ mappend = (<>)
44− let ds = simplify_style_diffs ( style_diffs d_0 ) ( style_diffs d_1 )
66+#endif
45− fcd = simplify_color_diffs ( fore_color_diff d_0 ) ( fore_color_diff d_1 )
46− bcd = simplify_color_diffs ( back_color_diff d_0 ) ( back_color_diff d_1 )
47− in DisplayAttrDiff ds fcd bcd
48
67
49-- | Used in the computation of a final style attribute change.
68-- | Used in the computation of a final style attribute change.
50−--
51−-- TODO(corey): not really a simplify but a monoid instance.
52−simplify_style_diffs :: [ StyleStateChange ] -> [ StyleStateChange ] -> [ StyleStateChange ]
69+simplifyStyleDiffs :: [StyleStateChange] -> [StyleStateChange] -> [StyleStateChange]
53−simplify_style_diffs cs_0 cs_1 = cs_0 `mappend` cs_1
70+simplifyStyleDiffs cs0 cs1 = cs0 `mappend` cs1
54
71
55−-- | Consider two display color attributes diffs. What display color attribute diff are these
72+-- | Consider two display color attributes diffs. What display color
56−-- equivalent to?
73+-- attribute diff are these equivalent to?
57−--
58−-- TODO(corey): not really a simplify but a monoid instance.
59−simplify_color_diffs :: DisplayColorDiff -> DisplayColorDiff -> DisplayColorDiff
74+simplifyColorDiffs :: DisplayColorDiff -> DisplayColorDiff -> DisplayColorDiff
60−simplify_color_diffs _cd ColorToDefault = ColorToDefault
75+simplifyColorDiffs _cd ColorToDefault = ColorToDefault
61−simplify_color_diffs cd NoColorChange = cd
76+simplifyColorDiffs cd NoColorChange = cd
62−simplify_color_diffs _cd ( SetColor !c ) = SetColor c
77+simplifyColorDiffs _cd (SetColor !c) = SetColor c
78+
79+-- | Consider two URL changes, which are mostly going to be the latter
80+-- unless the latter specifies no change.
81+simplifyUrlDiffs :: URLDiff -> URLDiff -> URLDiff
82+simplifyUrlDiffs ud NoLinkChange = ud
83+simplifyUrlDiffs _ ud = ud
63
84
64-- | Difference between two display color attribute changes.
85-- | Difference between two display color attribute changes.
65−data DisplayColorDiff
86+data DisplayColorDiff
66 = ColorToDefault
87 = ColorToDefault
67 | NoColorChange
88 | NoColorChange
68 | SetColor !Color
89 | SetColor !Color
69− deriving ( Show, Eq )
90+ deriving (Show, Eq)
70
91
71−-- | Style attribute changes are transformed into a sequence of apply/removes of the individual
92+-- | Style attribute changes are transformed into a sequence of
72−-- attributes.
93+-- apply/removes of the individual attributes.
73−data StyleStateChange
94+data StyleStateChange
74 = ApplyStandout
95 = ApplyStandout
75 | RemoveStandout
96 | RemoveStandout
97+ | ApplyItalic
98+ | RemoveItalic
99+ | ApplyStrikethrough
100+ | RemoveStrikethrough
76 | ApplyUnderline
101 | ApplyUnderline
77 | RemoveUnderline
102 | RemoveUnderline
78 | ApplyReverseVideo
103 | ApplyReverseVideo
@@ -83,44 +108,58 @@ data StyleStateChange
83 | RemoveDim
108 | RemoveDim
84 | ApplyBold
109 | ApplyBold
85 | RemoveBold
110 | RemoveBold
86− deriving ( Show, Eq )
111+ deriving (Show, Eq)
87−
112+
113+-- Setting and unsetting hyperlinks
114+data URLDiff
115+ = LinkTo !ByteString
116+ | NoLinkChange
117+ | EndLink
118+ deriving (Show, Eq)
119+
88−-- | Determines the diff between two display&color attributes. This diff determines the operations
120+-- | Determines the diff between two display&color attributes. This diff
89−-- that actually get output to the terminal.
121+-- determines the operations that actually get output to the terminal.
90−display_attr_diffs :: FixedAttr -> FixedAttr -> DisplayAttrDiff
122+displayAttrDiffs :: FixedAttr -> FixedAttr -> DisplayAttrDiff
91−display_attr_diffs attr attr' = DisplayAttrDiff
123+displayAttrDiffs attr attr' = DisplayAttrDiff
92− { style_diffs = diff_styles ( fixed_style attr ) ( fixed_style attr' )
124+ { styleDiffs = diffStyles (fixedStyle attr) (fixedStyle attr')
93− , fore_color_diff = diff_color ( fixed_fore_color attr ) ( fixed_fore_color attr' )
125+ , foreColorDiff = diffColor (fixedForeColor attr) (fixedForeColor attr')
94− , back_color_diff = diff_color ( fixed_back_color attr ) ( fixed_back_color attr' )
126+ , backColorDiff = diffColor (fixedBackColor attr) (fixedBackColor attr')
127+ , urlDiff = diffURL (fixedURL attr) (fixedURL attr')
95 }
128 }
96
129
130+diffURL :: Maybe Text -> Maybe Text -> URLDiff
131+diffURL Nothing Nothing = NoLinkChange
132+diffURL (Just _) Nothing = EndLink
133+diffURL _ (Just url) = LinkTo (encodeUtf8 url)
134+
97−diff_color :: Maybe Color -> Maybe Color -> DisplayColorDiff
135+diffColor :: Maybe Color -> Maybe Color -> DisplayColorDiff
98−diff_color Nothing (Just c') = SetColor c'
136+diffColor Nothing (Just c') = SetColor c'
99−diff_color (Just c) (Just c')
137+diffColor (Just c) (Just c')
100 | c == c' = NoColorChange
138 | c == c' = NoColorChange
101 | otherwise = SetColor c'
139 | otherwise = SetColor c'
102−diff_color Nothing Nothing = NoColorChange
140+diffColor Nothing Nothing = NoColorChange
103−diff_color (Just _) Nothing = ColorToDefault
141+diffColor (Just _) Nothing = ColorToDefault
104−
142+
105−diff_styles :: Style -> Style -> [StyleStateChange]
143+diffStyles :: Style -> Style -> [StyleStateChange]
106−diff_styles prev cur
144+diffStyles prev cur
107− = mconcat
145+ = mconcat
108− [ style_diff standout ApplyStandout RemoveStandout
146+ [ styleDiff standout ApplyStandout RemoveStandout
109− , style_diff underline ApplyUnderline RemoveUnderline
147+ , styleDiff underline ApplyUnderline RemoveUnderline
148+ , styleDiff italic ApplyItalic RemoveItalic
149+ , styleDiff strikethrough ApplyStrikethrough RemoveStrikethrough
110− , style_diff reverse_video ApplyReverseVideo RemoveReverseVideo
150+ , styleDiff reverseVideo ApplyReverseVideo RemoveReverseVideo
111− , style_diff blink ApplyBlink RemoveBlink
151+ , styleDiff blink ApplyBlink RemoveBlink
112− , style_diff dim ApplyDim RemoveDim
152+ , styleDiff dim ApplyDim RemoveDim
113− , style_diff bold ApplyBold RemoveBold
153+ , styleDiff bold ApplyBold RemoveBold
114 ]
154 ]
115− where
155+ where
116− style_diff s sm rm
156+ styleDiff s sm rm
117− = case ( 0 == prev .&. s, 0 == cur .&. s ) of
157+ = case (0 == prev .&. s, 0 == cur .&. s) of
118 -- not set in either
158 -- not set in either
119− ( True, True ) -> []
159+ (True, True) -> []
120 -- set in both
160 -- set in both
121− ( False, False ) -> []
161+ (False, False) -> []
122 -- now set
162 -- now set
123− ( True, False) -> [ sm ]
163+ (True, False) -> [sm]
124 -- now unset
164 -- now unset
125− ( False, True) -> [ rm ]
165+ (False, True) -> [rm]
126−
src/Graphics/Vty/DisplayRegion.hsdeleted−10
@@ -1,10 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−module Graphics.Vty.DisplayRegion
3− where
4−
5−-- | Region of the terminal that vty will output to. Units are columns not characters.
6−data DisplayRegion = DisplayRegion
7− { region_width :: !Int
8− , region_height :: !Int
9− } deriving ( Show, Eq )
10−
src/Graphics/Vty/Error.hs+3−5
@@ -1,10 +1,8 @@
1−module Graphics.Vty.Error
1+module Graphics.Vty.Error where
2− where
3−
4−import Control.Exception
5
2
6-- | The type of exceptions specific to vty.
3-- | The type of exceptions specific to vty.
7--
4--
8−-- These have fully qualified names by default since, IMO, exception handling requires this.
5+-- These have fully qualified names by default since, IMO, exception
6+-- handling requires this.
9data VtyException
7data VtyException
10 = VtyFailure String -- ^ Uncategorized failure specific to vty.
8 = VtyFailure String -- ^ Uncategorized failure specific to vty.
src/Graphics/Vty/Image.hs+307−282
@@ -1,46 +1,63 @@
1-- Copyright 2009-2010 Corey O'Connor
1-- Copyright 2009-2010 Corey O'Connor
2−{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
4{-# LANGUAGE DisambiguateRecordFields #-}
3{-# LANGUAGE DisambiguateRecordFields #-}
5−module Graphics.Vty.Image ( DisplayText
4+-- | A Vty program makes 'Picture's from 'Image's. This module provides
6− , Image
5+-- the core constructors for creating, combining, and modifying
7− , image_width
6+-- 'Image's.
8− , image_height
7+module Graphics.Vty.Image
9− , horiz_join
8+ (
10− , (<|>)
9+ -- * Images
11− , vert_join
10+ Image
12− , (<->)
11+ , imageWidth
13− , horiz_cat
12+ , imageHeight
14− , vert_cat
13+ -- * Image constructors
15− , background_fill
14+ , emptyImage
16− , text
17− , text'
18− , char
15+ , char
19− , string
16+ , string
20− , iso_10646_string
17+ , iso10646String
21− , utf8_string
18+ , utf8String
22− , utf8_bytestring
19+ , text
23− , utf8_bytestring'
20+ , text'
24− , char_fill
21+ , backgroundFill
25− , empty_image
22+ , utf8Bytestring
26− , safe_wcwidth
23+ , utf8Bytestring'
27− , safe_wcswidth
24+ , charFill
28− , wcwidth
25+ -- * Combinators
29− , wcswidth
26+ , horizJoin
27+ , (<|>)
28+ , vertJoin
29+ , (<->)
30+ , horizCat
31+ , vertCat
32+ -- * Image modifications
30− , crop
33+ , crop
31− , crop_right
34+ , cropRight
32− , crop_left
35+ , cropLeft
33− , crop_bottom
36+ , cropBottom
34− , crop_top
37+ , cropTop
35− , pad
38+ , pad
36− , resize
39+ , resize
37− , resize_width
40+ , resizeWidth
38− , resize_height
41+ , resizeHeight
39− , translate
42+ , translate
40− -- | The possible display attributes used in constructing an `Image`.
43+ , translateX
41− , module Graphics.Vty.Attributes
44+ , translateY
45+ -- * Character width functions
46+ , safeWcwidth
47+ , safeWcswidth
48+ , safeWctwidth
49+ , safeWctlwidth
50+ , wcwidth
51+ , wcswidth
52+ , wctwidth
53+ , wctlwidth
54+ -- * Display Regions
55+ , DisplayText
56+ , DisplayRegion
57+ , regionWidth
58+ , regionHeight
42− )
59+ )
43− where
60+where
44
61
45import Graphics.Vty.Attributes
62import Graphics.Vty.Attributes
46import Graphics.Vty.Image.Internal
63import Graphics.Vty.Image.Internal
@@ -48,309 +65,317 @@ import Graphics.Text.Width
48
65
49import qualified Data.ByteString as B
66import qualified Data.ByteString as B
50import qualified Data.ByteString.Lazy as BL
67import qualified Data.ByteString.Lazy as BL
51−import Data.Monoid
52import qualified Data.Text as T
68import qualified Data.Text as T
53import qualified Data.Text.Encoding as T
69import qualified Data.Text.Encoding as T
54import qualified Data.Text.Lazy as TL
70import qualified Data.Text.Lazy as TL
55import qualified Data.Text.Lazy.Encoding as TL
71import qualified Data.Text.Lazy.Encoding as TL
56import Data.Word
72import Data.Word
57
73
74+-- | A region of the display (first width, then height)
75+type DisplayRegion = (Int,Int)
76+
77+regionWidth :: DisplayRegion -> Int
78+regionWidth = fst
79+
80+regionHeight :: DisplayRegion -> Int
81+regionHeight = snd
82+
58infixr 5 <|>
83infixr 5 <|>
59infixr 4 <->
84infixr 4 <->
60
85
61−-- | Append in the Monoid instance is equivalent to <->.
62−instance Monoid Image where
63− mempty = EmptyImage
64− mappend = (<->)
65−
66−-- | combines two images side by side
67−--
68−-- Combines text chunks where possible. Assures output_width and output_height properties are not
69−-- violated.
70−--
71−-- The result image will have a width equal to the sum of the two images width. And the height will
72−-- equal the largest height of the two images. The area not defined in one image due to a height
73−-- missmatch will be filled with the background pattern.
74−--
75−-- TODO: the bg fill is biased towards top to bottom languages(?)
76−horiz_join :: Image -> Image -> Image
77−horiz_join EmptyImage i = i
78−horiz_join i EmptyImage = i
79−horiz_join i_0@(HorizText a_0 t_0 w_0 cw_0) i_1@(HorizText a_1 t_1 w_1 cw_1)
80− | a_0 == a_1 = HorizText a_0 (TL.append t_0 t_1) (w_0 + w_1) (cw_0 + cw_1)
81− -- TODO: assumes horiz text height is always 1
82− | otherwise = HorizJoin i_0 i_1 (w_0 + w_1) 1
83−horiz_join i_0 i_1
84− -- If the images are of the same height then no padding is required
85− | h_0 == h_1 = HorizJoin i_0 i_1 w h_0
86− -- otherwise one of the images needs to be padded to the right size.
87− | h_0 < h_1 -- Pad i_0
88− = let pad_amount = h_1 - h_0
89− in HorizJoin (VertJoin i_0 (BGFill w_0 pad_amount) w_0 h_1) i_1 w h_1
90− | h_0 > h_1 -- Pad i_1
91− = let pad_amount = h_0 - h_1
92− in HorizJoin i_0 (VertJoin i_1 (BGFill w_1 pad_amount) w_1 h_0) w h_0
93− where
94− w_0 = image_width i_0
95− w_1 = image_width i_1
96− w = w_0 + w_1
97− h_0 = image_height i_0
98− h_1 = image_height i_1
99−horiz_join _ _ = error "horiz_join applied to undefined values."
100−
101−-- | combines two images vertically
102−--
103−-- The result image will have a height equal to the sum of the heights of both images.
104−-- The width will equal the largest width of the two images.
105−-- The area not defined in one image due to a width missmatch will be filled with the background
106−-- pattern.
107−--
108−-- TODO: the bg fill is biased towards right to left languages(?)
109−vert_join :: Image -> Image -> Image
110−vert_join EmptyImage i = i
111−vert_join i EmptyImage = i
112−vert_join i_0 i_1
113− -- If the images are of the same width then no background padding is required
114− | w_0 == w_1 = VertJoin i_0 i_1 w_0 h
115− -- Otherwise one of the images needs to be padded to the size of the other image.
116− | w_0 < w_1
117− = let pad_amount = w_1 - w_0
118− in VertJoin (HorizJoin i_0 (BGFill pad_amount h_0) w_1 h_0) i_1 w_1 h
119− | w_0 > w_1
120− = let pad_amount = w_0 - w_1
121− in VertJoin i_0 (HorizJoin i_1 (BGFill pad_amount h_1) w_0 h_1) w_0 h
122− where
123− w_0 = image_width i_0
124− w_1 = image_width i_1
125− h_0 = image_height i_0
126− h_1 = image_height i_1
127− h = h_0 + h_1
128−vert_join _ _ = error "vert_join applied to undefined values."
129−
130−-- | An area of the picture's bacground (See Background) of w columns and h rows.
86+-- | An area of the picture's background (See 'Background').
131−background_fill :: Int -> Int -> Image
87+backgroundFill :: Int
132−background_fill w h
88+ -- ^ Fill width in columns
89+ -> Int
90+ -- ^ Fill height in rows
91+ -> Image
92+backgroundFill w h
133 | w == 0 = EmptyImage
93 | w == 0 = EmptyImage
134 | h == 0 = EmptyImage
94 | h == 0 = EmptyImage
135 | otherwise = BGFill w h
95 | otherwise = BGFill w h
136
96
137−-- | Combines two images horizontally. Alias for horiz_join
97+-- | Combines two images horizontally. This is an alias for 'horizJoin'.
138--
98--
139-- infixr 5
99-- infixr 5
140(<|>) :: Image -> Image -> Image
100(<|>) :: Image -> Image -> Image
141−(<|>) = horiz_join
101+(<|>) = horizJoin
142
102
143−-- | Combines two images vertically. Alias for vert_join
103+-- | Combines two images vertically. This is an alias for 'vertJoin'.
144--
104--
145-- infixr 4
105-- infixr 4
146(<->) :: Image -> Image -> Image
106(<->) :: Image -> Image -> Image
147−(<->) = vert_join
107+(<->) = vertJoin
148
108
149−-- | Compose any number of images horizontally.
109+-- | Compose any number of images together horizontally, with the first
110+-- in the list being leftmost.
150−horiz_cat :: [Image] -> Image
111+horizCat :: [Image] -> Image
151−horiz_cat = foldr (<|>) EmptyImage
112+horizCat = foldr horizJoin EmptyImage
152
113
153−-- | Compose any number of images vertically.
114+-- | Compose any number of images vertically, with the first in the list
115+-- being topmost.
154−vert_cat :: [Image] -> Image
116+vertCat :: [Image] -> Image
155−vert_cat = foldr (<->) EmptyImage
117+vertCat = foldr vertJoin EmptyImage
156
118
157−-- | A Data.Text.Lazy value
119+-- | Make an 'Image' from a lazy text value. The text value should be
120+-- sanitized of escape sequences (ASCII 27) and carriage returns;
121+-- otherwise layout and attribute problems may result.
158text :: Attr -> TL.Text -> Image
122text :: Attr -> TL.Text -> Image
159−text a txt
123+text a txt = let displayWidth = safeWctlwidth txt
160− | TL.length txt == 0 = EmptyImage
161− | otherwise = let display_width = safe_wcswidth (TL.unpack txt)
162− in HorizText a txt display_width (fromIntegral $! TL.length txt)
124+ in HorizText a txt displayWidth (fromIntegral $! TL.length txt)
163
125
164−-- | A Data.Text value
126+-- | Make an 'Image' from a text value. The text value should be
127+-- sanitized of escape sequences (ASCII 27) and carriage returns;
128+-- otherwise layout and attribute problems may result.
165text' :: Attr -> T.Text -> Image
129text' :: Attr -> T.Text -> Image
166−text' a txt
130+text' a txt = let displayWidth = safeWctwidth txt
167− | T.length txt == 0 = EmptyImage
168− | otherwise = let display_width = safe_wcswidth (T.unpack txt)
169− in HorizText a (TL.fromStrict txt) display_width (T.length txt)
131+ in HorizText a (TL.fromStrict txt) displayWidth (T.length txt)
170
132
171−-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
133+-- | Make an image from a single character. This is a standard Haskell
172−-- the ISO-10646 encoding.
134+-- 31-bit character assumed to be in the ISO-10646 encoding.
173char :: Attr -> Char -> Image
135char :: Attr -> Char -> Image
174−char a c =
136+char a c =
175− let display_width = safe_wcwidth c
137+ let displayWidth = safeWcwidth c
176− in HorizText a (TL.singleton c) display_width 1
138+ in HorizText a (TL.singleton c) displayWidth 1
177−
139+
140+-- | Make an image from a string of characters laid out on a single
178−-- | A string of characters layed out on a single row with the same display attribute. The string is
141+-- row with the same display attribute. The string is assumed to be a
179−-- assumed to be a sequence of ISO-10646 characters.
142+-- sequence of ISO-10646 characters. The input string should be
143+-- sanitized of escape sequences (ASCII 27) and carriage returns;
144+-- otherwise layout and attribute problems may result.
145+--
146+-- Note: depending on how the Haskell compiler represents string
147+-- literals, a string literal in a UTF-8 encoded source file, for
148+-- example, may be represented as a ISO-10646 string. That is, I think,
149+-- the case with GHC 6.10. This means, for the most part, you don't need
150+-- to worry about the encoding format when outputting string literals.
151+-- Just provide the string literal directly to iso10646String or string.
152+iso10646String :: Attr -> String -> Image
153+iso10646String a str =
154+ let displayWidth = safeWcswidth str
155+ in HorizText a (TL.pack str) displayWidth (length str)
156+
157+-- | Make an 'Image' from a 'String'.
180--
158--
181−-- Note: depending on how the Haskell compiler represents string literals a string literal in a
159+-- This is an alias for iso10646String since the usual case is that a
182−-- UTF-8 encoded source file, for example, may be represented as a ISO-10646 string.
183−-- That is, I think, the case with GHC 6.10. This means, for the most part, you don't need to worry
184−-- about the encoding format when outputting string literals. Just provide the string literal
185−-- directly to iso_10646_string or string.
186−--
187−iso_10646_string :: Attr -> String -> Image
188−iso_10646_string a str =
189− let display_width = safe_wcswidth str
190− in HorizText a (TL.pack str) display_width (length str)
191−
192−-- | Alias for iso_10646_string. Since the usual case is that a literal string like "foo" is
193−-- represented internally as a list of ISO 10646 31 bit characters.
160+-- literal string like "foo" is represented internally as a list of ISO
161+-- 10646 31 bit characters.
194--
162--
195−-- Note: Keep in mind that GHC will compile source encoded as UTF-8 but the literal strings, while
163+-- Note: Keep in mind that GHC will compile source encoded as UTF-8
164+-- but the literal strings, while UTF-8 encoded in the source, will be
196−-- UTF-8 encoded in the source, will be transcoded to a ISO 10646 31 bit characters runtime
165+-- transcoded to a ISO 10646 31 bit characters runtime representation.
197−-- representation.
198string :: Attr -> String -> Image
166string :: Attr -> String -> Image
199−string = iso_10646_string
167+string = iso10646String
200−
168+
201−-- | A string of characters layed out on a single row. The input is assumed to be the bytes for
169+-- | Make an 'Image' from a string of characters layed out on a single
202−-- UTF-8 encoded text.
170+-- row. The input is assumed to be the bytes for UTF-8 encoded text.
203−utf8_string :: Attr -> [Word8] -> Image
171+utf8String :: Attr -> [Word8] -> Image
204−utf8_string a bytes = utf8_bytestring a (BL.pack bytes)
172+utf8String a bytes = utf8Bytestring a (BL.pack bytes)
205−
173+
206−-- | Renders a UTF-8 encoded lazy bytestring.
174+-- | Make an 'Image' from a UTF-8 encoded lazy bytestring.
207−utf8_bytestring :: Attr -> BL.ByteString -> Image
175+utf8Bytestring :: Attr -> BL.ByteString -> Image
208−utf8_bytestring a bs = text a (TL.decodeUtf8 bs)
176+utf8Bytestring a bs = text a (TL.decodeUtf8 bs)
209−
177+
210−-- | Renders a UTF-8 encoded strict bytestring.
178+-- | Make an 'Image' from a UTF-8 encoded strict bytestring.
211−utf8_bytestring' :: Attr -> B.ByteString -> Image
179+utf8Bytestring' :: Attr -> B.ByteString -> Image
212−utf8_bytestring' a bs = text' a (T.decodeUtf8 bs)
180+utf8Bytestring' a bs = text' a (T.decodeUtf8 bs)
213−
181+
214−-- | creates a fill of the specified character. The dimensions are in number of characters wide and
182+-- | Make an image filling a region with the specified character.
215−-- number of rows high.
183+--
216−char_fill :: Integral d => Attr -> Char -> d -> d -> Image
184+-- If either the width or height are less than or equal to 0, then
217−char_fill _a _c 0 _h = EmptyImage
185+-- the result is the empty image.
218−char_fill _a _c _w 0 = EmptyImage
186+charFill :: Integral d
219−char_fill a c w h =
187+ => Attr
188+ -- ^ The attribute to use.
189+ -> Char
190+ -- ^ The character to use in filling the region.
191+ -> d
192+ -- ^ The region width.
193+ -> d
194+ -- ^ The region height.
195+ -> Image
196+charFill a c w h
197+ | w <= 0 || h <= 0 = EmptyImage
198+ | otherwise = vertCat
220− vert_cat $ replicate (fromIntegral h) $ HorizText a txt display_width char_width
199+ $ replicate (fromIntegral h)
200+ $ HorizText a txt displayWidth charWidth
221− where
201+ where
222− txt = TL.replicate (fromIntegral w) (TL.singleton c)
202+ txt = TL.replicate charWidth (TL.singleton c)
223− display_width = safe_wcwidth c * (fromIntegral w)
203+ displayWidth = safeWcwidth c * charWidth
204+
205+ charWidth :: Num a => a
224− char_width = fromIntegral w
206+ charWidth = fromIntegral w
225−
207+
226−-- | The empty image. Useful for fold combinators. These occupy no space nor define any display
208+-- | The empty image. Useful for fold combinators. These occupy no space
227−-- attributes.
209+-- and do not affect display attributes.
228−empty_image :: Image
210+emptyImage :: Image
229−empty_image = EmptyImage
211+emptyImage = EmptyImage
230−
212+
231−-- | pad the given image. This adds background character fills to the left, top, right, bottom.
213+-- | Pad the given image. This adds background character fills to the
232−-- The pad values are how many display columns or rows to add.
214+-- left, top, right, bottom.
233−pad :: Int -> Int -> Int -> Int -> Image -> Image
215+pad :: Int
216+ -- ^ How much padding to add to the left side of the image.
217+ -> Int
218+ -- ^ How much padding to add to the top of the image.
219+ -> Int
220+ -- ^ How much padding to add to the right side of the image.
221+ -> Int
222+ -- ^ How much padding to add to the bottom of the image.
223+ -> Image
224+ -- ^ The image to pad.
225+ -> Image
234pad 0 0 0 0 i = i
226pad 0 0 0 0 i = i
235−pad in_l in_t in_r in_b in_image
227+pad inL inT inR inB inImage
236− | in_l < 0 || in_t < 0 || in_r < 0 || in_b < 0 = error "cannot pad by negative amount"
228+ | inL < 0 || inT < 0 || inR < 0 || inB < 0 = error "cannot pad by negative amount"
237− | otherwise = go in_l in_t in_r in_b in_image
229+ | otherwise = go inL inT inR inB inImage
238− where
230+ where
239− -- TODO: uh.
240 go 0 0 0 0 i = i
231 go 0 0 0 0 i = i
241 go 0 0 0 b i = VertJoin i (BGFill w b) w h
232 go 0 0 0 b i = VertJoin i (BGFill w b) w h
242− where w = image_width i
233+ where w = imageWidth i
243− h = image_height i + b
234+ h = imageHeight i + b
244 go 0 0 r b i = go 0 0 0 b $ HorizJoin i (BGFill r h) w h
235 go 0 0 r b i = go 0 0 0 b $ HorizJoin i (BGFill r h) w h
245− where w = image_width i + r
236+ where w = imageWidth i + r
246− h = image_height i
237+ h = imageHeight i
247 go 0 t r b i = go 0 0 r b $ VertJoin (BGFill w t) i w h
238 go 0 t r b i = go 0 0 r b $ VertJoin (BGFill w t) i w h
248− where w = image_width i
239+ where w = imageWidth i
249− h = image_height i + t
240+ h = imageHeight i + t
250 go l t r b i = go 0 t r b $ HorizJoin (BGFill l h) i w h
241 go l t r b i = go 0 t r b $ HorizJoin (BGFill l h) i w h
251− where w = image_width i + l
242+ where w = imageWidth i + l
252− h = image_height i
243+ h = imageHeight i
253
244
254−-- | "translates" an image by padding the top and left.
245+-- | Translates an image by padding or cropping the left and top.
246+--
247+-- If translation offsets are negative then the image is cropped.
255−translate :: Int -> Int -> Image -> Image
248+translate :: Int
249+ -- ^ The horizontal translation offset (can be negative)
250+ -> Int
251+ -- ^ The vertical translation offset (can be negative)
252+ -> Image
253+ -- ^ The image to translate.
254+ -> Image
256−translate x y i = pad x y 0 0 i
255+translate x y i = translateX x (translateY y i)
257−
256+
257+-- | Translates an image by padding or cropping its left side.
258+translateX :: Int -> Image -> Image
259+translateX x i
260+ | x < 0 && (abs x > imageWidth i) = emptyImage
261+ | x < 0 = let s = abs x in CropLeft i s (imageWidth i - s) (imageHeight i)
262+ | x == 0 = i
263+ | otherwise = let h = imageHeight i in HorizJoin (BGFill x h) i (imageWidth i + x) h
264+
265+-- | Translates an image by padding or cropping its top.
266+translateY :: Int -> Image -> Image
267+translateY y i
268+ | y < 0 && (abs y > imageHeight i) = emptyImage
269+ | y < 0 = let s = abs y in CropTop i s (imageWidth i) (imageHeight i - s)
270+ | y == 0 = i
271+ | otherwise = let w = imageWidth i in VertJoin (BGFill w y) i w (imageHeight i + y)
272+
258−-- | Ensure an image is no larger than the provided size. If the image is larger then crop the right
273+-- | Ensure an image is no larger than the provided size. If the image
259−-- or bottom.
274+-- is larger then crop the right or bottom.
260--
275--
261−-- This is transformed to a vertical crop from the bottom followed by horizontal crop from the
276+-- This is equivalent to a vertical crop from the bottom followed by
262−-- right.
277+-- horizontal crop from the right.
263−crop :: Int -> Int -> Image -> Image
278+crop :: Int
279+ -- ^ Cropping width
280+ -> Int
281+ -- ^ Cropping height
282+ -> Image
283+ -- ^ The image to crop
284+ -> Image
264crop 0 _ _ = EmptyImage
285crop 0 _ _ = EmptyImage
265crop _ 0 _ = EmptyImage
286crop _ 0 _ = EmptyImage
266−crop w h i = crop_bottom h (crop_right w i)
287+crop w h i = cropBottom h (cropRight w i)
267−
288+
268−-- | crop the display height. If the image is less than or equal in height then this operation has
289+-- | Crop an image's height. If the image's height is less than or equal
290+-- to the specified height then this operation has no effect. Otherwise
269−-- no effect. Otherwise the image is cropped from the bottom.
291+-- the image is cropped from the bottom.
270−crop_bottom :: Int -> Image -> Image
292+cropBottom :: Int -> Image -> Image
271−crop_bottom 0 _ = EmptyImage
293+cropBottom 0 _ = EmptyImage
272−crop_bottom h in_i
294+cropBottom h inI
273 | h < 0 = error "cannot crop height to less than zero"
295 | h < 0 = error "cannot crop height to less than zero"
274− | otherwise = go in_i
296+ | otherwise = go inI
275 where
297 where
276 go EmptyImage = EmptyImage
298 go EmptyImage = EmptyImage
277− go i@(CropBottom {cropped_image, output_width, output_height})
299+ go i@(CropBottom {croppedImage, outputWidth, outputHeight})
278− | output_height <= h = i
300+ | outputHeight <= h = i
279− | otherwise = CropBottom cropped_image output_width h
301+ | otherwise = CropBottom croppedImage outputWidth h
280 go i
302 go i
281− | h >= image_height i = i
303+ | h >= imageHeight i = i
282− | otherwise = CropBottom i (image_width i) h
304+ | otherwise = CropBottom i (imageWidth i) h
283−
305+
284−-- | ensure the image is no wider than the given width. If the image is wider then crop the right
306+-- | Crop an image's width. If the image's width is less than or equal
285−-- side.
307+-- to the specified width then this operation has no effect. Otherwise
308+-- the image is cropped from the right.
286−crop_right :: Int -> Image -> Image
309+cropRight :: Int -> Image -> Image
287−crop_right 0 _ = EmptyImage
310+cropRight 0 _ = EmptyImage
288−crop_right w in_i
311+cropRight w inI
289 | w < 0 = error "cannot crop width to less than zero"
312 | w < 0 = error "cannot crop width to less than zero"
290− | otherwise = go in_i
313+ | otherwise = go inI
291 where
314 where
292 go EmptyImage = EmptyImage
315 go EmptyImage = EmptyImage
293− go i@(CropRight {cropped_image, output_width, output_height})
316+ go i@(CropRight {croppedImage, outputWidth, outputHeight})
294− | output_width <= w = i
317+ | outputWidth <= w = i
295− | otherwise = CropRight cropped_image w output_height
318+ | otherwise = CropRight croppedImage w outputHeight
296 go i
319 go i
297− | w >= image_width i = i
320+ | w >= imageWidth i = i
298− | otherwise = CropRight i w (image_height i)
321+ | otherwise = CropRight i w (imageHeight i)
299−
322+
300−-- | ensure the image is no wider than the given width. If the image is wider then crop the left
323+-- | Crop an image's width. If the image's width is less than or equal
301−-- side.
324+-- to the specified width then this operation has no effect. Otherwise
325+-- the image is cropped from the left.
302−crop_left :: Int -> Image -> Image
326+cropLeft :: Int -> Image -> Image
303−crop_left 0 _ = EmptyImage
327+cropLeft 0 _ = EmptyImage
304−crop_left w in_i
328+cropLeft w inI
305 | w < 0 = error "cannot crop the width to less than zero"
329 | w < 0 = error "cannot crop the width to less than zero"
306− | otherwise = go in_i
330+ | otherwise = go inI
307 where
331 where
308 go EmptyImage = EmptyImage
332 go EmptyImage = EmptyImage
309− go i@(CropLeft {cropped_image, left_skip, output_width, output_height})
333+ go i@(CropLeft {croppedImage, leftSkip, outputWidth, outputHeight})
310− | output_width <= w = i
334+ | outputWidth <= w = i
311 | otherwise =
335 | otherwise =
312− let left_skip' = left_skip + output_width - w
336+ let leftSkip' = leftSkip + outputWidth - w
313− in CropLeft cropped_image left_skip' w output_height
337+ in CropLeft croppedImage leftSkip' w outputHeight
314 go i
338 go i
315− | image_width i <= w = i
339+ | imageWidth i <= w = i
316− | otherwise = CropLeft i (image_width i - w) w (image_height i)
340+ | otherwise = CropLeft i (imageWidth i - w) w (imageHeight i)
317−
341+
318−-- | crop the display height. If the image is less than or equal in height then this operation has
342+-- | Crop an image's height. If the image's height is less than or equal
343+-- to the specified height then this operation has no effect. Otherwise
319−-- no effect. Otherwise the image is cropped from the top.
344+-- the image is cropped from the top.
320−crop_top :: Int -> Image -> Image
345+cropTop :: Int -> Image -> Image
321−crop_top 0 _ = EmptyImage
346+cropTop 0 _ = EmptyImage
322−crop_top h in_i
347+cropTop h inI
323 | h < 0 = error "cannot crop the height to less than zero"
348 | h < 0 = error "cannot crop the height to less than zero"
324− | otherwise = go in_i
349+ | otherwise = go inI
325 where
350 where
326 go EmptyImage = EmptyImage
351 go EmptyImage = EmptyImage
327− go i@(CropTop {cropped_image, top_skip, output_width, output_height})
352+ go i@(CropTop {croppedImage, topSkip, outputWidth, outputHeight})
328− | output_height <= h = i
353+ | outputHeight <= h = i
329− | otherwise =
354+ | otherwise =
330− let top_skip' = top_skip + output_height - h
355+ let topSkip' = topSkip + outputHeight - h
331− in CropTop cropped_image top_skip' output_width h
356+ in CropTop croppedImage topSkip' outputWidth h
332 go i
357 go i
333− | image_height i <= h = i
358+ | imageHeight i <= h = i
334− | otherwise = CropTop i (image_height i - h) (image_width i) h
359+ | otherwise = CropTop i (imageHeight i - h) (imageWidth i) h
335
360
336−-- | Generic resize. Pads and crops as required to assure the given display width and height.
361+-- | Generic resize. Pads and crops are added to ensure that the
362+-- resulting image matches the specified dimensions. This is biased to
337−-- This is biased to pad/crop the right and bottom.
363+-- pad/crop the right and bottom.
338resize :: Int -> Int -> Image -> Image
364resize :: Int -> Int -> Image -> Image
339−resize w h i = resize_height h (resize_width w i)
365+resize w h i = resizeHeight h (resizeWidth w i)
340
366
341−-- | Resize the width. Pads and crops as required to assure the given display width.
367+-- | Resize the width. Pads and crops as required to assure the given
342−-- This is biased to pad/crop the right.
368+-- display width. This is biased to pad/crop on the right.
343−resize_width :: Int -> Image -> Image
369+resizeWidth :: Int -> Image -> Image
344−resize_width w i = case w `compare` image_width i of
370+resizeWidth w i = case w `compare` imageWidth i of
345− LT -> crop_right w i
371+ LT -> cropRight w i
346 EQ -> i
372 EQ -> i
347− GT -> i <|> BGFill (w - image_width i) (image_height i)
373+ GT -> i <|> BGFill (w - imageWidth i) (imageHeight i)
348
374
349−-- | Resize the height. Pads and crops as required to assure the given display height.
375+-- | Resize the height. Pads and crops as required to assure the given
350−-- This is biased to pad/crop the bottom.
376+-- display height. This is biased to pad/crop on the bottom.
351−resize_height :: Int -> Image -> Image
377+resizeHeight :: Int -> Image -> Image
352−resize_height h i = case h `compare` image_height i of
378+resizeHeight h i = case h `compare` imageHeight i of
353− LT -> crop_bottom h i
379+ LT -> cropBottom h i
354 EQ -> i
380 EQ -> i
355− GT -> i <-> BGFill (image_width i) (h - image_height i)
381+ GT -> i <-> BGFill (imageWidth i) (h - imageHeight i)
356−
src/Graphics/Vty/Image/Internal.hs+222−149
@@ -1,180 +1,178 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3+{-# LANGUAGE DeriveGeneric #-}
4+{-# OPTIONS_HADDOCK hide #-}
5+
2module Graphics.Vty.Image.Internal where
6module Graphics.Vty.Image.Internal where
3
7
4import Graphics.Vty.Attributes
8import Graphics.Vty.Attributes
5import Graphics.Text.Width
9import Graphics.Text.Width
6
10
11+import GHC.Generics
12+
7import Control.DeepSeq
13import Control.DeepSeq
8
14
15+#if !(MIN_VERSION_base(4,11,0))
16+import Data.Semigroup (Semigroup(..))
17+#endif
9import qualified Data.Text.Lazy as TL
18import qualified Data.Text.Lazy as TL
10
19
11-- | A display text is a Data.Text.Lazy
20-- | A display text is a Data.Text.Lazy
12−--
13−-- TODO(corey): hm. there is an explicit equation for each type which goes to a lazy text. Each
14−-- application probably uses a single type. Perhaps parameterize the entire vty interface by the
15−-- input text type?
16type DisplayText = TL.Text
21type DisplayText = TL.Text
17
22
18−-- TODO: store a skip list in HorizText(?)
19−-- TODO: represent display strings containing chars that are not 1 column chars as a separate
20−-- display string value?
21−clip_text :: DisplayText -> Int -> Int -> DisplayText
23+clipText :: DisplayText -> Int -> Int -> DisplayText
22−clip_text txt left_skip right_clip =
24+clipText txt leftSkip rightClip =
23 -- CPS would clarify this I think
25 -- CPS would clarify this I think
24− let (to_drop,pad_prefix) = clip_for_char_width left_skip txt 0
26+ let (toDrop,padPrefix) = clipForCharWidth leftSkip txt 0
25− txt' = if pad_prefix then TL.cons '…' (TL.drop (to_drop+1) txt) else TL.drop to_drop txt
27+ txt' = if padPrefix then TL.cons '…' (TL.drop (toDrop+1) txt) else TL.drop toDrop txt
26− (to_take,pad_suffix) = clip_for_char_width right_clip txt' 0
28+ (toTake,padSuffix) = clipForCharWidth rightClip txt' 0
27− txt'' = TL.append (TL.take to_take txt') (if pad_suffix then TL.singleton '…' else TL.empty)
29+ txt'' = TL.append (TL.take toTake txt') (if padSuffix then TL.singleton '…' else TL.empty)
28− clip_for_char_width 0 _ n = (n, False)
30+ -- Note: some characters and zero-width and combining characters
29− clip_for_char_width w t n
31+ -- combine to the left, so keep taking characters even if the
32+ -- width is zero.
33+ clipForCharWidth w t n
30 | TL.null t = (n, False)
34 | TL.null t = (n, False)
31− | w < cw = (n, True)
35+ | w < cw = (n, w /= 0)
32− | w == cw = (n+1, False)
36+ | otherwise = clipForCharWidth (w - cw) (TL.tail t) (n + 1)
33− | w > cw = clip_for_char_width (w - cw) (TL.tail t) (n + 1)
34− where cw = safe_wcwidth (TL.head t)
37+ where cw = safeWcwidth (TL.head t)
35− clip_for_char_width _ _ _ = error "clip_for_char_width applied to undefined"
36 in txt''
38 in txt''
37
39
38−-- | An image in VTY is
40+-- | This is the internal representation of Images. Use the constructors
41+-- in "Graphics.Vty.Image" to create instances.
42+--
43+-- Images are:
44+--
45+-- * a horizontal span of text
39--
46--
40−-- * a horizontal line of characters with a single attribute.
47+-- * a horizontal or vertical join of two images
48+--
41−-- * a fill of the picture's background. (background_fill)
49+-- * a two dimensional fill of the 'Picture's background character
42−-- * a horizontal and vertical crops from both directions.
50+--
43−-- * horizontal and vertical combinations
51+-- * a cropped image
52+--
53+-- * an empty image of no size or content.
44−data Image =
54+data Image =
45− -- | A horizontal text span is always >= 1 column and has a row height of 1.
55+ -- | A horizontal text span has a row height of 1.
46 HorizText
56 HorizText
47 { attr :: Attr
57 { attr :: Attr
48− -- | The text to display. The display width of the text is always output_width.
58+ -- | The text to display. The display width of the text is always
59+ -- outputWidth.
49− , display_text :: DisplayText
60+ , displayText :: DisplayText
50 -- | The number of display columns for the text.
61 -- | The number of display columns for the text.
51− , output_width :: Int
62+ , outputWidth :: Int
52 -- | the number of characters in the text.
63 -- | the number of characters in the text.
53− , char_width :: Int
64+ , charWidth :: Int
54 }
65 }
55− -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
66+ -- | A horizontal join can be constructed between any two images.
67+ -- However a HorizJoin instance is required to be between two images
56− -- required to be between two images of equal height. The horiz_join constructor adds background
68+ -- of equal height. The horizJoin constructor adds background fills
57− -- fills to the provided images that assure this is true for the HorizJoin value produced.
69+ -- to the provided images that assure this is true for the HorizJoin
70+ -- value produced.
58 | HorizJoin
71 | HorizJoin
59− { part_left :: Image
72+ { partLeft :: Image
60− , part_right :: Image
73+ , partRight :: Image
61− , output_width :: Int -- ^ image_width part_left == image_width part_right. Always > 1
74+ , outputWidth :: Int
62− , output_height :: Int -- ^ image_height part_left == image_height part_right. Always > 0
75+ -- ^ imageWidth partLeft == imageWidth partRight. Always > 0
76+ , outputHeight :: Int
77+ -- ^ imageHeight partLeft == imageHeight partRight. Always > 0
63 }
78 }
64− -- | A veritical join can be constructed between any two images. However a VertJoin instance is
79+ -- | A veritical join can be constructed between any two images.
80+ -- However a VertJoin instance is required to be between two images
65− -- required to be between two images of equal width. The vert_join constructor adds background
81+ -- of equal width. The vertJoin constructor adds background fills
66− -- fills to the provides images that assure this is true for the VertJoin value produced.
82+ -- to the provides images that assure this is true for the VertJoin
83+ -- value produced.
67 | VertJoin
84 | VertJoin
68− { part_top :: Image
85+ { partTop :: Image
69− , part_bottom :: Image
86+ , partBottom :: Image
70− , output_width :: Int -- ^ image_width part_top == image_width part_bottom. always > 0
87+ , outputWidth :: Int
71− , output_height :: Int -- ^ image_height part_top == image_height part_bottom. always > 1
88+ -- ^ imageWidth partTop == imageWidth partBottom. always > 0
89+ , outputHeight :: Int
90+ -- ^ imageHeight partTop == imageHeight partBottom. always > 1
72 }
91 }
73− -- | A background fill will be filled with the background char. The background char is
92+ -- | A background fill will be filled with the background char. The
74− -- defined as a property of the Picture this Image is used to form.
93+ -- background char is defined as a property of the Picture this
94+ -- Image is used to form.
75 | BGFill
95 | BGFill
76− { output_width :: Int -- ^ always > 0
96+ { outputWidth :: Int -- ^ always > 0
77− , output_height :: Int -- ^ always > 0
97+ , outputHeight :: Int -- ^ always > 0
78 }
98 }
79− -- | Crop an image horizontally to a size by reducing the size from the right.
99+ -- | Crop an image horizontally to a size by reducing the size from
100+ -- the right.
80 | CropRight
101 | CropRight
81− { cropped_image :: Image
102+ { croppedImage :: Image
82− -- | Always < image_width cropped_image > 0
103+ -- | Always < imageWidth croppedImage > 0
83− , output_width :: Int
104+ , outputWidth :: Int
84− , output_height :: Int -- ^ image_height cropped_image
105+ , outputHeight :: Int -- ^ imageHeight croppedImage
85 }
106 }
86− -- | Crop an image horizontally to a size by reducing the size from the left.
107+ -- | Crop an image horizontally to a size by reducing the size from
108+ -- the left.
87 | CropLeft
109 | CropLeft
88− { cropped_image :: Image
110+ { croppedImage :: Image
89− -- | Always < image_width cropped_image > 0
111+ -- | Always < imageWidth croppedImage > 0
90− , left_skip :: Int
112+ , leftSkip :: Int
91− -- | Always < image_width cropped_image > 0
113+ -- | Always < imageWidth croppedImage > 0
92− , output_width :: Int
114+ , outputWidth :: Int
93− , output_height :: Int
115+ , outputHeight :: Int
94 }
116 }
95− -- | Crop an image vertically to a size by reducing the size from the bottom
117+ -- | Crop an image vertically to a size by reducing the size from
118+ -- the bottom
96 | CropBottom
119 | CropBottom
97− { cropped_image :: Image
120+ { croppedImage :: Image
98− -- | image_width cropped_image
121+ -- | imageWidth croppedImage
99− , output_width :: Int
122+ , outputWidth :: Int
100− -- | height image is cropped to. Always < image_height cropped_image > 0
123+ -- | height image is cropped to. Always < imageHeight croppedImage > 0
101− , output_height :: Int
124+ , outputHeight :: Int
102 }
125 }
103− -- | Crop an image vertically to a size by reducing the size from the top
126+ -- | Crop an image vertically to a size by reducing the size from
127+ -- the top
104 | CropTop
128 | CropTop
105− { cropped_image :: Image
129+ { croppedImage :: Image
106− -- | Always < image_height cropped_image > 0
130+ -- | Always < imageHeight croppedImage > 0
107− , top_skip :: Int
131+ , topSkip :: Int
108− -- | image_width cropped_image
132+ -- | imageWidth croppedImage
109− , output_width :: Int
133+ , outputWidth :: Int
110− -- | Always < image_height cropped_image > 0
134+ -- | Always < imageHeight croppedImage > 0
111− , output_height :: Int
135+ , outputHeight :: Int
112 }
136 }
113 -- | The empty image
137 -- | The empty image
114 --
138 --
115− -- The combining operators identity constant.
139+ -- The combining operators identity constant.
116 -- EmptyImage <|> a = a
140 -- EmptyImage <|> a = a
117 -- EmptyImage <-> a = a
141 -- EmptyImage <-> a = a
118− --
142+ --
119 -- Any image of zero size equals the empty image.
143 -- Any image of zero size equals the empty image.
120 | EmptyImage
144 | EmptyImage
121− deriving Eq
145+ deriving (Eq, Generic, Show, Read)
122−
146+
123−instance Show Image where
124− show ( HorizText { attr, display_text, output_width, char_width } )
125− = "HorizText " ++ show display_text
126− ++ "@(" ++ show attr ++ ","
127− ++ show output_width ++ ","
128− ++ show char_width ++ ")"
129− show ( BGFill { output_width, output_height } )
130− = "BGFill (" ++ show output_width ++ "," ++ show output_height ++ ")"
131− show ( HorizJoin { part_left = l, part_right = r, output_width = c } )
132− = "HorizJoin " ++ show c ++ " (" ++ show l ++ " <|> " ++ show r ++ ")"
133− show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } )
134− = "VertJoin [" ++ show c ++ ", " ++ show r ++ "] (" ++ show t ++ ") <-> (" ++ show b ++ ")"
135− show ( CropRight { cropped_image, output_width, output_height } )
136− = "CropRight [" ++ show output_width ++ "," ++ show output_height ++ "]"
137− ++ " (" ++ show cropped_image ++ ")"
138− show ( CropLeft { cropped_image, left_skip, output_width, output_height } )
139− = "CropLeft [" ++ show left_skip ++ "," ++ show output_width ++ "," ++ show output_height ++ "]"
140− ++ " (" ++ show cropped_image ++ ")"
141− show ( CropBottom { cropped_image, output_width, output_height } )
142− = "CropBottom [" ++ show output_width ++ "," ++ show output_height ++ "]"
143− ++ " (" ++ show cropped_image ++ ")"
144− show ( CropTop { cropped_image, top_skip, output_width, output_height } )
145− = "CropTop [" ++ show top_skip ++ "," ++ show output_width ++ "," ++ show output_height ++ "]"
146− ++ " (" ++ show cropped_image ++ ")"
147− show ( EmptyImage ) = "EmptyImage"
148−
149−-- | Attempts to pretty print just the structure of an image.
147+-- | pretty print just the structure of an image.
150−pp_image_structure :: Image -> String
148+ppImageStructure :: Image -> String
151−pp_image_structure in_img = go 0 in_img
149+ppImageStructure = go 0
152 where
150 where
153 go indent img = tab indent ++ pp indent img
151 go indent img = tab indent ++ pp indent img
154 tab indent = concat $ replicate indent " "
152 tab indent = concat $ replicate indent " "
155− pp _ (HorizText {output_width}) = "HorizText(" ++ show output_width ++ ")"
153+ pp _ (HorizText {outputWidth}) = "HorizText(" ++ show outputWidth ++ ")"
156− pp _ (BGFill {output_width, output_height})
154+ pp _ (BGFill {outputWidth, outputHeight})
157− = "BGFill(" ++ show output_width ++ "," ++ show output_height ++ ")"
155+ = "BGFill(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")"
158− pp i (HorizJoin {part_left = l, part_right = r, output_width = c})
156+ pp i (HorizJoin {partLeft = l, partRight = r, outputWidth = c})
159 = "HorizJoin(" ++ show c ++ ")\n" ++ go (i+1) l ++ "\n" ++ go (i+1) r
157 = "HorizJoin(" ++ show c ++ ")\n" ++ go (i+1) l ++ "\n" ++ go (i+1) r
160− pp i (VertJoin {part_top = t, part_bottom = b, output_width = c, output_height = r})
158+ pp i (VertJoin {partTop = t, partBottom = b, outputWidth = c, outputHeight = r})
161 = "VertJoin(" ++ show c ++ ", " ++ show r ++ ")\n"
159 = "VertJoin(" ++ show c ++ ", " ++ show r ++ ")\n"
162 ++ go (i+1) t ++ "\n"
160 ++ go (i+1) t ++ "\n"
163 ++ go (i+1) b
161 ++ go (i+1) b
164− pp i (CropRight {cropped_image, output_width, output_height})
162+ pp i (CropRight {croppedImage, outputWidth, outputHeight})
165− = "CropRight(" ++ show output_width ++ "," ++ show output_height ++ ")\n"
163+ = "CropRight(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"
166− ++ go (i+1) cropped_image
164+ ++ go (i+1) croppedImage
167− pp i (CropLeft {cropped_image, left_skip, output_width, output_height})
165+ pp i (CropLeft {croppedImage, leftSkip, outputWidth, outputHeight})
168− = "CropLeft(" ++ show left_skip ++ "->" ++ show output_width ++ "," ++ show output_height ++ ")\n"
166+ = "CropLeft(" ++ show leftSkip ++ "->" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"
169− ++ go (i+1) cropped_image
167+ ++ go (i+1) croppedImage
170− pp i (CropBottom {cropped_image, output_width, output_height})
168+ pp i (CropBottom {croppedImage, outputWidth, outputHeight})
171− = "CropBottom(" ++ show output_width ++ "," ++ show output_height ++ ")\n"
169+ = "CropBottom(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"
172− ++ go (i+1) cropped_image
170+ ++ go (i+1) croppedImage
173− pp i (CropTop {cropped_image, top_skip, output_width, output_height})
171+ pp i (CropTop {croppedImage, topSkip, outputWidth, outputHeight})
174− = "CropTop("++ show output_width ++ "," ++ show top_skip ++ "->" ++ show output_height ++ ")\n"
172+ = "CropTop("++ show outputWidth ++ "," ++ show topSkip ++ "->" ++ show outputHeight ++ ")\n"
175− ++ go (i+1) cropped_image
173+ ++ go (i+1) croppedImage
176 pp _ EmptyImage = "EmptyImage"
174 pp _ EmptyImage = "EmptyImage"
177−
175+
178instance NFData Image where
176instance NFData Image where
179 rnf EmptyImage = ()
177 rnf EmptyImage = ()
180 rnf (CropRight i w h) = i `deepseq` w `seq` h `seq` ()
178 rnf (CropRight i w h) = i `deepseq` w `seq` h `seq` ()
@@ -186,27 +184,102 @@ instance NFData Image where
186 rnf (HorizJoin l r w h) = l `deepseq` r `deepseq` w `seq` h `seq` ()
184 rnf (HorizJoin l r w h) = l `deepseq` r `deepseq` w `seq` h `seq` ()
187 rnf (HorizText a s w cw) = a `seq` s `deepseq` w `seq` cw `seq` ()
185 rnf (HorizText a s w cw) = a `seq` s `deepseq` w `seq` cw `seq` ()
188
186
189−-- | The width of an Image. This is the number display columns the image will occupy.
187+-- | The width of an Image. This is the number display columns the image
188+-- will occupy.
190−image_width :: Image -> Int
189+imageWidth :: Image -> Int
191−image_width HorizText { output_width = w } = w
190+imageWidth HorizText { outputWidth = w } = w
192−image_width HorizJoin { output_width = w } = w
191+imageWidth HorizJoin { outputWidth = w } = w
193−image_width VertJoin { output_width = w } = w
192+imageWidth VertJoin { outputWidth = w } = w
194−image_width BGFill { output_width = w } = w
193+imageWidth BGFill { outputWidth = w } = w
195−image_width CropRight { output_width = w } = w
194+imageWidth CropRight { outputWidth = w } = w
196−image_width CropLeft { output_width = w } = w
195+imageWidth CropLeft { outputWidth = w } = w
197−image_width CropBottom { output_width = w } = w
196+imageWidth CropBottom { outputWidth = w } = w
198−image_width CropTop { output_width = w } = w
197+imageWidth CropTop { outputWidth = w } = w
199−image_width EmptyImage = 0
198+imageWidth EmptyImage = 0
200−
199+
201−-- | The height of an Image. This is the number of display rows the image will occupy.
200+-- | The height of an Image. This is the number of display rows the
201+-- image will occupy.
202−image_height :: Image -> Int
202+imageHeight :: Image -> Int
203−image_height HorizText {} = 1
203+imageHeight HorizText {} = 1
204−image_height HorizJoin { output_height = h } = h
204+imageHeight HorizJoin { outputHeight = h } = h
205−image_height VertJoin { output_height = h } = h
205+imageHeight VertJoin { outputHeight = h } = h
206−image_height BGFill { output_height = h } = h
206+imageHeight BGFill { outputHeight = h } = h
207−image_height CropRight { output_height = h } = h
207+imageHeight CropRight { outputHeight = h } = h
208−image_height CropLeft { output_height = h } = h
208+imageHeight CropLeft { outputHeight = h } = h
209−image_height CropBottom { output_height = h } = h
209+imageHeight CropBottom { outputHeight = h } = h
210−image_height CropTop { output_height = h } = h
210+imageHeight CropTop { outputHeight = h } = h
211−image_height EmptyImage = 0
211+imageHeight EmptyImage = 0
212+
213+-- | Append in the 'Semigroup' instance is equivalent to '<->'.
214+instance Semigroup Image where
215+ (<>) = vertJoin
216+
217+-- | Append in the 'Monoid' instance is equivalent to '<->'.
218+instance Monoid Image where
219+ mempty = EmptyImage
220+#if !(MIN_VERSION_base(4,11,0))
221+ mappend = (<>)
222+#endif
223+
224+-- | combines two images side by side
225+--
226+-- Combines text chunks where possible. Assures outputWidth and
227+-- outputHeight properties are not violated.
228+--
229+-- The result image will have a width equal to the sum of the two images
230+-- width. And the height will equal the largest height of the two
231+-- images. The area not defined in one image due to a height mismatch
232+-- will be filled with the background pattern.
233+horizJoin :: Image -> Image -> Image
234+horizJoin EmptyImage i = i
235+horizJoin i EmptyImage = i
236+horizJoin i0@(HorizText a0 t0 w0 cw0) i1@(HorizText a1 t1 w1 cw1)
237+ | a0 == a1 = HorizText a0 (TL.append t0 t1) (w0 + w1) (cw0 + cw1)
238+ -- assumes horiz text height is always 1
239+ | otherwise = HorizJoin i0 i1 (w0 + w1) 1
240+horizJoin i0 i1
241+ -- If the images are of the same height then no padding is required
242+ | h0 == h1 = HorizJoin i0 i1 w h0
243+ -- otherwise one of the images needs to be padded to the right size.
244+ | h0 < h1 -- Pad i0
245+ = let padAmount = h1 - h0
246+ in HorizJoin (VertJoin i0 (BGFill w0 padAmount) w0 h1) i1 w h1
247+ | h0 > h1 -- Pad i1
248+ = let padAmount = h0 - h1
249+ in HorizJoin i0 (VertJoin i1 (BGFill w1 padAmount) w1 h0) w h0
250+ where
251+ w0 = imageWidth i0
252+ w1 = imageWidth i1
253+ w = w0 + w1
254+ h0 = imageHeight i0
255+ h1 = imageHeight i1
256+horizJoin _ _ = error "horizJoin applied to undefined values."
212
257
258+-- | combines two images vertically
259+--
260+-- The result image will have a height equal to the sum of the heights
261+-- of both images. The width will equal the largest width of the two
262+-- images. The area not defined in one image due to a width mismatch
263+-- will be filled with the background pattern.
264+vertJoin :: Image -> Image -> Image
265+vertJoin EmptyImage i = i
266+vertJoin i EmptyImage = i
267+vertJoin i0 i1
268+ -- If the images are of the same width then no background padding is
269+ -- required
270+ | w0 == w1 = VertJoin i0 i1 w0 h
271+ -- Otherwise one of the images needs to be padded to the size of the
272+ -- other image.
273+ | w0 < w1
274+ = let padAmount = w1 - w0
275+ in VertJoin (HorizJoin i0 (BGFill padAmount h0) w1 h0) i1 w1 h
276+ | w0 > w1
277+ = let padAmount = w0 - w1
278+ in VertJoin i0 (HorizJoin i1 (BGFill padAmount h1) w0 h1) w0 h
279+ where
280+ w0 = imageWidth i0
281+ w1 = imageWidth i1
282+ h0 = imageHeight i0
283+ h1 = imageHeight i1
284+ h = h0 + h1
285+vertJoin _ _ = error "vertJoin applied to undefined values."
src/Graphics/Vty/Inline.hs+86−76
@@ -1,116 +1,126 @@
1−-- | The inline module provides a limited interface to changing the style of terminal output. The
1+-- | The inline module provides a limited interface to changing the
2+-- style of terminal output. The intention is for this interface to be
2−-- intention is for this interface to be used inline with other output systems.
3+-- used inline with other output systems.
3--
4--
4−-- The changes specified by the InlineM monad are applied to the terminals display attributes. These
5+-- The changes specified by the InlineM monad are applied to the
6+-- terminal's display attributes. These display attributes affect the
5−-- display attributes effect the display of all following text output to the terminal file
7+-- display of all following text output to the terminal file descriptor.
6−-- descriptor.
7--
8--
8−-- For example, in an IO monad the following code with print the text \"Not styled. \" Followed by the
9+-- For example, in an IO monad the following code will print the text
10+-- \"Not styled. \" Followed by the text \" Styled! \" drawn over a red
9−-- text \" Styled! \" drawn over a red background and underlined.
11+-- background and underlined.
10--
12--
11-- @
13-- @
12-- putStr \"Not styled. \"
14-- putStr \"Not styled. \"
13−-- put_attr_change_ $ do
15+-- putAttrChange_ $ do
14−-- back_color red
16+-- backColor red
15−-- apply_style underline
17+-- applyStyle underline
16-- putStr \" Styled! \"
18-- putStr \" Styled! \"
17−-- put_attr_change_ $ default_all
19+-- putAttrChange_ $ defaultAll
18-- putStrLn \"Not styled.\"
20-- putStrLn \"Not styled.\"
19-- @
21-- @
20--
22--
21−-- 'put_attr_change' outputs the control codes to the terminal device 'Handle'. This is a duplicate
23+-- 'putAttrChange' emits the control codes to the terminal device
24+-- attached to 'Handle'. This is a duplicate of the 'stdout' handle when
22−-- of the 'stdout' handle when the 'terminal_handle' was (first) acquired. If 'stdout' has since been
25+-- the 'terminalHandle' was (first) acquired. If 'stdout' has since been
23−-- changed then 'putStr', 'putStrLn', 'print' etc.. will output to a different 'Handle' than
26+-- changed then 'putStr', 'putStrLn', 'print' etc. will output to a
24−-- 'put_attr_change'
27+-- different 'Handle' than 'putAttrChange'.
25--
28--
26-- Copyright 2009-2010 Corey O'Connor
29-- Copyright 2009-2010 Corey O'Connor
27−{-# LANGUAGE BangPatterns #-}
28−module Graphics.Vty.Inline (module Graphics.Vty.Inline)
30+module Graphics.Vty.Inline
31+ ( module Graphics.Vty.Inline
32+ , withVty
33+ )
29− where
34+where
30
35
31−import Graphics.Vty.Attributes
36+import Graphics.Vty
32import Graphics.Vty.DisplayAttributes
37import Graphics.Vty.DisplayAttributes
33import Graphics.Vty.Inline.Unsafe
38import Graphics.Vty.Inline.Unsafe
34−import Graphics.Vty.Terminal.Interface
35−import Graphics.Vty.Terminal
36
39
37−import Control.Applicative
40+import Blaze.ByteString.Builder (writeToByteString)
41+
38import Control.Monad.State.Strict
42import Control.Monad.State.Strict
39
43
40import Data.Bits ( (.&.), complement )
44import Data.Bits ( (.&.), complement )
41import Data.IORef
45import Data.IORef
42−import Data.Monoid ( mappend )
43
46
44import System.IO
47import System.IO
45
48
46−type InlineM v = State Attr v
49+type InlineM v = State InlineState v
50+
51+data InlineState =
52+ InlineState { inlineAttr :: Attr
53+ , inlineUrlsEnabled :: Bool
54+ }
47
55
48−-- | Set the background color to the provided 'Color'
56+-- | Set the background color to the provided 'Color'.
49−back_color :: Color -> InlineM ()
57+backColor :: Color -> InlineM ()
50−back_color c = modify $ flip mappend ( current_attr `with_back_color` c )
58+backColor c = modify $ \s ->
59+ s { inlineAttr = inlineAttr s `withBackColor` c
60+ }
51
61
52−-- | Set the foreground color to the provided 'Color'
62+-- | Set the foreground color to the provided 'Color'.
53−fore_color :: Color -> InlineM ()
63+foreColor :: Color -> InlineM ()
54−fore_color c = modify $ flip mappend ( current_attr `with_fore_color` c )
64+foreColor c = modify $ \s ->
65+ s { inlineAttr = inlineAttr s `withForeColor` c
66+ }
55
67
56−-- | Attempt to change the 'Style' of the following text.
68+-- | Attempt to change the 'Style' of the following text..
57--
69--
58−-- If the terminal does not support the style change no error is produced. The style can still be
70+-- If the terminal does not support the style change then no error is
59−-- removed.
71+-- produced. The style can still be removed.
60−apply_style :: Style -> InlineM ()
72+applyStyle :: Style -> InlineM ()
61−apply_style s = modify $ flip mappend ( current_attr `with_style` s )
73+applyStyle st = modify $ \s ->
74+ s { inlineAttr = inlineAttr s `withStyle` st
75+ }
62
76
63−-- | Attempt to remove the specified 'Style' from the display of the following text.
77+-- | Attempt to remove the specified 'Style' from the display of the
78+-- following text.
64--
79--
65−-- This will fail if apply_style for the given style has not been previously called.
80+-- This will fail if 'applyStyle' for the given style has not been
81+-- previously called.
66−remove_style :: Style -> InlineM ()
82+removeStyle :: Style -> InlineM ()
67−remove_style s_mask = modify $ \attr ->
83+removeStyle sMask = modify $ \s ->
84+ s { inlineAttr =
68− let style' = case attr_style attr of
85+ let style' = case attrStyle (inlineAttr s) of
69− Default -> error $ "Graphics.Vty.Inline: Cannot remove_style if apply_style never used."
86+ Default -> error "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."
70− KeepCurrent -> error $ "Graphics.Vty.Inline: Cannot remove_style if apply_style never used."
87+ KeepCurrent -> error "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."
71− SetTo s -> s .&. complement s_mask
88+ SetTo st -> st .&. complement sMask
72− in attr { attr_style = SetTo style' }
89+ in (inlineAttr s) { attrStyle = SetTo style' }
90+ }
73
91
74−-- | Reset the display attributes
92+-- | Reset the display attributes.
75−default_all :: InlineM ()
93+defaultAll :: InlineM ()
76−default_all = put def_attr
94+defaultAll = modify $ \s -> s { inlineAttr = defAttr }
77
95
78−-- | Apply the provided display attribute changes to the given terminal.
96+-- | Apply the provided display attribute changes to the given terminal
97+-- output device.
79--
98--
80-- This does not flush the terminal.
99-- This does not flush the terminal.
81−put_attr_change :: ( Applicative m, MonadIO m ) => Terminal -> InlineM () -> m ()
100+putAttrChange :: ( Applicative m, MonadIO m ) => Output -> InlineM () -> m ()
82−put_attr_change t c = liftIO $ do
101+putAttrChange out c = liftIO $ do
83− bounds <- display_bounds t
102+ bounds <- displayBounds out
84− dc <- display_context t bounds
103+ dc <- displayContext out bounds
85− mfattr <- prev_fattr <$> readIORef (assumed_state_ref t)
104+ mfattr <- prevFattr <$> readIORef (assumedStateRef out)
86 fattr <- case mfattr of
105 fattr <- case mfattr of
87 Nothing -> do
106 Nothing -> do
88− liftIO $ send_to_terminal t (default_attr_required_bytes dc) (serialize_default_attr dc)
107+ liftIO $ outputByteBuffer out $ writeToByteString $ writeDefaultAttr dc False
89− return $ FixedAttr default_style_mask Nothing Nothing
108+ return $ FixedAttr defaultStyleMask Nothing Nothing Nothing
90 Just v -> return v
109 Just v -> return v
91− let attr = execState c current_attr
110+ let InlineState attr urlsEnabled = execState c (InlineState currentAttr False)
92− attr' = limit_attr_for_display t attr
111+ attr' = limitAttrForDisplay out attr
93− fattr' = fix_display_attr fattr attr'
112+ fattr' = fixDisplayAttr fattr attr'
94− diffs = display_attr_diffs fattr fattr'
113+ diffs = displayAttrDiffs fattr fattr'
95− send_to_terminal t (attr_required_bytes dc fattr attr' diffs)
114+ outputByteBuffer out $ writeToByteString $ writeSetAttr dc urlsEnabled fattr attr' diffs
96− (serialize_set_attr dc fattr attr' diffs)
97− modifyIORef (assumed_state_ref t) $ \s -> s { prev_fattr = Just fattr' }
115+ modifyIORef (assumedStateRef out) $ \s -> s { prevFattr = Just fattr' }
98− inline_hack dc
116+ inlineHack dc
99
117
100−-- | Apply the provided display attributes changes to the terminal that was current at the time this
118+-- | Apply the provided display attributes changes to the terminal
101−-- was first used. Which, for most use cases, is the current terminal.
119+-- output device.
102--
120--
103-- This will flush the terminal output.
121-- This will flush the terminal output.
104−put_attr_change_ :: ( Applicative m, MonadIO m ) => InlineM () -> m ()
122+putAttrChange_ :: ( Applicative m, MonadIO m ) => InlineM () -> m ()
105−put_attr_change_ c = liftIO $ do
123+putAttrChange_ c = liftIO $ withOutput $ \out -> do
106− mt <- readIORef global_vty_terminal
107− t <- case mt of
108− Nothing -> do
109− t <- current_terminal
110− writeIORef global_vty_terminal (Just t)
111− return t
112− Just t -> return t
113 hFlush stdout
124 hFlush stdout
114− put_attr_change t c
125+ putAttrChange out c
115 hFlush stdout
126 hFlush stdout
116−
src/Graphics/Vty/Inline/Unsafe.hs+52−4
@@ -1,11 +1,59 @@
1+{-# OPTIONS_HADDOCK hide #-}
2+
1module Graphics.Vty.Inline.Unsafe where
3module Graphics.Vty.Inline.Unsafe where
2
4
3−import Graphics.Vty.Terminal.Interface (Terminal)
5+import Graphics.Vty
4
6
5import Data.IORef
7import Data.IORef
6
8
9+import GHC.IO.Handle (hDuplicate)
10+
11+import System.IO (stdin, stdout, hSetBuffering, BufferMode(NoBuffering))
12+
7import System.IO.Unsafe
13import System.IO.Unsafe
8
14
9−global_vty_terminal :: IORef (Maybe Terminal)
15+import System.Posix.IO (handleToFd)
10−{-# NOINLINE global_vty_terminal #-}
16+
17+globalVty :: IORef (Maybe Vty)
18+{-# NOINLINE globalVty #-}
19+globalVty = unsafePerformIO $ newIORef Nothing
20+
21+globalOutput :: IORef (Maybe Output)
22+{-# NOINLINE globalOutput #-}
11−global_vty_terminal = unsafePerformIO $ newIORef Nothing
23+globalOutput = unsafePerformIO $ newIORef Nothing
24+
25+mkDupeConfig :: IO Config
26+mkDupeConfig = do
27+ hSetBuffering stdout NoBuffering
28+ hSetBuffering stdin NoBuffering
29+ stdinDupe <- hDuplicate stdin >>= handleToFd
30+ stdoutDupe <- hDuplicate stdout >>= handleToFd
31+ return $ defaultConfig { inputFd = Just stdinDupe, outputFd = Just stdoutDupe }
32+
33+-- | This will create a Vty instance using 'mkVty' and execute an IO
34+-- action provided that instance. The created Vty instance will be
35+-- stored to the unsafe 'IORef' 'globalVty'.
36+--
37+-- This instance will use duplicates of the stdin and stdout Handles.
38+withVty :: (Vty -> IO b) -> IO b
39+withVty f = do
40+ mvty <- readIORef globalVty
41+ vty <- case mvty of
42+ Nothing -> do
43+ vty <- mkDupeConfig >>= mkVty
44+ writeIORef globalVty (Just vty)
45+ return vty
46+ Just vty -> return vty
47+ f vty
48+
49+withOutput :: (Output -> IO b) -> IO b
50+withOutput f = do
51+ mout <- readIORef globalOutput
52+ out <- case mout of
53+ Nothing -> do
54+ config <- mappend <$> userConfig <*> mkDupeConfig
55+ out <- outputForConfig config
56+ writeIORef globalOutput (Just out)
57+ return out
58+ Just out -> return out
59+ f out
src/Graphics/Vty/Input.hs+155−76
@@ -1,97 +1,176 @@
1−-- Copyright 2009-2010 Corey O'Connor
1+{-# LANGUAGE RecordWildCards, CPP #-}
2+
3+-- | This module provides the input layer for Vty, including methods
4+-- for initializing an 'Input' structure and reading 'Event's from the
5+-- terminal.
6+--
7+-- Note that due to the evolution of terminal emulators, some keys
8+-- and combinations will not reliably map to the expected events by
9+-- any terminal program. There is no 1:1 mapping from key events to
10+-- bytes read from the terminal input device. In very limited cases the
11+-- terminal and vty's input process can be customized to resolve these
12+-- issues; see "Graphics.Vty.Config" for how to configure vty's input
13+-- processing.
2--
14--
3−-- Right, I'm mostly guessing on these details. So, as far as I can figure:
15+-- = VTY's Implementation
4--
16--
5-- There are two input modes:
17-- There are two input modes:
6−-- 1. 7 bit
7−-- 2. 8 bit
8--
18--
9−-- Which is better? Well, depends on if you want to enter Unicode as UTF-8 or not.
19+-- 1. 7-bit
20+--
21+-- 2. 8-bit
22+--
23+-- The 7-bit input mode is the default and the expected mode in most use
24+-- cases. This is what Vty uses.
25+--
26+-- == 7-bit input encoding
27+--
28+-- Control key combinations are represented by masking the two high bits
29+-- of the 7-bit input. Historically the control key actually grounded
30+-- the two high bit wires: 6 and 7. This is why control key combos
31+-- map to single character events: the input bytes are identical. The
32+-- input byte is the bit encoding of the character with bits 6 and 7
33+-- masked. Bit 6 is set by shift. Bit 6 and 7 are masked by control. For
34+-- example,
35+--
36+-- * Control-I is 'i', `01101001`, and has bit 6 and 7 masked to become
37+-- `00001001`, which is the ASCII and UTF-8 encoding of the Tab key.
38+--
39+-- * Control+Shift-C is 'C', `01000011`, with bit 6 and 7 set to zero
40+-- which is `0000011` and is the "End of Text" code.
41+--
42+-- * Hypothesis: This is why capital-A, 'A', has value 65 in ASCII: this
43+-- is the value 1 with bit 7 set and 6 unset.
44+--
45+-- * Hypothesis: Bit 6 is unset by upper case letters because,
46+-- initially, there were only upper case letters used and a 5 bit
47+-- encoding.
48+--
49+-- == 8-bit encoding
50+--
51+-- The 8th bit was originally used for parity checking which is useless
52+-- for terminal emulators. Some terminal emulators support an 8-bit
53+-- input encoding. While this provides some advantages, the actual usage
54+-- is low. Most systems use 7-bit mode but recognize 8-bit control
55+-- characters when escaped. This is what Vty does.
56+--
57+-- == Escaped Control Keys
58+--
59+-- Using 7-bit input encoding, the @ESC@ byte can signal the start of
60+-- an encoded control key. To differentiate a single @ESC@ event from a
61+-- control key, the timing of the input is used.
62+--
63+-- 1. @ESC@ individually: @ESC@ byte; no bytes following for a period of
64+-- 'VMIN' milliseconds.
10--
65--
11−-- * 7 bit mode: UTF-8 can be input unambiguiously. Requires an escape delay to differentiate ESC
66+-- 2. Control keys that contain @ESC@ in their encoding: The @ESC byte
12−-- from extended control characters. EG: F1
67+-- is followed by more bytes read within 'VMIN' milliseconds. All bytes
13−-- * 8 bit mode: UTF-8 cannot be input. Does not require an escape delay.
68+-- up until the next valid input block are passed to the classifier.
14--
69--
15−-- By default 7 bit mode is used. If 8 bit moe is requested then vty will request 8 bit mode from
70+-- If the current runtime is the threaded runtime then the terminal's
16−-- the terminal. However, if the terminal does not report a capability that uses 8 bit encoding the
71+-- @VMIN@ and @VTIME@ behavior reliably implement the above rules. If
17−-- mode will fall back to 7 bit.
72+-- the current runtime does not support 'forkOS' then there is currently
73+-- no implementation.
18--
74--
19−-- By default, the escape delay is 10000 microseconds. Which is assumed to be well above the
75+-- == Unicode Input and Escaped Control Key Sequences
20−-- sampling rate required to detect a keyup for a person typing 200 wpm.
21--
76--
22−-- 0. set raw mode with min == 0 and time == 0
77+-- The input encoding determines how UTF-8 encoded characters are
78+-- recognized.
23--
79--
24−module Graphics.Vty.Input ( Key(..)
80+-- * 7-bit mode: UTF-8 can be input unambiguously. UTF-8 input is
81+-- a superset of ASCII. UTF-8 does not overlap escaped control key
82+-- sequences. However, the escape key must be differentiated from
83+-- escaped control key sequences by the timing of the input bytes.
84+--
85+-- * 8-bit mode: UTF-8 cannot be input unambiguously. This does not
86+-- require using the timing of input bytes to differentiate the escape
87+-- key. Many terminals do not support 8-bit mode.
88+--
89+-- == Terminfo
90+--
91+-- The terminfo system is used to determine how some keys are encoded.
92+-- Terminfo is incomplete and in some cases terminfo is incorrect. Vty
93+-- assumes terminfo is correct but provides a mechanism to override
94+-- terminfo; see "Graphics.Vty.Config", specifically 'inputOverrides'.
95+--
96+-- == Terminal Input is Broken
97+--
98+-- Clearly terminal input has fundamental issues. There is no easy way
99+-- to reliably resolve these issues.
100+--
101+-- One resolution would be to ditch standard terminal interfaces
102+-- entirely and just go directly to scancodes. This would be a
103+-- reasonable option for Vty if everybody used the linux kernel console
104+-- but for obvious reasons this is not possible.
105+--
106+-- The "Graphics.Vty.Config" module supports customizing the
107+-- input-byte-to-event mapping and xterm supports customizing the
108+-- scancode-to-input-byte mapping. With a lot of work a user's system
109+-- can be set up to encode all the key combos in an almost-sane manner.
110+--
111+-- == See also
112+--
113+-- * http://www.leonerd.org.uk/hacks/fixterms/
114+module Graphics.Vty.Input
115+ ( Key(..)
25− , Modifier(..)
116+ , Modifier(..)
26− , Button(..)
117+ , Button(..)
27− , Event(..)
118+ , Event(..)
28− , initTermInput
119+ , Input(..)
29− , defaultEscDelay
120+ , inputForConfig
30− )
121+ )
31− where
122+where
32
123
33−import Graphics.Vty.Input.Data
124+import Graphics.Vty.Config
125+import Graphics.Vty.Input.Events
34−import Graphics.Vty.Input.Internal
126+import Graphics.Vty.Input.Loop
35import Graphics.Vty.Input.Terminfo
127import Graphics.Vty.Input.Terminfo
36
128
37−import Control.Concurrent
129+import Control.Concurrent.STM
38−
130+import Lens.Micro
39−import System.Console.Terminfo
40
131
132+import qualified System.Console.Terminfo as Terminfo
41import System.Posix.Signals.Exts
133import System.Posix.Signals.Exts
42−import System.Posix.Terminal
43−import System.Posix.IO (stdInput)
44
134
45−defaultEscDelay :: Int
135+#if !MIN_VERSION_base(4,11,0)
46−defaultEscDelay = 4000
136+import Data.Monoid ((<>))
137+#endif
47
138
48−-- | Set up the terminal for input. Returns a function which reads key
139+-- | Set up the terminal with file descriptor `inputFd` for input.
49−-- events, and a function for shutting down the terminal access.
140+-- Returns an 'Input'.
50−--
51−-- The terminal is modified as follows:
52−--
53−-- * IXON disabled
54−-- - disables software flow control on outgoing data. This stops the process from being
55−-- suspended if the output terminal cannot keep up. I presume this has little effect these
56−-- days. I hope this means that output will be buffered if the terminal cannot keep up. In the
57−-- ol days the output might of been dropped.
58−--
59−-- "raw" mode is used for input.
60−--
61−-- * ISIG disabled
62−-- - enables keyboard combinations that result in signals. People would probably want this to
63−-- be an option.
64−--
141+--
65−-- * ECHO disabled
142+-- The table used to determine the 'Events' to produce for the input
66−-- - input is not echod to the output.
143+-- bytes comes from 'classifyMapForTerm' which is then overridden by
67−--
144+-- the the applicable entries from the configuration's 'inputMap'.
68−-- * ICANON disabled
69−-- - canonical mode (line mode) input is not used.
70−--
145+--
71−-- * IEXTEN disabled
146+-- The terminal device's mode flags are configured by the
72−-- - extended functions are disabled. Uh. Whatever these are.
147+-- 'attributeControl' function.
73−--
148+inputForConfig :: Config -> IO Input
74−initTermInput :: Int -> Terminal -> IO (IO Event, IO ())
149+inputForConfig config@Config{ termName = Just termName
75−initTermInput escDelay terminal = do
150+ , inputFd = Just termFd
76− attr <- getTerminalAttributes stdInput
151+ , vmin = Just _
77− let attr' = foldl withoutMode attr [ StartStopOutput, KeyboardInterrupts
152+ , vtime = Just _
78− , EnableEcho, ProcessInput, ExtendedFunctions
153+ , .. } = do
79− ]
154+ terminal <- Terminfo.setupTerm termName
80− setTerminalAttributes stdInput attr' Immediately
155+ let inputOverrides = [(s,e) | (t,s,e) <- inputMap, t == Nothing || t == Just termName]
81− set_term_timing
156+ activeInputMap = classifyMapForTerm termName terminal `mappend` inputOverrides
82− let caps_legacy_table = map_to_legacy_table $ caps_classify_table terminal keys_from_caps_table
157+ (setAttrs, unsetAttrs) <- attributeControl termFd
83− classify_table = concat $ caps_legacy_table : ansi_classify_table
158+ setAttrs
84− (eventChannel, shutdown_input) <- initInputForFd escDelay classify_table stdInput
159+ input <- initInput config activeInputMap
85 let pokeIO = Catch $ do
160 let pokeIO = Catch $ do
86− let e = error "(getsize in input layer)"
161+ setAttrs
87− setTerminalAttributes stdInput attr' Immediately
162+ atomically $ writeTChan (input^.eventChannel) ResumeAfterSignal
88− writeChan eventChannel (EvResize e e)
89 _ <- installHandler windowChange pokeIO Nothing
163 _ <- installHandler windowChange pokeIO Nothing
90 _ <- installHandler continueProcess pokeIO Nothing
164 _ <- installHandler continueProcess pokeIO Nothing
91− let shutdown_input' = do
165+
92− shutdown_input
166+ let restore = unsetAttrs
167+
168+ return $ input
169+ { shutdownInput = do
170+ shutdownInput input
93 _ <- installHandler windowChange Ignore Nothing
171 _ <- installHandler windowChange Ignore Nothing
94 _ <- installHandler continueProcess Ignore Nothing
172 _ <- installHandler continueProcess Ignore Nothing
95− setTerminalAttributes stdInput attr Immediately
173+ restore
96− return (readChan eventChannel, shutdown_input')
174+ , restoreInputState = restoreInputState input >> restore
97−
175+ }
176+inputForConfig config = (<> config) <$> standardIOConfig >>= inputForConfig
src/Graphics/Vty/Input/Classify.hsnew file+110
@@ -0,0 +1,110 @@
1+{-# OPTIONS_HADDOCK hide #-}
2+-- This makes a kind of trie. Has space efficiency issues with large
3+-- input blocks. Likely building a parser and just applying that would
4+-- be better.
5+module Graphics.Vty.Input.Classify
6+ ( classify
7+ , KClass(..)
8+ , ClassifierState(..)
9+ )
10+where
11+
12+import Graphics.Vty.Input.Events
13+import Graphics.Vty.Input.Mouse
14+import Graphics.Vty.Input.Focus
15+import Graphics.Vty.Input.Paste
16+import Graphics.Vty.Input.Classify.Types
17+
18+import Codec.Binary.UTF8.Generic (decode)
19+
20+import Control.Arrow (first)
21+import qualified Data.Map as M( fromList, lookup )
22+import Data.Maybe ( mapMaybe )
23+import qualified Data.Set as S( fromList, member )
24+
25+import Data.Word
26+
27+import qualified Data.ByteString as BS
28+import qualified Data.ByteString.Char8 as BS8
29+import Data.ByteString.Char8 (ByteString)
30+
31+-- | Whether the classifier is currently processing a chunked format.
32+-- Currently, only bracketed pastes use this.
33+data ClassifierState
34+ = ClassifierStart
35+ -- ^ Not processing a chunked format.
36+ | ClassifierInChunk ByteString [ByteString]
37+ -- ^ Currently processing a chunked format. The initial chunk is in the
38+ -- first argument and a reversed remainder of the chunks is collected in
39+ -- the second argument. At the end of the processing, the chunks are
40+ -- reversed and concatenated with the final chunk.
41+
42+compile :: ClassifyMap -> ByteString -> KClass
43+compile table = cl' where
44+ -- take all prefixes and create a set of these
45+ prefixSet = S.fromList $ concatMap (init . BS.inits . BS8.pack . fst) table
46+ maxValidInputLength = maximum (map (length . fst) table)
47+ eventForInput = M.fromList $ map (first BS8.pack) table
48+ cl' inputBlock | BS8.null inputBlock = Prefix
49+ cl' inputBlock = case M.lookup inputBlock eventForInput of
50+ -- if the inputBlock is exactly what is expected for an
51+ -- event then consume the whole block and return the event
52+ Just e -> Valid e BS8.empty
53+ Nothing -> case S.member inputBlock prefixSet of
54+ True -> Prefix
55+ -- look up progressively smaller tails of the input
56+ -- block until an event is found The assumption is that
57+ -- the event that consumes the most input bytes should
58+ -- be produced.
59+ -- The test verifyFullSynInputToEvent2x verifies this.
60+ -- H: There will always be one match. The prefixSet
61+ -- contains, by definition, all prefixes of an event.
62+ False ->
63+ let inputPrefixes = reverse . take maxValidInputLength . tail . BS8.inits $ inputBlock
64+ in case mapMaybe (\s -> (,) s `fmap` M.lookup s eventForInput) inputPrefixes of
65+ (s,e) : _ -> Valid e (BS8.drop (BS8.length s) inputBlock)
66+ -- neither a prefix or a full event.
67+ [] -> Invalid
68+
69+classify :: ClassifyMap -> ClassifierState -> ByteString -> KClass
70+classify table = process
71+ where
72+ standardClassifier = compile table
73+
74+ process ClassifierStart s =
75+ case BS.uncons s of
76+ _ | bracketedPasteStarted s ->
77+ if bracketedPasteFinished s
78+ then parseBracketedPaste s
79+ else Chunk
80+ _ | isMouseEvent s -> classifyMouseEvent s
81+ _ | isFocusEvent s -> classifyFocusEvent s
82+ Just (c,cs) | c >= 0xC2 -> classifyUtf8 c cs
83+ _ -> standardClassifier s
84+
85+ process (ClassifierInChunk p ps) s | bracketedPasteStarted p =
86+ if bracketedPasteFinished s
87+ then parseBracketedPaste $ BS.concat $ p:reverse (s:ps)
88+ else Chunk
89+ process ClassifierInChunk{} _ = Invalid
90+
91+classifyUtf8 :: Word8 -> ByteString -> KClass
92+classifyUtf8 c cs =
93+ let n = utf8Length c
94+ (codepoint,rest) = BS8.splitAt (n - 1) cs
95+
96+ codepoint8 :: [Word8]
97+ codepoint8 = c:BS.unpack codepoint
98+
99+ in case decode codepoint8 of
100+ _ | n < BS.length codepoint + 1 -> Prefix
101+ Just (unicodeChar, _) -> Valid (EvKey (KChar unicodeChar) []) rest
102+ -- something bad happened; just ignore and continue.
103+ Nothing -> Invalid
104+
105+utf8Length :: Word8 -> Int
106+utf8Length c
107+ | c < 0x80 = 1
108+ | c < 0xE0 = 2
109+ | c < 0xF0 = 3
110+ | otherwise = 4
src/Graphics/Vty/Input/Classify/Parse.hsnew file+61
@@ -0,0 +1,61 @@
1+-- | This module provides a simple parser for parsing input event
2+-- control sequences.
3+module Graphics.Vty.Input.Classify.Parse
4+ ( Parser
5+ , runParser
6+ , failParse
7+ , readInt
8+ , readChar
9+ , expectChar
10+ )
11+where
12+
13+import Graphics.Vty.Input.Events
14+import Graphics.Vty.Input.Classify.Types
15+
16+import Control.Monad.Trans.Maybe
17+import Control.Monad.State
18+
19+import qualified Data.ByteString.Char8 as BS8
20+import Data.ByteString.Char8 (ByteString)
21+
22+type Parser a = MaybeT (State ByteString) a
23+
24+-- | Run a parser on a given input string. If the parser fails, return
25+-- 'Invalid'. Otherwise return the valid event ('Valid') and the
26+-- remaining unparsed characters.
27+runParser :: ByteString -> Parser Event -> KClass
28+runParser s parser =
29+ case runState (runMaybeT parser) s of
30+ (Nothing, _) -> Invalid
31+ (Just e, remaining) -> Valid e remaining
32+
33+-- | Fail a parsing operation.
34+failParse :: Parser a
35+failParse = fail "invalid parse"
36+
37+-- | Read an integer from the input stream. If an integer cannot be
38+-- read, fail parsing. E.g. calling readInt on an input of "123abc" will
39+-- return '123' and consume those characters.
40+readInt :: Parser Int
41+readInt = do
42+ s <- BS8.unpack <$> get
43+ case (reads :: ReadS Int) s of
44+ [(i, rest)] -> put (BS8.pack rest) >> return i
45+ _ -> failParse
46+
47+-- | Read a character from the input stream. If one cannot be read (e.g.
48+-- we are out of characters), fail parsing.
49+readChar :: Parser Char
50+readChar = do
51+ s <- get
52+ case BS8.uncons s of
53+ Just (c,rest) -> put rest >> return c
54+ Nothing -> failParse
55+
56+-- | Read a character from the input stream and fail parsing if it is
57+-- not the specified character.
58+expectChar :: Char -> Parser ()
59+expectChar c = do
60+ c' <- readChar
61+ if c' == c then return () else failParse
src/Graphics/Vty/Input/Classify/Types.hsnew file+25
@@ -0,0 +1,25 @@
1+-- | This module exports the input classification type to avoid import
2+-- cycles between other modules that need this.
3+{-# LANGUAGE StrictData #-}
4+module Graphics.Vty.Input.Classify.Types
5+ ( KClass(..)
6+ )
7+where
8+
9+import Graphics.Vty.Input.Events
10+
11+import Data.ByteString.Char8 (ByteString)
12+
13+data KClass
14+ = Valid Event ByteString
15+ -- ^ A valid event was parsed. Any unused characters from the input
16+ -- stream are also provided.
17+ | Invalid
18+ -- ^ The input characters did not represent a valid event.
19+ | Prefix
20+ -- ^ The input characters form the prefix of a valid event character
21+ -- sequence.
22+ | Chunk
23+ -- ^ The input characters are either start of a bracketed paste chunk
24+ -- or in the middle of a bracketed paste chunk.
25+ deriving(Show, Eq)
src/Graphics/Vty/Input/Data.hsdeleted−141
@@ -1,141 +0,0 @@
1−-- Copyright Corey O'Connor
2−module Graphics.Vty.Input.Data where
3−
4−-- | Representations of non-modifier keys.
5−data Key = KEsc | KFun Int | KBackTab | KPrtScr | KPause | KASCII Char | KBS | KIns
6− | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KNP5 | KUp | KMenu
7− | KLeft | KDown | KRight | KEnter
8− deriving (Eq,Show,Ord)
9−
10−-- | Modifier keys. Key codes are interpreted such that users are more likely to
11−-- have Meta than Alt; for instance on the PC Linux console, 'MMeta' will
12−-- generally correspond to the physical Alt key.
13−data Modifier = MShift | MCtrl | MMeta | MAlt
14− deriving (Eq,Show,Ord)
15−
16−-- | Mouse buttons. Not yet used.
17−data Button = BLeft | BMiddle | BRight
18− deriving (Eq,Show,Ord)
19−
20−-- | Generic events.
21−data Event = EvKey Key [Modifier] | EvMouse Int Int Button [Modifier]
22− | EvResize Int Int
23− deriving (Eq,Show,Ord)
24−
25−type ClassifyTable = [(String, (Key, [Modifier]))]
26−
27−map_to_legacy_table :: [(String, Event)] -> ClassifyTable
28−map_to_legacy_table = map f
29− where f (s, EvKey k mods) = (s, (k, mods))
30− f _ = error "no mapping for mouse or resize events"
31−
32−-- | classify table directly generated from terminfo cap strings
33−keys_from_caps_table :: [(String, Event)]
34−keys_from_caps_table =
35− [ ("khome", EvKey KHome [])
36− , ("kend", EvKey KEnd [])
37− , ("cbt", EvKey KBackTab [])
38− , ("kcud1", EvKey KDown [])
39− , ("kcuu1", EvKey KUp [])
40− , ("kcuf1", EvKey KRight [])
41− , ("kcub1", EvKey KLeft [])
42− , ("kLFT", EvKey KLeft [MShift])
43− , ("kRIT", EvKey KRight [MShift])
44− ]
45−
46−nav_keys_0 :: ClassifyTable
47−nav_keys_0 =
48− [ k "G" KNP5
49− , k "P" KPause
50− , k "A" KUp
51− , k "B" KDown
52− , k "C" KRight
53− , k "D" KLeft
54− , k "H" KHome
55− , k "F" KEnd
56− , k "E" KBegin
57− ]
58− where k c s = ("\ESC["++c,(s,[]))
59−
60−-- Support for arrows and KHome/KEnd
61−nav_keys_1 :: ClassifyTable
62−nav_keys_1 =
63− [("\ESC[" ++ charCnt ++ show mc++c,(s,m))
64− | charCnt <- ["1;", ""], -- we can have a count or not
65− (m,mc) <- [([MShift],2::Int), ([MCtrl],5), ([MMeta],3),
66− ([MShift, MCtrl],6), ([MShift, MMeta],4)], -- modifiers and their codes
67− (c,s) <- [("A", KUp), ("B", KDown), ("C", KRight), ("D", KLeft), ("H", KHome), ("F", KEnd)] -- directions and their codes
68− ]
69−
70−nav_keys_2 :: ClassifyTable
71−nav_keys_2 =
72− let k n s = ("\ESC["++show n++"~",(s,[]))
73− in zipWith k [2::Int,3,5,6,1,4]
74− [KIns,KDel,KPageUp,KPageDown,KHome,KEnd]
75−
76−nav_keys_3 :: ClassifyTable
77−nav_keys_3 =
78− let k n s = ("\ESC["++show n++";5~",(s,[MCtrl]))
79− in zipWith k [2::Int,3,5,6,1,4]
80− [KIns,KDel,KPageUp,KPageDown,KHome,KEnd]
81−
82−-- Support for simple characters.
83−-- we limit to < 0xC1. The UTF8 sequence detector will catch all values 0xC2 and above before this
84−-- classify table is reached.
85−simple_chars :: ClassifyTable
86−simple_chars = [ (x:[],(KASCII x,[])) | x <- map toEnum [0..0xC1] ]
87−
88−-- TODO: Support for function keys (should use terminfo)
89−--
90−-- (corey): yes. This definitely should. On my terminal these keys do not operate as expected.
91−function_keys_0 :: ClassifyTable
92−function_keys_0 = [ ("\ESC[["++[toEnum(64+i)],(KFun i,[])) | i <- [1..5] ]
93−
94−function_keys_1 :: ClassifyTable
95−function_keys_1 =
96− let f ff nrs m = [ ("\ESC["++show n++"~",(KFun (n-(nrs!!0)+ff), m)) | n <- nrs ] in
97− concat [ f 6 [17..21] [], f 11 [23,24] [], f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ]
98−
99−function_keys_2 :: ClassifyTable
100−function_keys_2 = [ ('\ESC':[x],(KASCII x,[MMeta])) | x <- '\ESC':'\t':[' ' .. '\DEL'] ]
101−
102−-- Ctrl+Char
103−ctrl_char_keys :: ClassifyTable
104−ctrl_char_keys =
105− [ ([toEnum x],(KASCII y,[MCtrl]))
106− | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_']),
107− y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
108− ]
109−
110−-- Ctrl+Meta+Char
111−ctrl_meta_keys :: ClassifyTable
112−ctrl_meta_keys =
113− [ ('\ESC':[toEnum x],(KASCII y,[MMeta,MCtrl])) | (x,y) <- zip [0..31] ('@':['a'..'z']++['['..'_']) ]
114−
115−-- Special support
116−special_support_keys :: ClassifyTable
117−special_support_keys =
118− [ -- special support for ESC
119− ("\ESC",(KEsc,[])) , ("\ESC\ESC",(KEsc,[MMeta]))
120− -- Special support for backspace
121− , ("\DEL",(KBS,[])), ("\ESC\DEL",(KBS,[MMeta]))
122− -- Special support for Enter
123− , ("\ESC\^J",(KEnter,[MMeta])), ("\^J",(KEnter,[]))
124− ]
125−
126−-- | classify table for ANSI terminals
127−ansi_classify_table :: [ClassifyTable]
128−ansi_classify_table =
129− [ nav_keys_0
130− , nav_keys_1
131− , nav_keys_2
132− , nav_keys_3
133− , simple_chars
134− , function_keys_0
135− , function_keys_1
136− , function_keys_2
137− , ctrl_char_keys
138− , ctrl_meta_keys
139− , special_support_keys
140− ]
141−
src/Graphics/Vty/Input/Events.hsnew file+83
@@ -0,0 +1,83 @@
1+{-# Language DeriveGeneric #-}
2+{-# Language StrictData #-}
3+module Graphics.Vty.Input.Events where
4+
5+import Control.DeepSeq
6+import Data.ByteString
7+import GHC.Generics
8+
9+-- | Representations of non-modifier keys.
10+--
11+-- * KFun is indexed from 0 to 63. Range of supported FKeys varies by
12+-- terminal and keyboard.
13+--
14+-- * KUpLeft, KUpRight, KDownLeft, KDownRight, KCenter support varies by
15+-- terminal and keyboard.
16+--
17+-- * Actually, support for most of these but KEsc, KChar, KBS, and
18+-- KEnter vary by terminal and keyboard.
19+data Key = KEsc | KChar {-# UNPACK #-} Char | KBS | KEnter
20+ | KLeft | KRight | KUp | KDown
21+ | KUpLeft | KUpRight | KDownLeft | KDownRight | KCenter
22+ | KFun {-# UNPACK #-} Int | KBackTab | KPrtScr | KPause | KIns
23+ | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KMenu
24+ deriving (Eq,Show,Read,Ord,Generic)
25+
26+instance NFData Key
27+
28+-- | Modifier keys. Key codes are interpreted such that users are more
29+-- likely to have Meta than Alt; for instance on the PC Linux console,
30+-- 'MMeta' will generally correspond to the physical Alt key.
31+data Modifier = MShift | MCtrl | MMeta | MAlt
32+ deriving (Eq,Show,Read,Ord,Generic)
33+
34+instance NFData Modifier
35+
36+-- | Mouse buttons.
37+data Button = BLeft | BMiddle | BRight | BScrollUp | BScrollDown
38+ deriving (Eq,Show,Read,Ord,Generic)
39+
40+instance NFData Button
41+
42+-- | Events.
43+data Event
44+ = EvKey Key [Modifier]
45+ -- ^ A keyboard key was pressed with the specified modifiers.
46+ | EvMouseDown Int Int Button [Modifier]
47+ -- ^ A mouse button was pressed at the specified column and row. Any
48+ -- modifiers available in the event are also provided.
49+ | EvMouseUp Int Int (Maybe Button)
50+ -- ^ A mouse button was released at the specified column and
51+ -- row. Some terminals report only that a button was released
52+ -- without specifying which one; in that case, Nothing is provided.
53+ -- Otherwise Just the button released is included in the event.
54+ | EvResize Int Int
55+ -- The terminal window was resized and the size is provided in the
56+ -- integer fields (width, height).
57+ | EvPaste ByteString
58+ -- ^ A paste event occurs when a bracketed paste input sequence is
59+ -- received. For terminals that support bracketed paste mode, these
60+ -- events will be triggered on a paste event. Terminals that do not
61+ -- support bracketed pastes will send the paste contents as ordinary
62+ -- input (which is probably bad, so beware!) Note that the data is
63+ -- provided in raw form and you'll have to decode (e.g. as UTF-8) if
64+ -- that's what your application expects.
65+ | EvLostFocus
66+ -- ^ The terminal running the application lost input focus.
67+ | EvGainedFocus
68+ -- ^ The terminal running the application gained input focus.
69+ deriving (Eq,Show,Read,Ord,Generic)
70+
71+instance NFData Event
72+
73+type ClassifyMap = [(String,Event)]
74+
75+-- | The type of internal events that drive the internal Vty event
76+-- dispatching to the application.
77+data InternalEvent =
78+ ResumeAfterSignal
79+ -- ^ Vty resumed operation after the process was interrupted with a
80+ -- signal. In practice this translates into a screen redraw in the
81+ -- input event loop.
82+ | InputEvent Event
83+ -- ^ An input event was received.
src/Graphics/Vty/Input/Focus.hsnew file+49
@@ -0,0 +1,49 @@
1+module Graphics.Vty.Input.Focus
2+ ( requestFocusEvents
3+ , disableFocusEvents
4+ , isFocusEvent
5+ , classifyFocusEvent
6+ )
7+where
8+
9+import Graphics.Vty.Input.Events
10+import Graphics.Vty.Input.Classify.Types
11+import Graphics.Vty.Input.Classify.Parse
12+
13+import Control.Monad.State
14+
15+import qualified Data.ByteString.Char8 as BS8
16+import Data.ByteString.Char8 (ByteString)
17+
18+-- | These sequences set xterm-based terminals to send focus event
19+-- sequences.
20+requestFocusEvents :: ByteString
21+requestFocusEvents = BS8.pack "\ESC[?1004h"
22+
23+-- | These sequences disable focus events.
24+disableFocusEvents :: ByteString
25+disableFocusEvents = BS8.pack "\ESC[?1004l"
26+
27+-- | Does the specified string begin with a focus event?
28+isFocusEvent :: ByteString -> Bool
29+isFocusEvent s = BS8.isPrefixOf focusIn s ||
30+ BS8.isPrefixOf focusOut s
31+
32+focusIn :: ByteString
33+focusIn = BS8.pack "\ESC[I"
34+
35+focusOut :: ByteString
36+focusOut = BS8.pack "\ESC[O"
37+
38+-- | Attempt to classify an input string as a focus event.
39+classifyFocusEvent :: ByteString -> KClass
40+classifyFocusEvent s = runParser s $ do
41+ when (not $ isFocusEvent s) failParse
42+
43+ expectChar '\ESC'
44+ expectChar '['
45+ ty <- readChar
46+ case ty of
47+ 'I' -> return EvGainedFocus
48+ 'O' -> return EvLostFocus
49+ _ -> failParse
src/Graphics/Vty/Input/Internal.hsdeleted−151
@@ -1,151 +0,0 @@
1−module Graphics.Vty.Input.Internal where
2−
3−import Graphics.Vty.Input.Data
4−
5−import Codec.Binary.UTF8.Generic (decode)
6−import Control.Concurrent
7−import Control.Exception (try, IOException(..))
8−import Control.Monad (when, void)
9−
10−import Data.Char
11−import Data.List( inits )
12−import qualified Data.Map as M( fromList, lookup )
13−import Data.Maybe ( mapMaybe )
14−import qualified Data.Set as S( fromList, member )
15−import Data.Word
16−
17−import Foreign ( alloca, poke, peek, Ptr )
18−
19−import System.Posix.IO ( fdReadBuf
20− , setFdOption
21− , FdOption(..)
22− )
23−import System.Posix.Types (Fd(..))
24−
25−data KClass
26− = Valid Key [Modifier]
27− | Invalid
28− | EndLoop
29− | Prefix
30− | MisPfx Key [Modifier] [Char]
31− deriving(Show)
32−
33−inputToEventThread :: (String -> KClass) -> Chan Char -> Chan Event -> IO ()
34−inputToEventThread classifier inputChannel eventChannel = loop []
35− where loop kb = case classifier kb of
36− Prefix -> do
37− c <- readChan inputChannel
38− if c == '\xFFFD'
39− then return ()
40− else loop (kb ++ [c])
41− Invalid -> do
42− c <- readChan inputChannel
43− if c == '\xFFFD'
44− then return ()
45− else loop [c]
46− EndLoop -> return ()
47− MisPfx k m s -> writeChan eventChannel (EvKey k m) >> loop s
48− Valid k m -> writeChan eventChannel (EvKey k m) >> loop ""
49−
50−compile :: ClassifyTable -> [Char] -> KClass
51−compile table = cl' where
52− -- take all prefixes and create a set of these
53− prefix_set = S.fromList $ concatMap (init . inits . fst) $ table
54− -- create a map from strings to event
55− event_for_input = flip M.lookup (M.fromList table)
56− cl' [] = Prefix
57− cl' input_block = case S.member input_block prefix_set of
58− True -> Prefix
59− -- if the input_block is exactly what is expected for an event then consume the whole
60− -- block and return the event
61− False -> case event_for_input input_block of
62− Just (k,m) -> Valid k m
63− -- look up progressively large prefixes of the input block until an event is found
64− -- H: There will always be one match. The prefix_set contains, by definition, all
65− -- prefixes of an event.
66− Nothing ->
67− let input_prefixes = init $ inits input_block
68− in case mapMaybe (\s -> (,) s `fmap` event_for_input s) input_prefixes of
69− (s,(k,m)) : _ -> MisPfx k m (drop (length s) input_block)
70− [] -> error $ "vty internal inconsistency - "
71− ++ "input not a prefix nor contains any event data "
72− ++ show input_block
73−
74−classify, classifyTab :: ClassifyTable -> [Char] -> KClass
75−
76−-- As soon as
77−classify _table "\xFFFD" = EndLoop
78−classify _table "\xFFFE" = Invalid
79−classify _table s@(c:_) | ord c >= 0xC2
80− = if utf8Length (ord c) > length s then Prefix else classifyUtf8 s -- beginning of an utf8 sequence
81−classify table other
82− = classifyTab table other
83−
84−classifyUtf8 :: [Char] -> KClass
85−classifyUtf8 s = case decode ((map (fromIntegral . ord) s) :: [Word8]) of
86− Just (unicodeChar, _) -> Valid (KASCII unicodeChar) []
87− _ -> Invalid -- something bad happened; just ignore and continue.
88−
89−classifyTab table = compile table
90−
91−first :: (a -> b) -> (a,c) -> (b,c)
92−first f (x,y) = (f x, y)
93−
94−utf8Length :: (Num t, Ord a, Num a) => a -> t
95−utf8Length c
96− | c < 0x80 = 1
97− | c < 0xE0 = 2
98− | c < 0xF0 = 3
99− | otherwise = 4
100−
101−initInputForFd :: Int -> ClassifyTable -> Fd -> IO (Chan Event, IO ())
102−initInputForFd escDelay classify_table input_fd = do
103− inputChannel <- newChan
104− eventChannel <- newChan
105− hadInput <- newEmptyMVar
106− let finishAtomicInput = writeChan inputChannel '\xFFFE'
107− inputThread :: IO ()
108− inputThread = do
109− _ <- alloca $ \(input_buffer :: Ptr Word8) -> do
110− let loop = do
111− setFdOption input_fd NonBlockingRead False
112− threadWaitRead input_fd
113− setFdOption input_fd NonBlockingRead True
114− _ <- try readAll :: IO (Either IOException ())
115− when (escDelay == 0) finishAtomicInput
116− loop
117− readAll = do
118− poke input_buffer 0
119− bytes_read <- fdReadBuf input_fd input_buffer 1
120− input_char <- fmap (chr . fromIntegral) $ peek input_buffer
121− when (bytes_read > 0) $ do
122− _ <- tryPutMVar hadInput () -- signal input
123− writeChan inputChannel input_char
124− readAll
125− loop
126− return ()
127− -- | If there is no input for some time, this thread puts '\xFFFE' in the
128− -- inputChannel.
129− noInputThread :: IO ()
130− noInputThread = when (escDelay > 0) loop
131− where loop = do
132− takeMVar hadInput -- wait for some input
133− threadDelay escDelay -- microseconds
134− hadNoInput <- isEmptyMVar hadInput -- no input yet?
135− -- TODO(corey): there is a race between here and the inputThread.
136− when hadNoInput finishAtomicInput
137− loop
138− classifier = classify classify_table
139− eventThreadId <- forkIO $ void $ inputToEventThread classifier inputChannel eventChannel
140− inputThreadId <- forkIO $ inputThread
141− noInputThreadId <- forkIO $ noInputThread
142− -- TODO(corey): killThread is a bit risky for my tastes.
143− -- H - somewhat mitigated by sending a magic terminate character?
144− let shutdown_input = do
145− writeChan inputChannel '\xFFFD'
146− killThread noInputThreadId
147− killThread eventThreadId
148− killThread inputThreadId
149− return (eventChannel, shutdown_input)
150−
151−foreign import ccall "vty_set_term_timing" set_term_timing :: IO ()
src/Graphics/Vty/Input/Loop.hsnew file+286
@@ -0,0 +1,286 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE NoMonomorphismRestriction #-}
3+{-# LANGUAGE RecordWildCards #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE TemplateHaskell #-}
6+{-# OPTIONS_HADDOCK hide #-}
7+-- | The input layer used to be a single function that correctly
8+-- accounted for the non-threaded runtime by emulating the terminal
9+-- VMIN adn VTIME handling. This has been removed and replace with a
10+-- more straightforward parser. The non-threaded runtime is no longer
11+-- supported.
12+--
13+-- This is an example of an algorithm where code coverage could be high,
14+-- even 100%, but the behavior is still under tested. I should collect
15+-- more of these examples...
16+--
17+-- reference: http://www.unixwiz.net/techtips/termios-vmin-vtime.html
18+module Graphics.Vty.Input.Loop where
19+
20+import Graphics.Vty.Config
21+import Graphics.Vty.Input.Classify
22+import Graphics.Vty.Input.Events
23+
24+import Control.Applicative
25+import Control.Concurrent
26+import Control.Concurrent.STM
27+import Control.Exception (mask, try, SomeException)
28+import Lens.Micro hiding ((<>~))
29+import Lens.Micro.Mtl
30+import Lens.Micro.TH
31+import Control.Monad (when, mzero, forM_)
32+import Control.Monad.IO.Class (liftIO)
33+import Control.Monad.Trans.State (StateT(..), evalStateT)
34+import Control.Monad.State.Class (MonadState, modify)
35+import Control.Monad.Trans.Reader (ReaderT(..))
36+
37+import qualified Data.ByteString.Char8 as BS8
38+import qualified Data.ByteString as BS
39+import Data.ByteString.Char8 (ByteString)
40+import Data.IORef
41+import Data.Word (Word8)
42+
43+import Foreign (allocaArray)
44+import Foreign.C.Types (CInt(..))
45+import Foreign.Ptr (Ptr, castPtr)
46+
47+import System.IO
48+import System.Posix.IO (fdReadBuf, setFdOption, FdOption(..))
49+import System.Posix.Terminal
50+import System.Posix.Types (Fd(..))
51+
52+import Text.Printf (hPrintf)
53+
54+data Input = Input
55+ { -- | Channel of events direct from input processing. Unlike
56+ -- 'nextEvent' this will not refresh the display if the next event
57+ -- is an 'EvResize'.
58+ _eventChannel :: TChan InternalEvent
59+ -- | Shuts down the input processing. As part of shutting down the
60+ -- input, this should also restore the input state.
61+ , shutdownInput :: IO ()
62+ -- | Restore the terminal's input state to what it was prior
63+ -- to configuring input for Vty. This should be done as part of
64+ -- 'shutdownInput' but is exposed in case you need to access it
65+ -- directly.
66+ , restoreInputState :: IO ()
67+ -- | Changes to this value are reflected after the next event.
68+ , _configRef :: IORef Config
69+ -- | input debug log
70+ , _inputDebug :: Maybe Handle
71+ }
72+
73+makeLenses ''Input
74+
75+data InputBuffer = InputBuffer
76+ { _ptr :: Ptr Word8
77+ , _size :: Int
78+ }
79+
80+makeLenses ''InputBuffer
81+
82+data InputState = InputState
83+ { _unprocessedBytes :: ByteString
84+ , _classifierState :: ClassifierState
85+ , _appliedConfig :: Config
86+ , _inputBuffer :: InputBuffer
87+ , _classifier :: ClassifierState -> ByteString -> KClass
88+ }
89+
90+makeLenses ''InputState
91+
92+type InputM a = StateT InputState (ReaderT Input IO) a
93+
94+logMsg :: String -> InputM ()
95+logMsg msg = do
96+ d <- view inputDebug
97+ case d of
98+ Nothing -> return ()
99+ Just h -> liftIO $ hPutStrLn h msg >> hFlush h
100+
101+-- this must be run on an OS thread dedicated to this input handling.
102+-- otherwise the terminal timing read behavior will block the execution
103+-- of the lightweight threads.
104+loopInputProcessor :: InputM ()
105+loopInputProcessor = do
106+ readFromDevice >>= addBytesToProcess
107+ validEvents <- many parseEvent
108+ forM_ validEvents emit
109+ dropInvalid
110+ loopInputProcessor
111+
112+addBytesToProcess :: ByteString -> InputM ()
113+addBytesToProcess block = unprocessedBytes <>= block
114+
115+emit :: Event -> InputM ()
116+emit event = do
117+ logMsg $ "parsed event: " ++ show event
118+ view eventChannel >>= liftIO . atomically . flip writeTChan (InputEvent event)
119+
120+-- The timing requirements are assured by the VMIN and VTIME set for the
121+-- device.
122+--
123+-- Precondition: Under the threaded runtime. Only current use is from a
124+-- forkOS thread. That case satisfies precondition.
125+readFromDevice :: InputM ByteString
126+readFromDevice = do
127+ newConfig <- view configRef >>= liftIO . readIORef
128+ oldConfig <- use appliedConfig
129+ let Just fd = inputFd newConfig
130+ when (newConfig /= oldConfig) $ do
131+ logMsg $ "new config: " ++ show newConfig
132+ liftIO $ applyConfig fd newConfig
133+ appliedConfig .= newConfig
134+ bufferPtr <- use $ inputBuffer.ptr
135+ maxBytes <- use $ inputBuffer.size
136+ stringRep <- liftIO $ do
137+ -- The killThread used in shutdownInput will not interrupt the
138+ -- foreign call fdReadBuf uses this provides a location to be
139+ -- interrupted prior to the foreign call. If there is input on
140+ -- the FD then the fdReadBuf will return in a finite amount of
141+ -- time due to the vtime terminal setting.
142+ threadWaitRead fd
143+ bytesRead <- fdReadBuf fd bufferPtr (fromIntegral maxBytes)
144+ if bytesRead > 0
145+ then BS.packCStringLen (castPtr bufferPtr, fromIntegral bytesRead)
146+ else return BS.empty
147+ when (not $ BS.null stringRep) $
148+ logMsg $ "input bytes: " ++ show (BS8.unpack stringRep)
149+ return stringRep
150+
151+applyConfig :: Fd -> Config -> IO ()
152+applyConfig fd (Config{ vmin = Just theVmin, vtime = Just theVtime })
153+ = setTermTiming fd theVmin (theVtime `div` 100)
154+applyConfig _ _ = fail "(vty) applyConfig was not provided a complete configuration"
155+
156+parseEvent :: InputM Event
157+parseEvent = do
158+ c <- use classifier
159+ s <- use classifierState
160+ b <- use unprocessedBytes
161+ case c s b of
162+ Valid e remaining -> do
163+ logMsg $ "valid parse: " ++ show e
164+ logMsg $ "remaining: " ++ show remaining
165+ classifierState .= ClassifierStart
166+ unprocessedBytes .= remaining
167+ return e
168+ _ -> mzero
169+
170+dropInvalid :: InputM ()
171+dropInvalid = do
172+ c <- use classifier
173+ s <- use classifierState
174+ b <- use unprocessedBytes
175+ case c s b of
176+ Chunk -> do
177+ classifierState .=
178+ case s of
179+ ClassifierStart -> ClassifierInChunk b []
180+ ClassifierInChunk p bs -> ClassifierInChunk p (b:bs)
181+ unprocessedBytes .= BS8.empty
182+ Invalid -> do
183+ logMsg "dropping input bytes"
184+ classifierState .= ClassifierStart
185+ unprocessedBytes .= BS8.empty
186+ _ -> return ()
187+
188+runInputProcessorLoop :: ClassifyMap -> Input -> IO ()
189+runInputProcessorLoop classifyTable input = do
190+ let bufferSize = 1024
191+ allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do
192+ s0 <- InputState BS8.empty ClassifierStart
193+ <$> readIORef (_configRef input)
194+ <*> pure (InputBuffer bufferPtr bufferSize)
195+ <*> pure (classify classifyTable)
196+ runReaderT (evalStateT loopInputProcessor s0) input
197+
198+-- | Construct two IO actions: one to configure the terminal for Vty and
199+-- one to restore the terminal mode flags to the values they had at the
200+-- time this function was called.
201+--
202+-- This function constructs a configuration action to clear the
203+-- following terminal mode flags:
204+--
205+-- * IXON disabled: disables software flow control on outgoing data.
206+-- This stops the process from being suspended if the output terminal
207+-- cannot keep up.
208+--
209+-- * Raw mode is used for input.
210+--
211+-- * ISIG (enables keyboard combinations that result in
212+-- signals)
213+--
214+-- * ECHO (input is not echoed to the output)
215+--
216+-- * ICANON (canonical mode (line mode) input is not used)
217+--
218+-- * IEXTEN (extended functions are disabled)
219+--
220+-- The configuration action also explicitly sets these flags:
221+--
222+-- * ICRNL (input carriage returns are mapped to newlines)
223+attributeControl :: Fd -> IO (IO (), IO ())
224+attributeControl fd = do
225+ original <- getTerminalAttributes fd
226+ let vtyMode = foldl withMode clearedFlags flagsToSet
227+ clearedFlags = foldl withoutMode original flagsToUnset
228+ flagsToSet = [ MapCRtoLF -- ICRNL
229+ ]
230+ flagsToUnset = [ StartStopOutput -- IXON
231+ , KeyboardInterrupts -- ISIG
232+ , EnableEcho -- ECHO
233+ , ProcessInput -- ICANON
234+ , ExtendedFunctions -- IEXTEN
235+ ]
236+ let setAttrs = setTerminalAttributes fd vtyMode Immediately
237+ unsetAttrs = setTerminalAttributes fd original Immediately
238+ return (setAttrs, unsetAttrs)
239+
240+logInitialInputState :: Input -> ClassifyMap -> IO()
241+logInitialInputState input classifyTable = case _inputDebug input of
242+ Nothing -> return ()
243+ Just h -> do
244+ Config{ vmin = Just theVmin
245+ , vtime = Just theVtime
246+ , termName = Just theTerm } <- readIORef $ _configRef input
247+ _ <- hPrintf h "initial (vmin,vtime): %s\n" (show (theVmin, theVtime))
248+ forM_ classifyTable $ \i -> case i of
249+ (inBytes, EvKey k mods) -> hPrintf h "map %s %s %s %s\n" (show theTerm)
250+ (show inBytes)
251+ (show k)
252+ (show mods)
253+ _ -> return ()
254+
255+initInput :: Config -> ClassifyMap -> IO Input
256+initInput config classifyTable = do
257+ let Just fd = inputFd config
258+ setFdOption fd NonBlockingRead False
259+ applyConfig fd config
260+ stopSync <- newEmptyMVar
261+ input <- Input <$> atomically newTChan
262+ <*> pure (return ())
263+ <*> pure (return ())
264+ <*> newIORef config
265+ <*> maybe (return Nothing)
266+ (\f -> Just <$> openFile f AppendMode)
267+ (debugLog config)
268+ logInitialInputState input classifyTable
269+ inputThread <- forkOSFinally (runInputProcessorLoop classifyTable input)
270+ (\_ -> putMVar stopSync ())
271+ let killAndWait = do
272+ killThread inputThread
273+ takeMVar stopSync
274+ return $ input { shutdownInput = killAndWait }
275+
276+foreign import ccall "vty_set_term_timing" setTermTiming :: Fd -> Int -> Int -> IO ()
277+
278+forkOSFinally :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId
279+forkOSFinally action and_then =
280+ mask $ \restore -> forkOS $ try (restore action) >>= and_then
281+
282+(<>=) :: (MonadState s m, Monoid a) => ASetter' s a -> a -> m ()
283+l <>= a = modify (l <>~ a)
284+
285+(<>~) :: Monoid a => ASetter s t a a -> a -> s -> t
286+l <>~ n = over l (`mappend` n)
src/Graphics/Vty/Input/Mouse.hsnew file+163
@@ -0,0 +1,163 @@
1+-- | This module provides parsers for mouse events for both "normal" and
2+-- "extended" modes. This implementation was informed by
3+--
4+-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
5+module Graphics.Vty.Input.Mouse
6+ ( requestMouseEvents
7+ , disableMouseEvents
8+ , isMouseEvent
9+ , classifyMouseEvent
10+ )
11+where
12+
13+import Graphics.Vty.Input.Events
14+import Graphics.Vty.Input.Classify.Types
15+import Graphics.Vty.Input.Classify.Parse
16+
17+import Control.Monad.State
18+import Data.Maybe (catMaybes)
19+import Data.Bits ((.&.))
20+
21+import qualified Data.ByteString.Char8 as BS8
22+import Data.ByteString.Char8 (ByteString)
23+
24+-- A mouse event in SGR extended mode is
25+--
26+-- '\ESC' '[' '<' B ';' X ';' Y ';' ('M'|'m')
27+--
28+-- where
29+--
30+-- * B is the number with button and modifier bits set,
31+-- * X is the X coordinate of the event starting at 1
32+-- * Y is the Y coordinate of the event starting at 1
33+-- * the final character is 'M' for a press, 'm' for a release
34+
35+-- | These sequences set xterm-based terminals to send mouse event
36+-- sequences.
37+requestMouseEvents :: ByteString
38+requestMouseEvents = BS8.pack "\ESC[?1000h\ESC[?1002h\ESC[?1006h"
39+
40+-- | These sequences disable mouse events.
41+disableMouseEvents :: ByteString
42+disableMouseEvents = BS8.pack "\ESC[?1000l\ESC[?1002l\ESC[?1006l"
43+
44+-- | Does the specified string begin with a mouse event?
45+isMouseEvent :: ByteString -> Bool
46+isMouseEvent s = isSGREvent s || isNormalEvent s
47+
48+isSGREvent :: ByteString -> Bool
49+isSGREvent = BS8.isPrefixOf sgrPrefix
50+
51+sgrPrefix :: ByteString
52+sgrPrefix = BS8.pack "\ESC[M"
53+
54+isNormalEvent :: ByteString -> Bool
55+isNormalEvent = BS8.isPrefixOf normalPrefix
56+
57+normalPrefix :: ByteString
58+normalPrefix = BS8.pack "\ESC[<"
59+
60+-- Modifier bits:
61+shiftBit :: Int
62+shiftBit = 4
63+
64+metaBit :: Int
65+metaBit = 8
66+
67+ctrlBit :: Int
68+ctrlBit = 16
69+
70+-- These bits indicate the buttons involved:
71+buttonMask :: Int
72+buttonMask = 67
73+
74+leftButton :: Int
75+leftButton = 0
76+
77+middleButton :: Int
78+middleButton = 1
79+
80+rightButton :: Int
81+rightButton = 2
82+
83+scrollUp :: Int
84+scrollUp = 64
85+
86+scrollDown :: Int
87+scrollDown = 65
88+
89+hasBitSet :: Int -> Int -> Bool
90+hasBitSet val bit = val .&. bit > 0
91+
92+-- | Attempt to classify an input string as a mouse event.
93+classifyMouseEvent :: ByteString -> KClass
94+classifyMouseEvent s = runParser s $ do
95+ when (not $ isMouseEvent s) failParse
96+
97+ expectChar '\ESC'
98+ expectChar '['
99+ ty <- readChar
100+ case ty of
101+ '<' -> classifySGRMouseEvent
102+ 'M' -> classifyNormalMouseEvent
103+ _ -> failParse
104+
105+-- Given a modifier/button value, determine which button was indicated
106+getSGRButton :: Int -> Parser Button
107+getSGRButton mods =
108+ let buttonMap = [ (leftButton, BLeft)
109+ , (middleButton, BMiddle)
110+ , (rightButton, BRight)
111+ , (scrollUp, BScrollUp)
112+ , (scrollDown, BScrollDown)
113+ ]
114+ in case lookup (mods .&. buttonMask) buttonMap of
115+ Nothing -> failParse
116+ Just b -> return b
117+
118+getModifiers :: Int -> [Modifier]
119+getModifiers mods =
120+ catMaybes [ if mods `hasBitSet` shiftBit then Just MShift else Nothing
121+ , if mods `hasBitSet` metaBit then Just MMeta else Nothing
122+ , if mods `hasBitSet` ctrlBit then Just MCtrl else Nothing
123+ ]
124+
125+-- Attempt to classify a control sequence as a "normal" mouse event. To
126+-- get here we should have already read "\ESC[M" so that will not be
127+-- included in the string to be parsed.
128+classifyNormalMouseEvent :: Parser Event
129+classifyNormalMouseEvent = do
130+ statusChar <- readChar
131+ xCoordChar <- readChar
132+ yCoordChar <- readChar
133+
134+ let xCoord = fromEnum xCoordChar - 32
135+ yCoord = fromEnum yCoordChar - 32
136+ status = fromEnum statusChar
137+ modifiers = getModifiers status
138+
139+ let press = status .&. buttonMask /= 3
140+ case press of
141+ True -> do
142+ button <- getSGRButton status
143+ return $ EvMouseDown (xCoord-1) (yCoord-1) button modifiers
144+ False -> return $ EvMouseUp (xCoord-1) (yCoord-1) Nothing
145+
146+-- Attempt to classify a control sequence as an SGR mouse event. To
147+-- get here we should have already read "\ESC[<" so that will not be
148+-- included in the string to be parsed.
149+classifySGRMouseEvent :: Parser Event
150+classifySGRMouseEvent = do
151+ mods <- readInt
152+ expectChar ';'
153+ xCoord <- readInt
154+ expectChar ';'
155+ yCoord <- readInt
156+ final <- readChar
157+
158+ let modifiers = getModifiers mods
159+ button <- getSGRButton mods
160+ case final of
161+ 'M' -> return $ EvMouseDown (xCoord-1) (yCoord-1) button modifiers
162+ 'm' -> return $ EvMouseUp (xCoord-1) (yCoord-1) (Just button)
163+ _ -> failParse
src/Graphics/Vty/Input/Paste.hsnew file+41
@@ -0,0 +1,41 @@
1+-- | This module provides bracketed paste support as described at
2+--
3+-- http://cirw.in/blog/bracketed-paste
4+module Graphics.Vty.Input.Paste
5+ ( parseBracketedPaste
6+ , bracketedPasteStarted
7+ , bracketedPasteFinished
8+ )
9+where
10+
11+import qualified Data.ByteString.Char8 as BS8
12+import Data.ByteString.Char8 (ByteString)
13+
14+import Graphics.Vty.Input.Events
15+import Graphics.Vty.Input.Classify.Types
16+
17+bracketedPasteStart :: ByteString
18+bracketedPasteStart = BS8.pack "\ESC[200~"
19+
20+bracketedPasteEnd :: ByteString
21+bracketedPasteEnd = BS8.pack "\ESC[201~"
22+
23+-- | Does the input start a bracketed paste?
24+bracketedPasteStarted :: ByteString -> Bool
25+bracketedPasteStarted = BS8.isPrefixOf bracketedPasteStart
26+
27+-- | Does the input contain a complete bracketed paste?
28+bracketedPasteFinished :: ByteString -> Bool
29+bracketedPasteFinished = BS8.isInfixOf bracketedPasteEnd
30+
31+-- | Parse a bracketed paste. This should only be called on a string if
32+-- both 'bracketedPasteStarted' and 'bracketedPasteFinished' return
33+-- 'True'.
34+parseBracketedPaste :: ByteString -> KClass
35+parseBracketedPaste s =
36+ Valid (EvPaste p) (BS8.drop endLen rest')
37+ where
38+ startLen = BS8.length bracketedPasteStart
39+ endLen = BS8.length bracketedPasteEnd
40+ (_, rest ) = BS8.breakSubstring bracketedPasteStart s
41+ (p, rest') = BS8.breakSubstring bracketedPasteEnd . BS8.drop startLen $ rest
src/Graphics/Vty/Input/Terminfo.hs+181−5
@@ -1,10 +1,186 @@
1+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
1module Graphics.Vty.Input.Terminfo where
2module Graphics.Vty.Input.Terminfo where
2
3
3−import Graphics.Vty.Input.Data
4+import Graphics.Vty.Input.Events
4−import Graphics.Vty.Input.Internal
5+import qualified Graphics.Vty.Input.Terminfo.ANSIVT as ANSIVT
5
6
7+import Control.Arrow
6import System.Console.Terminfo
8import System.Console.Terminfo
7
9
8−caps_classify_table :: Terminal -> [(String,Event)] -> [(String,Event)]
10+-- | Queries the terminal for all capability-based input sequences and
11+-- then adds on a terminal-dependent input sequence mapping.
12+--
13+-- For reference see:
14+--
15+-- * http://vimdoc.sourceforge.net/htmldoc/term.html
16+--
17+-- * vim74/src/term.c
18+--
19+-- * http://invisible-island.net/vttest/
20+--
21+-- * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
22+--
23+-- Terminfo is incomplete. The vim source implies that terminfo is also
24+-- incorrect. Vty assumes that the internal terminfo table added to the
25+-- system-provided terminfo table is correct.
26+--
27+-- The procedure used here is:
28+--
29+-- 1. Build terminfo table for all caps. Missing caps are not added.
30+--
31+-- 2. Add tables for visible chars, esc, del, ctrl, and meta.
32+--
33+-- 3. Add internally-defined table for given terminal type.
34+--
35+-- Precedence is currently implicit in the 'compile' algorithm.
36+classifyMapForTerm :: String -> Terminal -> ClassifyMap
37+classifyMapForTerm termName term =
38+ concat $ capsClassifyMap term keysFromCapsTable
39+ : universalTable
40+ : termSpecificTables termName
41+
42+-- | The key table applicable to all terminals.
43+--
44+-- Note that some of these entries are probably only applicable to
45+-- ANSI/VT100 terminals.
46+universalTable :: ClassifyMap
47+universalTable = concat [visibleChars, ctrlChars, ctrlMetaChars, specialSupportKeys]
48+
49+capsClassifyMap :: Terminal -> [(String,Event)] -> ClassifyMap
9−caps_classify_table terminal table = [(x,y) | (Just x,y) <- map extract_cap table]
50+capsClassifyMap terminal table = [(x,y) | (Just x,y) <- map extractCap table]
10− where extract_cap = first (getCapability terminal . tiGetStr)
51+ where extractCap = first (getCapability terminal . tiGetStr)
52+
53+-- | Tables specific to a given terminal that are not derivable from
54+-- terminfo.
55+--
56+-- Note that this adds the ANSI/VT100/VT50 tables regardless of term
57+-- identifier.
58+termSpecificTables :: String -> [ClassifyMap]
59+termSpecificTables _termName = ANSIVT.classifyTable
60+
61+-- | Visible characters in the ISO-8859-1 and UTF-8 common set.
62+--
63+-- We limit to < 0xC1. The UTF8 sequence detector will catch all values
64+-- 0xC2 and above before this classify table is reached.
65+visibleChars :: ClassifyMap
66+visibleChars = [ ([x], EvKey (KChar x) [])
67+ | x <- [' ' .. toEnum 0xC1]
68+ ]
69+
70+-- | Non-printable characters in the ISO-8859-1 and UTF-8 common set
71+-- translated to ctrl + char.
72+--
73+-- This treats CTRL-i the same as tab.
74+ctrlChars :: ClassifyMap
75+ctrlChars =
76+ [ ([toEnum x],EvKey (KChar y) [MCtrl])
77+ | (x,y) <- zip [0..31] ('@':['a'..'z']++['['..'_'])
78+ , y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
79+ , y /= 'h' -- CTRL-h should not hide BS
80+ ]
81+
82+-- | Ctrl+Meta+Char
83+ctrlMetaChars :: ClassifyMap
84+ctrlMetaChars = map (\(s,EvKey c m) -> ('\ESC':s, EvKey c (MMeta:m))) ctrlChars
85+
86+-- | Esc, meta-esc, delete, meta-delete, enter, meta-enter.
87+specialSupportKeys :: ClassifyMap
88+specialSupportKeys =
89+ [ ("\ESC\ESC[5~",EvKey KPageUp [MMeta])
90+ , ("\ESC\ESC[6~",EvKey KPageDown [MMeta])
91+ -- special support for ESC
92+ , ("\ESC",EvKey KEsc []), ("\ESC\ESC",EvKey KEsc [MMeta])
93+ -- Special support for backspace
94+ , ("\DEL",EvKey KBS []), ("\ESC\DEL",EvKey KBS [MMeta]), ("\b",EvKey KBS [])
95+ -- Special support for Enter
96+ , ("\ESC\^J",EvKey KEnter [MMeta]), ("\^J",EvKey KEnter [])
97+ -- explicit support for tab
98+ , ("\t", EvKey (KChar '\t') [])
99+ ]
100+
101+-- | A classification table directly generated from terminfo cap
102+-- strings. These are:
103+--
104+-- * ka1 - keypad up-left
105+--
106+-- * ka3 - keypad up-right
107+--
108+-- * kb2 - keypad center
109+--
110+-- * kbs - keypad backspace
111+--
112+-- * kbeg - begin
113+--
114+-- * kcbt - back tab
115+--
116+-- * kc1 - keypad left-down
117+--
118+-- * kc3 - keypad right-down
119+--
120+-- * kdch1 - delete
121+--
122+-- * kcud1 - down
123+--
124+-- * kend - end
125+--
126+-- * kent - enter
127+--
128+-- * kf0 - kf63 - function keys
129+--
130+-- * khome - KHome
131+--
132+-- * kich1 - insert
133+--
134+-- * kcub1 - left
135+--
136+-- * knp - next page (page down)
137+--
138+-- * kpp - previous page (page up)
139+--
140+-- * kcuf1 - right
141+--
142+-- * kDC - shift delete
143+--
144+-- * kEND - shift end
145+--
146+-- * kHOM - shift home
147+--
148+-- * kIC - shift insert
149+--
150+-- * kLFT - shift left
151+--
152+-- * kRIT - shift right
153+--
154+-- * kcuu1 - up
155+keysFromCapsTable :: ClassifyMap
156+keysFromCapsTable =
157+ [ ("ka1", EvKey KUpLeft [])
158+ , ("ka3", EvKey KUpRight [])
159+ , ("kb2", EvKey KCenter [])
160+ , ("kbs", EvKey KBS [])
161+ , ("kbeg", EvKey KBegin [])
162+ , ("kcbt", EvKey KBackTab [])
163+ , ("kc1", EvKey KDownLeft [])
164+ , ("kc3", EvKey KDownRight [])
165+ , ("kdch1", EvKey KDel [])
166+ , ("kcud1", EvKey KDown [])
167+ , ("kend", EvKey KEnd [])
168+ , ("kent", EvKey KEnter [])
169+ , ("khome", EvKey KHome [])
170+ , ("kich1", EvKey KIns [])
171+ , ("kcub1", EvKey KLeft [])
172+ , ("knp", EvKey KPageDown [])
173+ , ("kpp", EvKey KPageUp [])
174+ , ("kcuf1", EvKey KRight [])
175+ , ("kDC", EvKey KDel [MShift])
176+ , ("kEND", EvKey KEnd [MShift])
177+ , ("kHOM", EvKey KHome [MShift])
178+ , ("kIC", EvKey KIns [MShift])
179+ , ("kLFT", EvKey KLeft [MShift])
180+ , ("kRIT", EvKey KRight [MShift])
181+ , ("kcuu1", EvKey KUp [])
182+ ] ++ functionKeyCapsTable
183+
184+-- | Cap names for function keys.
185+functionKeyCapsTable :: ClassifyMap
186+functionKeyCapsTable = flip map [0..63] $ \n -> ("kf" ++ show n, EvKey (KFun n) [])
src/Graphics/Vty/Input/Terminfo/ANSIVT.hsnew file+88
@@ -0,0 +1,88 @@
1+-- | Input mappings for ANSI/VT100/VT50 terminals that is missing from
2+-- terminfo.
3+--
4+-- Or that are sent regardless of terminfo by terminal emulators. EG:
5+-- Terminal emulators will often use VT50 input bytes regardless of
6+-- declared terminal type. This provides compatibility with programs
7+-- that don't follow terminfo.
8+module Graphics.Vty.Input.Terminfo.ANSIVT where
9+
10+import Graphics.Vty.Input.Events
11+
12+-- | Encoding for navigation keys.
13+navKeys0 :: ClassifyMap
14+navKeys0 =
15+ [ k "G" KCenter
16+ , k "P" KPause
17+ , k "A" KUp
18+ , k "B" KDown
19+ , k "C" KRight
20+ , k "D" KLeft
21+ , k "H" KHome
22+ , k "F" KEnd
23+ , k "E" KBegin
24+ ]
25+ where k c s = ("\ESC["++c,EvKey s [])
26+
27+-- | encoding for shift, meta and ctrl plus arrows/home/end
28+navKeys1 :: ClassifyMap
29+navKeys1 =
30+ [("\ESC[" ++ charCnt ++ show mc++c,EvKey s m)
31+ | charCnt <- ["1;", ""], -- we can have a count or not
32+ (m,mc) <- [([MShift],2::Int), ([MCtrl],5), ([MMeta],3),
33+ -- modifiers and their codes
34+ ([MShift, MCtrl],6), ([MShift, MMeta],4)],
35+ -- directions and their codes
36+ (c,s) <- [("A", KUp), ("B", KDown), ("C", KRight), ("D", KLeft), ("H", KHome), ("F", KEnd)]
37+ ]
38+
39+-- | encoding for ins, del, pageup, pagedown, home, end
40+navKeys2 :: ClassifyMap
41+navKeys2 =
42+ let k n s = ("\ESC["++show n++"~",EvKey s [])
43+ in zipWith k [2::Int,3,5,6,1,4]
44+ [KIns,KDel,KPageUp,KPageDown,KHome,KEnd]
45+
46+-- | encoding for ctrl + ins, del, pageup, pagedown, home, end
47+navKeys3 :: ClassifyMap
48+navKeys3 =
49+ let k n s = ("\ESC["++show n++";5~",EvKey s [MCtrl])
50+ in zipWith k [2::Int,3,5,6,1,4]
51+ [KIns,KDel,KPageUp,KPageDown,KHome,KEnd]
52+
53+-- | encoding for shift plus function keys
54+--
55+-- According to
56+--
57+-- * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
58+--
59+-- This encoding depends on the terminal.
60+functionKeys1 :: ClassifyMap
61+functionKeys1 =
62+ let f ff nrs m = [ ("\ESC["++show n++"~",EvKey (KFun $ n-head nrs+ff) m) | n <- nrs ] in
63+ concat [f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ]
64+
65+-- | encoding for meta plus char
66+--
67+-- 1. removed 'ESC' from second list due to duplication with
68+-- "special_support_keys".
69+-- 2. removed '[' from second list due to conflict with 7-bit encoding
70+-- for ESC. Whether meta+[ is the same as ESC should examine km and
71+-- current encoding.
72+-- 3. stopped enumeration at '~' instead of '\DEL'. The latter is mapped
73+-- to KBS by special_support_keys.
74+functionKeys2 :: ClassifyMap
75+functionKeys2 = [ ('\ESC':[x],EvKey (KChar x) [MMeta])
76+ | x <- '\t':[' ' .. '~']
77+ , x /= '['
78+ ]
79+
80+classifyTable :: [ClassifyMap]
81+classifyTable =
82+ [ navKeys0
83+ , navKeys1
84+ , navKeys2
85+ , navKeys3
86+ , functionKeys1
87+ , functionKeys2
88+ ]
src/Graphics/Vty/Output.hsnew file+107
@@ -0,0 +1,107 @@
1+{-# LANGUAGE RecordWildCards, CPP #-}
2+-- | This module provides functions for accessing the current terminal
3+-- or a specific terminal device.
4+--
5+-- See also:
6+--
7+-- 1. "Graphics.Vty.Output": This instantiates an abtract interface
8+-- to the terminal based on the @TERM@ and @COLORTERM@ environment
9+-- variables.
10+--
11+-- 2. "Graphics.Vty.Output.Interface": Defines the generic interface all
12+-- terminal modules need to implement.
13+--
14+-- 3. "Graphics.Vty.Output.TerminfoBased": Defines a terminal instance
15+-- that uses terminfo for all control strings. No attempt is made to
16+-- change the character set to UTF-8 for these terminals.
17+--
18+-- 4. "Graphics.Vty.Output.XTermColor": This module contains an
19+-- interface suitable for xterm-like terminals. These are the terminals
20+-- where @TERM@ begins with @xterm@. This does use terminfo for as many
21+-- control codes as possible.
22+module Graphics.Vty.Output
23+ ( outputForConfig
24+ , setCursorPos
25+ , hideCursor
26+ , showCursor
27+ )
28+where
29+
30+import Control.Monad (when)
31+
32+import Graphics.Vty.Config
33+import Graphics.Vty.Image (regionWidth, regionHeight)
34+import Graphics.Vty.Output.Interface
35+import Graphics.Vty.Output.XTermColor as XTermColor
36+import Graphics.Vty.Output.TerminfoBased as TerminfoBased
37+
38+import Blaze.ByteString.Builder (writeToByteString)
39+
40+import Data.List (isPrefixOf)
41+
42+#if !MIN_VERSION_base(4,11,0)
43+import Data.Monoid ((<>))
44+#endif
45+
46+-- | Returns an `Output` for the terminal specified in `Config`.
47+--
48+-- The specific Output implementation used is hidden from the API user.
49+-- All terminal implementations are assumed to perform more, or less,
50+-- the same. Currently, all implementations use terminfo for at least
51+-- some terminal specific information.
52+--
53+-- If a terminal implementation is developed for a terminal without
54+-- terminfo support then Vty should work as expected on that terminal.
55+--
56+-- Selection of a terminal is done as follows:
57+--
58+-- * If TERM contains "xterm" or "screen", use XTermColor.
59+-- * otherwise use the TerminfoBased driver.
60+outputForConfig :: Config -> IO Output
61+outputForConfig Config{ outputFd = Just fd, termName = Just termName
62+ , colorMode = Just colorMode, .. } = do
63+ t <- if "xterm" `isPrefixOf` termName || "screen" `isPrefixOf` termName
64+ then XTermColor.reserveTerminal termName fd colorMode
65+ -- Not an xterm-like terminal. try for generic terminfo.
66+ else TerminfoBased.reserveTerminal termName fd colorMode
67+
68+ case mouseMode of
69+ Just s -> setMode t Mouse s
70+ Nothing -> return ()
71+
72+ case bracketedPasteMode of
73+ Just s -> setMode t BracketedPaste s
74+ Nothing -> return ()
75+
76+ return t
77+outputForConfig config = (<> config) <$> standardIOConfig >>= outputForConfig
78+
79+-- | Sets the cursor position to the given output column and row.
80+--
81+-- This is not necessarily the same as the character position with the
82+-- same coordinates. Characters can be a variable number of columns in
83+-- width.
84+--
85+-- Currently, the only way to set the cursor position to a given
86+-- character coordinate is to specify the coordinate in the Picture
87+-- instance provided to 'outputPicture' or 'refresh'.
88+setCursorPos :: Output -> Int -> Int -> IO ()
89+setCursorPos t x y = do
90+ bounds <- displayBounds t
91+ when (x >= 0 && x < regionWidth bounds && y >= 0 && y < regionHeight bounds) $ do
92+ dc <- displayContext t bounds
93+ outputByteBuffer t $ writeToByteString $ writeMoveCursor dc x y
94+
95+-- | Hides the cursor.
96+hideCursor :: Output -> IO ()
97+hideCursor t = do
98+ bounds <- displayBounds t
99+ dc <- displayContext t bounds
100+ outputByteBuffer t $ writeToByteString $ writeHideCursor dc
101+
102+-- | Shows the cursor.
103+showCursor :: Output -> IO ()
104+showCursor t = do
105+ bounds <- displayBounds t
106+ dc <- displayContext t bounds
107+ outputByteBuffer t $ writeToByteString $ writeShowCursor dc
src/Graphics/Vty/Output/Interface.hsnew file+331
@@ -0,0 +1,331 @@
1+-- Copyright Corey O'Connor
2+{-# LANGUAGE RankNTypes #-}
3+{-# LANGUAGE ExistentialQuantification #-}
4+{-# LANGUAGE GADTs #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+-- | This module provides an abstract interface for performing terminal
8+-- output. The only user-facing part of this API is 'Output'.
9+module Graphics.Vty.Output.Interface
10+ ( Output(..)
11+ , AssumedState(..)
12+ , DisplayContext(..)
13+ , Mode(..)
14+ , displayContext
15+ , outputPicture
16+ , initialAssumedState
17+ , limitAttrForDisplay
18+ )
19+where
20+
21+import Graphics.Vty.Attributes
22+import Graphics.Vty.Image (DisplayRegion, regionHeight)
23+import Graphics.Vty.Picture
24+import Graphics.Vty.PictureToSpans
25+import Graphics.Vty.Span
26+
27+import Graphics.Vty.DisplayAttributes
28+
29+import Blaze.ByteString.Builder (Write, writeToByteString)
30+import Blaze.ByteString.Builder.ByteString (writeByteString)
31+
32+import qualified Data.ByteString as BS
33+import Data.IORef
34+import qualified Data.Text.Encoding as T
35+import qualified Data.Text.Lazy as TL
36+import qualified Data.Vector as Vector
37+
38+-- | Modal terminal features that can be enabled and disabled.
39+data Mode = Mouse
40+ -- ^ Mouse mode (whether the terminal is configured to provide
41+ -- mouse input events)
42+ | BracketedPaste
43+ -- ^ Paste mode (whether the terminal is configured to provide
44+ -- events on OS pastes)
45+ | Focus
46+ -- ^ Focus-in/focus-out events (whether the terminal is
47+ -- configured to provide events on focus change)
48+ | Hyperlink
49+ -- ^ Hyperlink mode via the 'withURL' attribute modifier (see
50+ -- https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
51+ -- Note that this may not work gracefully in all terminal
52+ -- emulators so be sure to test this mode with the terminals
53+ -- you intend to support. It is off by default.
54+ deriving (Eq, Read, Show)
55+
56+-- | The Vty terminal output interface.
57+data Output = Output
58+ { -- | Text identifier for the output device. Used for debugging.
59+ terminalID :: String
60+ -- | Release the terminal just prior to application exit and reset
61+ -- it to its state prior to application startup.
62+ , releaseTerminal :: IO ()
63+ -- | Clear the display and initialize the terminal to some initial
64+ -- display state.
65+ --
66+ -- The expectation of a program is that the display starts in some
67+ -- The initial state. initial state would consist of fixed values:
68+ --
69+ -- - cursor at top left
70+ -- - UTF-8 character encoding
71+ -- - drawing characteristics are the default
72+ , reserveDisplay :: IO ()
73+ -- | Return the display to the state before `reserveDisplay` If no
74+ -- previous state then set the display state to the initial state.
75+ , releaseDisplay :: IO ()
76+ -- | Sets the current display bounds (width, height).
77+ , setDisplayBounds :: (Int, Int) -> IO ()
78+ -- | Returns the current display bounds.
79+ , displayBounds :: IO DisplayRegion
80+ -- | Output the bytestring to the terminal device.
81+ , outputByteBuffer :: BS.ByteString -> IO ()
82+ -- | Specifies whether the cursor can be shown / hidden.
83+ , supportsCursorVisibility :: Bool
84+ -- | Indicates support for terminal modes for this output device.
85+ , supportsMode :: Mode -> Bool
86+ -- | Enables or disables a mode (does nothing if the mode is
87+ -- unsupported).
88+ , setMode :: Mode -> Bool -> IO ()
89+ -- | Returns whether a mode is enabled.
90+ , getModeStatus :: Mode -> IO Bool
91+ , assumedStateRef :: IORef AssumedState
92+ -- | Acquire display access to the given region of the display.
93+ -- Currently all regions have the upper left corner of (0,0) and
94+ -- the lower right corner at (max displayWidth providedWidth, max
95+ -- displayHeight providedHeight)
96+ , mkDisplayContext :: Output -> DisplayRegion -> IO DisplayContext
97+ -- | Ring the terminal bell if supported.
98+ , ringTerminalBell :: IO ()
99+ -- | Returns whether the terminal has an audio bell feature.
100+ , supportsBell :: IO Bool
101+ -- | Returns whether the terminal supports italicized text.
102+ --
103+ -- This is terminal-dependent and should make a best effort to
104+ -- determine whether this feature is supported, but even if the
105+ -- terminal advertises support (e.g. via terminfo) that might not
106+ -- be a reliable indicator of whether the feature will work as
107+ -- desired.
108+ , supportsItalics :: IO Bool
109+ -- | Returns whether the terminal supports strikethrough text.
110+ --
111+ -- This is terminal-dependent and should make a best effort to
112+ -- determine whether this feature is supported, but even if the
113+ -- terminal advertises support (e.g. via terminfo) that might not
114+ -- be a reliable indicator of whether the feature will work as
115+ -- desired.
116+ , supportsStrikethrough :: IO Bool
117+ -- | Returns how many colors the terminal supports.
118+ , outputColorMode :: ColorMode
119+ }
120+
121+displayContext :: Output -> DisplayRegion -> IO DisplayContext
122+displayContext t = mkDisplayContext t t
123+
124+data AssumedState = AssumedState
125+ { prevFattr :: Maybe FixedAttr
126+ , prevOutputOps :: Maybe DisplayOps
127+ }
128+
129+initialAssumedState :: AssumedState
130+initialAssumedState = AssumedState Nothing Nothing
131+
132+data DisplayContext = DisplayContext
133+ { contextDevice :: Output
134+ -- | Provide the bounds of the display context.
135+ , contextRegion :: DisplayRegion
136+ -- | Sets the output position to the specified row and column
137+ -- where the number of bytes required for the control codes can be
138+ -- specified seperate from the actual byte sequence.
139+ , writeMoveCursor :: Int -> Int -> Write
140+ , writeShowCursor :: Write
141+ , writeHideCursor :: Write
142+ -- Ensure that the specified output attributes will be applied to
143+ -- all the following text until the next output attribute change
144+ -- where the number of bytes required for the control codes can be
145+ -- specified seperately from the actual byte sequence. The required
146+ -- number of bytes must be at least the maximum number of bytes
147+ -- required by any attribute changes. The serialization equations
148+ -- must provide the ptr to the next byte to be specified in the
149+ -- output buffer.
150+ --
151+ -- The currently applied display attributes are provided as well.
152+ -- The Attr data type can specify the style or color should not be
153+ -- changed from the currently applied display attributes. In order
154+ -- to support this the currently applied display attributes are
155+ -- required. In addition it may be possible to optimize the state
156+ -- changes based off the currently applied display attributes.
157+ , writeSetAttr :: Bool -> FixedAttr -> Attr -> DisplayAttrDiff -> Write
158+ -- | Reset the display attributes to the default display attributes.
159+ , writeDefaultAttr :: Bool -> Write
160+ , writeRowEnd :: Write
161+ -- | See `Graphics.Vty.Output.XTermColor.inlineHack`
162+ , inlineHack :: IO ()
163+ }
164+
165+-- | All terminals serialize UTF8 text to the terminal device exactly as
166+-- serialized in memory.
167+writeUtf8Text :: BS.ByteString -> Write
168+writeUtf8Text = writeByteString
169+
170+-- | Displays the given `Picture`.
171+--
172+-- 1. The image is cropped to the display size.
173+--
174+-- 2. Converted into a sequence of attribute changes and text spans.
175+--
176+-- 3. The cursor is hidden.
177+--
178+-- 4. Serialized to the display.
179+--
180+-- 5. The cursor is then shown and positioned or kept hidden.
181+outputPicture :: DisplayContext -> Picture -> IO ()
182+outputPicture dc pic = do
183+ urlsEnabled <- getModeStatus (contextDevice dc) Hyperlink
184+ as <- readIORef (assumedStateRef $ contextDevice dc)
185+ let manipCursor = supportsCursorVisibility (contextDevice dc)
186+ r = contextRegion dc
187+ ops = displayOpsForPic pic r
188+ initialAttr = FixedAttr defaultStyleMask Nothing Nothing Nothing
189+ -- Diff the previous output against the requested output.
190+ -- Differences are currently on a per-row basis.
191+ diffs :: [Bool] = case prevOutputOps as of
192+ Nothing -> replicate (fromEnum $ regionHeight $ affectedRegion ops) True
193+ Just previousOps -> if affectedRegion previousOps /= affectedRegion ops
194+ then replicate (displayOpsRows ops) True
195+ else Vector.toList $ Vector.zipWith (/=) previousOps ops
196+ -- build the Write corresponding to the output image
197+ out = (if manipCursor then writeHideCursor dc else mempty)
198+ `mappend` writeOutputOps urlsEnabled dc initialAttr diffs ops
199+ `mappend`
200+ (let (w,h) = contextRegion dc
201+ clampX = max 0 . min (w-1)
202+ clampY = max 0 . min (h-1) in
203+ case picCursor pic of
204+ _ | not manipCursor -> mempty
205+ NoCursor -> mempty
206+ AbsoluteCursor x y ->
207+ writeShowCursor dc `mappend`
208+ writeMoveCursor dc (clampX x) (clampY y)
209+ PositionOnly isAbs x y ->
210+ if isAbs
211+ then writeMoveCursor dc (clampX x) (clampY y)
212+ else let (ox, oy) = charToOutputPos m (clampX x, clampY y)
213+ m = cursorOutputMap ops $ picCursor pic
214+ in writeMoveCursor dc (clampX ox) (clampY oy)
215+ Cursor x y ->
216+ let m = cursorOutputMap ops $ picCursor pic
217+ (ox, oy) = charToOutputPos m (clampX x, clampY y)
218+ in writeShowCursor dc `mappend`
219+ writeMoveCursor dc (clampX ox) (clampY oy)
220+ )
221+ -- ... then serialize
222+ outputByteBuffer (contextDevice dc) (writeToByteString out)
223+ -- Cache the output spans.
224+ let as' = as { prevOutputOps = Just ops }
225+ writeIORef (assumedStateRef $ contextDevice dc) as'
226+
227+writeOutputOps :: Bool -> DisplayContext -> FixedAttr -> [Bool] -> DisplayOps -> Write
228+writeOutputOps urlsEnabled dc initialAttr diffs ops =
229+ let (_, out, _) = Vector.foldl' writeOutputOps'
230+ (0, mempty, diffs)
231+ ops
232+ in out
233+ where
234+ writeOutputOps' (y, out, True : diffs') spanOps
235+ = let spanOut = writeSpanOps urlsEnabled dc y initialAttr spanOps
236+ out' = out `mappend` spanOut
237+ in (y+1, out', diffs')
238+ writeOutputOps' (y, out, False : diffs') _spanOps
239+ = (y + 1, out, diffs')
240+ writeOutputOps' (_y, _out, []) _spanOps
241+ = error "vty - output spans without a corresponding diff."
242+
243+writeSpanOps :: Bool -> DisplayContext -> Int -> FixedAttr -> SpanOps -> Write
244+writeSpanOps urlsEnabled dc y initialAttr spanOps =
245+ -- The first operation is to set the cursor to the start of the row
246+ let start = writeMoveCursor dc 0 y `mappend` writeDefaultAttr dc urlsEnabled
247+ -- then the span ops are serialized in the order specified
248+ in fst $ Vector.foldl' (\(out, fattr) op -> case writeSpanOp urlsEnabled dc op fattr of
249+ (opOut, fattr') -> (out `mappend` opOut, fattr')
250+ )
251+ (start, initialAttr)
252+ spanOps
253+
254+writeSpanOp :: Bool -> DisplayContext -> SpanOp -> FixedAttr -> (Write, FixedAttr)
255+writeSpanOp urlsEnabled dc (TextSpan attr _ _ str) fattr =
256+ let attr' = limitAttrForDisplay (contextDevice dc) attr
257+ fattr' = fixDisplayAttr fattr attr'
258+ diffs = displayAttrDiffs fattr fattr'
259+ out = writeSetAttr dc urlsEnabled fattr attr' diffs
260+ `mappend` writeUtf8Text (T.encodeUtf8 $ TL.toStrict str)
261+ in (out, fattr')
262+writeSpanOp _ _ (Skip _) _fattr = error "writeSpanOp for Skip"
263+writeSpanOp urlsEnabled dc (RowEnd _) fattr = (writeDefaultAttr dc urlsEnabled `mappend` writeRowEnd dc, fattr)
264+
265+-- | The cursor position is given in X,Y character offsets. Due to
266+-- multi-column characters this needs to be translated to column, row
267+-- positions.
268+data CursorOutputMap = CursorOutputMap
269+ { charToOutputPos :: (Int, Int) -> (Int, Int)
270+ }
271+
272+cursorOutputMap :: DisplayOps -> Cursor -> CursorOutputMap
273+cursorOutputMap spanOps _cursor = CursorOutputMap
274+ { charToOutputPos = \(cx, cy) -> (cursorColumnOffset spanOps cx cy, cy)
275+ }
276+
277+cursorColumnOffset :: DisplayOps -> Int -> Int -> Int
278+cursorColumnOffset ops cx cy =
279+ let cursorRowOps = Vector.unsafeIndex ops (fromEnum cy)
280+ (outOffset, _, _)
281+ = Vector.foldl' ( \(d, currentCx, done) op ->
282+ if done then (d, currentCx, done) else case spanOpHasWidth op of
283+ Nothing -> (d, currentCx, False)
284+ Just (cw, ow) -> case compare cx (currentCx + cw) of
285+ GT -> ( d + ow
286+ , currentCx + cw
287+ , False
288+ )
289+ EQ -> ( d + ow
290+ , currentCx + cw
291+ , True
292+ )
293+ LT -> ( d + columnsToCharOffset (cx - currentCx) op
294+ , currentCx + cw
295+ , True
296+ )
297+ )
298+ (0, 0, False)
299+ cursorRowOps
300+ in outOffset
301+
302+-- | Not all terminals support all display attributes. This filters a
303+-- display attribute to what the given terminal can display.
304+limitAttrForDisplay :: Output -> Attr -> Attr
305+limitAttrForDisplay t attr
306+ = attr { attrForeColor = clampColor $ attrForeColor attr
307+ , attrBackColor = clampColor $ attrBackColor attr
308+ }
309+ where
310+ clampColor Default = Default
311+ clampColor KeepCurrent = KeepCurrent
312+ clampColor (SetTo c) = clampColor' (outputColorMode t) c
313+
314+ clampColor' NoColor _ = Default
315+
316+ clampColor' ColorMode8 (ISOColor v)
317+ | v >= 8 = SetTo $ ISOColor (v - 8)
318+ | otherwise = SetTo $ ISOColor v
319+ clampColor' ColorMode8 _ = Default
320+
321+ clampColor' ColorMode16 c@(ISOColor _) = SetTo c
322+ clampColor' ColorMode16 _ = Default
323+
324+ clampColor' (ColorMode240 _) c@(ISOColor _) = SetTo c
325+ clampColor' (ColorMode240 colorCount) c@(Color240 n)
326+ | n <= colorCount = SetTo c
327+ | otherwise = Default
328+ clampColor' colorMode@(ColorMode240 _) (RGBColor r g b) =
329+ clampColor' colorMode (color240 r g b)
330+
331+ clampColor' FullColor c = SetTo c
src/Graphics/Vty/Output/Mock.hsnew file+90
@@ -0,0 +1,90 @@
1+-- Copyright Corey O'Connor
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE TypeFamilies #-}
4+-- | This provides a mock terminal implementation that is nice for
5+-- testing. This transforms the output operations to visible characters
6+-- which is useful for testing.
7+module Graphics.Vty.Output.Mock
8+ ( MockData
9+ , mockTerminal
10+ )
11+where
12+
13+import Graphics.Vty.Image (DisplayRegion)
14+import Graphics.Vty.Attributes.Color (ColorMode(ColorMode16))
15+import Graphics.Vty.Output.Interface
16+
17+import Blaze.ByteString.Builder.Word (writeWord8)
18+
19+import Control.Monad.Trans
20+
21+import qualified Data.ByteString as BS
22+import Data.IORef
23+import qualified Data.String.UTF8 as UTF8
24+
25+type MockData = IORef (UTF8.UTF8 BS.ByteString)
26+
27+-- | The mock display terminal produces a string representation of
28+-- the requested picture. There is *not* an isomorphism between the
29+-- string representation and the picture. The string representation is
30+-- a simplification of the picture that is only useful in debugging VTY
31+-- without considering terminal specific issues.
32+--
33+-- The mock implementation is useful in manually determining if the
34+-- sequence of terminal operations matches the expected sequence. The
35+-- requirement of the produced representation is simplicity in parsing
36+-- the text representation and determining how the picture was mapped to
37+-- terminal operations.
38+--
39+-- The string representation is a sequence of identifiers where each
40+-- identifier is the name of an operation in the algebra.
41+mockTerminal :: (Applicative m, MonadIO m) => DisplayRegion -> m (MockData, Output)
42+mockTerminal r = liftIO $ do
43+ outRef <- newIORef undefined
44+ newAssumedStateRef <- newIORef initialAssumedState
45+ let t = Output
46+ { terminalID = "mock terminal"
47+ , releaseTerminal = return ()
48+ , reserveDisplay = return ()
49+ , releaseDisplay = return ()
50+ , ringTerminalBell = return ()
51+ , supportsBell = return False
52+ , supportsItalics = return False
53+ , supportsStrikethrough = return False
54+ , setDisplayBounds = const $ return ()
55+ , displayBounds = return r
56+ , outputByteBuffer = \bytes -> do
57+ putStrLn $ "mock outputByteBuffer of " ++ show (BS.length bytes) ++ " bytes"
58+ writeIORef outRef $ UTF8.fromRep bytes
59+ , supportsCursorVisibility = True
60+ , supportsMode = const False
61+ , setMode = const $ const $ return ()
62+ , getModeStatus = const $ return False
63+ , assumedStateRef = newAssumedStateRef
64+ , outputColorMode = ColorMode16
65+ , mkDisplayContext = \tActual rActual -> return $ DisplayContext
66+ { contextRegion = rActual
67+ , contextDevice = tActual
68+ -- A cursor move is always visualized as the single
69+ -- character 'M'
70+ , writeMoveCursor = \_x _y -> writeWord8 $ toEnum $ fromEnum 'M'
71+ -- Show cursor is always visualized as the single
72+ -- character 'S'
73+ , writeShowCursor = writeWord8 $ toEnum $ fromEnum 'S'
74+ -- Hide cursor is always visualized as the single
75+ -- character 'H'
76+ , writeHideCursor = writeWord8 $ toEnum $ fromEnum 'H'
77+ -- An attr change is always visualized as the single
78+ -- character 'A'
79+ , writeSetAttr = \_ _fattr _diffs _attr -> writeWord8 $ toEnum $ fromEnum 'A'
80+ -- default attr is always visualized as the single
81+ -- character 'D'
82+ , writeDefaultAttr = const $ writeWord8 $ toEnum $ fromEnum 'D'
83+ -- row end is always visualized as the single character
84+ -- 'E'
85+ , writeRowEnd = writeWord8 $ toEnum $ fromEnum 'E'
86+ , inlineHack = return ()
87+ }
88+ }
89+ return (outRef, t)
90+
src/Graphics/Vty/Output/TerminfoBased.hsnew file+606
@@ -0,0 +1,606 @@
1+{-# LANGUAGE FlexibleInstances #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE CPP #-}
4+{-# OPTIONS_GHC -D_XOPEN_SOURCE=500 -fno-warn-warnings-deprecations #-}
5+{-# CFILES gwinsz.c #-}
6+
7+-- | Terminfo-based terminal output driver.
8+--
9+-- Copyright Corey O'Connor (coreyoconnor@gmail.com)
10+module Graphics.Vty.Output.TerminfoBased
11+ ( reserveTerminal
12+ , setWindowSize
13+ )
14+where
15+
16+import Control.Monad (when)
17+import Data.Bits (shiftL, (.&.))
18+import qualified Data.ByteString as BS
19+import Data.ByteString.Internal (toForeignPtr)
20+import Data.Terminfo.Parse
21+import Data.Terminfo.Eval
22+
23+import Graphics.Vty.Attributes
24+import Graphics.Vty.Image (DisplayRegion)
25+import Graphics.Vty.DisplayAttributes
26+import Graphics.Vty.Output.Interface
27+
28+import Blaze.ByteString.Builder (Write, writeToByteString, writeStorable, writeWord8)
29+
30+import Data.IORef
31+import Data.Maybe (isJust, isNothing, fromJust)
32+import Data.Word
33+
34+#if !MIN_VERSION_base(4,8,0)
35+import Data.Foldable (foldMap)
36+#endif
37+
38+import Foreign.C.Types ( CInt(..), CLong(..) )
39+import Foreign.ForeignPtr (withForeignPtr)
40+import Foreign.Ptr (Ptr, plusPtr)
41+
42+import qualified System.Console.Terminfo as Terminfo
43+import System.Posix.IO (fdWriteBuf)
44+import System.Posix.Types (Fd(..))
45+
46+data TerminfoCaps = TerminfoCaps
47+ { smcup :: Maybe CapExpression
48+ , rmcup :: Maybe CapExpression
49+ , cup :: CapExpression
50+ , cnorm :: Maybe CapExpression
51+ , civis :: Maybe CapExpression
52+ , useAltColorMap :: Bool
53+ , setForeColor :: CapExpression
54+ , setBackColor :: CapExpression
55+ , setDefaultAttr :: CapExpression
56+ , clearScreen :: CapExpression
57+ , clearEol :: CapExpression
58+ , displayAttrCaps :: DisplayAttrCaps
59+ , ringBellAudio :: Maybe CapExpression
60+ }
61+
62+data DisplayAttrCaps = DisplayAttrCaps
63+ { setAttrStates :: Maybe CapExpression
64+ , enterStandout :: Maybe CapExpression
65+ , exitStandout :: Maybe CapExpression
66+ , enterItalic :: Maybe CapExpression
67+ , exitItalic :: Maybe CapExpression
68+ , enterStrikethrough :: Maybe CapExpression
69+ , exitStrikethrough :: Maybe CapExpression
70+ , enterUnderline :: Maybe CapExpression
71+ , exitUnderline :: Maybe CapExpression
72+ , enterReverseVideo :: Maybe CapExpression
73+ , enterDimMode :: Maybe CapExpression
74+ , enterBoldMode :: Maybe CapExpression
75+ }
76+
77+-- kinda like:
78+-- https://code.google.com/p/vim/source/browse/src/fileio.c#10422
79+-- fdWriteBuf will throw on error. Unless the error is EINTR. On EINTR
80+-- the write will be retried.
81+fdWriteAll :: Fd -> Ptr Word8 -> Int -> Int -> IO Int
82+fdWriteAll outFd ptr len count
83+ | len < 0 = fail "fdWriteAll: len is less than 0"
84+ | len == 0 = return count
85+ | otherwise = do
86+ writeCount <- fromEnum <$> fdWriteBuf outFd ptr (toEnum len)
87+ let len' = len - writeCount
88+ ptr' = ptr `plusPtr` writeCount
89+ count' = count + writeCount
90+ fdWriteAll outFd ptr' len' count'
91+
92+sendCapToTerminal :: Output -> CapExpression -> [CapParam] -> IO ()
93+sendCapToTerminal t cap capParams = do
94+ outputByteBuffer t $ writeToByteString $ writeCapExpr cap capParams
95+
96+-- | Constructs an output driver that uses terminfo for all control
97+-- codes. While this should provide the most compatible terminal,
98+-- terminfo does not support some features that would increase
99+-- efficiency and improve compatibility:
100+--
101+-- * determining the character encoding supported by the terminal.
102+-- Should this be taken from the LANG environment variable?
103+--
104+-- * Providing independent string capabilities for all display
105+-- attributes.
106+reserveTerminal :: String -> Fd -> ColorMode -> IO Output
107+reserveTerminal termName outFd colorMode = do
108+ ti <- Terminfo.setupTerm termName
109+ -- assumes set foreground always implies set background exists.
110+ -- if set foreground is not set then all color changing style
111+ -- attributes are filtered.
112+ msetaf <- probeCap ti "setaf"
113+ msetf <- probeCap ti "setf"
114+ let (useAlt, setForeCap)
115+ = case msetaf of
116+ Just setaf -> (False, setaf)
117+ Nothing -> case msetf of
118+ Just setf -> (True, setf)
119+ Nothing -> (True, error $ "no fore color support for terminal " ++ termName)
120+ msetab <- probeCap ti "setab"
121+ msetb <- probeCap ti "setb"
122+ let setBackCap
123+ = case msetab of
124+ Just setab -> setab
125+ Nothing -> case msetb of
126+ Just setb -> setb
127+ Nothing -> error $ "no back color support for terminal " ++ termName
128+
129+ hyperlinkModeStatus <- newIORef False
130+ newAssumedStateRef <- newIORef initialAssumedState
131+
132+ let terminfoSetMode m newStatus = do
133+ curStatus <- terminfoModeStatus m
134+ when (newStatus /= curStatus) $
135+ case m of
136+ Hyperlink -> do
137+ writeIORef hyperlinkModeStatus newStatus
138+ writeIORef newAssumedStateRef initialAssumedState
139+ _ -> return ()
140+ terminfoModeStatus m =
141+ case m of
142+ Hyperlink -> readIORef hyperlinkModeStatus
143+ _ -> return False
144+ terminfoModeSupported Hyperlink = True
145+ terminfoModeSupported _ = False
146+
147+ terminfoCaps <- pure TerminfoCaps
148+ <*> probeCap ti "smcup"
149+ <*> probeCap ti "rmcup"
150+ <*> requireCap ti "cup"
151+ <*> probeCap ti "cnorm"
152+ <*> probeCap ti "civis"
153+ <*> pure useAlt
154+ <*> pure setForeCap
155+ <*> pure setBackCap
156+ <*> requireCap ti "sgr0"
157+ <*> requireCap ti "clear"
158+ <*> requireCap ti "el"
159+ <*> currentDisplayAttrCaps ti
160+ <*> probeCap ti "bel"
161+ let t = Output
162+ { terminalID = termName
163+ , releaseTerminal = do
164+ sendCap setDefaultAttr []
165+ maybeSendCap cnorm []
166+ , supportsBell = return $ isJust $ ringBellAudio terminfoCaps
167+ , supportsItalics = return $ (isJust $ enterItalic (displayAttrCaps terminfoCaps)) &&
168+ (isJust $ exitItalic (displayAttrCaps terminfoCaps))
169+ , supportsStrikethrough = return $ (isJust $ enterStrikethrough (displayAttrCaps terminfoCaps)) &&
170+ (isJust $ exitStrikethrough (displayAttrCaps terminfoCaps))
171+ , ringTerminalBell = maybeSendCap ringBellAudio []
172+ , reserveDisplay = do
173+ -- If there is no support for smcup: Clear the screen
174+ -- and then move the mouse to the home position to
175+ -- approximate the behavior.
176+ maybeSendCap smcup []
177+ sendCap clearScreen []
178+ , releaseDisplay = do
179+ maybeSendCap rmcup []
180+ maybeSendCap cnorm []
181+ , setDisplayBounds = \(w, h) ->
182+ setWindowSize outFd (w, h)
183+ , displayBounds = do
184+ rawSize <- getWindowSize outFd
185+ case rawSize of
186+ (w, h) | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show rawSize
187+ | otherwise -> return (w,h)
188+ , outputByteBuffer = \outBytes -> do
189+ let (fptr, offset, len) = toForeignPtr outBytes
190+ actualLen <- withForeignPtr fptr
191+ $ \ptr -> fdWriteAll outFd (ptr `plusPtr` offset) len 0
192+ when (toEnum len /= actualLen) $ fail $ "Graphics.Vty.Output: outputByteBuffer "
193+ ++ "length mismatch. " ++ show len ++ " /= " ++ show actualLen
194+ ++ " Please report this bug to vty project."
195+ , supportsCursorVisibility = isJust $ civis terminfoCaps
196+ , supportsMode = terminfoModeSupported
197+ , setMode = terminfoSetMode
198+ , getModeStatus = terminfoModeStatus
199+ , assumedStateRef = newAssumedStateRef
200+ , outputColorMode = colorMode
201+ -- I think fix would help assure tActual is the only
202+ -- reference. I was having issues tho.
203+ , mkDisplayContext = (`terminfoDisplayContext` terminfoCaps)
204+ }
205+ sendCap s = sendCapToTerminal t (s terminfoCaps)
206+ maybeSendCap s = when (isJust $ s terminfoCaps) . sendCap (fromJust . s)
207+ return t
208+
209+requireCap :: Terminfo.Terminal -> String -> IO CapExpression
210+requireCap ti capName
211+ = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
212+ Nothing -> fail $ "Terminal does not define required capability \"" ++ capName ++ "\""
213+ Just capStr -> parseCap capStr
214+
215+probeCap :: Terminfo.Terminal -> String -> IO (Maybe CapExpression)
216+probeCap ti capName
217+ = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
218+ Nothing -> return Nothing
219+ Just capStr -> Just <$> parseCap capStr
220+
221+parseCap :: String -> IO CapExpression
222+parseCap capStr = do
223+ case parseCapExpression capStr of
224+ Left e -> fail $ show e
225+ Right cap -> return cap
226+
227+currentDisplayAttrCaps :: Terminfo.Terminal -> IO DisplayAttrCaps
228+currentDisplayAttrCaps ti
229+ = pure DisplayAttrCaps
230+ <*> probeCap ti "sgr"
231+ <*> probeCap ti "smso"
232+ <*> probeCap ti "rmso"
233+ <*> probeCap ti "sitm"
234+ <*> probeCap ti "ritm"
235+ <*> probeCap ti "smxx"
236+ <*> probeCap ti "rmxx"
237+ <*> probeCap ti "smul"
238+ <*> probeCap ti "rmul"
239+ <*> probeCap ti "rev"
240+ <*> probeCap ti "dim"
241+ <*> probeCap ti "bold"
242+
243+foreign import ccall "gwinsz.h vty_c_get_window_size" c_getWindowSize :: Fd -> IO CLong
244+
245+getWindowSize :: Fd -> IO (Int,Int)
246+getWindowSize fd = do
247+ (a,b) <- (`divMod` 65536) `fmap` c_getWindowSize fd
248+ return (fromIntegral b, fromIntegral a)
249+
250+foreign import ccall "gwinsz.h vty_c_set_window_size" c_setWindowSize :: Fd -> CLong -> IO ()
251+
252+setWindowSize :: Fd -> (Int, Int) -> IO ()
253+setWindowSize fd (w, h) = do
254+ let val = (h `shiftL` 16) + w
255+ c_setWindowSize fd $ fromIntegral val
256+
257+terminfoDisplayContext :: Output -> TerminfoCaps -> DisplayRegion -> IO DisplayContext
258+terminfoDisplayContext tActual terminfoCaps r = return dc
259+ where dc = DisplayContext
260+ { contextDevice = tActual
261+ , contextRegion = r
262+ , writeMoveCursor = \x y -> writeCapExpr (cup terminfoCaps) [toEnum y, toEnum x]
263+ , writeShowCursor = case cnorm terminfoCaps of
264+ Nothing -> error "this terminal does not support show cursor"
265+ Just c -> writeCapExpr c []
266+ , writeHideCursor = case civis terminfoCaps of
267+ Nothing -> error "this terminal does not support hide cursor"
268+ Just c -> writeCapExpr c []
269+ , writeSetAttr = terminfoWriteSetAttr dc terminfoCaps
270+ , writeDefaultAttr = \urlsEnabled ->
271+ writeCapExpr (setDefaultAttr terminfoCaps) [] `mappend`
272+ (if urlsEnabled then writeURLEscapes EndLink else mempty) `mappend`
273+ (case exitStrikethrough $ displayAttrCaps terminfoCaps of
274+ Just cap -> writeCapExpr cap []
275+ Nothing -> mempty
276+ )
277+ , writeRowEnd = writeCapExpr (clearEol terminfoCaps) []
278+ , inlineHack = return ()
279+ }
280+
281+-- | Write the escape sequences that are used in some terminals to
282+-- include embedded hyperlinks. As of yet, this information isn't
283+-- included in termcap or terminfo, so this writes them directly
284+-- instead of looking up the appropriate capabilities.
285+writeURLEscapes :: URLDiff -> Write
286+writeURLEscapes (LinkTo url) =
287+ foldMap writeStorable (BS.unpack "\x1b]8;;") `mappend`
288+ foldMap writeStorable (BS.unpack url) `mappend`
289+ writeStorable (0x07 :: Word8)
290+writeURLEscapes EndLink =
291+ foldMap writeStorable (BS.unpack "\x1b]8;;\a")
292+writeURLEscapes NoLinkChange =
293+ mempty
294+
295+-- | Portably setting the display attributes is a giant pain in the ass.
296+--
297+-- If the terminal supports the sgr capability (which sets the on/off
298+-- state of each style directly; and, for no good reason, resets the
299+-- colors to the default) this procedure is used:
300+--
301+-- 0. set the style attributes. This resets the fore and back color.
302+--
303+-- 1, If a foreground color is to be set then set the foreground color
304+--
305+-- 2. likewise with the background color
306+--
307+-- If the terminal does not support the sgr cap then: if there is a
308+-- change from an applied color to the default (in either the fore or
309+-- back color) then:
310+--
311+-- 0. reset all display attributes (sgr0)
312+--
313+-- 1. enter required style modes
314+--
315+-- 2. set the fore color if required
316+--
317+-- 3. set the back color if required
318+--
319+-- Entering the required style modes could require a reset of the
320+-- display attributes. If this is the case then the back and fore colors
321+-- always need to be set if not default.
322+--
323+-- This equation implements the above logic.
324+--
325+-- Note that this assumes the removal of color changes in the
326+-- display attributes is done as expected with noColors == True. See
327+-- `limitAttrForDisplay`.
328+--
329+-- Note that this optimizes for fewer state changes followed by fewer
330+-- bytes.
331+terminfoWriteSetAttr :: DisplayContext -> TerminfoCaps -> Bool -> FixedAttr -> Attr -> DisplayAttrDiff -> Write
332+terminfoWriteSetAttr dc terminfoCaps urlsEnabled prevAttr reqAttr diffs =
333+ urlAttrs urlsEnabled `mappend` case (foreColorDiff diffs == ColorToDefault) || (backColorDiff diffs == ColorToDefault) of
334+ -- The only way to reset either color, portably, to the default
335+ -- is to use either the set state capability or the set default
336+ -- capability.
337+ True -> do
338+ case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps)
339+ (fixedStyle attr)
340+ (styleToApplySeq $ fixedStyle attr) of
341+ -- only way to reset a color to the defaults
342+ EnterExitSeq caps -> writeDefaultAttr dc urlsEnabled
343+ `mappend`
344+ foldMap (\cap -> writeCapExpr cap []) caps
345+ `mappend`
346+ setColors
347+ -- implicitly resets the colors to the defaults
348+ SetState state -> writeCapExpr (fromJust $ setAttrStates
349+ $ displayAttrCaps terminfoCaps
350+ )
351+ (sgrArgsForState state)
352+ `mappend` setItalics
353+ `mappend` setStrikethrough
354+ `mappend` setColors
355+ -- Otherwise the display colors are not changing or changing
356+ -- between two non-default points.
357+ False -> do
358+ -- Still, it could be the case that the change in display
359+ -- attributes requires the colors to be reset because the
360+ -- required capability was not available.
361+ case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps)
362+ (fixedStyle attr)
363+ (styleDiffs diffs) of
364+ -- Really, if terminals were re-implemented with modern
365+ -- concepts instead of bowing down to 40 yr old dumb
366+ -- terminal requirements this would be the only case
367+ -- ever reached! Changes the style and color states
368+ -- according to the differences with the currently
369+ -- applied states.
370+ EnterExitSeq caps -> foldMap (\cap -> writeCapExpr cap []) caps
371+ `mappend`
372+ writeColorDiff Foreground (foreColorDiff diffs)
373+ `mappend`
374+ writeColorDiff Background (backColorDiff diffs)
375+ -- implicitly resets the colors to the defaults
376+ SetState state -> writeCapExpr (fromJust $ setAttrStates
377+ $ displayAttrCaps terminfoCaps
378+ )
379+ (sgrArgsForState state)
380+ `mappend` setItalics
381+ `mappend` setStrikethrough
382+ `mappend` setColors
383+ where
384+ urlAttrs True = writeURLEscapes (urlDiff diffs)
385+ urlAttrs False = mempty
386+ colorMap = case useAltColorMap terminfoCaps of
387+ False -> ansiColorIndex
388+ True -> altColorIndex
389+ attr = fixDisplayAttr prevAttr reqAttr
390+
391+ -- italics can't be set via SGR, so here we manually
392+ -- apply the enter and exit sequences as needed after
393+ -- changing the SGR
394+ setItalics
395+ | hasStyle (fixedStyle attr) italic
396+ , Just sitm <- enterItalic (displayAttrCaps terminfoCaps)
397+ = writeCapExpr sitm []
398+ | otherwise = mempty
399+ setStrikethrough
400+ | hasStyle (fixedStyle attr) strikethrough
401+ , Just smxx <- enterStrikethrough (displayAttrCaps terminfoCaps)
402+ = writeCapExpr smxx []
403+ | otherwise = mempty
404+ setColors =
405+ (case fixedForeColor attr of
406+ Just c -> writeColor Foreground c
407+ Nothing -> mempty)
408+ `mappend`
409+ (case fixedBackColor attr of
410+ Just c -> writeColor Background c
411+ Nothing -> mempty)
412+ writeColorDiff _side NoColorChange
413+ = mempty
414+ writeColorDiff _side ColorToDefault
415+ = error "ColorToDefault is not a possible case for applyColorDiffs"
416+ writeColorDiff side (SetColor c)
417+ = writeColor side c
418+
419+ writeColor side (RGBColor r g b) =
420+ case outputColorMode (contextDevice dc) of
421+ FullColor ->
422+ hardcodeColor side (r, g, b)
423+ _ ->
424+ error "clampColor should remove rgb colors in standard mode"
425+ writeColor side c =
426+ writeCapExpr (setSideColor side terminfoCaps) [toEnum $ colorMap c]
427+
428+-- a color can either be in the foreground or the background
429+data ColorSide = Foreground | Background
430+
431+-- get the capability for drawing a color on a specific side
432+setSideColor :: ColorSide -> TerminfoCaps -> CapExpression
433+setSideColor Foreground = setForeColor
434+setSideColor Background = setBackColor
435+
436+hardcodeColor :: ColorSide -> (Word8, Word8, Word8) -> Write
437+hardcodeColor side (r, g, b) =
438+ -- hardcoded color codes are formatted as "\x1b[{side};2;{r};{g};{b}m"
439+ mconcat [ writeStr "\x1b[", sideCode, delimiter, writeChar '2', delimiter
440+ , writeColor r, delimiter, writeColor g, delimiter, writeColor b
441+ , writeChar 'm']
442+ where
443+ writeChar = writeWord8 . fromIntegral . fromEnum
444+ writeStr = mconcat . map writeChar
445+ writeColor = writeStr . show
446+ delimiter = writeChar ';'
447+ -- 38/48 are used to set whether we should write to the
448+ -- foreground/background. I really don't want to know why.
449+ sideCode = case side of
450+ Foreground -> writeStr "38"
451+ Background -> writeStr "48"
452+
453+-- | The color table used by a terminal is a 16 color set followed by a
454+-- 240 color set that might not be supported by the terminal.
455+--
456+-- This takes a Color which clearly identifies which palette to use and
457+-- computes the index into the full 256 color palette.
458+ansiColorIndex :: Color -> Int
459+ansiColorIndex (ISOColor v) = fromEnum v
460+ansiColorIndex (Color240 v) = 16 + fromEnum v
461+ansiColorIndex (RGBColor _ _ _) =
462+ error $ unlines [ "Attempted to create color index from rgb color."
463+ , "This is currently unsupported, and shouldn't ever happen"
464+ ]
465+
466+-- | For terminals without setaf/setab
467+--
468+-- See table in `man terminfo`
469+-- Will error if not in table.
470+altColorIndex :: Color -> Int
471+altColorIndex (ISOColor 0) = 0
472+altColorIndex (ISOColor 1) = 4
473+altColorIndex (ISOColor 2) = 2
474+altColorIndex (ISOColor 3) = 6
475+altColorIndex (ISOColor 4) = 1
476+altColorIndex (ISOColor 5) = 5
477+altColorIndex (ISOColor 6) = 3
478+altColorIndex (ISOColor 7) = 7
479+altColorIndex (ISOColor v) = fromEnum v
480+altColorIndex (Color240 v) = 16 + fromEnum v
481+altColorIndex (RGBColor _ _ _) =
482+ error $ unlines [ "Attempted to create color index from rgb color."
483+ , "This is currently unsupported, and shouldn't ever happen"
484+ ]
485+
486+{- | The sequence of terminfo caps to apply a given style are determined
487+ - according to these rules.
488+ -
489+ - 1. The assumption is that it's preferable to use the simpler
490+ - enter/exit mode capabilities than the full set display attribute
491+ - state capability.
492+ -
493+ - 2. If a mode is supposed to be removed but there is not an exit
494+ - capability defined then the display attributes are reset to defaults
495+ - then the display attribute state is set.
496+ -
497+ - 3. If a mode is supposed to be applied but there is not an enter
498+ - capability defined then then display attribute state is set if
499+ - possible. Otherwise the mode is not applied.
500+ -
501+ - 4. If the display attribute state is being set then just update the
502+ - arguments to that for any apply/remove.
503+ -}
504+data DisplayAttrSeq
505+ = EnterExitSeq [CapExpression]
506+ | SetState DisplayAttrState
507+
508+data DisplayAttrState = DisplayAttrState
509+ { applyStandout :: Bool
510+ , applyUnderline :: Bool
511+ , applyItalic :: Bool
512+ , applyStrikethrough :: Bool
513+ , applyReverseVideo :: Bool
514+ , applyBlink :: Bool
515+ , applyDim :: Bool
516+ , applyBold :: Bool
517+ }
518+
519+sgrArgsForState :: DisplayAttrState -> [CapParam]
520+sgrArgsForState attrState = map (\b -> if b then 1 else 0)
521+ [ applyStandout attrState
522+ , applyUnderline attrState
523+ , applyReverseVideo attrState
524+ , applyBlink attrState
525+ , applyDim attrState
526+ , applyBold attrState
527+ , False -- invis
528+ , False -- protect
529+ , False -- alt char set
530+ ]
531+
532+reqDisplayCapSeqFor :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq
533+reqDisplayCapSeqFor caps s diffs
534+ -- if the state transition implied by any diff cannot be supported
535+ -- with an enter/exit mode cap then either the state needs to be set
536+ -- or the attribute change ignored.
537+ = case (any noEnterExitCap diffs, isJust $ setAttrStates caps) of
538+ -- If all the diffs have an enter-exit cap then just use those
539+ ( False, _ ) -> EnterExitSeq $ map enterExitCap diffs
540+ -- If not all the diffs have an enter-exit cap and there is no
541+ -- set state cap then filter out all unsupported diffs and just
542+ -- apply the rest
543+ ( True, False ) -> EnterExitSeq $ map enterExitCap
544+ $ filter (not . noEnterExitCap) diffs
545+ -- if not all the diffs have an enter-exit can and there is a
546+ -- set state cap then just use the set state cap.
547+ ( True, True ) -> SetState $ stateForStyle s
548+ where
549+ noEnterExitCap ApplyStrikethrough = isNothing $ enterStrikethrough caps
550+ noEnterExitCap RemoveStrikethrough = isNothing $ exitStrikethrough caps
551+ noEnterExitCap ApplyItalic = isNothing $ enterItalic caps
552+ noEnterExitCap RemoveItalic = isNothing $ exitItalic caps
553+ noEnterExitCap ApplyStandout = isNothing $ enterStandout caps
554+ noEnterExitCap RemoveStandout = isNothing $ exitStandout caps
555+ noEnterExitCap ApplyUnderline = isNothing $ enterUnderline caps
556+ noEnterExitCap RemoveUnderline = isNothing $ exitUnderline caps
557+ noEnterExitCap ApplyReverseVideo = isNothing $ enterReverseVideo caps
558+ noEnterExitCap RemoveReverseVideo = True
559+ noEnterExitCap ApplyBlink = True
560+ noEnterExitCap RemoveBlink = True
561+ noEnterExitCap ApplyDim = isNothing $ enterDimMode caps
562+ noEnterExitCap RemoveDim = True
563+ noEnterExitCap ApplyBold = isNothing $ enterBoldMode caps
564+ noEnterExitCap RemoveBold = True
565+ enterExitCap ApplyStrikethrough = fromJust $ enterStrikethrough caps
566+ enterExitCap RemoveStrikethrough = fromJust $ exitStrikethrough caps
567+ enterExitCap ApplyItalic = fromJust $ enterItalic caps
568+ enterExitCap RemoveItalic = fromJust $ exitItalic caps
569+ enterExitCap ApplyStandout = fromJust $ enterStandout caps
570+ enterExitCap RemoveStandout = fromJust $ exitStandout caps
571+ enterExitCap ApplyUnderline = fromJust $ enterUnderline caps
572+ enterExitCap RemoveUnderline = fromJust $ exitUnderline caps
573+ enterExitCap ApplyReverseVideo = fromJust $ enterReverseVideo caps
574+ enterExitCap ApplyDim = fromJust $ enterDimMode caps
575+ enterExitCap ApplyBold = fromJust $ enterBoldMode caps
576+ enterExitCap _ = error "enterExitCap applied to diff that was known not to have one."
577+
578+stateForStyle :: Style -> DisplayAttrState
579+stateForStyle s = DisplayAttrState
580+ { applyStandout = isStyleSet standout
581+ , applyUnderline = isStyleSet underline
582+ , applyItalic = isStyleSet italic
583+ , applyStrikethrough = isStyleSet strikethrough
584+ , applyReverseVideo = isStyleSet reverseVideo
585+ , applyBlink = isStyleSet blink
586+ , applyDim = isStyleSet dim
587+ , applyBold = isStyleSet bold
588+ }
589+ where isStyleSet = hasStyle s
590+
591+styleToApplySeq :: Style -> [StyleStateChange]
592+styleToApplySeq s = concat
593+ [ applyIfRequired ApplyStandout standout
594+ , applyIfRequired ApplyUnderline underline
595+ , applyIfRequired ApplyItalic italic
596+ , applyIfRequired ApplyStrikethrough strikethrough
597+ , applyIfRequired ApplyReverseVideo reverseVideo
598+ , applyIfRequired ApplyBlink blink
599+ , applyIfRequired ApplyDim dim
600+ , applyIfRequired ApplyBold bold
601+ ]
602+ where
603+ applyIfRequired op flag
604+ = if 0 == (flag .&. s)
605+ then []
606+ else [op]
src/Graphics/Vty/Output/XTermColor.hsnew file+131
@@ -0,0 +1,131 @@
1+{-# Language CPP #-}
2+-- Copyright 2009-2010 Corey O'Connor
3+-- | Xterm output driver. This uses the Terminfo driver with some
4+-- extensions for Xterm.
5+module Graphics.Vty.Output.XTermColor
6+ ( reserveTerminal
7+ )
8+where
9+
10+import Graphics.Vty.Output.Interface
11+import Graphics.Vty.Input.Mouse
12+import Graphics.Vty.Input.Focus
13+import Graphics.Vty.Attributes.Color (ColorMode)
14+import qualified Graphics.Vty.Output.TerminfoBased as TerminfoBased
15+
16+import Blaze.ByteString.Builder (writeToByteString)
17+import Blaze.ByteString.Builder.Word (writeWord8)
18+
19+import qualified Data.ByteString.Char8 as BS8
20+import Data.ByteString.Char8 (ByteString)
21+import Foreign.Ptr (castPtr)
22+
23+import Control.Monad (void, when)
24+import Control.Monad.Trans
25+import Data.Char (toLower)
26+import Data.IORef
27+
28+import System.Posix.IO (fdWriteBuf)
29+import System.Posix.Types (ByteCount, Fd)
30+import System.Posix.Env (getEnv)
31+
32+import Data.List (isInfixOf)
33+import Data.Maybe (catMaybes)
34+
35+#if !MIN_VERSION_base(4,11,0)
36+import Data.Monoid ((<>))
37+#endif
38+
39+-- | Write a 'ByteString' to an 'Fd'.
40+fdWrite :: Fd -> ByteString -> IO ByteCount
41+fdWrite fd s =
42+ BS8.useAsCStringLen s $ \(buf,len) -> do
43+ fdWriteBuf fd (castPtr buf) (fromIntegral len)
44+
45+-- | Construct an Xterm output driver. Initialize the display to UTF-8.
46+reserveTerminal :: ( Applicative m, MonadIO m ) => String -> Fd -> ColorMode -> m Output
47+reserveTerminal variant outFd colorMode = liftIO $ do
48+ let flushedPut = void . fdWrite outFd
49+ -- If the terminal variant is xterm-color use xterm instead since,
50+ -- more often than not, xterm-color is broken.
51+ let variant' = if variant == "xterm-color" then "xterm" else variant
52+
53+ utf8a <- utf8Active
54+ when (not utf8a) $ flushedPut setUtf8CharSet
55+ t <- TerminfoBased.reserveTerminal variant' outFd colorMode
56+
57+ mouseModeStatus <- newIORef False
58+ focusModeStatus <- newIORef False
59+ pasteModeStatus <- newIORef False
60+
61+ let xtermSetMode t' m newStatus = do
62+ curStatus <- getModeStatus t' m
63+ when (newStatus /= curStatus) $
64+ case m of
65+ Focus -> liftIO $ do
66+ case newStatus of
67+ True -> flushedPut requestFocusEvents
68+ False -> flushedPut disableFocusEvents
69+ writeIORef focusModeStatus newStatus
70+ Mouse -> liftIO $ do
71+ case newStatus of
72+ True -> flushedPut requestMouseEvents
73+ False -> flushedPut disableMouseEvents
74+ writeIORef mouseModeStatus newStatus
75+ BracketedPaste -> liftIO $ do
76+ case newStatus of
77+ True -> flushedPut enableBracketedPastes
78+ False -> flushedPut disableBracketedPastes
79+ writeIORef pasteModeStatus newStatus
80+ Hyperlink -> setMode t Hyperlink newStatus
81+
82+ xtermGetMode Mouse = liftIO $ readIORef mouseModeStatus
83+ xtermGetMode Focus = liftIO $ readIORef focusModeStatus
84+ xtermGetMode BracketedPaste = liftIO $ readIORef pasteModeStatus
85+ xtermGetMode Hyperlink = getModeStatus t Hyperlink
86+
87+ let t' = t
88+ { terminalID = terminalID t ++ " (xterm-color)"
89+ , releaseTerminal = do
90+ when (not utf8a) $ liftIO $ flushedPut setDefaultCharSet
91+ setMode t' BracketedPaste False
92+ setMode t' Mouse False
93+ setMode t' Focus False
94+ releaseTerminal t
95+ , mkDisplayContext = \tActual r -> do
96+ dc <- mkDisplayContext t tActual r
97+ return $ dc { inlineHack = xtermInlineHack t' }
98+ , supportsMode = const True
99+ , getModeStatus = xtermGetMode
100+ , setMode = xtermSetMode t'
101+ }
102+ return t'
103+
104+utf8Active :: IO Bool
105+utf8Active = do
106+ let vars = ["LC_ALL", "LANG", "LC_CTYPE"]
107+ results <- map (toLower <$>) . catMaybes <$> mapM getEnv vars
108+ let matches = filter ("utf8" `isInfixOf`) results <>
109+ filter ("utf-8" `isInfixOf`) results
110+ return $ not $ null matches
111+
112+-- | Enable bracketed paste mode:
113+-- http://cirw.in/blog/bracketed-paste
114+enableBracketedPastes :: ByteString
115+enableBracketedPastes = BS8.pack "\ESC[?2004h"
116+
117+-- | Disable bracketed paste mode:
118+disableBracketedPastes :: ByteString
119+disableBracketedPastes = BS8.pack "\ESC[?2004l"
120+
121+-- | These sequences set xterm based terminals to UTF-8 output.
122+--
123+-- There is no known terminfo capability equivalent to this.
124+setUtf8CharSet, setDefaultCharSet :: ByteString
125+setUtf8CharSet = BS8.pack "\ESC%G"
126+setDefaultCharSet = BS8.pack "\ESC%@"
127+
128+xtermInlineHack :: Output -> IO ()
129+xtermInlineHack t = do
130+ let writeReset = foldMap (writeWord8.toEnum.fromEnum) "\ESC[K"
131+ outputByteBuffer t $ writeToByteString writeReset
src/Graphics/Vty/Picture.hs+98−60
@@ -1,26 +1,36 @@
1−{-# LANGUAGE BangPatterns #-}
1+-- A 'Picture' is a background paired with a set of 'Image' layers. The
2−-- | The Picture data structure is representative of the final terminal view.
2+-- 'Picture' data structure is representative of the final terminal
3−--
3+-- view.
4−-- This module re-exports most of the Graphics.Vty.Image and Graphics.Vty.Attributes modules.
4+module Graphics.Vty.Picture
5−--
5+ ( Picture(..)
6−-- Copyright 2009-2010 Corey O'Connor
6+ , Cursor(..)
7−module Graphics.Vty.Picture ( module Graphics.Vty.Picture
7+ , Background(..)
8− , module Graphics.Vty.Image
8+ , emptyPicture
9+ , addToTop
10+ , addToBottom
11+ , picForImage
12+ , picForLayers
13+ , picImage
9− )
14+ )
10− where
15+where
11
16
12import Graphics.Vty.Image
17import Graphics.Vty.Image
18+import Graphics.Vty.Attributes
13
19
14import Control.DeepSeq
20import Control.DeepSeq
15
21
16−-- | The type of images to be displayed using 'update'.
22+-- | A Vty picture.
23+--
17−-- Can be constructed directly or using `pic_for_image`. Which provides an initial instance with
24+-- These can be constructed directly or using `picForImage`.
18−-- reasonable defaults for pic_cursor and pic_background.
19data Picture = Picture
25data Picture = Picture
20− { pic_cursor :: Cursor
26+ { picCursor :: Cursor
21− , pic_layers :: [Image]
27+ -- ^ The picture's cursor.
28+ , picLayers :: [Image]
29+ -- ^ The picture's image layers (top-most first).
22− , pic_background :: Background
30+ , picBackground :: Background
23− }
31+ -- ^ The picture's background to be displayed in locations with no
32+ -- Image data.
33+ } deriving Eq
24
34
25instance Show Picture where
35instance Show Picture where
26 show (Picture _ layers _ ) = "Picture ?? " ++ show layers ++ " ??"
36 show (Picture _ layers _ ) = "Picture ?? " ++ show layers ++ " ??"
@@ -28,66 +38,94 @@ instance Show Picture where
28instance NFData Picture where
38instance NFData Picture where
29 rnf (Picture c l b) = c `deepseq` l `deepseq` b `deepseq` ()
39 rnf (Picture c l b) = c `deepseq` l `deepseq` b `deepseq` ()
30
40
31−-- | Create a picture for display for the given image. The picture will not have a displayed cursor
41+-- | A picture with no cursor, background or image layers.
32−-- and the background display attribute will be `current_attr`.
42+emptyPicture :: Picture
33−pic_for_image :: Image -> Picture
34−pic_for_image i = Picture
35− { pic_cursor = NoCursor
36− , pic_layers = [i]
37− , pic_background = ClearBackground
43+emptyPicture = Picture NoCursor [] ClearBackground
38− }
44+
45+-- | Add an 'Image' as the top-most layer of a 'Picture'.
46+addToTop :: Picture -> Image -> Picture
47+addToTop p i = p {picLayers = i : picLayers p}
39
48
40−-- | Create a picture for display with the given layers. Ordered top to bottom.
49+-- | Add an 'Image' as the bottom-most layer of a 'Picture'.
41−--
50+addToBottom :: Picture -> Image -> Picture
42−-- The first image is the top layer.
51+addToBottom p i = p {picLayers = picLayers p ++ [i]}
52+
53+-- | Create a picture from the given image. The picture will not have a
54+-- displayed cursor and no background pattern (ClearBackground) will be
55+-- used.
43−pic_for_layers :: [Image] -> Picture
56+picForImage :: Image -> Picture
44−pic_for_layers is = Picture
57+picForImage i = Picture
45− { pic_cursor = NoCursor
58+ { picCursor = NoCursor
46− , pic_layers = is
59+ , picLayers = [i]
47− , pic_background = ClearBackground
60+ , picBackground = ClearBackground
48 }
61 }
49
62
50−-- | A picture can be configured either to not show the cursor or show the cursor at the specified
63+-- | Create a picture with the given layers, top-most first.
51−-- character position.
52--
64--
53−-- There is not a 1 to 1 map from character positions to a row and column on the screen due to
65+-- The picture will not have a displayed cursor and no background
54−-- characters that take more than 1 column.
66+-- pattern (ClearBackgroun) will be used.
67+picForLayers :: [Image] -> Picture
68+picForLayers is = Picture
69+ { picCursor = NoCursor
70+ , picLayers = is
71+ , picBackground = ClearBackground
72+ }
73+
74+-- | A picture can be configured to hide the cursor or to show the
75+-- cursor at the specified character position.
55--
76--
56−-- todo: The Cursor can be given a (character,row) offset outside of the visible bounds of the
77+-- There is not a 1:1 map from character positions to a row and column
57−-- output region. In this case the cursor will not be shown.
78+-- on the screen due to characters that take more than 1 column.
58−data Cursor =
79+data Cursor =
80+ -- | Hide the cursor
59− NoCursor
81+ NoCursor
82+ -- | Set the terminal's cursor position without displaying a cursor
83+ -- character. This is important for accessibility with screen
84+ -- readers where a cursor position needs to be reported but we may
85+ -- not want to show a block cursor in that location for cosmetic
86+ -- reasons. The boolean argument indicates whether the positioning
87+ -- should be absolute as with 'AbsoluteCursor' ('True') or logical
88+ -- as with 'Cursor' ('False').
89+ | PositionOnly !Bool !Int !Int
90+ -- | Show the cursor at the given logical column accounting for
91+ -- character width in the presence of multi-column characters.
60− | Cursor Int Int
92+ | Cursor !Int !Int
93+ -- | Show the cursor at the given absolute terminal column and row
94+ | AbsoluteCursor !Int !Int
95+ deriving Eq
61
96
62instance NFData Cursor where
97instance NFData Cursor where
63− rnf NoCursor = ()
98+ rnf c = c `seq` ()
64− rnf (Cursor w h) = w `seq` h `seq` ()
65
99
66−-- | Unspecified regions are filled with the picture's background pattern. The background pattern
100+-- | A 'Picture' has a background pattern. The background is either:
67−-- can specify a character and a display attribute. If the display attribute used previously should
68−-- be used for a background fill then use `current_attr` for the background attribute. This is the
69−-- default background display attribute.
70--
101--
71−-- \todo The current attribute is always set to the default attributes at the start of updating the
102+-- * ClearBackground, which shows the layer below or is blank if the
72−-- screen to a picture.
103+-- bottom layer
104+-- * A character and a display attribute
105+--
106+-- If the display attribute used previously should be used for a
107+-- background fill then use `currentAttr` for the background attribute.
73data Background
108data Background
74− = Background
109+ = Background
75− { background_char :: Char
110+ { backgroundChar :: Char
76− , background_attr :: Attr
111+ , backgroundAttr :: Attr
77 }
112 }
78− -- | The background is:
113+ -- | A ClearBackground is:
79 --
114 --
80 -- * the space character if there are remaining non-skip ops
115 -- * the space character if there are remaining non-skip ops
81− -- * (?) nothing if there are no remaining non-skip ops.
82 --
116 --
83− -- Might require the terminal interface to do a line clear?
117+ -- * End of line if there are no remaining non-skip ops.
84− -- Or should the nothing case be switched with crlf?
85 | ClearBackground
118 | ClearBackground
119+ deriving Eq
86
120
87instance NFData Background where
121instance NFData Background where
88 rnf (Background c a) = c `seq` a `seq` ()
122 rnf (Background c a) = c `seq` a `seq` ()
89 rnf ClearBackground = ()
123 rnf ClearBackground = ()
90
124
125+-- | Return the top-most 'Image' layer for a picture. This is unsafe for
126+-- 'Picture's without at least one layer.
127+--
128+-- This is provided for compatibility with applications that do not use
91−-- | Compatibility with applications that do not use more than a single layer.
129+-- more than a single layer.
92−pic_image :: Picture -> Image
130+picImage :: Picture -> Image
93−pic_image = head . pic_layers
131+picImage = head . picLayers
src/Graphics/Vty/PictureToSpans.hs+243−241
@@ -1,27 +1,27 @@
1-- Copyright Corey O'Connor<coreyoconnor@gmail.com>
1-- Copyright Corey O'Connor<coreyoconnor@gmail.com>
2−{-# LANGUAGE RankNTypes #-}
3−{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE BangPatterns #-}
5{-# LANGUAGE ExistentialQuantification #-}
3{-# LANGUAGE ExistentialQuantification #-}
6{-# LANGUAGE NamedFieldPuns #-}
4{-# LANGUAGE NamedFieldPuns #-}
5+{-# LANGUAGE RankNTypes #-}
6+{-# LANGUAGE TemplateHaskell #-}
7+
7−{- | Transforms an image into rows of operations.
8+-- | Transforms an image into rows of operations.
8− -}
9module Graphics.Vty.PictureToSpans where
9module Graphics.Vty.PictureToSpans where
10
10
11−import Graphics.Vty.DisplayRegion
11+import Graphics.Vty.Attributes (Attr, currentAttr)
12import Graphics.Vty.Image
12import Graphics.Vty.Image
13import Graphics.Vty.Image.Internal
13import Graphics.Vty.Image.Internal
14import Graphics.Vty.Picture
14import Graphics.Vty.Picture
15import Graphics.Vty.Span
15import Graphics.Vty.Span
16
16
17−import Control.Applicative
17+import Lens.Micro
18−import Control.Lens hiding ( op )
18+import Lens.Micro.Mtl
19+import Lens.Micro.TH
19import Control.Monad.Reader
20import Control.Monad.Reader
20import Control.Monad.State.Strict hiding ( state )
21import Control.Monad.State.Strict hiding ( state )
21−import Control.Monad.ST.Strict hiding ( unsafeIOToST )
22+import Control.Monad.ST.Strict
22
23
23import qualified Data.Vector as Vector hiding ( take, replicate )
24import qualified Data.Vector as Vector hiding ( take, replicate )
24−import Data.Monoid (mappend)
25import Data.Vector.Mutable ( MVector(..))
25import Data.Vector.Mutable ( MVector(..))
26import qualified Data.Vector.Mutable as MVector
26import qualified Data.Vector.Mutable as MVector
27
27
@@ -33,255 +33,259 @@ type MSpanOps s = MVector s SpanOp
33
33
34-- transform plus clip. More or less.
34-- transform plus clip. More or less.
35data BlitState = BlitState
35data BlitState = BlitState
36− -- we always snoc to the operation vectors. Thus the column_offset = length of row at row_offset
36+ -- we always snoc to the operation vectors. Thus the columnOffset =
37+ -- length of row at rowOffset although, one possibility is to merge
37− -- although, one possibility is to merge layers right in snoc_op (naming it something else, of
38+ -- layers right in snocOp (naming it something else, of course). In
38− -- course). In which case columnn_offset would be applicable.
39+ -- which case columnnOffset would be applicable. Right now we need
39− -- Right now we need it to exist.
40+ -- it to exist.
40− { _column_offset :: Int
41+ { _columnOffset :: Int
41− , _row_offset :: Int
42+ , _rowOffset :: Int
42− -- clip coordinate space is in image space. Which means it's >= 0 and < image_width.
43+ -- clip coordinate space is in image space. Which means it's >= 0
43− , _skip_columns :: Int
44+ -- and < imageWidth.
44− -- >= 0 and < image_height
45+ , _skipColumns :: Int
45− , _skip_rows :: Int
46+ -- >= 0 and < imageHeight
47+ , _skipRows :: Int
46− -- includes consideration of skip_columns. In display space.
48+ -- includes consideration of skipColumns. In display space. The
47− -- The number of columns from the next column to be defined to the end of the display for the
49+ -- number of columns from the next column to be defined to the end
48− -- row.
50+ -- of the display for the row.
49− , _remaining_columns :: Int
51+ , _remainingColumns :: Int
50− -- includes consideration of skip_rows. In display space.
52+ -- includes consideration of skipRows. In display space.
51− , _remaining_rows :: Int
53+ , _remainingRows :: Int
52 }
54 }
53
55
54makeLenses ''BlitState
56makeLenses ''BlitState
55
57
56data BlitEnv s = BlitEnv
58data BlitEnv s = BlitEnv
57 { _region :: DisplayRegion
59 { _region :: DisplayRegion
58− , _mrow_ops :: MRowOps s
60+ , _mrowOps :: MRowOps s
59 }
61 }
60
62
61makeLenses ''BlitEnv
63makeLenses ''BlitEnv
62
64
63type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
65type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
64
66
65−-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
67+-- | Produces the span ops that will render the given picture, possibly
66−-- specified region.
68+-- cropped or padded, into the specified region.
67−display_ops_for_pic :: Picture -> DisplayRegion -> DisplayOps
69+displayOpsForPic :: Picture -> DisplayRegion -> DisplayOps
68−display_ops_for_pic pic r = Vector.create (combined_ops_for_layers pic r)
70+displayOpsForPic pic r = Vector.create (combinedOpsForLayers pic r)
69
71
70−-- | Returns the DisplayOps for an image rendered to a window the size of the image.
72+-- | Returns the DisplayOps for an image rendered to a window the size
73+-- of the image.
71--
74--
72−-- largerly used only for debugging.
75+-- largely used only for debugging.
73−display_ops_for_image :: Image -> DisplayOps
76+displayOpsForImage :: Image -> DisplayOps
74−display_ops_for_image i = display_ops_for_pic (pic_for_image i)
77+displayOpsForImage i = displayOpsForPic (picForImage i) (imageWidth i, imageHeight i)
75− (DisplayRegion (image_width i) (image_height i))
76
78
77-- | Produces the span ops for each layer then combines them.
79-- | Produces the span ops for each layer then combines them.
78−--
79−-- TODO: a fold over a builder function. start with span ops that are a bg fill of the entire
80−-- region.
81−combined_ops_for_layers :: Picture -> DisplayRegion -> ST s (MRowOps s)
80+combinedOpsForLayers :: Picture -> DisplayRegion -> ST s (MRowOps s)
82−combined_ops_for_layers pic r
81+combinedOpsForLayers pic r
83− | region_width r == 0 || region_height r == 0 = MVector.new 0
82+ | regionWidth r == 0 || regionHeight r == 0 = MVector.new 0
84 | otherwise = do
83 | otherwise = do
85− layer_ops <- mapM (\layer -> build_spans layer r) (pic_layers pic)
84+ layerOps <- mapM (`buildSpans` r) (picLayers pic)
86− case layer_ops of
85+ case layerOps of
87 [] -> fail "empty picture"
86 [] -> fail "empty picture"
88− [ops] -> substitute_skips (pic_background pic) ops
87+ [ops] -> substituteSkips (picBackground pic) ops
89− -- instead of merging ops after generation the merging can be performed as part of
88+ -- instead of merging ops after generation the merging can
90− -- snoc_op.
89+ -- be performed as part of snocOp.
91− top_ops : lower_ops -> do
90+ topOps : lowerOps -> do
92− ops <- foldM merge_under top_ops lower_ops
91+ ops <- foldM mergeUnder topOps lowerOps
93− substitute_skips (pic_background pic) ops
92+ substituteSkips (picBackground pic) ops
94
93
95−substitute_skips :: Background -> MRowOps s -> ST s (MRowOps s)
94+substituteSkips :: Background -> MRowOps s -> ST s (MRowOps s)
96−substitute_skips ClearBackground ops = do
95+substituteSkips ClearBackground ops = do
97 forM_ [0 .. MVector.length ops - 1] $ \row -> do
96 forM_ [0 .. MVector.length ops - 1] $ \row -> do
98− row_ops <- MVector.read ops row
97+ rowOps <- MVector.read ops row
99− -- the image operations assure that background fills are combined.
98+ -- the image operations assure that background fills are
100− -- clipping a background fill does not split the background fill.
99+ -- combined. clipping a background fill does not split the
101− -- merging of image layers can split a skip, but only by the insertion of a non skip.
100+ -- background fill. merging of image layers can split a skip,
101+ -- but only by the insertion of a non skip. all this combines to
102− -- all this combines to mean we can check the last operation and remove it if it's a skip
102+ -- mean we can check the last operation and remove it if it's a
103− -- todo: or does it?
103+ -- skip
104− let row_ops' = case Vector.last row_ops of
104+ let rowOps' = case Vector.last rowOps of
105− Skip w -> Vector.init row_ops `Vector.snoc` RowEnd w
105+ Skip w -> Vector.init rowOps `Vector.snoc` RowEnd w
106− _ -> row_ops
106+ _ -> rowOps
107− -- now all the skips can be replaced by replications of ' ' of the required width.
107+ -- now all the skips can be replaced by replications of ' ' of
108− let row_ops'' = swap_skips_for_single_column_char_span ' ' current_attr row_ops'
108+ -- the required width.
109+ let rowOps'' = swapSkipsForSingleColumnCharSpan ' ' currentAttr rowOps'
109− MVector.write ops row row_ops''
110+ MVector.write ops row rowOps''
110 return ops
111 return ops
111−substitute_skips (Background {background_char, background_attr}) ops = do
112+substituteSkips (Background {backgroundChar, backgroundAttr}) ops = do
112− -- At this point we decide if the background character is single column or not.
113+ -- At this point we decide if the background character is single
113− -- obviously, single column is easier.
114+ -- column or not. obviously, single column is easier.
114− case safe_wcwidth background_char of
115+ case safeWcwidth backgroundChar of
115− w | w == 0 -> fail $ "invalid background character " ++ show background_char
116+ w | w == 0 -> fail $ "invalid background character " ++ show backgroundChar
116 | w == 1 -> do
117 | w == 1 -> do
117 forM_ [0 .. MVector.length ops - 1] $ \row -> do
118 forM_ [0 .. MVector.length ops - 1] $ \row -> do
118− row_ops <- MVector.read ops row
119+ rowOps <- MVector.read ops row
119− let row_ops' = swap_skips_for_single_column_char_span background_char background_attr row_ops
120+ let rowOps' = swapSkipsForSingleColumnCharSpan backgroundChar backgroundAttr rowOps
120− MVector.write ops row row_ops'
121+ MVector.write ops row rowOps'
121 | otherwise -> do
122 | otherwise -> do
122 forM_ [0 .. MVector.length ops - 1] $ \row -> do
123 forM_ [0 .. MVector.length ops - 1] $ \row -> do
123− row_ops <- MVector.read ops row
124+ rowOps <- MVector.read ops row
124− let row_ops' = swap_skips_for_char_span w background_char background_attr row_ops
125+ let rowOps' = swapSkipsForCharSpan w backgroundChar backgroundAttr rowOps
125− MVector.write ops row row_ops'
126+ MVector.write ops row rowOps'
126 return ops
127 return ops
127
128
128−merge_under :: MRowOps s -> MRowOps s -> ST s (MRowOps s)
129+mergeUnder :: MRowOps s -> MRowOps s -> ST s (MRowOps s)
129−merge_under upper lower = do
130+mergeUnder upper lower = do
130 forM_ [0 .. MVector.length upper - 1] $ \row -> do
131 forM_ [0 .. MVector.length upper - 1] $ \row -> do
131− upper_row_ops <- MVector.read upper row
132+ upperRowOps <- MVector.read upper row
132− lower_row_ops <- MVector.read lower row
133+ lowerRowOps <- MVector.read lower row
133− let row_ops = merge_row_under upper_row_ops lower_row_ops
134+ let rowOps = mergeRowUnder upperRowOps lowerRowOps
134− MVector.write upper row row_ops
135+ MVector.write upper row rowOps
135 return upper
136 return upper
136
137
137−-- fugly
138−merge_row_under :: SpanOps -> SpanOps -> SpanOps
138+mergeRowUnder :: SpanOps -> SpanOps -> SpanOps
139−merge_row_under upper_row_ops lower_row_ops =
139+mergeRowUnder upperRowOps =
140− on_upper_op Vector.empty (Vector.head upper_row_ops) (Vector.tail upper_row_ops) lower_row_ops
140+ onUpperOp Vector.empty (Vector.head upperRowOps) (Vector.tail upperRowOps)
141 where
141 where
142− -- H: it will never be the case that we are out of upper ops before lower ops.
142+ -- H: it will never be the case that we are out of upper ops
143+ -- before lower ops.
143− on_upper_op :: SpanOps -> SpanOp -> SpanOps -> SpanOps -> SpanOps
144+ onUpperOp :: SpanOps -> SpanOp -> SpanOps -> SpanOps -> SpanOps
144− on_upper_op out_ops op@(TextSpan _ w _ _) upper_ops lower_ops =
145+ onUpperOp outOps op@(TextSpan _ w _ _) upperOps lowerOps =
145− let lower_ops' = drop_ops w lower_ops
146+ let lowerOps' = dropOps w lowerOps
146− out_ops' = Vector.snoc out_ops op
147+ outOps' = Vector.snoc outOps op
147− in if Vector.null lower_ops'
148+ in if Vector.null lowerOps'
148− then out_ops'
149+ then outOps'
149− else on_upper_op out_ops' (Vector.head upper_ops) (Vector.tail upper_ops) lower_ops'
150+ else onUpperOp outOps' (Vector.head upperOps) (Vector.tail upperOps) lowerOps'
150− on_upper_op out_ops (Skip w) upper_ops lower_ops =
151+ onUpperOp outOps (Skip w) upperOps lowerOps =
151− let (ops', lower_ops') = split_ops_at w lower_ops
152+ let (ops', lowerOps') = splitOpsAt w lowerOps
152− out_ops' = out_ops `mappend` ops'
153+ outOps' = outOps `mappend` ops'
153− in if Vector.null lower_ops'
154+ in if Vector.null lowerOps'
154− then out_ops'
155+ then outOps'
155− else on_upper_op out_ops' (Vector.head upper_ops) (Vector.tail upper_ops) lower_ops'
156+ else onUpperOp outOps' (Vector.head upperOps) (Vector.tail upperOps) lowerOps'
156− on_upper_op _ (RowEnd _) _ _ = error "cannot merge rows containing RowEnd ops"
157+ onUpperOp _ (RowEnd _) _ _ = error "cannot merge rows containing RowEnd ops"
157
158
158
159
159−swap_skips_for_single_column_char_span :: Char -> Attr -> SpanOps -> SpanOps
160+swapSkipsForSingleColumnCharSpan :: Char -> Attr -> SpanOps -> SpanOps
160−swap_skips_for_single_column_char_span c a = Vector.map f
161+swapSkipsForSingleColumnCharSpan c a = Vector.map f
161 where f (Skip ow) = let txt = TL.pack $ replicate ow c
162 where f (Skip ow) = let txt = TL.pack $ replicate ow c
162 in TextSpan a ow ow txt
163 in TextSpan a ow ow txt
163 f v = v
164 f v = v
164
165
165−swap_skips_for_char_span :: Int -> Char -> Attr -> SpanOps -> SpanOps
166+swapSkipsForCharSpan :: Int -> Char -> Attr -> SpanOps -> SpanOps
166−swap_skips_for_char_span w c a = Vector.map f
167+swapSkipsForCharSpan w c a = Vector.map f
167 where
168 where
168− f (Skip ow) = let txt_0_cw = ow `div` w
169+ f (Skip ow) = let txt0Cw = ow `div` w
169− txt_0 = TL.pack $ replicate txt_0_cw c
170+ txt0 = TL.pack $ replicate txt0Cw c
170− txt_1_cw = ow `mod` w
171+ txt1Cw = ow `mod` w
171− txt_1 = TL.pack $ replicate txt_1_cw '…'
172+ txt1 = TL.pack $ replicate txt1Cw '…'
172− cw = txt_0_cw + txt_1_cw
173+ cw = txt0Cw + txt1Cw
173− txt = txt_0 `TL.append` txt_1
174+ txt = txt0 `TL.append` txt1
174 in TextSpan a ow cw txt
175 in TextSpan a ow cw txt
175 f v = v
176 f v = v
176
177
177−-- | Builds a vector of row operations that will output the given picture to the terminal.
178+-- | Builds a vector of row operations that will output the given
179+-- picture to the terminal.
178--
180--
179-- Crops to the given display region.
181-- Crops to the given display region.
180−--
181−-- TODO: I'm pretty sure there is an algorithm that does not require a mutable buffer.
182−build_spans :: Image -> DisplayRegion -> ST s (MRowOps s)
182+buildSpans :: Image -> DisplayRegion -> ST s (MRowOps s)
183−build_spans image out_region = do
183+buildSpans image outRegion = do
184 -- First we create a mutable vector for each rows output operations.
184 -- First we create a mutable vector for each rows output operations.
185− out_ops <- MVector.replicate (region_height out_region) Vector.empty
185+ outOps <- MVector.replicate (regionHeight outRegion) Vector.empty
186− -- \todo I think building the span operations in display order would provide better performance.
186+ -- It's possible that building the span operations in display order
187− -- However, I got stuck trying to implement an algorithm that did this. This will be considered
187+ -- would provide better performance.
188− -- as a possible future optimization.
189 --
188 --
190− -- A depth first traversal of the image is performed. ordered according to the column range
189+ -- A depth first traversal of the image is performed. ordered
190+ -- according to the column range defined by the image from least
191− -- defined by the image from least to greatest. The output row ops will at least have the
191+ -- to greatest. The output row ops will at least have the region
192− -- region of the image specified. Iterate over all output rows and output background fills for
192+ -- of the image specified. Iterate over all output rows and output
193− -- all unspecified columns.
193+ -- background fills for all unspecified columns.
194 --
194 --
195− -- The images are made into span operations from left to right. It's possible that this could
195+ -- The images are made into span operations from left to right. It's
196− -- easily be made to assure top to bottom output as well.
196+ -- possible that this could easily be made to assure top to bottom
197− when (region_height out_region > 0 && region_width out_region > 0) $ do
197+ -- output as well.
198+ when (regionHeight outRegion > 0 && regionWidth outRegion > 0) $ do
198− -- The ops builder recursively descends the image and outputs span ops that would
199+ -- The ops builder recursively descends the image and outputs
200+ -- span ops that would display that image. The number of columns
199− -- display that image. The number of columns remaining in this row before exceeding the
201+ -- remaining in this row before exceeding the bounds is also
200− -- bounds is also provided. This is used to clip the span ops produced to the display.
202+ -- provided. This is used to clip the span ops produced to the
201− let full_build = do
203+ -- display.
202− start_image_build image
204+ let fullBuild = do
205+ startImageBuild image
203 -- Fill in any unspecified columns with a skip.
206 -- Fill in any unspecified columns with a skip.
204− forM_ [0 .. (region_height out_region - 1)] (add_row_completion out_region)
207+ forM_ [0 .. (regionHeight outRegion - 1)] (addRowCompletion outRegion)
205− init_env = BlitEnv out_region out_ops
208+ initEnv = BlitEnv outRegion outOps
206− init_state = BlitState 0 0 0 0 (region_width out_region) (region_height out_region)
209+ initState = BlitState 0 0 0 0 (regionWidth outRegion) (regionHeight outRegion)
207− _ <- runStateT (runReaderT full_build init_env) init_state
210+ _ <- runStateT (runReaderT fullBuild initEnv) initState
208 return ()
211 return ()
209− return out_ops
212+ return outOps
210
213
211−-- | Add the operations required to build a given image to the current set of row operations
214+-- | Add the operations required to build a given image to the current
212−-- returns the number of columns and rows contributed to the output.
215+-- set of row operations.
213−start_image_build :: Image -> BlitM s ()
216+startImageBuild :: Image -> BlitM s ()
214−start_image_build image = do
217+startImageBuild image = do
215− out_of_bounds <- is_out_of_bounds image <$> get
218+ outOfBounds <- isOutOfBounds image <$> get
216− when (not out_of_bounds) $ add_maybe_clipped image
219+ when (not outOfBounds) $ addMaybeClipped image
217
220
218−is_out_of_bounds :: Image -> BlitState -> Bool
221+isOutOfBounds :: Image -> BlitState -> Bool
219−is_out_of_bounds i s
222+isOutOfBounds i s
220− | s ^. remaining_columns <= 0 = True
223+ | s ^. remainingColumns <= 0 = True
221− | s ^. remaining_rows <= 0 = True
224+ | s ^. remainingRows <= 0 = True
222− | s ^. skip_columns >= image_width i = True
225+ | s ^. skipColumns >= imageWidth i = True
223− | s ^. skip_rows >= image_height i = True
226+ | s ^. skipRows >= imageHeight i = True
224 | otherwise = False
227 | otherwise = False
225
228
226−-- | This adds an image that might be partially clipped to the output ops.
229+-- | This adds an image that might be partially clipped to the output
230+-- ops.
227--
231--
228−-- This is a very touchy algorithm. Too touchy. For instance, the CropRight and CropBottom
232+-- This is a very touchy algorithm. Too touchy. For instance, the
233+-- CropRight and CropBottom implementations are odd. They pass the
229−-- implementations are odd. They pass the current tests but something seems terribly wrong about all
234+-- current tests but something seems terribly wrong about all this.
230−-- this.
231−--
232−-- TODO: prove this cannot be called in an out of bounds case.
233−add_maybe_clipped :: forall s . Image -> BlitM s ()
235+addMaybeClipped :: forall s . Image -> BlitM s ()
234−add_maybe_clipped EmptyImage = return ()
236+addMaybeClipped EmptyImage = return ()
235−add_maybe_clipped (HorizText a text_str ow _cw) = do
237+addMaybeClipped (HorizText a textStr ow _cw) = do
236− -- TODO: assumption that text spans are only 1 row high.
238+ -- This assumes that text spans are only 1 row high.
237− s <- use skip_rows
239+ s <- use skipRows
238 when (s < 1) $ do
240 when (s < 1) $ do
239− left_clip <- use skip_columns
241+ leftClip <- use skipColumns
240− right_clip <- use remaining_columns
242+ rightClip <- use remainingColumns
241− let left_clipped = left_clip > 0
243+ let leftClipped = leftClip > 0
242− right_clipped = (ow - left_clip) > right_clip
244+ rightClipped = (ow - leftClip) > rightClip
243− if left_clipped || right_clipped
245+ if leftClipped || rightClipped
244− then let text_str' = clip_text text_str left_clip right_clip
246+ then let textStr' = clipText textStr leftClip rightClip
245− in add_unclipped_text a text_str'
247+ in addUnclippedText a textStr'
246− else add_unclipped_text a text_str
248+ else addUnclippedText a textStr
247−add_maybe_clipped (VertJoin top_image bottom_image _ow oh) = do
249+addMaybeClipped (VertJoin topImage bottomImage _ow oh) = do
248− add_maybe_clipped_join "vert_join" skip_rows remaining_rows row_offset
250+ when (imageHeight topImage + imageHeight bottomImage > 0) $
249− (image_height top_image)
251+ addMaybeClippedJoin "vert_join" skipRows remainingRows rowOffset
250− top_image
252+ (imageHeight topImage)
251− bottom_image
253+ topImage
254+ bottomImage
252− oh
255+ oh
253−add_maybe_clipped (HorizJoin left_image right_image ow _oh) = do
256+addMaybeClipped (HorizJoin leftImage rightImage ow _oh) = do
254− add_maybe_clipped_join "horiz_join" skip_columns remaining_columns column_offset
257+ when (imageWidth leftImage + imageWidth rightImage > 0) $
255− (image_width left_image)
258+ addMaybeClippedJoin "horiz_join" skipColumns remainingColumns columnOffset
256− left_image
259+ (imageWidth leftImage)
257− right_image
260+ leftImage
261+ rightImage
258− ow
262+ ow
259−add_maybe_clipped BGFill {output_width, output_height} = do
263+addMaybeClipped BGFill {outputWidth, outputHeight} = do
260 s <- get
264 s <- get
261− let output_width' = min (output_width - s^.skip_columns) (s^.remaining_columns)
265+ let outputWidth' = min (outputWidth - s^.skipColumns) (s^.remainingColumns)
262− output_height' = min (output_height - s^.skip_rows ) (s^.remaining_rows)
266+ outputHeight' = min (outputHeight - s^.skipRows ) (s^.remainingRows)
263− y <- use row_offset
267+ y <- use rowOffset
264− forM_ [y..y+output_height'-1] $ snoc_op (Skip output_width')
268+ forM_ [y..y+outputHeight'-1] $ snocOp (Skip outputWidth')
265−add_maybe_clipped CropRight {cropped_image, output_width} = do
269+addMaybeClipped CropRight {croppedImage, outputWidth} = do
266− s <- use skip_columns
270+ s <- use skipColumns
267− r <- use remaining_columns
271+ r <- use remainingColumns
268− let x = output_width - s
272+ let x = outputWidth - s
269− when (x < r) $ remaining_columns .= x
273+ when (x < r) $ remainingColumns .= x
270− add_maybe_clipped cropped_image
274+ addMaybeClipped croppedImage
271−add_maybe_clipped CropLeft {cropped_image, left_skip} = do
275+addMaybeClipped CropLeft {croppedImage, leftSkip} = do
272− skip_columns += left_skip
276+ skipColumns += leftSkip
273− add_maybe_clipped cropped_image
277+ addMaybeClipped croppedImage
274−add_maybe_clipped CropBottom {cropped_image, output_height} = do
278+addMaybeClipped CropBottom {croppedImage, outputHeight} = do
275− s <- use skip_rows
279+ s <- use skipRows
276− r <- use remaining_rows
280+ r <- use remainingRows
277− let x = output_height - s
281+ let x = outputHeight - s
278− when (x < r) $ remaining_rows .= x
282+ when (x < r) $ remainingRows .= x
279− add_maybe_clipped cropped_image
283+ addMaybeClipped croppedImage
280−add_maybe_clipped CropTop {cropped_image, top_skip} = do
284+addMaybeClipped CropTop {croppedImage, topSkip} = do
281− skip_rows += top_skip
285+ skipRows += topSkip
282− add_maybe_clipped cropped_image
286+ addMaybeClipped croppedImage
283
287
284−add_maybe_clipped_join :: forall s . String
288+addMaybeClippedJoin :: forall s . String
285 -> Lens BlitState BlitState Int Int
289 -> Lens BlitState BlitState Int Int
286 -> Lens BlitState BlitState Int Int
290 -> Lens BlitState BlitState Int Int
287 -> Lens BlitState BlitState Int Int
291 -> Lens BlitState BlitState Int Int
@@ -290,57 +294,55 @@ add_maybe_clipped_join :: forall s . String
290 -> Image
294 -> Image
291 -> Int
295 -> Int
292 -> BlitM s ()
296 -> BlitM s ()
293−add_maybe_clipped_join name skip remaining offset i0_dim i0 i1 size = do
297+addMaybeClippedJoin name skip remaining offset i0Dim i0 i1 size = do
294 state <- get
298 state <- get
295 when (state^.remaining <= 0) $ fail $ name ++ " with remaining <= 0"
299 when (state^.remaining <= 0) $ fail $ name ++ " with remaining <= 0"
296 case state^.skip of
300 case state^.skip of
297− s -- TODO: check if clipped in other dim. if not use add_unclipped
301+ s | s > size -> put $ state & skip %~ subtract size
298− | s >= size -> fail $ name ++ " on fully clipped"
299− | s == 0 -> if state^.remaining > i0_dim
302+ | s == 0 -> if state^.remaining > i0Dim
300 then do
303 then do
301− add_maybe_clipped i0
304+ addMaybeClipped i0
302− put $ state & offset +~ i0_dim & remaining -~ i0_dim
305+ put $ state & offset %~ (+ i0Dim) & remaining %~ subtract i0Dim
303− add_maybe_clipped i1
306+ addMaybeClipped i1
304− else add_maybe_clipped i0
307+ else addMaybeClipped i0
305− | s < i0_dim ->
308+ | s < i0Dim ->
306− let i0_dim' = i0_dim - s
309+ let i0Dim' = i0Dim - s
307− in if state^.remaining <= i0_dim'
310+ in if state^.remaining <= i0Dim'
308− then add_maybe_clipped i0
311+ then addMaybeClipped i0
309 else do
312 else do
310− add_maybe_clipped i0
313+ addMaybeClipped i0
311− put $ state & offset +~ i0_dim' & remaining -~ i0_dim' & skip .~ 0
314+ put $ state & offset %~ (+ i0Dim') & remaining %~ subtract i0Dim' & skip .~ 0
312− add_maybe_clipped i1
315+ addMaybeClipped i1
313− | s >= i0_dim -> do
316+ | s >= i0Dim -> do
314− put $ state & skip -~ i0_dim
317+ put $ state & skip %~ subtract i0Dim
315− add_maybe_clipped i1
318+ addMaybeClipped i1
316 _ -> fail $ name ++ " has unhandled skip class"
319 _ -> fail $ name ++ " has unhandled skip class"
317
320
318−add_unclipped_text :: Attr -> DisplayText -> BlitM s ()
321+addUnclippedText :: Attr -> DisplayText -> BlitM s ()
319−add_unclipped_text a txt = do
322+addUnclippedText a txt = do
320− let op = TextSpan a used_display_columns
323+ let op = TextSpan a usedDisplayColumns
321 (fromIntegral $ TL.length txt)
324 (fromIntegral $ TL.length txt)
322 txt
325 txt
323− used_display_columns = wcswidth $ TL.unpack txt
326+ usedDisplayColumns = wctlwidth txt
324− use row_offset >>= snoc_op op
327+ use rowOffset >>= snocOp op
325
328
326−add_row_completion :: DisplayRegion -> Int -> BlitM s ()
329+addRowCompletion :: DisplayRegion -> Int -> BlitM s ()
327−add_row_completion display_region row = do
330+addRowCompletion displayRegion row = do
328− all_row_ops <- view mrow_ops
331+ allRowOps <- view mrowOps
329− row_ops <- lift $ lift $ MVector.read all_row_ops row
332+ rowOps <- lift $ lift $ MVector.read allRowOps row
330− let end_x = span_ops_effected_columns row_ops
333+ let endX = spanOpsAffectedColumns rowOps
331− when (end_x < region_width display_region) $ do
334+ when (endX < regionWidth displayRegion) $ do
332− let ow = region_width display_region - end_x
335+ let ow = regionWidth displayRegion - endX
333− snoc_op (Skip ow) row
336+ snocOp (Skip ow) row
334
337
335-- | snocs the operation to the operations for the given row.
338-- | snocs the operation to the operations for the given row.
336−snoc_op :: SpanOp -> Int -> BlitM s ()
339+snocOp :: SpanOp -> Int -> BlitM s ()
337−snoc_op !op !row = do
340+snocOp !op !row = do
338− the_mrow_ops <- view mrow_ops
341+ theMrowOps <- view mrowOps
339− the_region <- view region
342+ theRegion <- view region
340 lift $ lift $ do
343 lift $ lift $ do
341− ops <- MVector.read the_mrow_ops row
344+ ops <- MVector.read theMrowOps row
342 let ops' = Vector.snoc ops op
345 let ops' = Vector.snoc ops op
343− when (span_ops_effected_columns ops' > region_width the_region)
346+ when (spanOpsAffectedColumns ops' > regionWidth theRegion)
344 $ fail $ "row " ++ show row ++ " now exceeds region width"
347 $ fail $ "row " ++ show row ++ " now exceeds region width"
345− MVector.write the_mrow_ops row ops'
348+ MVector.write theMrowOps row ops'
346−
src/Graphics/Vty/Span.hs+89−98
@@ -1,97 +1,89 @@
1-- Copyright Corey O'Connor
1-- Copyright Corey O'Connor
2−{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE NamedFieldPuns #-}
4{-# LANGUAGE GADTs #-}
2{-# LANGUAGE GADTs #-}
5−{- | A picture is translated into a sequences of state changes and character spans.
3+-- | A picture is translated into a sequences of state changes and
6− - State changes are currently limited to new attribute values. The attribute is applied to all
4+-- character spans. The attribute is applied to all following spans,
7− - following spans. Including spans of the next row. The nth element of the sequence represents the
5+-- including spans of the next row. The nth element of the sequence
8− - nth row (from top to bottom) of the picture to render.
6+-- represents the nth row (from top to bottom) of the picture to render.
9− -
7+--
10− - A span op sequence will be defined for all rows and columns (and no more) of the region provided
8+-- A span op sequence will be defined for all rows and columns (and no
11− - with the picture to spans_for_pic.
9+-- more) of the region provided with the picture to 'spansForPic'.
12− -
13− - todo: Partition attribute changes into multiple categories according to the serialized
14− - representation of the various attributes.
15− -}
16−module Graphics.Vty.Span
10+module Graphics.Vty.Span where
17− where
18
11
19−import Graphics.Vty.DisplayRegion
12+import Graphics.Vty.Attributes (Attr)
20import Graphics.Vty.Image
13import Graphics.Vty.Image
14+import Graphics.Vty.Image.Internal ( clipText )
21
15
22−import Graphics.Vty.Image.Internal ( clip_text )
16+import qualified Data.Text.Lazy as TL
23−
24import Data.Vector (Vector)
17import Data.Vector (Vector)
25import qualified Data.Vector as Vector
18import qualified Data.Vector as Vector
26
19
27−import qualified Data.Text.Lazy as TL
28−
29−-- | This represents an operation on the terminal. Either an attribute change or the output of a
20+-- | This represents an operation on the terminal: either an attribute
30−-- text string.
21+-- change or the output of a text string.
31data SpanOp =
22data SpanOp =
32− -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
23+ -- | A span of UTF-8 text occupies a specific number of screen space
24+ -- columns. A single UTF character does not necessarily represent 1
33− -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
25+ -- colunm. See Codec.Binary.UTF8.Width TextSpan [Attr] [output width
34− -- TextSpan [Attr] [output width in columns] [number of characters] [data]
26+ -- in columns] [number of characters] [data]
35− TextSpan
27+ TextSpan
36− { text_span_attr :: !Attr
28+ { textSpanAttr :: !Attr
37− , text_span_output_width :: !Int
29+ , textSpanOutputWidth :: !Int
38− , text_span_char_width :: !Int
30+ , textSpanCharWidth :: !Int
39− , text_span_text :: DisplayText
31+ , textSpanText :: DisplayText
40 }
32 }
41− -- | Skips the given number of columns
33+ -- | Skips the given number of columns.
42− -- A skip is transparent.... maybe? I am not sure how attribute changes interact.
43− -- todo: separate from this type.
44 | Skip !Int
34 | Skip !Int
45− -- | Marks the end of a row. specifies how many columns are remaining. These columns will not be
35+ -- | Marks the end of a row. Specifies how many columns are
36+ -- remaining. These columns will not be explicitly overwritten with
46− -- explicitly overwritten with the span ops. The terminal is require to assure the remaining
37+ -- the span ops. The terminal is require to assure the remaining
47 -- columns are clear.
38 -- columns are clear.
48− -- todo: separate from this type.
49 | RowEnd !Int
39 | RowEnd !Int
50 deriving Eq
40 deriving Eq
51
41
52−-- | vector of span operations. executed in succession. This represents the operations required to
42+-- | A vector of span operations executed in succession. This represents
43+-- the operations required to render a row of the terminal. The
53−-- render a row of the terminal. The operations in one row may effect subsequent rows.
44+-- operations in one row may affect subsequent rows. For example,
54−-- EG: Setting the foreground color in one row will effect all subsequent rows until the foreground
45+-- setting the foreground color in one row will affect all subsequent
55−-- color is changed.
46+-- rows until the foreground color is changed.
56type SpanOps = Vector SpanOp
47type SpanOps = Vector SpanOp
57
48
58−drop_ops :: Int -> SpanOps -> SpanOps
49+dropOps :: Int -> SpanOps -> SpanOps
59−drop_ops w = snd . split_ops_at w
50+dropOps w = snd . splitOpsAt w
60
51
61−split_ops_at :: Int -> SpanOps -> (SpanOps, SpanOps)
52+splitOpsAt :: Int -> SpanOps -> (SpanOps, SpanOps)
62−split_ops_at in_w in_ops = split_ops_at' in_w in_ops
53+splitOpsAt = splitOpsAt'
63 where
54 where
64− split_ops_at' 0 ops = (Vector.empty, ops)
55+ splitOpsAt' 0 ops = (Vector.empty, ops)
65− split_ops_at' remaining_columns ops = case Vector.head ops of
56+ splitOpsAt' remainingColumns ops = case Vector.head ops of
66− t@(TextSpan {}) -> if remaining_columns >= text_span_output_width t
57+ t@(TextSpan {}) -> if remainingColumns >= textSpanOutputWidth t
67− then let (pre,post) = split_ops_at' (remaining_columns - text_span_output_width t) (Vector.tail ops)
58+ then let (pre,post) = splitOpsAt' (remainingColumns - textSpanOutputWidth t)
59+ (Vector.tail ops)
68 in (Vector.cons t pre, post)
60 in (Vector.cons t pre, post)
69− else let pre_txt = clip_text (text_span_text t) 0 remaining_columns
61+ else let preTxt = clipText (textSpanText t) 0 remainingColumns
70− pre_op = TextSpan { text_span_attr = text_span_attr t
62+ preOp = TextSpan { textSpanAttr = textSpanAttr t
71− , text_span_output_width = remaining_columns
63+ , textSpanOutputWidth = remainingColumns
72− , text_span_char_width = fromIntegral $! TL.length pre_txt
64+ , textSpanCharWidth = fromIntegral $! TL.length preTxt
73− , text_span_text = pre_txt
65+ , textSpanText = preTxt
74 }
66 }
75− post_width = text_span_output_width t - remaining_columns
67+ postWidth = textSpanOutputWidth t - remainingColumns
76− post_txt = clip_text (text_span_text t) remaining_columns post_width
68+ postTxt = clipText (textSpanText t) remainingColumns postWidth
77− post_op = TextSpan { text_span_attr = text_span_attr t
69+ postOp = TextSpan { textSpanAttr = textSpanAttr t
78− , text_span_output_width = post_width
70+ , textSpanOutputWidth = postWidth
79− , text_span_char_width = fromIntegral $! TL.length post_txt
71+ , textSpanCharWidth = fromIntegral $! TL.length postTxt
80− , text_span_text = post_txt
72+ , textSpanText = postTxt
81 }
73 }
82− in ( Vector.singleton pre_op
74+ in ( Vector.singleton preOp
83− , Vector.cons post_op (Vector.tail ops)
75+ , Vector.cons postOp (Vector.tail ops)
84 )
76 )
85− Skip w -> if remaining_columns >= w
77+ Skip w -> if remainingColumns >= w
86− then let (pre,post) = split_ops_at' (remaining_columns - w) (Vector.tail ops)
78+ then let (pre,post) = splitOpsAt' (remainingColumns - w) (Vector.tail ops)
87 in (Vector.cons (Skip w) pre, post)
79 in (Vector.cons (Skip w) pre, post)
88− else ( Vector.singleton $ Skip remaining_columns
80+ else ( Vector.singleton $ Skip remainingColumns
89− , Vector.cons (Skip (w - remaining_columns)) (Vector.tail ops)
81+ , Vector.cons (Skip (w - remainingColumns)) (Vector.tail ops)
90 )
82 )
91 RowEnd _ -> error "cannot split ops containing a row end"
83 RowEnd _ -> error "cannot split ops containing a row end"
92−
93
84
94−-- | vector of span operation vectors for display. One per row of the output region.
85+-- | A vector of span operation vectors for display, one per row of the
86+-- output region.
95type DisplayOps = Vector SpanOps
87type DisplayOps = Vector SpanOps
96
88
97instance Show SpanOp where
89instance Show SpanOp where
@@ -99,40 +91,39 @@ instance Show SpanOp where
99 show (Skip ow) = "Skip(" ++ show ow ++ ")"
91 show (Skip ow) = "Skip(" ++ show ow ++ ")"
100 show (RowEnd ow) = "RowEnd(" ++ show ow ++ ")"
92 show (RowEnd ow) = "RowEnd(" ++ show ow ++ ")"
101
93
102−-- | Number of columns the DisplayOps are defined for
94+-- | The number of columns the DisplayOps are defined for.
103--
95--
104−-- All spans are verified to define same number of columns. See: VerifySpanOps
96+-- All spans are verified to define same number of columns.
105−display_ops_columns :: DisplayOps -> Int
97+displayOpsColumns :: DisplayOps -> Int
106−display_ops_columns ops
98+displayOpsColumns ops
107 | Vector.length ops == 0 = 0
99 | Vector.length ops == 0 = 0
108 | otherwise = Vector.length $ Vector.head ops
100 | otherwise = Vector.length $ Vector.head ops
109
101
110−-- | Number of rows the DisplayOps are defined for
102+-- | The number of rows the DisplayOps are defined for.
111−display_ops_rows :: DisplayOps -> Int
103+displayOpsRows :: DisplayOps -> Int
112−display_ops_rows ops = Vector.length ops
104+displayOpsRows = Vector.length
113−
114−effected_region :: DisplayOps -> DisplayRegion
115−effected_region ops = DisplayRegion (display_ops_columns ops) (display_ops_rows ops)
116−
117−-- | The number of columns a SpanOps effects.
118−span_ops_effected_columns :: SpanOps -> Int
119−span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops
120− where
121− span_ops_effected_columns' t (TextSpan _ w _ _ ) = t + w
122− span_ops_effected_columns' t (Skip w) = t + w
123− span_ops_effected_columns' t (RowEnd w) = t + w
124
105
125−-- | The width of a single SpanOp in columns
106+affectedRegion :: DisplayOps -> DisplayRegion
126−span_op_has_width :: SpanOp -> Maybe (Int, Int)
107+affectedRegion ops = (displayOpsColumns ops, displayOpsRows ops)
127−span_op_has_width (TextSpan _ ow cw _) = Just (cw, ow)
128−span_op_has_width (Skip ow) = Just (ow,ow)
129−span_op_has_width (RowEnd ow) = Just (ow,ow)
130−
131−-- | returns the number of columns to the character at the given position in the span op
132−columns_to_char_offset :: Int -> SpanOp -> Int
133−columns_to_char_offset cx (TextSpan _ _ _ utf8_str) =
134− let str = TL.unpack utf8_str
135− in wcswidth (take cx str)
136−columns_to_char_offset cx (Skip _) = cx
137−columns_to_char_offset cx (RowEnd _) = cx
138
108
109+-- | The number of columns a SpanOps affects.
110+spanOpsAffectedColumns :: SpanOps -> Int
111+spanOpsAffectedColumns inOps = Vector.foldl' spanOpsAffectedColumns' 0 inOps
112+ where
113+ spanOpsAffectedColumns' t (TextSpan _ w _ _ ) = t + w
114+ spanOpsAffectedColumns' t (Skip w) = t + w
115+ spanOpsAffectedColumns' t (RowEnd w) = t + w
116+
117+-- | The width of a single SpanOp in columns.
118+spanOpHasWidth :: SpanOp -> Maybe (Int, Int)
119+spanOpHasWidth (TextSpan _ ow cw _) = Just (cw, ow)
120+spanOpHasWidth (Skip ow) = Just (ow,ow)
121+spanOpHasWidth (RowEnd ow) = Just (ow,ow)
122+
123+-- | The number of columns to the character at the given position in the
124+-- span op.
125+columnsToCharOffset :: Int -> SpanOp -> Int
126+columnsToCharOffset cx (TextSpan _ _ _ utf8Str) =
127+ wctlwidth (TL.take (fromIntegral cx) utf8Str)
128+columnsToCharOffset cx (Skip _) = cx
129+columnsToCharOffset cx (RowEnd _) = cx
src/Graphics/Vty/Terminal.hsdeleted−146
@@ -1,146 +0,0 @@
1−-- | Terminal IO device.
2−--
3−-- Access to the current terminal or a specific terminal device.
4−--
5−-- See also:
6−--
7−-- 1. Graphics.Vty.Terminal: This instantiates an abtract interface to the terminal interface based
8−-- on the TERM and COLORTERM environment variables.
9−--
10−-- 2. Graphics.Vty.Terminal.Interface: Defines the generic interface all terminals need to implement.
11−--
12−-- 3. Graphics.Vty.Terminal.TerminfoBased: Defines a terminal instance that uses terminfo for all
13−-- control strings. No attempt is made to change the character set to UTF-8 for these terminals.
14−-- I don't know a way to reliably determine if that is required or how to do so.
15−--
16−-- 4. Graphics.Vty.Terminal.XTermColor: This module contains an interface suitable for xterm-like
17−-- terminals. These are the terminals where TERM == xterm. This does use terminfo for as many
18−-- control codes as possible.
19−--
20−-- Copyright 2009-2010 Corey O'Connor
21−module Graphics.Vty.Terminal ( module Graphics.Vty.Terminal
22− , Terminal(..) -- \todo hide constructors
23− , AssumedState(..)
24− , DisplayContext(..) -- \todo hide constructors
25− , output_picture
26− , display_context
27− )
28− where
29−
30−
31−import Graphics.Vty.DisplayRegion
32−import Graphics.Vty.Terminal.Interface
33−import Graphics.Vty.Terminal.MacOSX as MacOSX
34−import Graphics.Vty.Terminal.XTermColor as XTermColor
35−import Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
36−
37−import Control.Applicative
38−import Control.Exception ( SomeException, try )
39−import Control.Monad
40−import Control.Monad.Trans
41−
42−import Data.List ( isPrefixOf )
43−
44−import GHC.IO.Handle
45−
46−import System.Environment
47−import System.IO
48−
49−-- | Returns a `Terminal` for the current terminal.
50−--
51−-- The specific Terminal implementation used is hidden from the API user. All terminal
52−-- implementations are assumed to perform more, or less, the same. Currently all implementations use
53−-- terminfo for at least some terminal specific information. This is why platforms without terminfo
54−-- are not supported. However, as mentioned before, any specifics about it being based on terminfo
55−-- are hidden from the API user. If a terminal implementation is developed for a terminal for a
56−-- platform without terminfo support then Vty should work as expected on that terminal.
57−--
58−-- Selection of a terminal is done as follows:
59−--
60−-- * If TERM == xterm
61−-- then the terminal might be one of the Mac OS X .app terminals. Check if that might be
62−-- the case and use MacOSX if so.
63−-- otherwise use XTermColor.
64−--
65−-- * for any other TERM value TerminfoBased is used.
66−--
67−--
68−-- The terminal has to be determined dynamically at runtime. To satisfy this requirement all
69−-- terminals instances are lifted into an abstract terminal handle via existential qualification.
70−-- This implies that the only equations that can used are those in the terminal class.
71−--
72−-- To differentiate between Mac OS X terminals this uses the TERM_PROGRAM environment variable.
73−-- However, an xterm started by Terminal or iTerm *also* has TERM_PROGRAM defined since the
74−-- environment variable is not reset/cleared by xterm. However a Terminal.app or iTerm.app started
75−-- from an xterm under X11 on mac os x will likely be done via open. Since this does not propogate
76−-- environment variables (I think?) this assumes that XTERM_VERSION will never be set for a true
77−-- Terminal.app or iTerm.app session.
78−--
79−--
80−-- The file descriptor used for output will a duplicate of the current stdout file descriptor.
81−--
82−-- todo: add an implementation for windows that does not depend on terminfo. Should be installable
83−-- with only what is provided in the haskell platform.
84−--
85−-- todo: The Terminal interface does not provide any input support.
86−current_terminal :: ( Applicative m, MonadIO m ) => m Terminal
87−current_terminal = do
88− term_type <- liftIO $ getEnv "TERM"
89− out_handle <- liftIO $ hDuplicate stdout
90− terminal_with_name_and_io term_type out_handle
91−
92−terminal_with_name_and_io :: (Applicative m, MonadIO m) => String -> Handle -> m Terminal
93−terminal_with_name_and_io term_type out_handle = do
94− t <- if "xterm" `isPrefixOf` term_type
95− then do
96− maybe_terminal_app <- get_env "TERM_PROGRAM"
97− case maybe_terminal_app of
98− Nothing
99− -> XTermColor.reserve_terminal term_type out_handle
100− Just v | v == "Apple_Terminal" || v == "iTerm.app"
101− -> do
102− maybe_xterm <- get_env "XTERM_VERSION"
103− case maybe_xterm of
104− Nothing -> MacOSX.reserve_terminal v out_handle
105− Just _ -> XTermColor.reserve_terminal term_type out_handle
106− -- Assume any other terminal that sets TERM_PROGRAM to not be an OS X terminal.app
107− -- like terminal?
108− _ -> XTermColor.reserve_terminal term_type out_handle
109− -- Not an xterm-like terminal. try for generic terminfo.
110− else TerminfoBased.reserve_terminal term_type out_handle
111− return t
112− where
113− get_env var = do
114− mv <- liftIO $ try $ getEnv var
115− case mv of
116− Left (_e :: SomeException) -> return $ Nothing
117− Right v -> return $ Just v
118−
119−-- | Sets the cursor position to the given output column and row.
120−--
121−-- This is not necessarially the same as the character position with the same coordinates.
122−-- Characters can be a variable number of columns in width.
123−--
124−-- Currently, the only way to set the cursor position to a given character coordinate is to specify
125−-- the coordinate in the Picture instance provided to output_picture or refresh.
126−set_cursor_pos :: MonadIO m => Terminal -> Int -> Int -> m ()
127−set_cursor_pos t x y = do
128− bounds <- display_bounds t
129− when (x >= 0 && x < region_width bounds && y >= 0 && y < region_height bounds) $ do
130− dc <- display_context t bounds
131− liftIO $ send_to_terminal t (move_cursor_required_bytes dc x y) (serialize_move_cursor dc x y)
132−
133−-- | Hides the cursor
134−hide_cursor :: MonadIO m => Terminal -> m ()
135−hide_cursor t = do
136− bounds <- display_bounds t
137− dc <- display_context t bounds
138− liftIO $ send_to_terminal t (hide_cursor_required_bytes dc) (serialize_hide_cursor dc)
139−
140−-- | Shows the cursor
141−show_cursor :: MonadIO m => Terminal -> m ()
142−show_cursor t = do
143− bounds <- display_bounds t
144− dc <- display_context t bounds
145− liftIO $ send_to_terminal t (show_cursor_required_bytes dc) (serialize_show_cursor dc)
146−
src/Graphics/Vty/Terminal/Interface.hsdeleted−344
@@ -1,344 +0,0 @@
1−-- Copyright Corey O'Connor
2−-- General philosophy is: MonadIO is for equations exposed to clients.
3−{-# LANGUAGE BangPatterns #-}
4−{-# LANGUAGE RankNTypes #-}
5−{-# LANGUAGE ExistentialQuantification #-}
6−{-# LANGUAGE GADTs #-}
7−{-# LANGUAGE FlexibleContexts #-}
8−module Graphics.Vty.Terminal.Interface ( module Graphics.Vty.Terminal.Interface
9− , OutputBuffer
10− )
11− where
12−
13−import Data.Marshalling
14−
15−import Graphics.Vty.Picture
16−import Graphics.Vty.PictureToSpans
17−import Graphics.Vty.Span
18−import Graphics.Vty.DisplayRegion
19−
20−import Graphics.Vty.DisplayAttributes
21−
22−import Control.Monad.Trans
23−
24−import qualified Data.ByteString.Internal as BS
25−import qualified Data.Text.Encoding as T
26−import qualified Data.Text.Lazy as TL
27−import Data.IORef
28−import qualified Data.Vector as Vector
29−
30−data Terminal = Terminal
31− { -- | Text identifier for the terminal. Used for debugging.
32− terminal_ID :: String
33− , release_terminal :: MonadIO m => m ()
34− -- | Clear the display and initialize the terminal to some initial display state.
35− --
36− -- The expectation of a program is that the display starts in some initial state.
37− -- The initial state would consist of fixed values:
38− -- - cursor at top left
39− -- - UTF-8 character encoding
40− -- - drawing characteristics are the default
41− -- The abstract operation I think all these behaviors are instances of is reserving exclusive
42− -- access to a display such that:
43− -- - The previous state cannot be determined
44− -- - When exclusive access to a display is released the display returns to the previous state.
45− , reserve_display :: MonadIO m => m ()
46− -- | Return the display to the state before reserve_display
47− -- If no previous state then set the display state to the initial state.
48− , release_display :: MonadIO m => m ()
49− -- | Returns the current display bounds.
50− , display_bounds :: MonadIO m => m DisplayRegion
51− -- | Output the byte buffer of the specified size to the terminal device. The size is equal to
52− -- end_ptr - start_ptr
53− , output_byte_buffer :: OutputBuffer -> Int -> IO ()
54− -- | Maximum number of colors supported by the context.
55− , context_color_count :: Int
56− -- | if the cursor can be shown / hidden
57− , supports_cursor_visibility :: Bool
58− , assumed_state_ref :: IORef AssumedState
59− -- | Acquire display access to the given region of the display.
60− -- Currently all regions have the upper left corner of (0,0) and the lower right corner at
61− -- (max display_width provided_width, max display_height provided_height)
62− , mk_display_context :: MonadIO m => Terminal -> DisplayRegion -> m DisplayContext
63− }
64−
65−display_context :: MonadIO m => Terminal -> DisplayRegion -> m DisplayContext
66−display_context t = liftIO . mk_display_context t t
67−
68−data AssumedState = AssumedState
69− { prev_fattr :: Maybe FixedAttr
70− , prev_output_ops :: Maybe DisplayOps
71− }
72−
73−initial_assumed_state :: AssumedState
74−initial_assumed_state = AssumedState Nothing Nothing
75−
76−data DisplayContext = DisplayContext
77− { context_device :: Terminal
78− -- | Provide the bounds of the display context.
79− , context_region :: DisplayRegion
80− -- | sets the output position to the specified row and column. Where the number of bytes
81− -- required for the control codes can be specified seperate from the actual byte sequence.
82− , move_cursor_required_bytes :: Int -> Int -> Int
83− , serialize_move_cursor :: Int -> Int -> OutputBuffer -> IO OutputBuffer
84− , show_cursor_required_bytes :: Int
85− , serialize_show_cursor :: OutputBuffer -> IO OutputBuffer
86− , hide_cursor_required_bytes :: Int
87− , serialize_hide_cursor :: OutputBuffer -> IO OutputBuffer
88− -- | Assure the specified output attributes will be applied to all the following text until the
89− -- next output attribute change. Where the number of bytes required for the control codes can
90− -- be specified seperate from the actual byte sequence. The required number of bytes must be
91− -- at least the maximum number of bytes required by any attribute changes. The serialization
92− -- equations must provide the ptr to the next byte to be specified in the output buffer.
93− --
94− -- The currently applied display attributes are provided as well. The Attr data type can
95− -- specify the style or color should not be changed from the currently applied display
96− -- attributes. In order to support this the currently applied display attributes are required.
97− -- In addition it may be possible to optimize the state changes based off the currently applied
98− -- display attributes.
99− , attr_required_bytes :: FixedAttr -> Attr -> DisplayAttrDiff -> Int
100− , serialize_set_attr :: FixedAttr -> Attr -> DisplayAttrDiff -> OutputBuffer -> IO OutputBuffer
101− -- | Reset the display attributes to the default display attributes
102− , default_attr_required_bytes :: Int
103− , serialize_default_attr :: OutputBuffer -> IO OutputBuffer
104− , row_end_required_bytes :: Int
105− , serialize_row_end :: OutputBuffer -> IO OutputBuffer
106− -- | See Graphics.Vty.Terminal.XTermColor.inline_hack
107− , inline_hack :: IO ()
108− }
109−
110−-- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.
111−utf8_text_required_bytes :: BS.ByteString -> Int
112−utf8_text_required_bytes str =
113− let (_, _, src_bytes_length) = BS.toForeignPtr str
114− in src_bytes_length
115−
116−-- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.
117−serialize_utf8_text :: BS.ByteString -> OutputBuffer -> IO OutputBuffer
118−serialize_utf8_text str dest_ptr =
119− let (src_fptr, src_ptr_offset, src_bytes_length) = BS.toForeignPtr str
120− in withForeignPtr src_fptr $ \src_ptr -> do
121− let src_ptr' = src_ptr `plusPtr` src_ptr_offset
122− BS.memcpy dest_ptr src_ptr' (toEnum src_bytes_length)
123− return (dest_ptr `plusPtr` src_bytes_length)
124−
125−-- | Displays the given `Picture`.
126−--
127−-- 0. The image is cropped to the display size.
128−--
129−-- 1. Converted into a sequence of attribute changes and text spans.
130−--
131−-- 2. The cursor is hidden.
132−--
133−-- 3. Serialized to the display.
134−--
135−-- 4. The cursor is then shown and positioned or kept hidden.
136−--
137−-- todo: specify possible IO exceptions.
138−-- abstract from IO monad to a MonadIO instance.
139−output_picture :: MonadIO m => DisplayContext -> Picture -> m ()
140−output_picture dc pic = liftIO $ do
141− as <- readIORef (assumed_state_ref $ context_device dc)
142− let manip_cursor = supports_cursor_visibility (context_device dc)
143− let !r = context_region dc
144− !ops = display_ops_for_pic pic r
145− !initial_attr = FixedAttr default_style_mask Nothing Nothing
146− -- Diff the previous output against the requested output. Differences are currently on a per-row
147− -- basis.
148− -- \todo handle resizes that crop the dominate directions better.
149− diffs :: [Bool] = case prev_output_ops as of
150− Nothing -> replicate (fromEnum $ region_height $ effected_region ops) True
151− Just previous_ops -> if effected_region previous_ops /= effected_region ops
152− then replicate (display_ops_rows ops) True
153− else zipWith (/=) (Vector.toList previous_ops)
154− (Vector.toList ops)
155− -- determine the number of bytes required to completely serialize the output ops.
156− total = (if manip_cursor then hide_cursor_required_bytes dc else 0)
157− + default_attr_required_bytes dc
158− + required_bytes dc initial_attr diffs ops
159− + case pic_cursor pic of
160− _ | not manip_cursor -> 0
161− NoCursor -> 0
162− Cursor x y ->
163− let { m = cursor_output_map ops $ pic_cursor pic; (ox, oy) = char_to_output_pos m ( x, y ) }
164− in show_cursor_required_bytes dc + move_cursor_required_bytes dc ox oy
165− -- ... then serialize
166− allocaBytes (fromEnum total) $ \start_ptr -> do
167− ptr <- case manip_cursor of
168− True -> serialize_hide_cursor dc start_ptr
169− False -> return start_ptr
170− ptr' <- serialize_default_attr dc ptr
171− ptr'' <- serialize_output_ops dc ptr' initial_attr diffs ops
172− end_ptr <- case pic_cursor pic of
173− _ | not manip_cursor -> return ptr''
174− NoCursor -> return ptr''
175− Cursor x y -> do
176− let m = cursor_output_map ops $ pic_cursor pic
177− (ox, oy) = char_to_output_pos m (x,y)
178− serialize_show_cursor dc ptr'' >>= serialize_move_cursor dc ox oy
179− -- todo: How to handle exceptions?
180− -- probably set the prev_output_ops to Nothing
181− case end_ptr `minusPtr` start_ptr of
182− count | count < 0 -> fail "End pointer before start of buffer."
183− | toEnum count > total -> fail $ "End pointer past end of buffer by " ++ show (toEnum count - total)
184− | otherwise -> output_byte_buffer (context_device dc) start_ptr (toEnum count)
185− -- Cache the output spans.
186− let as' = as { prev_output_ops = Just ops }
187− writeIORef (assumed_state_ref $ context_device dc) as'
188−
189−required_bytes :: DisplayContext -> FixedAttr -> [Bool] -> DisplayOps -> Int
190−required_bytes dc in_fattr diffs ops =
191− let (_, n, _, _) = Vector.foldl' required_bytes' (0, 0, in_fattr, diffs) ops
192− in n
193− where
194− required_bytes' (y, current_sum, fattr, True : diffs') span_ops
195− = let (s, fattr') = span_ops_required_bytes dc y fattr span_ops
196− in ( y + 1, s + current_sum, fattr', diffs' )
197− required_bytes' (y, current_sum, fattr, False : diffs') _span_ops
198− = ( y + 1, current_sum, fattr, diffs' )
199− required_bytes' (_y, _current_sum, _fattr, [] ) _span_ops
200− = error "shouldn't be possible"
201−
202−span_ops_required_bytes :: DisplayContext -> Int -> FixedAttr -> SpanOps -> (Int, FixedAttr)
203−span_ops_required_bytes dc y in_fattr span_ops =
204− -- The first operation is to set the cursor to the start of the row
205− let header_required_bytes = move_cursor_required_bytes dc 0 y
206− -- then the span ops are serialized in the order specified
207− in Vector.foldl' ( \(current_sum, fattr) op -> let (c, fattr') = span_op_required_bytes dc fattr op
208− in (c + current_sum, fattr')
209− )
210− (header_required_bytes, in_fattr)
211− span_ops
212−
213−span_op_required_bytes :: DisplayContext -> FixedAttr -> SpanOp -> (Int, FixedAttr)
214−span_op_required_bytes dc fattr (TextSpan attr _ _ str) =
215− let attr' = limit_attr_for_display (context_device dc) attr
216− diffs = display_attr_diffs fattr fattr'
217− c = attr_required_bytes dc fattr attr' diffs
218− fattr' = fix_display_attr fattr attr'
219− in (c + utf8_text_required_bytes (T.encodeUtf8 $ TL.toStrict str), fattr')
220−span_op_required_bytes _dc _fattr (Skip _) = error "span_op_required_bytes for Skip."
221−span_op_required_bytes dc fattr (RowEnd _) = (row_end_required_bytes dc, fattr)
222−
223−serialize_output_ops :: DisplayContext
224− -> OutputBuffer
225− -> FixedAttr
226− -> [Bool]
227− -> DisplayOps
228− -> IO OutputBuffer
229−serialize_output_ops dc start_ptr in_fattr diffs ops = do
230− (_, end_ptr, _, _) <- Vector.foldM' serialize_output_ops'
231− ( 0, start_ptr, in_fattr, diffs )
232− ops
233− return end_ptr
234− where
235− serialize_output_ops' ( y, out_ptr, fattr, True : diffs' ) span_ops
236− = serialize_span_ops dc y out_ptr fattr span_ops
237− >>= return . ( \(out_ptr', fattr') -> ( y + 1, out_ptr', fattr', diffs' ) )
238− serialize_output_ops' ( y, out_ptr, fattr, False : diffs' ) _span_ops
239− = return ( y + 1, out_ptr, fattr, diffs' )
240− serialize_output_ops' (_y, _out_ptr, _fattr, [] ) _span_ops
241− = error "shouldn't be possible"
242−
243−serialize_span_ops :: DisplayContext
244− -> Int
245− -> OutputBuffer
246− -> FixedAttr
247− -> SpanOps
248− -> IO (OutputBuffer, FixedAttr)
249−serialize_span_ops dc y out_ptr in_fattr span_ops = do
250− -- The first operation is to set the cursor to the start of the row
251− out_ptr' <- serialize_move_cursor dc 0 y out_ptr
252− -- then the span ops are serialized in the order specified
253− Vector.foldM ( \(out_ptr'', fattr) op -> serialize_span_op dc op out_ptr'' fattr )
254− (out_ptr', in_fattr)
255− span_ops
256−
257−-- |
258−-- TODO: move this into the terminal implementation?
259−serialize_span_op :: DisplayContext
260− -> SpanOp
261− -> OutputBuffer
262− -> FixedAttr
263− -> IO (OutputBuffer, FixedAttr)
264−serialize_span_op dc (TextSpan attr _ _ str) out_ptr fattr = do
265− let attr' = limit_attr_for_display (context_device dc) attr
266− fattr' = fix_display_attr fattr attr'
267− diffs = display_attr_diffs fattr fattr'
268− out_ptr' <- serialize_set_attr dc fattr attr' diffs out_ptr
269− out_ptr'' <- serialize_utf8_text (T.encodeUtf8 $ TL.toStrict str) out_ptr'
270− return (out_ptr'', fattr')
271−serialize_span_op _dc (Skip _) _out_ptr _fattr = error "serialize_span_op for Skip"
272−serialize_span_op dc (RowEnd _) out_ptr fattr = do
273− out_ptr' <- serialize_row_end dc out_ptr
274− return (out_ptr', fattr)
275−
276−send_to_terminal :: Terminal -> Int -> (Ptr Word8 -> IO (Ptr Word8)) -> IO ()
277−send_to_terminal t c f = allocaBytes (fromEnum c) $ \start_ptr -> do
278− end_ptr <- f start_ptr
279− case end_ptr `minusPtr` start_ptr of
280− count | count < 0 -> fail "End pointer before start pointer."
281− | toEnum count > c -> fail $ "End pointer past end of buffer by " ++ show (toEnum count - c)
282− | otherwise -> output_byte_buffer t start_ptr (toEnum count)
283−
284−-- | The cursor position is given in X,Y character offsets. Due to multi-column characters this
285−-- needs to be translated to column, row positions.
286−data CursorOutputMap = CursorOutputMap
287− { char_to_output_pos :: (Int, Int) -> (Int, Int)
288− }
289−
290−cursor_output_map :: DisplayOps -> Cursor -> CursorOutputMap
291−cursor_output_map span_ops _cursor = CursorOutputMap
292− { char_to_output_pos = \(cx, cy) -> (cursor_column_offset span_ops cx cy, cy)
293− }
294−
295−cursor_column_offset :: DisplayOps -> Int -> Int -> Int
296−cursor_column_offset ops cx cy =
297− let cursor_row_ops = Vector.unsafeIndex ops (fromEnum cy)
298− (out_offset, _, _)
299− = Vector.foldl' ( \(d, current_cx, done) op ->
300− if done then (d, current_cx, done) else case span_op_has_width op of
301− Nothing -> (d, current_cx, False)
302− Just (cw, ow) -> case compare cx (current_cx + cw) of
303− GT -> ( d + ow
304− , current_cx + cw
305− , False
306− )
307− EQ -> ( d + ow
308− , current_cx + cw
309− , True
310− )
311− LT -> ( d + columns_to_char_offset (cx - current_cx) op
312− , current_cx + cw
313− , True
314− )
315− )
316− (0, 0, False)
317− cursor_row_ops
318− in out_offset
319−
320−-- | Not all terminals support all display attributes. This filters a display attribute to what the
321−-- given terminal can display.
322−limit_attr_for_display :: Terminal -> Attr -> Attr
323−limit_attr_for_display t attr
324− = attr { attr_fore_color = clamp_color $ attr_fore_color attr
325− , attr_back_color = clamp_color $ attr_back_color attr
326− }
327− where
328− clamp_color Default = Default
329− clamp_color KeepCurrent = KeepCurrent
330− clamp_color (SetTo c) = clamp_color' c
331− clamp_color' (ISOColor v)
332− | context_color_count t < 8 = Default
333− | context_color_count t < 16 && v >= 8 = SetTo $ ISOColor (v - 8)
334− | otherwise = SetTo $ ISOColor v
335− clamp_color' (Color240 v)
336− -- TODO: Choose closes ISO color?
337− | context_color_count t < 8 = Default
338− | context_color_count t < 16 = Default
339− | context_color_count t == 240 = SetTo $ Color240 v
340− | otherwise
341− = let p :: Double = fromIntegral v / 240.0
342− v' = floor $ p * (fromIntegral $ context_color_count t)
343− in SetTo $ Color240 v'
344−
src/Graphics/Vty/Terminal/MacOSX.hsdeleted−66
@@ -1,66 +0,0 @@
1−-- Copyright 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 MagicHash #-}
9−{-# LANGUAGE FlexibleInstances #-}
10−{-# LANGUAGE TypeFamilies #-}
11−module Graphics.Vty.Terminal.MacOSX ( reserve_terminal )
12− where
13−
14−import Graphics.Vty.Terminal.Interface
15−import qualified Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
16−
17−import Control.Applicative
18−import Control.Monad.Trans
19−
20−import System.IO
21−
22−-- | for Terminal.app the terminal identifier "xterm" is used. For iTerm.app the terminal identifier
23−-- "xterm-256color" is used.
24−--
25−-- This effects the terminfo lookup.
26−reserve_terminal :: ( Applicative m, MonadIO m ) => String -> Handle -> m Terminal
27−reserve_terminal v out_handle = do
28− let remap_term "iTerm.app" = "xterm-256color"
29− remap_term _ = "xterm"
30− flushed_put :: String -> IO ()
31− flushed_put str = do
32− hPutStr out_handle str
33− hFlush out_handle
34− t <- TerminfoBased.reserve_terminal (remap_term v) out_handle
35− let t' = t
36− { terminal_ID = terminal_ID t ++ " (Mac)"
37− , reserve_display = terminal_app_reserve_display flushed_put
38− , release_display = terminal_app_release_display flushed_put
39− }
40− return t'
41−
42−
43−-- | Terminal.app requires the xterm-color smcup and rmcup caps. Not the generic xterm ones.
44−-- Otherwise, Terminal.app expects the xterm caps.
45−smcup_str, rmcup_str :: String
46−smcup_str = "\ESC7\ESC[?47h"
47−rmcup_str = "\ESC[2J\ESC[?47l\ESC8"
48−
49−-- | always smcup then clear the screen on terminal.app
50−--
51−-- \todo really?
52−terminal_app_reserve_display :: MonadIO m => (String -> IO ()) -> m ()
53−terminal_app_reserve_display flushed_put = liftIO $ do
54− flushed_put smcup_str
55− flushed_put clear_screen_str
56−
57−terminal_app_release_display :: MonadIO m => (String -> IO ()) -> m ()
58−terminal_app_release_display flushed_put = liftIO $ do
59− flushed_put rmcup_str
60−
61−-- | iTerm needs a clear screen after smcup as well.
62−--
63−-- \todo but we apply to all mac terminals?
64−clear_screen_str :: String
65−clear_screen_str = "\ESC[H\ESC[2J"
66−
src/Graphics/Vty/Terminal/Mock.hsdeleted−94
@@ -1,94 +0,0 @@
1−-- Copyright Corey O'Connor
2−{-# LANGUAGE FlexibleInstances #-}
3−{-# LANGUAGE TypeFamilies #-}
4−-- | This provides a mock terminal implementation that is nice for testing. This transforms the
5−-- output operations to visible characters. Which is nice for some tests.
6−module Graphics.Vty.Terminal.Mock ( MockData, mock_terminal )
7− where
8−
9−import Graphics.Vty.DisplayRegion
10−import Graphics.Vty.Terminal.Interface
11−
12−import Control.Applicative
13−import Control.Monad.Trans
14−
15−import qualified Data.ByteString as BS
16−import Data.IORef
17−import qualified Data.String.UTF8 as UTF8
18−
19−import Foreign.Marshal.Array ( peekArray )
20−import Foreign.Ptr ( plusPtr )
21−import Foreign.Storable ( poke )
22−
23−type MockData = IORef (UTF8.UTF8 BS.ByteString)
24−
25−-- | The mock display terminal produces a string representation of the requested picture. There is
26−-- *not* an isomorphism between the string representation and the picture. The string
27−-- representation is a simplification of the picture that is only useful in debugging VTY without
28−-- considering terminal specific issues.
29−--
30−-- The mock implementation is useful in manually determining if the sequence of terminal operations
31−-- matches the expected sequence. So requirement of the produced representation is simplicity in
32−-- parsing the text representation and determining how the picture was mapped to terminal
33−-- operations.
34−--
35−-- All terminals support the operations specified in the Terminal class defined in
36−-- Graphics.Vty.Terminal. As an instance of the Terminal class is also an instance of the Monad
37−-- class there exists a monoid that defines it's algebra. The string representation is a sequence of
38−-- identifiers where each identifier is the name of an operation in the algebra.
39−mock_terminal :: (Applicative m, MonadIO m) => DisplayRegion -> m (MockData, Terminal)
40−mock_terminal r = liftIO $ do
41− out_ref <- newIORef undefined
42− new_assumed_state_ref <- newIORef initial_assumed_state
43− let t = Terminal
44− { terminal_ID = "mock terminal"
45− , release_terminal = return ()
46− , reserve_display = return ()
47− , release_display = return ()
48− , display_bounds = return r
49− , output_byte_buffer = \out_buffer buffer_size -> do
50− putStrLn $ "mock output_byte_buffer of " ++ show buffer_size ++ " bytes"
51− peekArray (fromEnum buffer_size) out_buffer
52− >>= return . UTF8.fromRep . BS.pack
53− >>= writeIORef out_ref
54− , context_color_count = 16
55− , supports_cursor_visibility = True
56− , assumed_state_ref = new_assumed_state_ref
57− , mk_display_context = \t_actual r_actual -> return $ DisplayContext
58− { context_region = r_actual
59− , context_device = t_actual
60− -- A cursor move is always visualized as the single character 'M'
61− , move_cursor_required_bytes = \_x _y -> 1
62− , serialize_move_cursor = \_x _y ptr -> do
63− poke ptr (toEnum $ fromEnum 'M')
64− return $ ptr `plusPtr` 1
65− -- Show cursor is always visualized as the single character 'S'
66− , show_cursor_required_bytes = 1
67− , serialize_show_cursor = \ptr -> do
68− poke ptr (toEnum $ fromEnum 'S')
69− return $ ptr `plusPtr` 1
70− -- Hide cursor is always visualized as the single character 'H'
71− , hide_cursor_required_bytes = 1
72− , serialize_hide_cursor = \ptr -> do
73− poke ptr (toEnum $ fromEnum 'H')
74− return $ ptr `plusPtr` 1
75− -- An attr change is always visualized as the single character 'A'
76− , attr_required_bytes = \_fattr _diffs _attr -> 1
77− , serialize_set_attr = \_fattr _diffs _attr ptr -> do
78− poke ptr (toEnum $ fromEnum 'A')
79− return $ ptr `plusPtr` 1
80− -- default attr is always visualized as the single character 'D'
81− , default_attr_required_bytes = 1
82− , serialize_default_attr = \ptr -> do
83− poke ptr (toEnum $ fromEnum 'D')
84− return $ ptr `plusPtr` 1
85− -- row end is always visualized as the single character 'E'
86− , row_end_required_bytes = 1
87− , serialize_row_end = \ptr -> do
88− poke ptr (toEnum $ fromEnum 'E')
89− return $ ptr `plusPtr` 1
90− , inline_hack = return ()
91− }
92− }
93− return (out_ref, t)
94−
src/Graphics/Vty/Terminal/TerminfoBased.hsdeleted−468
@@ -1,468 +0,0 @@
1−-- | Terminfo based terminal handling.
2−--
3−-- The color handling assumes tektronix like. No HP support provided. If the terminal is not one I
4−-- have easy access to then color support is entirely based of the docs. Probably with some
5−-- assumptions mixed in.
6−--
7−-- Copyright Corey O'Connor (coreyoconnor@gmail.com)
8−{-# LANGUAGE FlexibleInstances #-}
9−{-# OPTIONS_GHC -D_XOPEN_SOURCE=500 #-}
10−module Graphics.Vty.Terminal.TerminfoBased ( reserve_terminal )
11− where
12−
13−import Data.Terminfo.Parse
14−import Data.Terminfo.Eval
15−
16−import Graphics.Vty.Attributes
17−import Graphics.Vty.DisplayAttributes
18−import Graphics.Vty.Terminal.Interface
19−import Graphics.Vty.DisplayRegion
20−
21−import Control.Applicative
22−import Control.Monad ( foldM, when )
23−import Control.Monad.Trans
24−
25−import Data.Bits ( (.&.) )
26−import Data.IORef
27−import Data.Maybe ( isJust, isNothing, fromJust )
28−
29−import Foreign.C.Types ( CLong(..) )
30−
31−import GHC.IO.Handle
32−
33−import qualified System.Console.Terminfo as Terminfo
34−
35−data TerminfoCaps = TerminfoCaps
36− { smcup :: Maybe CapExpression
37− , rmcup :: Maybe CapExpression
38− , cup :: CapExpression
39− , cnorm :: Maybe CapExpression
40− , civis :: Maybe CapExpression
41− , supports_no_colors :: Bool
42− , use_alt_color_map :: Bool
43− , set_fore_color :: CapExpression
44− , set_back_color :: CapExpression
45− , set_default_attr :: CapExpression
46− , clear_screen :: CapExpression
47− , clear_eol :: CapExpression
48− , display_attr_caps :: DisplayAttrCaps
49− }
50−
51−data DisplayAttrCaps = DisplayAttrCaps
52− { set_attr_states :: Maybe CapExpression
53− , enter_standout :: Maybe CapExpression
54− , exit_standout :: Maybe CapExpression
55− , enter_underline :: Maybe CapExpression
56− , exit_underline :: Maybe CapExpression
57− , enter_reverse_video :: Maybe CapExpression
58− , enter_dim_mode :: Maybe CapExpression
59− , enter_bold_mode :: Maybe CapExpression
60− }
61−
62−send_cap_to_terminal :: Terminal -> CapExpression -> [CapParam] -> IO ()
63−send_cap_to_terminal t cap cap_params = do
64− send_to_terminal t (cap_expression_required_bytes cap cap_params)
65− (serialize_cap_expression cap cap_params)
66−
67−{- | Uses terminfo for all control codes. While this should provide the most compatible terminal
68− - terminfo does not support some features that would increase efficiency and improve compatibility:
69− - * determine the character encoding supported by the terminal. Should this be taken from the LANG
70− - environment variable?
71− - * Provide independent string capabilities for all display attributes.
72− -
73− -
74− - todo: Some display attributes like underline and bold have independent string capabilities that
75− - should be used instead of the generic "sgr" string capability.
76− -}
77−reserve_terminal :: ( Applicative m, MonadIO m ) => String -> Handle -> m Terminal
78−reserve_terminal in_ID out_handle = liftIO $ do
79− ti <- Terminfo.setupTerm in_ID
80− -- assumes set foreground always implies set background exists.
81− -- if set foreground is not set then all color changing style attributes are filtered.
82− msetaf <- probe_cap ti "setaf"
83− msetf <- probe_cap ti "setf"
84− let (no_colors, use_alt, set_fore_cap)
85− = case msetaf of
86− Just setaf -> (False, False, setaf)
87− Nothing -> case msetf of
88− Just setf -> (False, True, setf)
89− Nothing -> (True, True, error $ "no fore color support for terminal " ++ in_ID)
90− msetab <- probe_cap ti "setab"
91− msetb <- probe_cap ti "setb"
92− let set_back_cap
93− = case msetab of
94− Nothing -> case msetb of
95− Just setb -> setb
96− Nothing -> error $ "no back color support for terminal " ++ in_ID
97− Just setab -> setab
98− terminfo_caps <- pure TerminfoCaps
99− <*> probe_cap ti "smcup"
100− <*> probe_cap ti "rmcup"
101− <*> require_cap ti "cup"
102− <*> probe_cap ti "cnorm"
103− <*> probe_cap ti "civis"
104− <*> pure no_colors
105− <*> pure use_alt
106− <*> pure set_fore_cap
107− <*> pure set_back_cap
108− <*> require_cap ti "sgr0"
109− <*> require_cap ti "clear"
110− <*> require_cap ti "el"
111− <*> current_display_attr_caps ti
112− new_assumed_state_ref <- newIORef initial_assumed_state
113− let t = Terminal
114− { terminal_ID = in_ID
115− , release_terminal = liftIO $ do
116− send_cap set_default_attr []
117− maybe_send_cap cnorm []
118− hClose out_handle
119− , reserve_display = liftIO $ do
120− -- If there is no support for smcup: Clear the screen and then move the mouse to the
121− -- home position to approximate the behavior.
122− maybe_send_cap smcup []
123− hFlush out_handle
124− send_cap clear_screen []
125− , release_display = liftIO $ do
126− maybe_send_cap rmcup []
127− maybe_send_cap cnorm []
128− , display_bounds = do
129− raw_size <- liftIO $ get_window_size
130− case raw_size of
131− (w, h) | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show raw_size
132− | otherwise -> return $ DisplayRegion (toEnum w) (toEnum h)
133− , output_byte_buffer = \out_ptr out_byte_count -> do
134− hPutBuf out_handle out_ptr (fromEnum out_byte_count)
135− hFlush out_handle
136− , context_color_count
137− = case supports_no_colors terminfo_caps of
138− False -> case Terminfo.getCapability ti (Terminfo.tiGetNum "colors" ) of
139− Nothing -> 8
140− Just v -> toEnum v
141− True -> 1
142− , supports_cursor_visibility = isJust $ civis terminfo_caps
143− , assumed_state_ref = new_assumed_state_ref
144− -- I think fix would help assure t_actual is the only reference. I was having issues
145− -- tho.
146− , mk_display_context = \t_actual -> liftIO . terminfo_display_context t_actual terminfo_caps
147− }
148− send_cap s = send_cap_to_terminal t (s terminfo_caps)
149− maybe_send_cap s = when (isJust $ s terminfo_caps) . send_cap (fromJust . s)
150− return t
151−
152−require_cap :: (Applicative m, MonadIO m) => Terminfo.Terminal -> String -> m CapExpression
153−require_cap ti cap_name
154− = case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
155− Nothing -> fail $ "Terminal does not define required capability \"" ++ cap_name ++ "\""
156− Just cap_str -> parse_cap cap_str
157−
158−probe_cap :: (Applicative m, MonadIO m) => Terminfo.Terminal -> String -> m (Maybe CapExpression)
159−probe_cap ti cap_name
160− = case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
161− Nothing -> return Nothing
162− Just cap_str -> Just <$> parse_cap cap_str
163−
164−parse_cap :: (Applicative m, MonadIO m) => String -> m CapExpression
165−parse_cap cap_str = do
166− parse_result <- parse_cap_expression cap_str
167− case parse_result of
168− Left e -> fail $ show e
169− Right cap -> return cap
170−
171−current_display_attr_caps :: ( Applicative m, MonadIO m )
172− => Terminfo.Terminal
173− -> m DisplayAttrCaps
174−current_display_attr_caps ti
175− = pure DisplayAttrCaps
176− <*> probe_cap ti "sgr"
177− <*> probe_cap ti "smso"
178− <*> probe_cap ti "rmso"
179− <*> probe_cap ti "smul"
180− <*> probe_cap ti "rmul"
181− <*> probe_cap ti "rev"
182− <*> probe_cap ti "dim"
183− <*> probe_cap ti "bold"
184−
185−foreign import ccall "gwinsz.h vty_c_get_window_size" c_get_window_size :: IO CLong
186−
187−get_window_size :: IO (Int,Int)
188−get_window_size = do
189− (a,b) <- (`divMod` 65536) `fmap` c_get_window_size
190− return (fromIntegral b, fromIntegral a)
191−
192−terminfo_display_context :: Terminal -> TerminfoCaps -> DisplayRegion -> IO DisplayContext
193−terminfo_display_context t_actual terminfo_caps r =
194− let dc = DisplayContext
195− { context_device = t_actual
196− , context_region = r
197− , move_cursor_required_bytes = \x y -> cap_expression_required_bytes (cup terminfo_caps)
198− [toEnum y, toEnum x]
199− , serialize_move_cursor = \x y out_ptr -> serialize_cap_expression (cup terminfo_caps)
200− [toEnum y, toEnum x]
201− out_ptr
202− , show_cursor_required_bytes = case cnorm terminfo_caps of
203− Nothing -> error "this terminal does not support show cursor"
204− Just c -> cap_expression_required_bytes c []
205− , serialize_show_cursor = \out_ptr -> do
206− case cnorm terminfo_caps of
207− Nothing -> fail "this terminal does not support show cursor"
208− Just c -> serialize_cap_expression c [] out_ptr
209− , hide_cursor_required_bytes = case civis terminfo_caps of
210− Nothing -> error "this terminal does not support hide cursor"
211− Just c -> cap_expression_required_bytes c []
212− , serialize_hide_cursor = \out_ptr -> do
213− case civis terminfo_caps of
214− Nothing -> fail "this terminal does not support hide cursor"
215− Just c -> serialize_cap_expression c [] out_ptr
216− , attr_required_bytes = \_prev_attr _req_attr _diffs -> assumed_attr_required_bytes
217− , serialize_set_attr = terminfo_serialize_set_attr dc terminfo_caps
218− , default_attr_required_bytes = cap_expression_required_bytes (set_default_attr terminfo_caps) []
219− , serialize_default_attr = serialize_cap_expression (set_default_attr terminfo_caps) []
220− , row_end_required_bytes = cap_expression_required_bytes (clear_eol terminfo_caps) []
221− , serialize_row_end = serialize_cap_expression (clear_eol terminfo_caps) []
222− , inline_hack = return ()
223− }
224− in return dc
225−
226−-- | Instead of evaluating all the rules related to setting display attributes twice (once in
227−-- required bytes and again in serialize) or some memoization scheme just return a size
228−-- requirement as big the longest possible control string.
229−--
230−-- Which is assumed to the be less than 512 for now.
231−--
232−-- \todo Not verified as safe and wastes memory.
233−assumed_attr_required_bytes :: Int
234−assumed_attr_required_bytes = 512
235−
236−-- | Portably setting the display attributes is a giant pain in the ass.
237−--
238−-- If the terminal supports the sgr capability (which sets the on/off state of each style
239−-- directly ; and, for no good reason, resets the colors to the default) this procedure is used:
240−--
241−-- 0. set the style attributes. This resets the fore and back color.
242−-- 1, If a foreground color is to be set then set the foreground color
243−-- 2. likewise with the background color
244−--
245−-- If the terminal does not support the sgr cap then:
246−-- if there is a change from an applied color to the default (in either the fore or back color)
247−-- then:
248−-- 0. reset all display attributes (sgr0)
249−-- 1. enter required style modes
250−-- 2. set the fore color if required
251−-- 3. set the back color if required
252−--
253−-- Entering the required style modes could require a reset of the display attributes. If this is
254−-- the case then the back and fore colors always need to be set if not default.
255−--
256−-- This equation implements the above logic.
257−--
258−-- \todo This assumes the removal of color changes in the display attributes is done as expected
259−-- with no_colors == True. See `limit_attr_for_display`
260−--
261−-- \todo This assumes that fewer state changes, followed by fewer bytes, is what to optimize. I
262−-- haven't measured this or even examined terminal implementations. *shrug*
263−terminfo_serialize_set_attr :: DisplayContext
264− -> TerminfoCaps
265− -> FixedAttr -> Attr -> DisplayAttrDiff -> OutputBuffer -> IO OutputBuffer
266−terminfo_serialize_set_attr dc terminfo_caps prev_attr req_attr diffs out_ptr = do
267− case (fore_color_diff diffs == ColorToDefault) || (back_color_diff diffs == ColorToDefault) of
268− -- The only way to reset either color, portably, to the default is to use either the set
269− -- state capability or the set default capability.
270− True -> do
271− case req_display_cap_seq_for ( display_attr_caps terminfo_caps )
272− ( fixed_style attr )
273− ( style_to_apply_seq $ fixed_style attr )
274− of
275− EnterExitSeq caps
276− -- only way to reset a color to the defaults
277− -> serialize_default_attr dc out_ptr
278− >>= (\out_ptr' -> liftIO $ foldM (\ptr cap -> serialize_cap_expression cap [] ptr) out_ptr' caps)
279− >>= set_colors
280− SetState state
281− -- implicitly resets the colors to the defaults
282− -> liftIO $ serialize_cap_expression ( fromJust $ set_attr_states
283− $ display_attr_caps
284− $ terminfo_caps
285− )
286− ( sgr_args_for_state state )
287− out_ptr
288− >>= set_colors
289− -- Otherwise the display colors are not changing or changing between two non-default
290− -- points.
291− False -> do
292− -- Still, it could be the case that the change in display attributes requires the
293− -- colors to be reset because the required capability was not available.
294− case req_display_cap_seq_for (display_attr_caps terminfo_caps)
295− (fixed_style attr)
296− (style_diffs diffs)
297− of
298− -- Really, if terminals were re-implemented with modern concepts instead of
299− -- bowing down to 40 yr old dumb terminal requirements this would be the
300− -- only case ever reached!
301− -- Changes the style and color states according to the differences with the
302− -- currently applied states.
303− EnterExitSeq caps
304− -> foldM (\ptr cap -> serialize_cap_expression cap [] ptr) out_ptr caps
305− >>= apply_color_diff set_fore_color (fore_color_diff diffs)
306− >>= apply_color_diff set_back_color (back_color_diff diffs)
307− SetState state
308− -- implicitly resets the colors to the defaults
309− -> serialize_cap_expression (fromJust $ set_attr_states
310− $ display_attr_caps terminfo_caps
311− )
312− (sgr_args_for_state state)
313− out_ptr
314− >>= set_colors
315− where
316− color_map = case use_alt_color_map terminfo_caps of
317− False -> ansi_color_index
318− True -> alt_color_index
319− attr = fix_display_attr prev_attr req_attr
320− set_colors ptr = do
321− ptr' <- case fixed_fore_color attr of
322− Just c -> serialize_cap_expression (set_fore_color terminfo_caps)
323− [ toEnum $ color_map c ]
324− ptr
325− Nothing -> return ptr
326− ptr'' <- case fixed_back_color attr of
327− Just c -> serialize_cap_expression (set_back_color terminfo_caps)
328− [ toEnum $ color_map c ]
329− ptr'
330− Nothing -> return ptr'
331− return ptr''
332− apply_color_diff _f NoColorChange ptr
333− = return ptr
334− apply_color_diff _f ColorToDefault _ptr
335− = fail "ColorToDefault is not a possible case for apply_color_diffs"
336− apply_color_diff f ( SetColor c ) ptr
337− = serialize_cap_expression (f terminfo_caps)
338− [ toEnum $ color_map c ]
339− ptr
340−
341−-- | The color table used by a terminal is a 16 color set followed by a 240 color set that might not
342−-- be supported by the terminal.
343−--
344−-- This takes a Color which clearly identifies which pallete to use and computes the index
345−-- into the full 256 color pallete.
346−ansi_color_index :: Color -> Int
347−ansi_color_index (ISOColor v) = fromEnum v
348−ansi_color_index (Color240 v) = 16 + fromEnum v
349−
350−-- | For terminals without setaf/setab
351−--
352−-- See table in `man terminfo`
353−-- Will error if not in table.
354−alt_color_index :: Color -> Int
355−alt_color_index (ISOColor 0) = 0
356−alt_color_index (ISOColor 1) = 4
357−alt_color_index (ISOColor 2) = 2
358−alt_color_index (ISOColor 3) = 6
359−alt_color_index (ISOColor 4) = 1
360−alt_color_index (ISOColor 5) = 5
361−alt_color_index (ISOColor 6) = 3
362−alt_color_index (ISOColor 7) = 7
363−alt_color_index (ISOColor v) = fromEnum v
364−alt_color_index (Color240 v) = 16 + fromEnum v
365−
366−{- | The sequence of terminfo caps to apply a given style are determined according to these rules.
367− -
368− - 1. The assumption is that it's preferable to use the simpler enter/exit mode capabilities than
369− - the full set display attribute state capability.
370− -
371− - 2. If a mode is supposed to be removed but there is not an exit capability defined then the
372− - display attributes are reset to defaults then the display attribute state is set.
373− -
374− - 3. If a mode is supposed to be applied but there is not an enter capability defined then then
375− - display attribute state is set if possible. Otherwise the mode is not applied.
376− -
377− - 4. If the display attribute state is being set then just update the arguments to that for any
378− - apply/remove.
379− -
380− -}
381−data DisplayAttrSeq
382− = EnterExitSeq [CapExpression]
383− | SetState DisplayAttrState
384−
385−data DisplayAttrState = DisplayAttrState
386− { apply_standout :: Bool
387− , apply_underline :: Bool
388− , apply_reverse_video :: Bool
389− , apply_blink :: Bool
390− , apply_dim :: Bool
391− , apply_bold :: Bool
392− }
393−
394−sgr_args_for_state :: DisplayAttrState -> [CapParam]
395−sgr_args_for_state attr_state = map (\b -> if b then 1 else 0)
396− [ apply_standout attr_state
397− , apply_underline attr_state
398− , apply_reverse_video attr_state
399− , apply_blink attr_state
400− , apply_dim attr_state
401− , apply_bold attr_state
402− , False -- invis
403− , False -- protect
404− , False -- alt char set
405− ]
406−
407−req_display_cap_seq_for :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq
408−req_display_cap_seq_for caps s diffs
409− -- if the state transition implied by any diff cannot be supported with an enter/exit mode cap
410− -- then either the state needs to be set or the attribute change ignored.
411− = case (any no_enter_exit_cap diffs, isJust $ set_attr_states caps) of
412− -- If all the diffs have an enter-exit cap then just use those
413− ( False, _ ) -> EnterExitSeq $ map enter_exit_cap diffs
414− -- If not all the diffs have an enter-exit cap and there is no set state cap then filter out
415− -- all unsupported diffs and just apply the rest
416− ( True, False ) -> EnterExitSeq $ map enter_exit_cap
417− $ filter (not . no_enter_exit_cap) diffs
418− -- if not all the diffs have an enter-exit can and there is a set state cap then just use
419− -- the set state cap.
420− ( True, True ) -> SetState $ state_for_style s
421− where
422− no_enter_exit_cap ApplyStandout = isNothing $ enter_standout caps
423− no_enter_exit_cap RemoveStandout = isNothing $ exit_standout caps
424− no_enter_exit_cap ApplyUnderline = isNothing $ enter_underline caps
425− no_enter_exit_cap RemoveUnderline = isNothing $ exit_underline caps
426− no_enter_exit_cap ApplyReverseVideo = isNothing $ enter_reverse_video caps
427− no_enter_exit_cap RemoveReverseVideo = True
428− no_enter_exit_cap ApplyBlink = True
429− no_enter_exit_cap RemoveBlink = True
430− no_enter_exit_cap ApplyDim = isNothing $ enter_dim_mode caps
431− no_enter_exit_cap RemoveDim = True
432− no_enter_exit_cap ApplyBold = isNothing $ enter_bold_mode caps
433− no_enter_exit_cap RemoveBold = True
434− enter_exit_cap ApplyStandout = fromJust $ enter_standout caps
435− enter_exit_cap RemoveStandout = fromJust $ exit_standout caps
436− enter_exit_cap ApplyUnderline = fromJust $ enter_underline caps
437− enter_exit_cap RemoveUnderline = fromJust $ exit_underline caps
438− enter_exit_cap ApplyReverseVideo = fromJust $ enter_reverse_video caps
439− enter_exit_cap ApplyDim = fromJust $ enter_dim_mode caps
440− enter_exit_cap ApplyBold = fromJust $ enter_bold_mode caps
441− enter_exit_cap _ = error "enter_exit_cap applied to diff that was known not to have one."
442−
443−state_for_style :: Style -> DisplayAttrState
444−state_for_style s = DisplayAttrState
445− { apply_standout = is_style_set standout
446− , apply_underline = is_style_set underline
447− , apply_reverse_video = is_style_set reverse_video
448− , apply_blink = is_style_set blink
449− , apply_dim = is_style_set dim
450− , apply_bold = is_style_set bold
451− }
452− where is_style_set = has_style s
453−
454−style_to_apply_seq :: Style -> [StyleStateChange]
455−style_to_apply_seq s = concat
456− [ apply_if_required ApplyStandout standout
457− , apply_if_required ApplyUnderline underline
458− , apply_if_required ApplyReverseVideo reverse_video
459− , apply_if_required ApplyBlink blink
460− , apply_if_required ApplyDim dim
461− , apply_if_required ApplyBlink bold
462− ]
463− where
464− apply_if_required ap flag
465− = if 0 == ( flag .&. s )
466− then []
467− else [ ap ]
468−
src/Graphics/Vty/Terminal/XTermColor.hsdeleted−53
@@ -1,53 +0,0 @@
1−-- Copyright 2009-2010 Corey O'Connor
2−module Graphics.Vty.Terminal.XTermColor ( reserve_terminal )
3− where
4−
5−import Graphics.Vty.Terminal.Interface
6−import qualified Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
7−
8−import Control.Applicative
9−import Control.Monad.Trans
10−
11−import qualified Data.Text as T
12−import qualified Data.Text.Encoding as T
13−
14−import System.IO
15−
16−-- | Initialize the display to UTF-8.
17−reserve_terminal :: ( Applicative m, MonadIO m ) => String -> Handle -> m Terminal
18−reserve_terminal variant out_handle = liftIO $ do
19− let flushed_put str = do
20− hPutStr out_handle str
21− hFlush out_handle
22− -- If the terminal variant is xterm-color use xterm instead since, more often than not,
23− -- xterm-color is broken.
24− let variant' = if variant == "xterm-color" then "xterm" else variant
25− flushed_put set_utf8_char_set
26− t <- TerminfoBased.reserve_terminal variant' out_handle
27− let t' = t
28− { terminal_ID = terminal_ID t ++ " (xterm-color)"
29− , release_terminal = do
30− liftIO $ flushed_put set_default_char_set
31− release_terminal t
32− , mk_display_context = \t_actual r -> do
33− dc <- mk_display_context t t_actual r
34− return $ dc { inline_hack = xterm_inline_hack t' }
35− }
36− return t'
37−
38−-- | These sequences set xterm based terminals to UTF-8 output.
39−--
40−-- \todo I don't know of a terminfo cap that is equivalent to this.
41−set_utf8_char_set, set_default_char_set :: String
42−set_utf8_char_set = "\ESC%G"
43−set_default_char_set = "\ESC%@"
44−
45−-- | I think xterm is broken: Reseting the background color as the first bytes serialized on a
46−-- new line does not effect the background color xterm uses to clear the line. Which is used
47−-- *after* the next newline.
48−xterm_inline_hack :: Terminal -> IO ()
49−xterm_inline_hack t = do
50− let s_utf8 = T.encodeUtf8 $ T.pack "\ESC[K"
51− send_to_terminal t (utf8_text_required_bytes s_utf8)
52− (serialize_utf8_text s_utf8)
53−
src/Graphics/Vty/UnicodeWidthTable/IO.hsnew file+105
@@ -0,0 +1,105 @@
1+{-# LANGUAGE CPP #-}
2+module Graphics.Vty.UnicodeWidthTable.IO
3+ ( readUnicodeWidthTable
4+ , parseUnicodeWidthTable
5+ , writeUnicodeWidthTable
6+ )
7+where
8+
9+import Control.Monad (when, forM)
10+import Data.Binary
11+import Data.Binary.Get
12+import Data.Binary.Put
13+import qualified Data.ByteString.Lazy as BSL
14+#if !(MIN_VERSION_base(4,11,0))
15+import Data.Semigroup ((<>))
16+#endif
17+
18+import Graphics.Vty.UnicodeWidthTable.Types
19+
20+-- | Load a binary unicode width table from the specified file.
21+--
22+-- This either returns a successfully parsed table or a table parsing
23+-- error message. This does not handle I/O exceptions.
24+readUnicodeWidthTable :: FilePath -> IO (Either String UnicodeWidthTable)
25+readUnicodeWidthTable path = parseUnicodeWidthTable <$> BSL.readFile path
26+
27+-- | Parse a binary unicode width table.
28+parseUnicodeWidthTable :: BSL.ByteString -> Either String UnicodeWidthTable
29+parseUnicodeWidthTable bs =
30+ case runGetOrFail tableParser bs of
31+ Left (_, _, msg) ->
32+ Left msg
33+
34+ -- Even if we parsed a table, leftover bytes indicate something
35+ -- could be wrong.
36+ Right (remainingBytes, _, _) | not (BSL.null remainingBytes) ->
37+ Left $ "Error: " <> show (BSL.length remainingBytes) <>
38+ " byte(s) left unconsumed"
39+
40+ Right (_, _, table) ->
41+ Right table
42+
43+-- | Write the unicode width table to the specified path.
44+--
45+-- This does not handle I/O exceptions.
46+writeUnicodeWidthTable :: FilePath -> UnicodeWidthTable -> IO ()
47+writeUnicodeWidthTable path table = do
48+ let body = runPut (tableV1Writer table)
49+ BSL.writeFile path body
50+
51+-- | Width table magic bytes for use in the binary format.
52+widthTableMagic :: Word32
53+widthTableMagic = 0xc1a9f7e0
54+
55+tableParser :: Get UnicodeWidthTable
56+tableParser = do
57+ magic <- getWord32le
58+
59+ when (magic /= widthTableMagic) $
60+ fail "Table magic number invalid"
61+
62+ version <- getWord8
63+
64+ case version of
65+ 1 -> tableV1Parser
66+ _ -> fail "Table version invalid"
67+
68+tableV1Parser :: Get UnicodeWidthTable
69+tableV1Parser = do
70+ numRanges <- getWord32le
71+
72+ let parseRange = do
73+ start <- getWord32le
74+ size <- getWord32le
75+ cols <- getWord8
76+ return WidthTableRange { rangeStart = start
77+ , rangeSize = size
78+ , rangeColumns = cols
79+ }
80+
81+ ranges <- forM [1..numRanges] $ const parseRange
82+
83+ return UnicodeWidthTable { unicodeWidthTableRanges = ranges
84+ }
85+
86+tableV1Writer :: UnicodeWidthTable -> Put
87+tableV1Writer table = do
88+ -- Magic bytes
89+ putWord32le widthTableMagic
90+
91+ -- Version
92+ putWord8 1
93+
94+ -- Number of ranges
95+ let ranges = unicodeWidthTableRanges table
96+ let numRanges = length ranges
97+ putWord32le (fromIntegral numRanges)
98+
99+ -- Ranges
100+ let putRange r = do
101+ putWord32le $ rangeStart r
102+ putWord32le $ rangeSize r
103+ putWord8 $ rangeColumns r
104+
105+ mapM_ putRange ranges
src/Graphics/Vty/UnicodeWidthTable/Install.hsnew file+129
@@ -0,0 +1,129 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ForeignFunctionInterface #-}
3+module Graphics.Vty.UnicodeWidthTable.Install
4+ ( TableInstallException(..)
5+ , installUnicodeWidthTable
6+ , isCustomTableReady
7+ )
8+where
9+
10+import Control.Monad (when, forM_)
11+import qualified Control.Exception as E
12+import GHC.Conc.Sync (withMVar)
13+import Control.Concurrent.MVar (MVar, newMVar)
14+import Data.Word (Word8, Word32)
15+#if !(MIN_VERSION_base(4,11,0))
16+import Data.Semigroup ((<>))
17+#endif
18+import System.IO.Unsafe (unsafePerformIO)
19+
20+import Graphics.Vty.UnicodeWidthTable.Types
21+
22+-- | The lock used to make functions in this module thread-safe.
23+installLock :: MVar ()
24+{-# NOINLINE installLock #-}
25+installLock = unsafePerformIO $ newMVar ()
26+
27+-- | Returns True if and only if a custom table has been allocated and
28+-- marked as ready for use.
29+--
30+-- This function is thread-safe.
31+isCustomTableReady :: IO Bool
32+isCustomTableReady = withInstallLock $ (== 1) <$> c_isCustomTableReady
33+
34+withInstallLock :: IO a -> IO a
35+withInstallLock act = withMVar installLock $ const act
36+
37+-- This is the size of the allocated custom character width table, in
38+-- character slots. It's important that this be large enough to hold all
39+-- possible Unicode character values. At the time of this writing, the
40+-- valid Unicode range is 0 - 0x10ffff, hence this value.
41+tableSize :: Int
42+tableSize = 0x110000
43+
44+-- | Exception type raised by 'installUnicodeWidthTable'.
45+data TableInstallException =
46+ TableInitFailure Int Int
47+ -- ^ The width table could not be initialized. Args: failure status,
48+ -- requested table size.
49+ | TableRangeFailure Int WidthTableRange
50+ -- ^ A code point range could not be configured. Args: failure
51+ -- status, offending range.
52+ | TableActivationFailure Int
53+ -- ^ The table could not be activated. Args: failure status.
54+ deriving (Eq, Show)
55+
56+instance E.Exception TableInstallException
57+
58+-- | Install a custom unicode character width
59+-- table. Such tables are obtained with
60+-- 'Graphics.Vty.UnicodeWidthTable.Query.buildUnicodeWidthTable' and
61+-- 'Graphics.Vty.UnicodeWidthTable.IO.readUnicodeWidthTable'.
62+--
63+-- ALERT! This function is probably not what you want to use because
64+-- it is automatically called by 'Graphics.Vty.mkVty'. You will only
65+-- ever need to call this function if you want to use functions
66+-- in 'Graphics.Text.Width' without controlling the terminal with
67+-- 'Graphics.Vty.mkVty'.
68+--
69+-- This affects the behavior of the 'Graphics.Vty.Image.wcwidth'
70+-- function and functions that call it. It does so by
71+-- changing global state available to the C implementation
72+-- of 'Graphics.Vty.Image.wcwidth'. To ensure that your
73+-- program gets consistent results from evaluating calls to
74+-- 'Graphics.Vty.Image.wcwidth', the installation of a custom table
75+-- should be performed before you call 'Graphics.Vty.Image.wcwidth' in
76+-- your program.
77+--
78+-- This is best done at Vty startup, and if you use
79+-- 'Graphics.Vty.mkVty', that function calls this automatically based on
80+-- the Vty configuration's declared width tables. It is exposed as part
81+-- of the public API so that applications can call this as needed if
82+-- they don't want to control the terminal with 'mkVty' but do want to
83+-- make calls to 'Graphics.Vty.Image.wcwidth'.
84+--
85+-- It's also important to note that once a custom table has been
86+-- installed, it is permanent for the life of the process. No new table
87+-- can be installed, and the new custom table cannot be removed.
88+--
89+-- If this function fails for any reason -- if the table cannot be
90+-- installed or is invalid, or if a custom table already exists -- this
91+-- will raise a 'TableInstallException' exception.
92+--
93+-- This function is thread-safe.
94+installUnicodeWidthTable :: UnicodeWidthTable -> IO ()
95+installUnicodeWidthTable table = withInstallLock $ do
96+ initResult <- initCustomTable tableSize
97+ when (initResult /= 0) $
98+ E.throwIO $ TableInitFailure initResult tableSize
99+
100+ forM_ (unicodeWidthTableRanges table) $ \r -> do
101+ result <- setCustomTableRange (rangeStart r)
102+ (rangeSize r)
103+ (rangeColumns r)
104+
105+ when (result /= 0) $ do
106+ deallocateCustomTable
107+ E.throwIO $ TableRangeFailure result r
108+
109+ actResult <- activateCustomTable
110+ when (actResult /= 0) $
111+ E.throwIO $ TableActivationFailure actResult
112+
113+------------------------------------------------------------------------
114+-- C imports
115+
116+foreign import ccall unsafe "vty_init_custom_table"
117+ initCustomTable :: Int -> IO Int
118+
119+foreign import ccall unsafe "vty_set_custom_table_range"
120+ setCustomTableRange :: Word32 -> Word32 -> Word8 -> IO Int
121+
122+foreign import ccall unsafe "vty_activate_custom_table"
123+ activateCustomTable :: IO Int
124+
125+foreign import ccall unsafe "vty_custom_table_ready"
126+ c_isCustomTableReady :: IO Int
127+
128+foreign import ccall unsafe "vty_deallocate_custom_table"
129+ deallocateCustomTable :: IO ()
src/Graphics/Vty/UnicodeWidthTable/Query.hsnew file+79
@@ -0,0 +1,79 @@
1+{-# LANGUAGE TupleSections #-}
2+module Graphics.Vty.UnicodeWidthTable.Query
3+ ( buildUnicodeWidthTable
4+ , defaultUnicodeTableUpperBound
5+ )
6+where
7+
8+import Control.Monad (forM)
9+import Data.Char (generalCategory, GeneralCategory(..))
10+import System.Console.ANSI (getCursorPosition)
11+import Text.Printf (printf)
12+
13+import Graphics.Vty.UnicodeWidthTable.Types
14+
15+shouldConsider :: Char -> Bool
16+shouldConsider c =
17+ case generalCategory c of
18+ Control -> False
19+ NotAssigned -> False
20+ Surrogate -> False
21+ _ -> True
22+
23+charWidth :: Char -> IO Int
24+charWidth c = do
25+ printf "\r"
26+ putChar c
27+ Just (_, col) <- getCursorPosition
28+ return col
29+
30+-- | Convert a sequence of character/width pairs into a list of
31+-- run-length encoded ranges. This function assumes the pairs come
32+-- sorted by character ordinal value. It does not require that the
33+-- character range is fully covered by the sequence.
34+--
35+-- The result of this function is a list of ranges in reverse order
36+-- relative to the input sequence.
37+mkRanges :: [(Char, Int)] -> [WidthTableRange]
38+mkRanges pairs =
39+ let convertedPairs = convert <$> pairs
40+ convert (c, i) = (fromIntegral $ fromEnum c, fromIntegral i)
41+
42+ go Nothing finishedRanges [] = finishedRanges
43+ go (Just r) finishedRanges [] = r:finishedRanges
44+
45+ go Nothing finishedRanges ((c, width):rest) =
46+ go (Just $ WidthTableRange c 1 width) finishedRanges rest
47+
48+ go (Just r@(WidthTableRange prevCh sz prevWidth)) finishedRanges ((c, width):rest) =
49+ if c == prevCh + sz && prevWidth == width
50+ then go (Just (WidthTableRange prevCh (sz + 1) prevWidth)) finishedRanges rest
51+ else go (Just (WidthTableRange c 1 width)) (r:finishedRanges) rest
52+
53+ in go Nothing [] convertedPairs
54+
55+-- The uppermost code point to consider when building Unicode width
56+-- tables.
57+defaultUnicodeTableUpperBound :: Char
58+defaultUnicodeTableUpperBound = '\xe0000'
59+
60+-- | Construct a unicode character width table by querying the terminal
61+-- connected to stdout. This works by emitting characters to stdout
62+-- and then querying the terminal to determine the resulting cursor
63+-- position in order to measure character widths. Consequently this will
64+-- generate a lot of output and may take a while, depending on your
65+-- system performance. This should not be run in a terminal while it is
66+-- controlled by Vty.
67+--
68+-- The argument specifies the upper bound code point to test when
69+-- building the table. This allows callers to decide how much of the
70+-- Unicode code point space to scan when building the table.
71+--
72+-- This does not handle exceptions.
73+buildUnicodeWidthTable :: Char -> IO UnicodeWidthTable
74+buildUnicodeWidthTable tableUpperBound = do
75+ pairs <- forM (filter shouldConsider ['\0'..tableUpperBound]) $ \i ->
76+ (i,) <$> charWidth i
77+
78+ return UnicodeWidthTable { unicodeWidthTableRanges = reverse $ mkRanges pairs
79+ }
src/Graphics/Vty/UnicodeWidthTable/Types.hsnew file+29
@@ -0,0 +1,29 @@
1+module Graphics.Vty.UnicodeWidthTable.Types
2+ ( UnicodeWidthTable(..)
3+ , WidthTableRange(..)
4+ )
5+where
6+
7+import Data.Word (Word8, Word32)
8+
9+-- | A range of code points in a width table.
10+data WidthTableRange =
11+ WidthTableRange { rangeStart :: Word32
12+ -- ^ The range's starting code point.
13+ , rangeSize :: Word32
14+ -- ^ The number of code points in the contiguous
15+ -- range, beginning with the starting code point
16+ -- ('rangeStart').
17+ , rangeColumns :: Word8
18+ -- ^ The terminal width, in columns, of all of the
19+ -- characters corresponding to the code points in
20+ -- this range.
21+ }
22+ deriving (Eq, Show)
23+
24+-- | A run-length-encoded table of Unicode character widths.
25+data UnicodeWidthTable =
26+ UnicodeWidthTable { unicodeWidthTableRanges :: [WidthTableRange]
27+ -- ^ The ranges in the table.
28+ }
29+ deriving (Show)
test/BenchImageFuzz.hs+7−7
@@ -13,15 +13,15 @@ import System.Random
13rand :: Arbitrary a => IO a
13rand :: Arbitrary a => IO a
14rand = head <$> sample' arbitrary
14rand = head <$> sample' arbitrary
15
15
16−random_image :: IO Image
16+randomImage :: IO Image
17−random_image = rand
17+randomImage = rand
18
18
19−random_picture = pic_for_image <$> random_image
19+randomPicture = picForImage <$> randomImage
20
20
21−bench_0 = do
21+bench0 = do
22− vty <- mkVty
22+ vty <- mkVty defaultConfig
23− DisplayRegion w h <- display_bounds $ terminal vty
23+ (w,h) <- displayBounds $ outputIface vty
24− let pictures = replicateM 3000 random_picture
24+ let pictures = replicateM 3000 randomPicture
25 bench ps = do
25 bench ps = do
26 forM ps (update vty)
26 forM ps (update vty)
27 shutdown vty
27 shutdown vty
test/BenchNoDiffOpt.hs+17−17
@@ -15,24 +15,24 @@ import System.Environment( getArgs )
15import System.IO
15import System.IO
16import System.Random
16import System.Random
17
17
18−bench_0 = do
18+bench0 = do
19− let fixed_gen = mkStdGen 0
19+ let fixedGen = mkStdGen 0
20− setStdGen fixed_gen
20+ setStdGen fixedGen
21− vty <- mkVty
21+ vty <- mkVty defaultConfig
22− DisplayRegion w h <- display_bounds $ terminal vty
22+ (w,h) <- displayBounds $ outputIface vty
23− let images = return $ (image_0, image_1)
23+ let images = return $ (image0, image1)
24− image_0 = char_fill def_attr 'X' w h
24+ image0 = charFill defAttr 'X' w h
25− image_1 = char_fill def_attr '0' w h
25+ image1 = charFill defAttr '0' w h
26 bench d = do
26 bench d = do
27− flip_out vty 300 image_0 image_1
27+ flipOut vty 300 image0 image1
28 shutdown vty
28 shutdown vty
29 return $ Bench images bench
29 return $ Bench images bench
30
30
31−flip_out vty n image_0 image_1 =
31+flipOut vty n image0 image1 =
32− let !p_left = pic_for_image image_0
32+ let !pLeft = picForImage image0
33− !p_right = pic_for_image image_1
33+ !pRight = picForImage image1
34− w_left 0 = return ()
34+ wLeft 0 = return ()
35− w_left n = update vty p_left >> w_right (n-1)
35+ wLeft n = update vty pLeft >> wRight (n-1)
36− w_right 0 = return ()
36+ wRight 0 = return ()
37− w_right n = update vty p_right >> w_left (n-1)
37+ wRight n = update vty pRight >> wLeft (n-1)
38− in w_left n
38+ in wLeft n
test/BenchRenderChar.hs+25−25
@@ -1,7 +1,7 @@
1−{- benchmarks composing images using the renderChar operation.
1+-- benchmarks composing images using the renderChar operation. This is
2− - This is what Yi uses in Yi.UI.Vty.drawText. Ideally a sequence of renderChar images horizontally
2+-- what Yi uses in Yi.UI.Vty.drawText. Ideally a sequence of renderChar
3− - composed should provide no worse performance than a fill render op.
3+-- images horizontally composed should provide no worse performance than
4− -}
4+-- a fill render op.
5module BenchRenderChar where
5module BenchRenderChar where
6
6
7import Graphics.Vty
7import Graphics.Vty
@@ -9,30 +9,30 @@ import Verify
9
9
10import Control.Monad ( forM_ )
10import Control.Monad ( forM_ )
11
11
12−bench_0 = do
12+bench0 = do
13− vty <- mkVty
13+ vty <- mkVty defaultConfig
14− DisplayRegion w h <- display_bounds $ terminal vty
14+ (w,h) <- displayBounds $ outputIface vty
15− let test_chars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
15+ let testChars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
16 bench d = do
16 bench d = do
17− forM_ d $ \test_char -> do
17+ forM_ d $ \testChar -> do
18− let test_image = test_image_using_char test_char w h
18+ let testImage = testImageUsingChar testChar w h
19− out_pic = pic_for_image test_image
19+ outPic = picForImage testImage
20− update vty out_pic
20+ update vty outPic
21 shutdown vty
21 shutdown vty
22− return $ Bench test_chars bench
22+ return $ Bench testChars bench
23
23
24−test_image_using_char c w h
24+testImageUsingChar c w h
25− = vert_cat $ replicate (fromIntegral h)
25+ = vertCat $ replicate (fromIntegral h)
26− $ horiz_cat $ map (char def_attr) (replicate (fromIntegral w) c)
26+ $ horizCat $ map (char defAttr) (replicate (fromIntegral w) c)
27
27
28−bench_1 = do
28+bench1 = do
29− vty <- mkVty
29+ vty <- mkVty defaultConfig
30− DisplayRegion w h <- display_bounds $ terminal vty
30+ (w,h) <- displayBounds $ outputIface vty
31− let test_chars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
31+ let testChars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
32 bench d = do
32 bench d = do
33− forM_ d $ \test_char -> do
33+ forM_ d $ \testChar -> do
34− let test_image = char_fill def_attr test_char w h
34+ let testImage = charFill defAttr testChar w h
35− out_pic = pic_for_image test_image
35+ outPic = picForImage testImage
36− update vty out_pic
36+ update vty outPic
37 shutdown vty
37 shutdown vty
38− return $ Bench test_chars bench
38+ return $ Bench testChars bench
test/BenchVerticalScroll.hs+10−11
@@ -13,12 +13,12 @@ import System.Environment( getArgs )
13import System.IO
13import System.IO
14import System.Random
14import System.Random
15
15
16−bench_0 = do
16+bench0 = do
17− let fixed_gen = mkStdGen 0
17+ let fixedGen = mkStdGen 0
18− setStdGen fixed_gen
18+ setStdGen fixedGen
19− return $ Bench (return ()) (\() -> mkVty >>= liftM2 (>>) run shutdown)
19+ return $ Bench (return ()) (\() -> mkVty defaultConfig >>= liftM2 (>>) run shutdown)
20
20
21−run vt = mapM_ (\p -> update vt p) . benchgen =<< display_bounds (terminal vt)
21+run vt = mapM_ (\p -> update vt p) . benchgen =<< displayBounds (outputIface vt)
22
22
23-- Currently, we just do scrolling.
23-- Currently, we just do scrolling.
24takem :: (a -> Int) -> Int -> [a] -> ([a],[a])
24takem :: (a -> Int) -> Int -> [a] -> ([a],[a])
@@ -40,21 +40,20 @@ lengths ml g =
40nums :: StdGen -> [(Attr, String)]
40nums :: StdGen -> [(Attr, String)]
41nums g = let (x,g2) = (random g :: (Int, StdGen))
41nums g = let (x,g2) = (random g :: (Int, StdGen))
42 (c,g3) = random g2
42 (c,g3) = random g2
43− in ( if c then def_attr `with_fore_color` red else def_attr
43+ in ( if c then defAttr `withForeColor` red else defAttr
44 , shows x " "
44 , shows x " "
45 ) : nums g3
45 ) : nums g3
46
46
47pad :: Int -> Image -> Image
47pad :: Int -> Image -> Image
48−pad ml img = img <|> char_fill def_attr ' ' (ml - image_width img) 1
48+pad ml img = img <|> charFill defAttr ' ' (ml - imageWidth img) 1
49
49
50clines :: StdGen -> Int -> [Image]
50clines :: StdGen -> Int -> [Image]
51−clines g maxll = map (pad maxll . horiz_cat . map (uncurry string))
51+clines g maxll = map (pad maxll . horizCat . map (uncurry string))
52 $ fold (length . snd) (lengths maxll g1) (nums g2)
52 $ fold (length . snd) (lengths maxll g1) (nums g2)
53 where (g1,g2) = split g
53 where (g1,g2) = split g
54
54
55benchgen :: DisplayRegion -> [Picture]
55benchgen :: DisplayRegion -> [Picture]
56−benchgen (DisplayRegion w h)
56+benchgen (w,h)
57− = take 2000 $ map ((\i -> pic_for_image i) . vert_cat . take (fromEnum h))
57+ = take 2000 $ map ((\i -> picForImage i) . vertCat . take (fromEnum h))
58 $ tails
58 $ tails
59 $ clines (mkStdGen 80) w
59 $ clines (mkStdGen 80) w
60−
test/ControlTable.hs+4−4
@@ -5,10 +5,10 @@ import Graphics.Vty.ControlStrings
5import System.Console.Terminfo
5import System.Console.Terminfo
6
6
7main = do
7main = do
8− terminal <- setupTermFromEnv
8+ terminal <- setupTermFromEnv
9− control_table <- init_control_table terminal
9+ controlTable <- init_controlTable terminal
10 putStrLn $ "ANSI terminal show cursor string: " ++ show cvis
10 putStrLn $ "ANSI terminal show cursor string: " ++ show cvis
11− putStrLn $ "Current terminal show cursor string: " ++ show (show_cursor_str control_table)
11+ putStrLn $ "Current terminal show cursor string: " ++ show (showCursorStr controlTable)
12 putStrLn $ "ANSI terminal hide cursor string: " ++ show civis
12 putStrLn $ "ANSI terminal hide cursor string: " ++ show civis
13− putStrLn $ "Current terminal hide cursor string: " ++ show (hide_cursor_str control_table)
13+ putStrLn $ "Current terminal hide cursor string: " ++ show (hideCursorStr controlTable)
14
14
test/EventEcho.hs+17−19
@@ -10,33 +10,31 @@ import Data.Sequence (Seq, (<|) )
10import qualified Data.Sequence as Seq
10import qualified Data.Sequence as Seq
11import Data.Foldable
11import Data.Foldable
12
12
13−event_buffer_size = 1000
13+eventBufferSize = 1000
14
14
15type App = RWST Vty () (Seq String) IO
15type App = RWST Vty () (Seq String) IO
16
16
17main = do
17main = do
18− vty <- mkVty
18+ vty <- mkVty defaultConfig
19− _ <- execRWST vty_interact vty Seq.empty
19+ _ <- execRWST (vtyInteract False) vty Seq.empty
20 shutdown vty
20 shutdown vty
21
21
22−vty_interact :: App ()
22+vtyInteract :: Bool -> App ()
23−vty_interact = do
23+vtyInteract shouldExit = do
24− update_display
24+ updateDisplay
25− done <- handle_next_event
25+ unless shouldExit $ handleNextEvent >>= vtyInteract
26− unless done vty_interact
26+
27−
27+updateDisplay :: App ()
28−update_display :: App ()
28+updateDisplay = do
29−update_display = do
30− let info = string def_attr "Press ESC to exit."
29+ let info = string defAttr "Press ESC to exit."
31− event_log <- foldMap (string def_attr) <$> get
30+ eventLog <- foldMap (string defAttr) <$> get
32− let pic = pic_for_image $ info <-> event_log
31+ let pic = picForImage $ info <-> eventLog
33 vty <- ask
32 vty <- ask
34 liftIO $ update vty pic
33 liftIO $ update vty pic
35
34
36−handle_next_event = ask >>= liftIO . next_event >>= handle_event
35+handleNextEvent = ask >>= liftIO . nextEvent >>= handleEvent
37 where
36 where
38− handle_event (EvKey KEsc []) = return True
37+ handleEvent e = do
39− handle_event e = do
40− modify $ (<|) (show e) >>> Seq.take event_buffer_size
38+ modify $ (<|) (show e) >>> Seq.take eventBufferSize
41− return False
39+ return $ e == EvKey KEsc []
42
40
test/HereDoc.hs+2−4
@@ -1,8 +1,7 @@
1{-# LANGUAGE QuasiQuotes #-}
1{-# LANGUAGE QuasiQuotes #-}
2{-# LANGUAGE TemplateHaskell #-}
2{-# LANGUAGE TemplateHaskell #-}
3−{- From http://www.reddit.com/r/haskell/comments/8ereh/a_here_document_syntax/
3+-- From http://www.reddit.com/r/haskell/comments/8ereh/a_here_document_syntax/
4− - copyright unknown?
4+-- copyright unknown?
5− -}
6module HereDoc (heredoc) where
5module HereDoc (heredoc) where
7
6
8import Language.Haskell.TH.Quote
7import Language.Haskell.TH.Quote
@@ -11,4 +10,3 @@ import Language.Haskell.TH.Lib
11
10
12heredoc :: QuasiQuoter
11heredoc :: QuasiQuoter
13heredoc = QuasiQuoter (litE . stringL) (litP . stringL) undefined undefined
12heredoc = QuasiQuoter (litE . stringL) (litP . stringL) undefined undefined
14−
test/vty_issue_18.hs → test/Issue18.hsrenamed+4−6
@@ -1,5 +1,4 @@
1−module Main
1+module Main where
2− where
3
2
4import Graphics.Vty
3import Graphics.Vty
5import Graphics.Vty.Debug
4import Graphics.Vty.Debug
@@ -13,8 +12,8 @@ main = do
13 play vty sx sy
12 play vty sx sy
14
13
15play :: Vty -> Int -> Int -> IO ()
14play :: Vty -> Int -> Int -> IO ()
16−play vty sx sy =
15+play vty sx sy =
17− let
16+ let
18 testScreen = pic {
17 testScreen = pic {
19 pCursor = NoCursor
18 pCursor = NoCursor
20 , pImage = box 10 10 }
19 , pImage = box 10 10 }
@@ -31,6 +30,5 @@ box w h =
31 vertLine = renderFill attr '|' 1 (h - 2)
30 vertLine = renderFill attr '|' 1 (h - 2)
32 horizLine = corner <|> renderHFill attr '-' (w - 2) <|> corner
31 horizLine = corner <|> renderHFill attr '-' (w - 2) <|> corner
33 centerArea = vertLine <|> renderFill attr 'X' (w - 2) (h - 2) <|> vertLine
32 centerArea = vertLine <|> renderFill attr 'X' (w - 2) (h - 2) <|> vertLine
34− in
33+ in
35 horizLine <-> centerArea <-> horizLine
34 horizLine <-> centerArea <-> horizLine
36−
test/Issue76.hsnew file+17
@@ -0,0 +1,17 @@
1+module Main where
2+
3+import Control.Monad (void)
4+import Graphics.Vty
5+
6+main :: IO ()
7+main = do
8+ cfg <- standardIOConfig
9+ vty <- mkVty cfg
10+ let line1 = charFill (defAttr `withBackColor` blue) ' ' 10 1
11+ line2 = charFill (defAttr `withBackColor` green) ' ' 10 1
12+ img = translate 10 5 (line1 `vertJoin` line2)
13+ pic = picForImage img
14+
15+ update vty pic
16+ void $ nextEvent vty
17+ shutdown vty
test/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+
test/Makefile+2−2
@@ -45,7 +45,7 @@ all : $(VERIF_TESTS)
45
45
46.PHONY: $(TESTS)
46.PHONY: $(TESTS)
47.SECONDEXPANSION:
47.SECONDEXPANSION:
48−$(TESTS) :
48+$(TESTS) :
49 @echo running test $@
49 @echo running test $@
50 @mkdir -p results/$@
50 @mkdir -p results/$@
51 ( ghc $(GHC_PROF_ARGS) $@ \
51 ( ghc $(GHC_PROF_ARGS) $@ \
@@ -54,7 +54,7 @@ $(TESTS) :
54 )
54 )
55
55
56.PHONY: interactive_terminal_test
56.PHONY: interactive_terminal_test
57−interactive_terminal_test :
57+interactive_terminal_test :
58 ghc $(GHC_ARGS) $@ && ./$@
58 ghc $(GHC_ARGS) $@ && ./$@
59
59
60.PHONY: core
60.PHONY: core
test/MinimalExample.hsnew file+14
@@ -0,0 +1,14 @@
1+module Main where
2+
3+import Graphics.Vty
4+
5+main = do
6+ vty <- mkVty defaultConfig
7+ let line0 = string (def `withForeColor` green) "first line"
8+ line1 = string (def `withBackColor` blue) "second line"
9+ img = line0 <-> line1
10+ pic = picForImage img
11+ update vty pic
12+ e <- nextEvent vty
13+ shutdown vty
14+ print $ "Last event was: " ++ show e
test/Rogue.hsnew file+175
@@ -0,0 +1,175 @@
1+module Main where
2+
3+import Graphics.Vty
4+
5+import Data.Array
6+
7+import Control.Applicative
8+import Control.Monad
9+import Control.Monad.RWS
10+
11+import System.Random
12+
13+data Player = Player
14+ { playerCoord :: Coord
15+ } deriving (Show,Eq)
16+
17+data World = World
18+ { player :: Player
19+ , level :: Level
20+ }
21+ deriving (Show,Eq)
22+
23+data Level = Level
24+ { levelStart :: Coord
25+ , levelEnd :: Coord
26+ , levelGeo :: Geo
27+ -- building the geo image is expensive. Cache it. Though VTY should go
28+ -- through greater lengths to avoid the need to cache images.
29+ , levelGeoImage :: Image
30+ }
31+ deriving (Show,Eq)
32+
33+data LevelPiece
34+ = EmptySpace
35+ | Rock
36+ deriving (Show, Eq)
37+
38+type Game = RWST Vty () World IO
39+type Geo = Array Coord LevelPiece
40+type Coord = (Int, Int)
41+
42+main :: IO ()
43+main = do
44+ vty <- mkVty defaultConfig
45+ level0 <- mkLevel 1
46+ let world0 = World (Player (levelStart level0)) level0
47+ (_finalWorld, ()) <- execRWST play vty world0
48+ shutdown vty
49+
50+-- |Generate a level randomly using the specified difficulty. Higher
51+-- difficulty means the level will have more rooms and cover a larger area.
52+mkLevel :: Int -> IO Level
53+mkLevel difficulty = do
54+ let size = 80 * difficulty
55+ [levelWidth, levelHeight] <- replicateM 2 $ randomRIO (size,size)
56+ let randomP = (,) <$> randomRIO (2, levelWidth-3) <*> randomRIO (2, levelHeight-3)
57+ start <- randomP
58+ end <- randomP
59+ -- first the base geography: all rocks
60+ let baseGeo = array ((0,0), (levelWidth-1, levelHeight-1))
61+ [((x,y),Rock) | x <- [0..levelWidth-1], y <- [0..levelHeight-1]]
62+ -- next the empty spaces that make the rooms
63+ -- for this we generate a number of center points
64+ centers <- replicateM (2 ^ difficulty + difficulty) randomP
65+ -- generate rooms for all those points, plus the start and end
66+ geo <- foldM (addRoom levelWidth levelHeight) baseGeo (start : end : centers)
67+ return $ Level start end geo (buildGeoImage geo)
68+
69+-- |Add a room to a geography and return a new geography. Adds a
70+-- randomly-sized room centered at the specified coordinates.
71+addRoom :: Int
72+ -> Int
73+ -- ^The width and height of the geographical area
74+ -> Geo
75+ -- ^The geographical area to which a new room should be added
76+ -> Coord
77+ -- ^The desired center of the new room.
78+ -> IO Geo
79+addRoom levelWidth levelHeight geo (centerX, centerY) = do
80+ size <- randomRIO (5,15)
81+ let xMin = max 1 (centerX - size)
82+ xMax = min (levelWidth - 1) (centerX + size)
83+ yMin = max 1 (centerY - size)
84+ yMax = min (levelHeight - 1) (centerY + size)
85+ let room = [((x,y), EmptySpace) | x <- [xMin..xMax - 1], y <- [yMin..yMax - 1]]
86+ return (geo // room)
87+
88+pieceA, dumpA :: Attr
89+pieceA = defAttr `withForeColor` blue `withBackColor` green
90+dumpA = defAttr `withStyle` reverseVideo
91+
92+play :: Game ()
93+play = do
94+ updateDisplay
95+ done <- processEvent
96+ unless done play
97+
98+processEvent :: Game Bool
99+processEvent = do
100+ k <- ask >>= liftIO . nextEvent
101+ if k == EvKey KEsc []
102+ then return True
103+ else do
104+ case k of
105+ EvKey (KChar 'r') [MCtrl] -> ask >>= liftIO . refresh
106+ EvKey KLeft [] -> movePlayer (-1) 0
107+ EvKey KRight [] -> movePlayer 1 0
108+ EvKey KUp [] -> movePlayer 0 (-1)
109+ EvKey KDown [] -> movePlayer 0 1
110+ _ -> return ()
111+ return False
112+
113+movePlayer :: Int -> Int -> Game ()
114+movePlayer dx dy = do
115+ world <- get
116+ let Player (x, y) = player world
117+ let x' = x + dx
118+ y' = y + dy
119+ -- this is only valid because the level generation assures the border is
120+ -- always Rock
121+ case levelGeo (level world) ! (x',y') of
122+ EmptySpace -> put $ world { player = Player (x',y') }
123+ _ -> return ()
124+
125+updateDisplay :: Game ()
126+updateDisplay = do
127+ let info = string defAttr "Move with the arrows keys. Press ESC to exit."
128+ -- determine offsets to place the player in the center of the level.
129+ (w,h) <- asks outputIface >>= liftIO . displayBounds
130+ thePlayer <- gets player
131+ let ox = (w `div` 2) - playerX thePlayer
132+ oy = (h `div` 2) - playerY thePlayer
133+ -- translate the world images to place the player in the center of the
134+ -- level.
135+ world' <- map (translate ox oy) <$> worldImages
136+ let pic = picForLayers $ info : world'
137+ vty <- ask
138+ liftIO $ update vty pic
139+
140+--
141+-- Image-generation functions
142+--
143+
144+worldImages :: Game [Image]
145+worldImages = do
146+ thePlayer <- gets player
147+ theLevel <- gets level
148+ let playerImage = translate (playerX thePlayer) (playerY thePlayer) (char pieceA '@')
149+ return [playerImage, levelGeoImage theLevel]
150+
151+imageForGeo :: LevelPiece -> Image
152+imageForGeo EmptySpace = char (defAttr `withBackColor` green) ' '
153+imageForGeo Rock = char defAttr 'X'
154+
155+buildGeoImage :: Geo -> Image
156+buildGeoImage geo =
157+ let (geoWidth, geoHeight) = snd $ bounds geo
158+ -- seems like a the repeated index operation should be removable. This is
159+ -- not performing random access but (presumably) access in order of index.
160+ in vertCat [ geoRow
161+ | y <- [0..geoHeight-1]
162+ , let geoRow = horizCat [ i
163+ | x <- [0..geoWidth-1]
164+ , let i = imageForGeo (geo ! (x,y))
165+ ]
166+ ]
167+
168+--
169+-- Miscellaneous
170+--
171+playerX :: Player -> Int
172+playerX = fst . playerCoord
173+
174+playerY :: Player -> Int
175+playerY = snd . playerCoord
test/Rouge.hsdeleted−105
@@ -1,105 +0,0 @@
1−module Main where
2−
3−import Graphics.Vty
4−
5−import Data.Array
6−import qualified Data.ByteString as B
7−import Data.Word
8−
9−import Control.Applicative
10−import Control.Monad
11−import Control.Monad.RWS
12−import Control.Monad.Writer
13−
14−import System.IO
15−import System.Random
16−
17−data Dude = Dude Int Int
18− deriving (Show,Eq)
19−
20−data World = World
21− { dude :: Dude
22− , level :: Level
23− }
24− deriving (Show,Eq)
25−
26−data Level = Level
27− { start :: (Int, Int)
28− , end :: (Int, Int)
29− , geo :: Array (Int, Int) LevelPiece
30− }
31− deriving (Show,Eq)
32−
33−data LevelPiece
34− = EmptySpace
35− | Rock
36− deriving (Show, Eq)
37−
38−type Game = RWST Vty () World IO
39−
40−main = do
41− vty <- mkVty
42− level_0 <- mkLevel 0
43− let world_0 = World (Dude (fst $ start level_0) (snd $ start level_0)) level_0
44− (_final_world, ()) <- execRWST (play >> view_world) vty world_0
45− shutdown vty
46−
47−mkLevel _difficulty = do
48− level_width <- randomRIO (10,15)
49− level_height <- randomRIO (10,15)
50− start <- (,) <$> randomRIO (2, level_width-3) <*> randomRIO (2, level_height-3)
51− end <- (,) <$> randomRIO (2, level_width-3) <*> randomRIO (2, level_height-3)
52− -- first the base geography: all rocks
53− let base_geo = array ((0,0), (level_width, level_height))
54− [((x,y),Rock) | x <- [0..level_width-1], y <- [0..level_height-1]]
55− return $ Level start end base_geo
56−
57−image_for_geo EmptySpace = char (def_attr `with_back_color` green) ' '
58−image_for_geo Rock = char (def_attr `with_fore_color` white) 'X'
59−
60−pieceA = def_attr `with_fore_color` red
61−dumpA = def_attr `with_style` reverse_video
62−
63−play = do
64− view_world
65− done <- process_event
66− unless done play
67−
68−process_event = do
69− k <- ask >>= liftIO . next_event
70− if k == EvKey KEsc []
71− then return True
72− else do
73− case k of
74− EvKey (KASCII 'r') [MCtrl] -> ask >>= liftIO . refresh
75− EvKey KLeft [] -> move_dude (-1) 0
76− EvKey KRight [] -> move_dude 1 0
77− EvKey KUp [] -> move_dude 0 (-1)
78− EvKey KDown [] -> move_dude 0 1
79− _ -> return ()
80− return False
81−
82−move_dude dx dy = do
83− vty <- ask
84− world <- get
85− let Dude x y = dude world
86− (w, h) <- gets (snd . bounds . geo . level)
87− put $ world { dude = Dude (min (w - 2) $ max 1 (x + dx))
88− (min (h - 2) $ max 1 (y + dy))
89− }
90−
91−view_world :: Game ()
92−view_world = do
93− Dude x y <- gets dude
94− the_level <- gets level
95− let dude_image = translate x y (char pieceA '@')
96− let (geo_width, geo_height) = snd $ bounds (geo the_level)
97− geo_image = vert_cat [ geo_row | y <- [0..geo_height-1],
98− let geo_row = horiz_cat [ i | x <- [0..geo_width-1],
99− let i = image_for_geo (geo the_level ! (x,y))
100− ]
101− ]
102− info = string def_attr "Move with the arrows keys. Press ESC to exit."
103− let pic = pic_for_layers [info, translate 0 1 dude_image,translate 0 1 geo_image]
104− vty <- ask
105− liftIO $ update vty pic
test/Setup.lhsnew file+3
@@ -0,0 +1,3 @@
1+#!/usr/bin/env runhaskell
2+> import Distribution.Simple
3+> main = defaultMain
test/Test2.hs+1
@@ -1,4 +1,5 @@
1module Main where
1module Main where
2+
2import Graphics.Vty
3import Graphics.Vty
3
4
4main = do
5main = do
test/Verify.hs+25−37
@@ -1,30 +1,29 @@
1{-# LANGUAGE RankNTypes #-}
1{-# LANGUAGE RankNTypes #-}
2{-# LANGUAGE GADTs #-}
2{-# LANGUAGE GADTs #-}
3{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE RecordWildCards #-}
4−{-# LANGUAGE CPP #-}
5{-# LANGUAGE TypeSynonymInstances #-}
4{-# LANGUAGE TypeSynonymInstances #-}
6{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE StandaloneDeriving #-}
6{-# LANGUAGE StandaloneDeriving #-}
8{-# LANGUAGE DisambiguateRecordFields #-}
7{-# LANGUAGE DisambiguateRecordFields #-}
9{-# LANGUAGE NamedFieldPuns #-}
8{-# LANGUAGE NamedFieldPuns #-}
10−module Verify ( module Verify
9+module Verify
10+ ( module Verify
11− , module Control.Applicative
11+ , module Control.Applicative
12− , module Control.DeepSeq
12+ , module Control.DeepSeq
13− , module Control.Exception
13+ , module Control.Exception
14− , module Control.Monad
14+ , module Control.Monad
15− , module Test.QuickCheck
15+ , module Test.QuickCheck
16− , module Test.QuickCheck.Modifiers
16+ , module Test.QuickCheck.Modifiers
17− , module Text.Printf
17+ , module Text.Printf
18− , succeeded
18+ , succeeded
19− , failed
19+ , failed
20− , result
21− , monadicIO
20+ , monadicIO
22− , liftIO
21+ , liftIO
23− , liftBool
22+ , liftBool
24− , Test(..)
23+ , Test(..)
25− , Prop.Result(..)
24+ , Prop.Result(..)
26− )
25+ )
27− where
26+where
28
27
29import Control.Exception ( bracket, try, SomeException(..) )
28import Control.Exception ( bracket, try, SomeException(..) )
30
29
@@ -36,7 +35,7 @@ import qualified Test.QuickCheck as QC
36import Test.QuickCheck.Modifiers
35import Test.QuickCheck.Modifiers
37import Test.QuickCheck.Property hiding ( Result(..) )
36import Test.QuickCheck.Property hiding ( Result(..) )
38import qualified Test.QuickCheck.Property as Prop
37import qualified Test.QuickCheck.Property as Prop
39−import Test.QuickCheck.Monadic ( monadicIO )
38+import Test.QuickCheck.Monadic ( monadicIO )
40
39
41import Text.Printf
40import Text.Printf
42
41
@@ -50,14 +49,14 @@ import Control.Monad.State.Strict
50import Numeric ( showHex )
49import Numeric ( showHex )
51
50
52verify :: Testable t => String -> t -> Test
51verify :: Testable t => String -> t -> Test
53−verify test_name p = Test $ TestInstance
52+verify testName p = Test $ TestInstance
54− { name = test_name
53+ { name = testName
55 , run = do
54 , run = do
56− qc_result <- quickCheckResult p
55+ qcResult <- quickCheckWithResult (stdArgs {chatty = False}) p
57− case qc_result of
56+ case qcResult of
58 QC.Success {..} -> return $ Finished TS.Pass
57 QC.Success {..} -> return $ Finished TS.Pass
59 QC.Failure {numShrinks,reason} -> return $ Finished
58 QC.Failure {numShrinks,reason} -> return $ Finished
60− $ TS.Fail $ "After "
59+ $ TS.Fail $ "After "
61 ++ show numShrinks ++ " shrinks determined failure to be: "
60 ++ show numShrinks ++ " shrinks determined failure to be: "
62 ++ show reason
61 ++ show reason
63 _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"
62 _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"
@@ -84,18 +83,7 @@ instance Arbitrary DoubleColumnChar where
84 ++ [ toEnum 0x309B .. toEnum 0xA4CF ]
83 ++ [ toEnum 0x309B .. toEnum 0xA4CF ]
85
84
86liftIOResult :: Testable prop => IO prop -> Property
85liftIOResult :: Testable prop => IO prop -> Property
87−liftIOResult = morallyDubiousIOProperty
86+liftIOResult = ioProperty
88−
89−#if __GLASGOW_HASKELL__ <= 701
90−instance Random Word where
91− random g =
92− let (i :: Int, g') = random g
93− in (toEnum i, g')
94− randomR (l,h) g =
95− let (i :: Int, g') = randomR (fromEnum l,fromEnum h) g
96− in (toEnum i, g')
97−#endif
98
87
99data Bench where
88data Bench where
100 Bench :: forall v . NFData v => IO v -> (v -> IO ()) -> Bench
89 Bench :: forall v . NFData v => IO v -> (v -> IO ()) -> Bench
101−
test/Verify/Data/Terminfo/Parse.hs+60−61
@@ -1,39 +1,41 @@
1−module Verify.Data.Terminfo.Parse ( module Verify.Data.Terminfo.Parse
1+module Verify.Data.Terminfo.Parse
2+ ( module Verify.Data.Terminfo.Parse
2− , module Data.Terminfo.Parse
3+ , module Data.Terminfo.Parse
3− )
4+ )
4− where
5+where
5
6
6import Data.Terminfo.Parse
7import Data.Terminfo.Parse
7import Data.Terminfo.Eval
8import Data.Terminfo.Eval
8import Verify
9import Verify
9
10
10import Data.Word
11import Data.Word
12+import qualified Data.Vector.Unboxed as Vector
11
13
12import Numeric
14import Numeric
13
15
14−hex_dump :: [Word8] -> String
16+hexDump :: [Word8] -> String
15−hex_dump bytes = foldr (\b s -> showHex b s) "" bytes
17+hexDump bytes = foldr (\b s -> showHex b s) "" bytes
16
18
17data NonParamCapString = NonParamCapString String
19data NonParamCapString = NonParamCapString String
18 deriving Show
20 deriving Show
19
21
20instance Arbitrary NonParamCapString where
22instance Arbitrary NonParamCapString where
21− arbitrary
23+ arbitrary
22 = ( do
24 = ( do
23 s <- listOf1 $ (choose (0, 255) >>= return . toEnum) `suchThat` (/= '%')
25 s <- listOf1 $ (choose (0, 255) >>= return . toEnum) `suchThat` (/= '%')
24 return $ NonParamCapString s
26 return $ NonParamCapString s
25− ) `suchThat` ( \(NonParamCapString str) -> length str < 255 )
27+ ) `suchThat` ( \(NonParamCapString str) -> length str < 255 )
26
28
27data LiteralPercentCap = LiteralPercentCap String [Word8]
29data LiteralPercentCap = LiteralPercentCap String [Word8]
28 deriving ( Show )
30 deriving ( Show )
29
31
30instance Arbitrary LiteralPercentCap where
32instance Arbitrary LiteralPercentCap where
31− arbitrary
33+ arbitrary
32 = ( do
34 = ( do
33 NonParamCapString s <- arbitrary
35 NonParamCapString s <- arbitrary
34− (s', bytes) <- insert_escape_op "%" [toEnum $ fromEnum '%'] s
36+ (s', bytes) <- insertEscapeOp "%" [toEnum $ fromEnum '%'] s
35 return $ LiteralPercentCap s' bytes
37 return $ LiteralPercentCap s' bytes
36− ) `suchThat` ( \(LiteralPercentCap str _) -> length str < 255 )
38+ ) `suchThat` ( \(LiteralPercentCap str _) -> length str < 255 )
37
39
38data IncFirstTwoCap = IncFirstTwoCap String [Word8]
40data IncFirstTwoCap = IncFirstTwoCap String [Word8]
39 deriving ( Show )
41 deriving ( Show )
@@ -42,72 +44,69 @@ instance Arbitrary IncFirstTwoCap where
42 arbitrary
44 arbitrary
43 = ( do
45 = ( do
44 NonParamCapString s <- arbitrary
46 NonParamCapString s <- arbitrary
45− (s', bytes) <- insert_escape_op "i" [] s
47+ (s', bytes) <- insertEscapeOp "i" [] s
46 return $ IncFirstTwoCap s' bytes
48 return $ IncFirstTwoCap s' bytes
47− ) `suchThat` ( \(IncFirstTwoCap str _) -> length str < 255 )
49+ ) `suchThat` ( \(IncFirstTwoCap str _) -> length str < 255 )
48
50
49−data PushParamCap = PushParamCap String Word [Word8]
51+data PushParamCap = PushParamCap String Int [Word8]
50 deriving ( Show )
52 deriving ( Show )
51
53
52instance Arbitrary PushParamCap where
54instance Arbitrary PushParamCap where
53 arbitrary
55 arbitrary
54 = ( do
56 = ( do
55 NonParamCapString s <- arbitrary
57 NonParamCapString s <- arbitrary
56− n :: Word <- choose (1,9) >>= return . toEnum
58+ n <- choose (1,9)
57− (s', bytes) <- insert_escape_op ("p" ++ show n) [] s
59+ (s', bytes) <- insertEscapeOp ("p" ++ show n) [] s
58 return $ PushParamCap s' n bytes
60 return $ PushParamCap s' n bytes
59− ) `suchThat` ( \(PushParamCap str _ _) -> length str < 255 )
61+ ) `suchThat` ( \(PushParamCap str _ _) -> length str < 255 )
60
62
61−data DecPrintCap = DecPrintCap String Word [Word8]
63+data DecPrintCap = DecPrintCap String Int [Word8]
62 deriving ( Show )
64 deriving ( Show )
63
65
64instance Arbitrary DecPrintCap where
66instance Arbitrary DecPrintCap where
65 arbitrary
67 arbitrary
66 = ( do
68 = ( do
67 NonParamCapString s <- arbitrary
69 NonParamCapString s <- arbitrary
68− n :: Word <- choose (1,9) >>= return . toEnum
70+ n <- choose (1,9)
69− (s', bytes) <- insert_escape_op ("p" ++ show n ++ "%d") [] s
71+ (s', bytes) <- insertEscapeOp ("p" ++ show n ++ "%d") [] s
70 return $ DecPrintCap s' n bytes
72 return $ DecPrintCap s' n bytes
71− ) `suchThat` ( \(DecPrintCap str _ _) -> length str < 255 )
73+ ) `suchThat` ( \(DecPrintCap str _ _) -> length str < 255 )
72−
74+
73−insert_escape_op op_str repl_bytes s = do
75+insertEscapeOp opStr replBytes s = do
74− insert_points <- listOf1 $ elements [0 .. length s - 1]
76+ insertPoints <- listOf1 $ elements [0 .. length s - 1]
75− let s' = f s ('%' : op_str)
77+ let s' = f s ('%' : opStr)
76− remaining_bytes = f (map (toEnum . fromEnum) s) repl_bytes
78+ remainingBytes = f (map (toEnum . fromEnum) s) replBytes
77− f in_vs out_v = concat [ vs
79+ f inVs out_v = concat [ vs
78− | vi <- zip in_vs [0 .. length s - 1]
80+ | vi <- zip inVs [0 .. length s - 1]
79− , let vs = fst vi : ( if snd vi `elem` insert_points
81+ , let vs = fst vi : ( if snd vi `elem` insertPoints
80− then out_v
82+ then out_v
81− else []
83+ else []
82− )
84+ )
83− ]
85+ ]
84− return (s', remaining_bytes)
86+ return (s', remainingBytes)
85−
87+
86−is_bytes_op :: CapOp -> Bool
88+isBytesOp :: CapOp -> Bool
87−is_bytes_op (Bytes {}) = True
89+isBytesOp (Bytes {}) = True
88−-- is_bytes_op _ = False
90+-- isBytesOp _ = False
89−
91+
90−bytes_for_range cap offset c
92+bytesForRange cap offset count
91− = take (fromEnum c)
93+ = Vector.toList $ Vector.take count $ Vector.drop offset $ capBytes cap
92− $ drop (fromEnum offset)
94+
93− $ ( map ( toEnum . fromEnum ) $! source_string cap )
94−
95−collect_bytes :: CapExpression -> [Word8]
95+collectBytes :: CapExpression -> [Word8]
96−collect_bytes e = concat [ bytes
96+collectBytes e = concat [ bytes
97− | Bytes offset c _ <- cap_ops e
97+ | Bytes offset count <- capOps e
98− , let bytes = bytes_for_range e offset c
98+ , let bytes = bytesForRange e offset count
99− ]
99+ ]
100−
100+
101−
101+
102−verify_bytes_equal :: [Word8] -> [Word8] -> Result
102+verifyBytesEqual :: [Word8] -> [Word8] -> Result
103−verify_bytes_equal out_bytes expected_bytes
103+verifyBytesEqual outBytes expectedBytes
104− = if out_bytes == expected_bytes
104+ = if outBytes == expectedBytes
105 then succeeded
105 then succeeded
106− else failed
106+ else failed
107− { reason = "out_bytes ["
107+ { reason = "outBytes ["
108− ++ hex_dump out_bytes
108+ ++ hexDump outBytes
109− ++ "] /= expected_bytes ["
109+ ++ "] /= expectedBytes ["
110− ++ hex_dump expected_bytes
110+ ++ hexDump expectedBytes
111− ++ "]"
111+ ++ "]"
112 }
112 }
113−
test/Verify/Graphics/Vty/Attributes.hs+35−35
@@ -1,15 +1,16 @@
1−module Verify.Graphics.Vty.Attributes ( module Verify.Graphics.Vty.Attributes
1+module Verify.Graphics.Vty.Attributes
2+ ( module Verify.Graphics.Vty.Attributes
2− , module Graphics.Vty.Attributes
3+ , module Graphics.Vty.Attributes
3− )
4+ )
4− where
5+where
5
6
6import Graphics.Vty.Attributes
7import Graphics.Vty.Attributes
7import Verify
8import Verify
8
9
9import Data.List ( delete )
10import Data.List ( delete )
10
11
11−all_colors :: [Color]
12+allColors :: [Color]
12−all_colors =
13+allColors =
13 [ black
14 [ black
14 , red
15 , red
15 , green
16 , green
@@ -18,56 +19,55 @@ all_colors =
18 , magenta
19 , magenta
19 , cyan
20 , cyan
20 , white
21 , white
21− , bright_black
22+ , brightBlack
22− , bright_red
23+ , brightRed
23− , bright_green
24+ , brightGreen
24− , bright_yellow
25+ , brightYellow
25− , bright_blue
26+ , brightBlue
26− , bright_magenta
27+ , brightMagenta
27− , bright_cyan
28+ , brightCyan
28− , bright_white
29+ , brightWhite
29 ] ++ map Color240 [0..239]
30 ] ++ map Color240 [0..239]
30
31
31−all_styles :: [Style]
32+allStyles :: [Style]
32−all_styles =
33+allStyles =
33 [ standout
34 [ standout
34 , underline
35 , underline
35− , reverse_video
36+ , reverseVideo
36 , blink
37 , blink
37 , dim
38 , dim
38 , bold
39 , bold
39 ]
40 ]
40
41
41-- Limit the possible attributes to just a few for now.
42-- Limit the possible attributes to just a few for now.
42−possible_attr_mods :: [ AttrOp ]
43+possibleAttrMods :: [ AttrOp ]
43−possible_attr_mods =
44+possibleAttrMods =
44− [ id_op
45+ [ idOp
45− ] ++ map set_fore_color_op all_colors
46+ ] ++ map setForeColorOp allColors
46− ++ map set_back_color_op all_colors
47+ ++ map setBackColorOp allColors
47− ++ map set_style_op all_styles
48+ ++ map setStyleOp allStyles
48
49
49instance Arbitrary Attr where
50instance Arbitrary Attr where
50− arbitrary = elements possible_attr_mods >>= return . flip apply_op def_attr
51+ arbitrary = elements possibleAttrMods >>= return . flip applyOp defAttr
51
52
52data DiffAttr = DiffAttr Attr Attr
53data DiffAttr = DiffAttr Attr Attr
53
54
54instance Arbitrary DiffAttr where
55instance Arbitrary DiffAttr where
55 arbitrary = do
56 arbitrary = do
56− op0 <- elements possible_attr_mods
57+ op0 <- elements possibleAttrMods
57− let possible_attr_mods' = delete op0 possible_attr_mods
58+ let possibleAttrMods' = delete op0 possibleAttrMods
58− op1 <- elements possible_attr_mods'
59+ op1 <- elements possibleAttrMods'
59− return $ DiffAttr (apply_op op0 def_attr) (apply_op op1 def_attr)
60+ return $ DiffAttr (applyOp op0 defAttr) (applyOp op1 defAttr)
60
61
61data AttrOp = AttrOp String (Attr -> Attr)
62data AttrOp = AttrOp String (Attr -> Attr)
62
63
63instance Eq AttrOp where
64instance Eq AttrOp where
64 AttrOp n0 _ == AttrOp n1 _ = n0 == n1
65 AttrOp n0 _ == AttrOp n1 _ = n0 == n1
65
66
66−set_style_op s = AttrOp "set_style" (flip with_style s)
67+setStyleOp s = AttrOp "set_style" (flip withStyle s)
67−set_fore_color_op c = AttrOp "set_fore_color" (flip with_fore_color c)
68+setForeColorOp c = AttrOp "set_fore_color" (flip withForeColor c)
68−set_back_color_op c = AttrOp "set_back_color" (flip with_back_color c)
69+setBackColorOp c = AttrOp "set_back_color" (flip withBackColor c)
69−id_op = AttrOp "id" id
70+idOp = AttrOp "id" id
70−
71−apply_op :: AttrOp -> Attr -> Attr
72−apply_op (AttrOp _ f) a = f a
73
71
72+applyOp :: AttrOp -> Attr -> Attr
73+applyOp (AttrOp _ f) a = f a
test/Verify/Graphics/Vty/DisplayAttributes.hs+5−5
@@ -1,8 +1,8 @@
1−module Verify.Graphics.Vty.DisplayAttributes ( module Verify.Graphics.Vty.DisplayAttributes
1+module Verify.Graphics.Vty.DisplayAttributes
2+ ( module Verify.Graphics.Vty.DisplayAttributes
2− , module Graphics.Vty.DisplayAttributes
3+ , module Graphics.Vty.DisplayAttributes
3− )
4+ )
4− where
5+where
5
6
6import Graphics.Vty.DisplayAttributes
7import Graphics.Vty.DisplayAttributes
7import Verify
8import Verify
8−
test/Verify/Graphics/Vty/Image.hs+97−97
@@ -1,10 +1,11 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3{-# LANGUAGE DisambiguateRecordFields #-}
3{-# LANGUAGE DisambiguateRecordFields #-}
4−module Verify.Graphics.Vty.Image ( module Verify.Graphics.Vty.Image
4+module Verify.Graphics.Vty.Image
5+ ( module Verify.Graphics.Vty.Image
5− , module Graphics.Vty.Image
6+ , module Graphics.Vty.Image
6− )
7+ )
7− where
8+where
8
9
9import Verify.Graphics.Vty.Attributes
10import Verify.Graphics.Vty.Attributes
10import Graphics.Vty.Image
11import Graphics.Vty.Image
@@ -26,23 +27,23 @@ instance Show UnitImage where
26data DefaultImage = DefaultImage Image
27data DefaultImage = DefaultImage Image
27
28
28instance Show DefaultImage where
29instance Show DefaultImage where
29− show (DefaultImage i)
30+ show (DefaultImage i)
30− = "DefaultImage (" ++ show i ++ ") " ++ show (image_width i, image_height i)
31+ = "DefaultImage (" ++ show i ++ ") " ++ show (imageWidth i, imageHeight i)
31
32
32instance Arbitrary DefaultImage where
33instance Arbitrary DefaultImage where
33 arbitrary = do
34 arbitrary = do
34− i <- return $ char def_attr 'X'
35+ i <- return $ char defAttr 'X'
35 return $ DefaultImage i
36 return $ DefaultImage i
36
37
37−data SingleRowSingleAttrImage
38+data SingleRowSingleAttrImage
38− = SingleRowSingleAttrImage
39+ = SingleRowSingleAttrImage
39− { expected_attr :: Attr
40+ { expectedAttr :: Attr
40− , expected_columns :: Int
41+ , expectedColumns :: Int
41− , row_image :: Image
42+ , rowImage :: Image
42 }
43 }
43
44
44instance Show SingleRowSingleAttrImage where
45instance Show SingleRowSingleAttrImage where
45− show (SingleRowSingleAttrImage attr columns image)
46+ show (SingleRowSingleAttrImage attr columns image)
46 = "SingleRowSingleAttrImage (" ++ show attr ++ ") " ++ show columns ++ " ( " ++ show image ++ " )"
47 = "SingleRowSingleAttrImage (" ++ show attr ++ ") " ++ show columns ++ " ( " ++ show image ++ " )"
47
48
48newtype WidthResize = WidthResize (Image -> (Image, Int))
49newtype WidthResize = WidthResize (Image -> (Image, Int))
@@ -52,10 +53,10 @@ instance Arbitrary WidthResize where
52 WidthResize f <- arbitrary
53 WidthResize f <- arbitrary
53 w <- choose (1,64)
54 w <- choose (1,64)
54 oneof $ map (return . WidthResize)
55 oneof $ map (return . WidthResize)
55− [ \i -> (i, image_width i)
56+ [ \i -> (i, imageWidth i)
56− , \i -> (resize_width w $ fst $ f i, w)
57+ , \i -> (resizeWidth w $ fst $ f i, w)
57− , \i -> let i' = fst $ f i in (crop_left w i', min (image_width i') w)
58+ , \i -> let i' = fst $ f i in (cropLeft w i', min (imageWidth i') w)
58− , \i -> let i' = fst $ f i in (crop_right w i', min (image_width i') w)
59+ , \i -> let i' = fst $ f i in (cropRight w i', min (imageWidth i') w)
59 ]
60 ]
60
61
61newtype HeightResize = HeightResize (Image -> (Image, Int))
62newtype HeightResize = HeightResize (Image -> (Image, Int))
@@ -65,134 +66,134 @@ instance Arbitrary HeightResize where
65 HeightResize f <- arbitrary
66 HeightResize f <- arbitrary
66 h <- choose (1,64)
67 h <- choose (1,64)
67 oneof $ map (return . HeightResize)
68 oneof $ map (return . HeightResize)
68− [ \i -> (i, image_height i)
69+ [ \i -> (i, imageHeight i)
69− , \i -> (resize_height h $ fst $ f i, h)
70+ , \i -> (resizeHeight h $ fst $ f i, h)
70− , \i -> let i' = fst $ f i in (crop_top h i', min (image_height i') h)
71+ , \i -> let i' = fst $ f i in (cropTop h i', min (imageHeight i') h)
71− , \i -> let i' = fst $ f i in (crop_bottom h i', min (image_height i') h)
72+ , \i -> let i' = fst $ f i in (cropBottom h i', min (imageHeight i') h)
72 ]
73 ]
73
74
74newtype ImageResize = ImageResize (Image -> (Image, (Int, Int)))
75newtype ImageResize = ImageResize (Image -> (Image, (Int, Int)))
75
76
76instance Arbitrary ImageResize where
77instance Arbitrary ImageResize where
77 arbitrary = oneof
78 arbitrary = oneof
78− [ return $! ImageResize $! \i -> (i, (image_width i, image_height i))
79+ [ return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
79− , return $! ImageResize $! \i -> (i, (image_width i, image_height i))
80+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
80− , return $! ImageResize $! \i -> (i, (image_width i, image_height i))
81+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
81− , return $! ImageResize $! \i -> (i, (image_width i, image_height i))
82+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
82− , return $! ImageResize $! \i -> (i, (image_width i, image_height i))
83+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
83− , return $! ImageResize $! \i -> (i, (image_width i, image_height i))
84+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
84 , do
85 , do
85 ImageResize f <- arbitrary
86 ImageResize f <- arbitrary
86 WidthResize g <- arbitrary
87 WidthResize g <- arbitrary
87− return $! ImageResize $! \i ->
88+ return $! ImageResize $! \i ->
88− let (i_0, (_, out_height)) = f i
89+ let (i0, (_, outHeight)) = f i
89− g_i = g i_0
90+ gI = g i0
90− in (fst g_i, (snd g_i, out_height))
91+ in (fst gI, (snd gI, outHeight))
91 , do
92 , do
92 ImageResize f <- arbitrary
93 ImageResize f <- arbitrary
93 HeightResize g <- arbitrary
94 HeightResize g <- arbitrary
94− return $! ImageResize $! \i ->
95+ return $! ImageResize $! \i ->
95− let (i_0, (out_width, _)) = f i
96+ let (i0, (outWidth, _)) = f i
96− g_i = g i_0
97+ gI = g i0
97− in (fst g_i, (out_width, snd g_i))
98+ in (fst gI, (outWidth, snd gI))
98 ]
99 ]
99
100
100
101
101instance Arbitrary SingleRowSingleAttrImage where
102instance Arbitrary SingleRowSingleAttrImage where
102 arbitrary = do
103 arbitrary = do
103− -- The text must contain at least one character. Otherwise the image simplifies to the
104+ -- The text must contain at least one character. Otherwise the
105+ -- image simplifies to the IdImage which has a height of 0. If
104− -- IdImage which has a height of 0. If this is to represent a single row then the height
106+ -- this is to represent a single row then the height must be 1
105− -- must be 1
106− single_column_row_text <- Verify.resize 16 (listOf1 arbitrary)
107+ singleColumnRowText <- Verify.resize 16 (listOf1 arbitrary)
107 a <- arbitrary
108 a <- arbitrary
108− let out_image = horiz_cat $ [char a c | SingleColumnChar c <- single_column_row_text]
109+ let outImage = horizCat $ [char a c | SingleColumnChar c <- singleColumnRowText]
109− out_width = length single_column_row_text
110+ outWidth = length singleColumnRowText
110− return $ SingleRowSingleAttrImage a out_width out_image
111+ return $ SingleRowSingleAttrImage a outWidth outImage
111−
112+
112−data SingleRowTwoAttrImage
113+data SingleRowTwoAttrImage
113− = SingleRowTwoAttrImage
114+ = SingleRowTwoAttrImage
114− { part_0 :: SingleRowSingleAttrImage
115+ { part0 :: SingleRowSingleAttrImage
115− , part_1 :: SingleRowSingleAttrImage
116+ , part1 :: SingleRowSingleAttrImage
116− , join_image :: Image
117+ , joinImage :: Image
117 } deriving Show
118 } deriving Show
118
119
119instance Arbitrary SingleRowTwoAttrImage where
120instance Arbitrary SingleRowTwoAttrImage where
120 arbitrary = do
121 arbitrary = do
121 p0 <- arbitrary
122 p0 <- arbitrary
122 p1 <- arbitrary
123 p1 <- arbitrary
123− return $ SingleRowTwoAttrImage p0 p1 (row_image p0 <|> row_image p1)
124+ return $ SingleRowTwoAttrImage p0 p1 (rowImage p0 <|> rowImage p1)
124
125
125−data SingleAttrSingleSpanStack = SingleAttrSingleSpanStack
126+data SingleAttrSingleSpanStack = SingleAttrSingleSpanStack
126− { stack_image :: Image
127+ { stackImage :: Image
127− , stack_source_images :: [SingleRowSingleAttrImage]
128+ , stackSourceImages :: [SingleRowSingleAttrImage]
128− , stack_width :: Int
129+ , stackWidth :: Int
129− , stack_height :: Int
130+ , stackHeight :: Int
130 }
131 }
131 deriving Show
132 deriving Show
132
133
133instance Arbitrary SingleAttrSingleSpanStack where
134instance Arbitrary SingleAttrSingleSpanStack where
134 arbitrary = do
135 arbitrary = do
135− image_list <- Verify.resize 16 (listOf1 arbitrary)
136+ imageList <- Verify.resize 16 (listOf1 arbitrary)
136− return $ mk_single_attr_single_span_stack image_list
137+ return $ mkSingleAttrSingleSpanStack imageList
137 shrink s = do
138 shrink s = do
138− image_list <- shrink $ stack_source_images s
139+ imageList <- shrink $ stackSourceImages s
139− if null image_list
140+ if null imageList
140 then []
141 then []
141− else return $ mk_single_attr_single_span_stack image_list
142+ else return $ mkSingleAttrSingleSpanStack imageList
142
143
143−mk_single_attr_single_span_stack image_list =
144+mkSingleAttrSingleSpanStack imageList =
144− let image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- image_list ]
145+ let image = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- imageList ]
145− in SingleAttrSingleSpanStack image image_list (maximum $ map expected_columns image_list)
146+ in SingleAttrSingleSpanStack image imageList (maximum $ map expectedColumns imageList)
146− (toEnum $ length image_list)
147+ (toEnum $ length imageList)
147
148
148instance Arbitrary Image where
149instance Arbitrary Image where
149 arbitrary = oneof
150 arbitrary = oneof
150 [ return EmptyImage
151 [ return EmptyImage
151 , do
152 , do
152− SingleAttrSingleSpanStack {stack_image} <- Verify.resize 8 arbitrary
153+ SingleAttrSingleSpanStack {stackImage} <- Verify.resize 8 arbitrary
153 ImageResize f <- Verify.resize 2 arbitrary
154 ImageResize f <- Verify.resize 2 arbitrary
154− return $! fst $! f stack_image
155+ return $! fst $! f stackImage
155 , do
156 , do
156− SingleAttrSingleSpanStack {stack_image} <- Verify.resize 8 arbitrary
157+ SingleAttrSingleSpanStack {stackImage} <- Verify.resize 8 arbitrary
157 ImageResize f <- Verify.resize 2 arbitrary
158 ImageResize f <- Verify.resize 2 arbitrary
158− return $! fst $! f stack_image
159+ return $! fst $! f stackImage
159 , do
160 , do
160− i_0 <- arbitrary
161+ i0 <- arbitrary
161− i_1 <- arbitrary
162+ i1 <- arbitrary
162− let i = i_0 <|> i_1
163+ let i = i0 <|> i1
163 ImageResize f <- Verify.resize 2 arbitrary
164 ImageResize f <- Verify.resize 2 arbitrary
164 return $! fst $! f i
165 return $! fst $! f i
165 , do
166 , do
166− i_0 <- arbitrary
167+ i0 <- arbitrary
167− i_1 <- arbitrary
168+ i1 <- arbitrary
168− let i = i_0 <-> i_1
169+ let i = i0 <-> i1
169 ImageResize f <- Verify.resize 2 arbitrary
170 ImageResize f <- Verify.resize 2 arbitrary
170 return $! fst $! f i
171 return $! fst $! f i
171 ]
172 ]
172 {-
173 {-
173− shrink i@(HorizJoin {part_left, part_right}) = do
174+ shrink i@(HorizJoin {partLeft, partRight}) = do
174− let !i_alt = background_fill (image_width i) (image_height i)
175+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
175− !part_left' <- shrink part_left
176+ !partLeft' <- shrink partLeft
176− !part_right' <- shrink part_right
177+ !partRight' <- shrink partRight
177− [i_alt, part_left' <|> part_right']
178+ [i_alt, partLeft' <|> partRight']
178− shrink i@(VertJoin {part_top, part_bottom}) = do
179+ shrink i@(VertJoin {partTop, partBottom}) = do
179− let !i_alt = background_fill (image_width i) (image_height i)
180+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
180− !part_top' <- shrink part_top
181+ !partTop' <- shrink partTop
181− !part_bottom' <- shrink part_bottom
182+ !partBottom' <- shrink partBottom
182− [i_alt, part_top' <-> part_bottom']
183+ [i_alt, partTop' <-> partBottom']
183− shrink i@(CropRight {cropped_image, output_width}) = do
184+ shrink i@(CropRight {croppedImage, outputWidth}) = do
184− let !i_alt = background_fill (image_width i) (image_height i)
185+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
185− [i_alt, cropped_image]
186+ [i_alt, croppedImage]
186− shrink i@(CropLeft {cropped_image, left_skip, output_width}) = do
187+ shrink i@(CropLeft {croppedImage, leftSkip, outputWidth}) = do
187− let !i_alt = background_fill (image_width i) (image_height i)
188+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
188− [i_alt, cropped_image]
189+ [i_alt, croppedImage]
189− shrink i@(CropBottom {cropped_image, output_height}) = do
190+ shrink i@(CropBottom {croppedImage, outputHeight}) = do
190− let !i_alt = background_fill (image_width i) (image_height i)
191+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
191− [i_alt, cropped_image]
192+ [i_alt, croppedImage]
192− shrink i@(CropTop {cropped_image, top_skip, output_height}) = do
193+ shrink i@(CropTop {croppedImage, topSkip, outputHeight}) = do
193− let !i_alt = background_fill (image_width i) (image_height i)
194+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
194− [i_alt, cropped_image]
195+ [i_alt, croppedImage]
195− shrink i = [empty_image, background_fill (image_width i) (image_height i)]
196+ shrink i = [emptyImage, backgroundFill (imageWidth i) (imageHeight i)]
196 -}
197 -}
197
198
198data CropOperation
199data CropOperation
@@ -215,4 +216,3 @@ instance Arbitrary Translation where
215 y <- arbitrary `suchThat` (> 0)
216 y <- arbitrary `suchThat` (> 0)
216 let i' = translate x y i
217 let i' = translate x y i
217 return $ Translation i (x,y) i'
218 return $ Translation i (x,y) i'
218−
test/Verify/Graphics/Vty/Output.hsnew file+59
@@ -0,0 +1,59 @@
1+module Verify.Graphics.Vty.Output where
2+
3+import Control.Applicative ((<$>))
4+
5+import Graphics.Vty.Output.Mock
6+
7+import Data.IORef
8+import qualified Data.String.UTF8 as UTF8
9+
10+import Test.QuickCheck.Property
11+
12+-- A list of terminals that should be supported. This started with a
13+-- list of terminals ubuntu supported. Then those terminals that really
14+-- could not be supported were removed. Then a few more were pruned
15+-- until a reasonable looking set was made.
16+terminalsOfInterest :: [String]
17+terminalsOfInterest =
18+ [ "vt100"
19+ , "vt220"
20+ , "vt102"
21+ , "xterm-r5"
22+ , "xterm-xfree86"
23+ , "xterm-r6"
24+ , "xterm-256color"
25+ , "xterm-vt220"
26+ , "xterm-debian"
27+ , "xterm-mono"
28+ , "xterm-color"
29+ , "xterm"
30+ , "mach"
31+ , "mach-bold"
32+ , "mach-color"
33+ , "linux"
34+ , "ansi"
35+ , "hurd"
36+ , "Eterm"
37+ , "pcansi"
38+ , "screen-256color"
39+ , "screen-bce"
40+ , "screen-s"
41+ , "screen-w"
42+ , "screen"
43+ , "screen-256color-bce"
44+ , "sun"
45+ , "rxvt"
46+ , "rxvt-unicode"
47+ , "rxvt-basic"
48+ , "cygwin"
49+ ]
50+
51+compareMockOutput :: MockData -> String -> IO Result
52+compareMockOutput mockData expectedStr = do
53+ outStr <- UTF8.toString <$> readIORef mockData
54+ if outStr /= expectedStr
55+ then return $ failed { reason = "bytes\n" ++ outStr
56+ ++ "\nare not the expected bytes\n"
57+ ++ expectedStr
58+ }
59+ else return succeeded
test/Verify/Graphics/Vty/Picture.hs+23−22
@@ -1,21 +1,23 @@
1{-# LANGUAGE DisambiguateRecordFields #-}
1{-# LANGUAGE DisambiguateRecordFields #-}
2−module Verify.Graphics.Vty.Picture ( module Verify.Graphics.Vty.Picture
2+module Verify.Graphics.Vty.Picture
3+ ( module Verify.Graphics.Vty.Picture
3− , module Graphics.Vty.Picture
4+ , module Graphics.Vty.Picture
4− )
5+ )
5− where
6+where
7+
8+import Verify.Graphics.Vty.Prelude
6
9
7import Graphics.Vty.Picture
10import Graphics.Vty.Picture
8import Graphics.Vty.Debug
11import Graphics.Vty.Debug
9
12
10import Verify.Graphics.Vty.Attributes
13import Verify.Graphics.Vty.Attributes
11import Verify.Graphics.Vty.Image
14import Verify.Graphics.Vty.Image
12−import Verify.Graphics.Vty.DisplayRegion
13
15
14import Verify
16import Verify
15
17
16−data DefaultPic = DefaultPic
18+data DefaultPic = DefaultPic
17− { default_pic :: Picture
19+ { defaultPic :: Picture
18− , default_win :: MockWindow
20+ , defaultWin :: MockWindow
19 }
21 }
20
22
21instance Show DefaultPic where
23instance Show DefaultPic where
@@ -25,27 +27,26 @@ instance Show DefaultPic where
25instance Arbitrary DefaultPic where
27instance Arbitrary DefaultPic where
26 arbitrary = do
28 arbitrary = do
27 DefaultImage image <- arbitrary
29 DefaultImage image <- arbitrary
28− let win = MockWindow (image_width image) (image_height image)
30+ let win = MockWindow (imageWidth image) (imageHeight image)
29− return $ DefaultPic (pic_for_image image)
31+ return $ DefaultPic (picForImage image)
30− win
32+ win
31−
33+
32−data PicWithBGAttr = PicWithBGAttr
34+data PicWithBGAttr = PicWithBGAttr
33− { with_attr_pic :: Picture
35+ { withAttrPic :: Picture
34− , with_attr_win :: MockWindow
36+ , withAttrWin :: MockWindow
35− , with_attr_specified_attr :: Attr
37+ , withAttrSpecifiedAttr :: Attr
36 } deriving ( Show )
38 } deriving ( Show )
37
39
38instance Arbitrary PicWithBGAttr where
40instance Arbitrary PicWithBGAttr where
39 arbitrary = do
41 arbitrary = do
40 DefaultImage image <- arbitrary
42 DefaultImage image <- arbitrary
41− let win = MockWindow (image_width image) (image_height image)
43+ let win = MockWindow (imageWidth image) (imageHeight image)
42 attr <- arbitrary
44 attr <- arbitrary
43− return $ PicWithBGAttr (pic_for_image image)
45+ return $ PicWithBGAttr (picForImage image)
44− win
46+ win
45 attr
47 attr
46−
48+
47instance Arbitrary Picture where
49instance Arbitrary Picture where
48 arbitrary = do
50 arbitrary = do
49 layers <- Verify.resize 20 (listOf1 arbitrary)
51 layers <- Verify.resize 20 (listOf1 arbitrary)
50− return $ pic_for_layers layers
52+ return $ picForLayers layers
51−
test/Verify/Graphics/Vty/DisplayRegion.hs → test/Verify/Graphics/Vty/Prelude.hsrenamed+5−6
@@ -1,11 +1,10 @@
1−module Verify.Graphics.Vty.DisplayRegion ( module Verify.Graphics.Vty.DisplayRegion
1+module Verify.Graphics.Vty.Prelude
2− , module Graphics.Vty.DisplayRegion
2+ ( module Verify.Graphics.Vty.Prelude
3− , MockWindow(..)
3+ , MockWindow(..)
4− )
4+ )
5− where
5+where
6
6
7import Graphics.Vty.Debug
7import Graphics.Vty.Debug
8−import Graphics.Vty.DisplayRegion
9
8
10import Verify
9import Verify
11
10
test/Verify/Graphics/Vty/Span.hs+12−12
@@ -1,8 +1,9 @@
1{-# LANGUAGE FlexibleInstances #-}
1{-# LANGUAGE FlexibleInstances #-}
2−module Verify.Graphics.Vty.Span ( module Verify.Graphics.Vty.Span
2+module Verify.Graphics.Vty.Span
3+ ( module Verify.Graphics.Vty.Span
3− , module Graphics.Vty.Span
4+ , module Graphics.Vty.Span
4− )
5+ )
5− where
6+where
6
7
7import Graphics.Vty.Debug
8import Graphics.Vty.Debug
8import Graphics.Vty.Span
9import Graphics.Vty.Span
@@ -14,24 +15,23 @@ import Data.Word
14
15
15import Verify
16import Verify
16
17
17−is_attr_span_op :: SpanOp -> Bool
18+isAttrSpanOp :: SpanOp -> Bool
18−is_attr_span_op TextSpan {} = True
19+isAttrSpanOp TextSpan {} = True
19−is_attr_span_op _ = False
20+isAttrSpanOp _ = False
20
21
21−verify_all_spans_have_width i spans w
22+verifyAllSpansHaveWidth i spans w
22− = let v = map (\s -> (span_ops_effected_columns s /= w, s)) (Vector.toList spans)
23+ = let v = map (\s -> (spanOpsAffectedColumns s /= w, s)) (Vector.toList spans)
23 in case any ((== True) . fst) v of
24 in case any ((== True) . fst) v of
24 False -> succeeded
25 False -> succeeded
25− True -> failed { reason = "Not all spans contained operations defining exactly "
26+ True -> failed { reason = "Not all spans contained operations defining exactly "
26 ++ show w
27 ++ show w
27 ++ " columns of output - \n"
28 ++ " columns of output - \n"
28 ++ (concatMap ((++ "\n") . show)) v
29 ++ (concatMap ((++ "\n") . show)) v
29 }
30 }
30
31
31−verify_ops_equality i_ops i_alt_ops =
32+verifyOpsEquality i_ops i_alt_ops =
32 if i_ops == i_alt_ops
33 if i_ops == i_alt_ops
33 then succeeded
34 then succeeded
34 else failed { reason = "ops for alternate image " ++ show i_alt_ops
35 else failed { reason = "ops for alternate image " ++ show i_alt_ops
35 ++ " are not the same as " ++ show i_ops
36 ++ " are not the same as " ++ show i_ops
36 }
37 }
37−
test/Verify/Graphics/Vty/Terminal.hsdeleted−60
@@ -1,60 +0,0 @@
1−module Verify.Graphics.Vty.Terminal where
2−
3−import Graphics.Vty.Terminal.Mock
4−
5−import qualified Data.ByteString as BS
6−import Data.IORef
7−import qualified Data.String.UTF8 as UTF8
8−
9−import Test.QuickCheck.Property
10−
11−-- A list of terminals that should be supported.
12−-- This started with a list of terminals ubuntu supported. Then those terminals that really could
13−-- not be supported were removed. Then a few more were pruned until a reasonable looking set was
14−-- made.
15−terminals_of_interest :: [String]
16−terminals_of_interest =
17− [ "vt100"
18− , "vt220"
19− , "vt102"
20− , "xterm-r5"
21− , "xterm-xfree86"
22− , "xterm-r6"
23− , "xterm-256color"
24− , "xterm-vt220"
25− , "xterm-debian"
26− , "xterm-mono"
27− , "xterm-color"
28− , "xterm"
29− , "mach"
30− , "mach-bold"
31− , "mach-color"
32− , "linux"
33− , "ansi"
34− , "hurd"
35− , "Eterm"
36− , "pcansi"
37− , "screen-256color"
38− , "screen-bce"
39− , "screen-s"
40− , "screen-w"
41− , "screen"
42− , "screen-256color-bce"
43− , "sun"
44− , "rxvt"
45− , "rxvt-unicode"
46− , "rxvt-basic"
47− , "cygwin"
48− ]
49−
50−compare_mock_output :: MockData -> String -> IO Result
51−compare_mock_output mock_data expected_str = do
52− out_bytes <- readIORef mock_data >>= return . UTF8.toRep
53− let expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected_str
54− if out_bytes /= expected_bytes
55− then return $ failed { reason = "bytes\n" ++ show out_bytes
56− ++ "\nare not the expected bytes\n"
57− ++ show expected_bytes
58− }
59− else return succeeded
60−
test/VerifyAttributeOps.hsdeleted−9
@@ -1,9 +0,0 @@
1−module VerifyAttributeOps where
2−
3−import Verify.Graphics.Vty.Attributes
4−
5−import Verify
6−
7−tests :: IO [Test]
8−tests = return []
9−
test/VerifyColor240.hsnew file+269
@@ -0,0 +1,269 @@
1+module VerifyColor240 where
2+
3+import Data.Word(Word8)
4+import Verify
5+
6+import Graphics.Vty.Attributes.Color240(rgbColorToColor240)
7+
8+oldRgbColorToColor240 :: Integral i => i -> i -> i -> Word8
9+oldRgbColorToColor240 r g b
10+ | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"
11+ | r == 8 && g == 8 && b == 8 = 216
12+ | r == 18 && g == 18 && b == 18 = 217
13+ | r == 28 && g == 28 && b == 28 = 218
14+ | r == 38 && g == 38 && b == 38 = 219
15+ | r == 48 && g == 48 && b == 48 = 220
16+ | r == 58 && g == 58 && b == 58 = 221
17+ | r == 68 && g == 68 && b == 68 = 222
18+ | r == 78 && g == 78 && b == 78 = 223
19+ | r == 88 && g == 88 && b == 88 = 224
20+ | r == 98 && g == 98 && b == 98 = 225
21+ | r == 108 && g == 108 && b == 108 = 226
22+ | r == 118 && g == 118 && b == 118 = 227
23+ | r == 128 && g == 128 && b == 128 = 228
24+ | r == 138 && g == 138 && b == 138 = 229
25+ | r == 148 && g == 148 && b == 148 = 230
26+ | r == 158 && g == 158 && b == 158 = 231
27+ | r == 168 && g == 168 && b == 168 = 232
28+ | r == 178 && g == 178 && b == 178 = 233
29+ | r == 188 && g == 188 && b == 188 = 234
30+ | r == 198 && g == 198 && b == 198 = 235
31+ | r == 208 && g == 208 && b == 208 = 236
32+ | r == 218 && g == 218 && b == 218 = 237
33+ | r == 228 && g == 228 && b == 228 = 238
34+ | r == 238 && g == 238 && b == 238 = 239
35+ | r <= 0 && g <= 0 && b <= 0 = 0
36+ | r <= 0 && g <= 0 && b <= 95 = 1
37+ | r <= 0 && g <= 0 && b <= 135 = 2
38+ | r <= 0 && g <= 0 && b <= 175 = 3
39+ | r <= 0 && g <= 0 && b <= 215 = 4
40+ | r <= 0 && g <= 0 && b <= 255 = 5
41+ | r <= 0 && g <= 95 && b <= 0 = 6
42+ | r <= 0 && g <= 95 && b <= 95 = 7
43+ | r <= 0 && g <= 95 && b <= 135 = 8
44+ | r <= 0 && g <= 95 && b <= 175 = 9
45+ | r <= 0 && g <= 95 && b <= 215 = 10
46+ | r <= 0 && g <= 95 && b <= 255 = 11
47+ | r <= 0 && g <= 135 && b <= 0 = 12
48+ | r <= 0 && g <= 135 && b <= 95 = 13
49+ | r <= 0 && g <= 135 && b <= 135 = 14
50+ | r <= 0 && g <= 135 && b <= 175 = 15
51+ | r <= 0 && g <= 135 && b <= 215 = 16
52+ | r <= 0 && g <= 135 && b <= 255 = 17
53+ | r <= 0 && g <= 175 && b <= 0 = 18
54+ | r <= 0 && g <= 175 && b <= 95 = 19
55+ | r <= 0 && g <= 175 && b <= 135 = 20
56+ | r <= 0 && g <= 175 && b <= 175 = 21
57+ | r <= 0 && g <= 175 && b <= 215 = 22
58+ | r <= 0 && g <= 175 && b <= 255 = 23
59+ | r <= 0 && g <= 215 && b <= 0 = 24
60+ | r <= 0 && g <= 215 && b <= 95 = 25
61+ | r <= 0 && g <= 215 && b <= 135 = 26
62+ | r <= 0 && g <= 215 && b <= 175 = 27
63+ | r <= 0 && g <= 215 && b <= 215 = 28
64+ | r <= 0 && g <= 215 && b <= 255 = 29
65+ | r <= 0 && g <= 255 && b <= 0 = 30
66+ | r <= 0 && g <= 255 && b <= 95 = 31
67+ | r <= 0 && g <= 255 && b <= 135 = 32
68+ | r <= 0 && g <= 255 && b <= 175 = 33
69+ | r <= 0 && g <= 255 && b <= 215 = 34
70+ | r <= 0 && g <= 255 && b <= 255 = 35
71+ | r <= 95 && g <= 0 && b <= 0 = 36
72+ | r <= 95 && g <= 0 && b <= 95 = 37
73+ | r <= 95 && g <= 0 && b <= 135 = 38
74+ | r <= 95 && g <= 0 && b <= 175 = 39
75+ | r <= 95 && g <= 0 && b <= 215 = 40
76+ | r <= 95 && g <= 0 && b <= 255 = 41
77+ | r <= 95 && g <= 95 && b <= 0 = 42
78+ | r <= 95 && g <= 95 && b <= 95 = 43
79+ | r <= 95 && g <= 95 && b <= 135 = 44
80+ | r <= 95 && g <= 95 && b <= 175 = 45
81+ | r <= 95 && g <= 95 && b <= 215 = 46
82+ | r <= 95 && g <= 95 && b <= 255 = 47
83+ | r <= 95 && g <= 135 && b <= 0 = 48
84+ | r <= 95 && g <= 135 && b <= 95 = 49
85+ | r <= 95 && g <= 135 && b <= 135 = 50
86+ | r <= 95 && g <= 135 && b <= 175 = 51
87+ | r <= 95 && g <= 135 && b <= 215 = 52
88+ | r <= 95 && g <= 135 && b <= 255 = 53
89+ | r <= 95 && g <= 175 && b <= 0 = 54
90+ | r <= 95 && g <= 175 && b <= 95 = 55
91+ | r <= 95 && g <= 175 && b <= 135 = 56
92+ | r <= 95 && g <= 175 && b <= 175 = 57
93+ | r <= 95 && g <= 175 && b <= 215 = 58
94+ | r <= 95 && g <= 175 && b <= 255 = 59
95+ | r <= 95 && g <= 215 && b <= 0 = 60
96+ | r <= 95 && g <= 215 && b <= 95 = 61
97+ | r <= 95 && g <= 215 && b <= 135 = 62
98+ | r <= 95 && g <= 215 && b <= 175 = 63
99+ | r <= 95 && g <= 215 && b <= 215 = 64
100+ | r <= 95 && g <= 215 && b <= 255 = 65
101+ | r <= 95 && g <= 255 && b <= 0 = 66
102+ | r <= 95 && g <= 255 && b <= 95 = 67
103+ | r <= 95 && g <= 255 && b <= 135 = 68
104+ | r <= 95 && g <= 255 && b <= 175 = 69
105+ | r <= 95 && g <= 255 && b <= 215 = 70
106+ | r <= 95 && g <= 255 && b <= 255 = 71
107+ | r <= 135 && g <= 0 && b <= 0 = 72
108+ | r <= 135 && g <= 0 && b <= 95 = 73
109+ | r <= 135 && g <= 0 && b <= 135 = 74
110+ | r <= 135 && g <= 0 && b <= 175 = 75
111+ | r <= 135 && g <= 0 && b <= 215 = 76
112+ | r <= 135 && g <= 0 && b <= 255 = 77
113+ | r <= 135 && g <= 95 && b <= 0 = 78
114+ | r <= 135 && g <= 95 && b <= 95 = 79
115+ | r <= 135 && g <= 95 && b <= 135 = 80
116+ | r <= 135 && g <= 95 && b <= 175 = 81
117+ | r <= 135 && g <= 95 && b <= 215 = 82
118+ | r <= 135 && g <= 95 && b <= 255 = 83
119+ | r <= 135 && g <= 135 && b <= 0 = 84
120+ | r <= 135 && g <= 135 && b <= 95 = 85
121+ | r <= 135 && g <= 135 && b <= 135 = 86
122+ | r <= 135 && g <= 135 && b <= 175 = 87
123+ | r <= 135 && g <= 135 && b <= 215 = 88
124+ | r <= 135 && g <= 135 && b <= 255 = 89
125+ | r <= 135 && g <= 175 && b <= 0 = 90
126+ | r <= 135 && g <= 175 && b <= 95 = 91
127+ | r <= 135 && g <= 175 && b <= 135 = 92
128+ | r <= 135 && g <= 175 && b <= 175 = 93
129+ | r <= 135 && g <= 175 && b <= 215 = 94
130+ | r <= 135 && g <= 175 && b <= 255 = 95
131+ | r <= 135 && g <= 215 && b <= 0 = 96
132+ | r <= 135 && g <= 215 && b <= 95 = 97
133+ | r <= 135 && g <= 215 && b <= 135 = 98
134+ | r <= 135 && g <= 215 && b <= 175 = 99
135+ | r <= 135 && g <= 215 && b <= 215 = 100
136+ | r <= 135 && g <= 215 && b <= 255 = 101
137+ | r <= 135 && g <= 255 && b <= 0 = 102
138+ | r <= 135 && g <= 255 && b <= 95 = 103
139+ | r <= 135 && g <= 255 && b <= 135 = 104
140+ | r <= 135 && g <= 255 && b <= 175 = 105
141+ | r <= 135 && g <= 255 && b <= 215 = 106
142+ | r <= 135 && g <= 255 && b <= 255 = 107
143+ | r <= 175 && g <= 0 && b <= 0 = 108
144+ | r <= 175 && g <= 0 && b <= 95 = 109
145+ | r <= 175 && g <= 0 && b <= 135 = 110
146+ | r <= 175 && g <= 0 && b <= 175 = 111
147+ | r <= 175 && g <= 0 && b <= 215 = 112
148+ | r <= 175 && g <= 0 && b <= 255 = 113
149+ | r <= 175 && g <= 95 && b <= 0 = 114
150+ | r <= 175 && g <= 95 && b <= 95 = 115
151+ | r <= 175 && g <= 95 && b <= 135 = 116
152+ | r <= 175 && g <= 95 && b <= 175 = 117
153+ | r <= 175 && g <= 95 && b <= 215 = 118
154+ | r <= 175 && g <= 95 && b <= 255 = 119
155+ | r <= 175 && g <= 135 && b <= 0 = 120
156+ | r <= 175 && g <= 135 && b <= 95 = 121
157+ | r <= 175 && g <= 135 && b <= 135 = 122
158+ | r <= 175 && g <= 135 && b <= 175 = 123
159+ | r <= 175 && g <= 135 && b <= 215 = 124
160+ | r <= 175 && g <= 135 && b <= 255 = 125
161+ | r <= 175 && g <= 175 && b <= 0 = 126
162+ | r <= 175 && g <= 175 && b <= 95 = 127
163+ | r <= 175 && g <= 175 && b <= 135 = 128
164+ | r <= 175 && g <= 175 && b <= 175 = 129
165+ | r <= 175 && g <= 175 && b <= 215 = 130
166+ | r <= 175 && g <= 175 && b <= 255 = 131
167+ | r <= 175 && g <= 215 && b <= 0 = 132
168+ | r <= 175 && g <= 215 && b <= 95 = 133
169+ | r <= 175 && g <= 215 && b <= 135 = 134
170+ | r <= 175 && g <= 215 && b <= 175 = 135
171+ | r <= 175 && g <= 215 && b <= 215 = 136
172+ | r <= 175 && g <= 215 && b <= 255 = 137
173+ | r <= 175 && g <= 255 && b <= 0 = 138
174+ | r <= 175 && g <= 255 && b <= 95 = 139
175+ | r <= 175 && g <= 255 && b <= 135 = 140
176+ | r <= 175 && g <= 255 && b <= 175 = 141
177+ | r <= 175 && g <= 255 && b <= 215 = 142
178+ | r <= 175 && g <= 255 && b <= 255 = 143
179+ | r <= 215 && g <= 0 && b <= 0 = 144
180+ | r <= 215 && g <= 0 && b <= 95 = 145
181+ | r <= 215 && g <= 0 && b <= 135 = 146
182+ | r <= 215 && g <= 0 && b <= 175 = 147
183+ | r <= 215 && g <= 0 && b <= 215 = 148
184+ | r <= 215 && g <= 0 && b <= 255 = 149
185+ | r <= 215 && g <= 95 && b <= 0 = 150
186+ | r <= 215 && g <= 95 && b <= 95 = 151
187+ | r <= 215 && g <= 95 && b <= 135 = 152
188+ | r <= 215 && g <= 95 && b <= 175 = 153
189+ | r <= 215 && g <= 95 && b <= 215 = 154
190+ | r <= 215 && g <= 95 && b <= 255 = 155
191+ | r <= 215 && g <= 135 && b <= 0 = 156
192+ | r <= 215 && g <= 135 && b <= 95 = 157
193+ | r <= 215 && g <= 135 && b <= 135 = 158
194+ | r <= 215 && g <= 135 && b <= 175 = 159
195+ | r <= 215 && g <= 135 && b <= 215 = 160
196+ | r <= 215 && g <= 135 && b <= 255 = 161
197+ | r <= 215 && g <= 175 && b <= 0 = 162
198+ | r <= 215 && g <= 175 && b <= 95 = 163
199+ | r <= 215 && g <= 175 && b <= 135 = 164
200+ | r <= 215 && g <= 175 && b <= 175 = 165
201+ | r <= 215 && g <= 175 && b <= 215 = 166
202+ | r <= 215 && g <= 175 && b <= 255 = 167
203+ | r <= 215 && g <= 215 && b <= 0 = 168
204+ | r <= 215 && g <= 215 && b <= 95 = 169
205+ | r <= 215 && g <= 215 && b <= 135 = 170
206+ | r <= 215 && g <= 215 && b <= 175 = 171
207+ | r <= 215 && g <= 215 && b <= 215 = 172
208+ | r <= 215 && g <= 215 && b <= 255 = 173
209+ | r <= 215 && g <= 255 && b <= 0 = 174
210+ | r <= 215 && g <= 255 && b <= 95 = 175
211+ | r <= 215 && g <= 255 && b <= 135 = 176
212+ | r <= 215 && g <= 255 && b <= 175 = 177
213+ | r <= 215 && g <= 255 && b <= 215 = 178
214+ | r <= 215 && g <= 255 && b <= 255 = 179
215+ | r <= 255 && g <= 0 && b <= 0 = 180
216+ | r <= 255 && g <= 0 && b <= 95 = 181
217+ | r <= 255 && g <= 0 && b <= 135 = 182
218+ | r <= 255 && g <= 0 && b <= 175 = 183
219+ | r <= 255 && g <= 0 && b <= 215 = 184
220+ | r <= 255 && g <= 0 && b <= 255 = 185
221+ | r <= 255 && g <= 95 && b <= 0 = 186
222+ | r <= 255 && g <= 95 && b <= 95 = 187
223+ | r <= 255 && g <= 95 && b <= 135 = 188
224+ | r <= 255 && g <= 95 && b <= 175 = 189
225+ | r <= 255 && g <= 95 && b <= 215 = 190
226+ | r <= 255 && g <= 95 && b <= 255 = 191
227+ | r <= 255 && g <= 135 && b <= 0 = 192
228+ | r <= 255 && g <= 135 && b <= 95 = 193
229+ | r <= 255 && g <= 135 && b <= 135 = 194
230+ | r <= 255 && g <= 135 && b <= 175 = 195
231+ | r <= 255 && g <= 135 && b <= 215 = 196
232+ | r <= 255 && g <= 135 && b <= 255 = 197
233+ | r <= 255 && g <= 175 && b <= 0 = 198
234+ | r <= 255 && g <= 175 && b <= 95 = 199
235+ | r <= 255 && g <= 175 && b <= 135 = 200
236+ | r <= 255 && g <= 175 && b <= 175 = 201
237+ | r <= 255 && g <= 175 && b <= 215 = 202
238+ | r <= 255 && g <= 175 && b <= 255 = 203
239+ | r <= 255 && g <= 215 && b <= 0 = 204
240+ | r <= 255 && g <= 215 && b <= 95 = 205
241+ | r <= 255 && g <= 215 && b <= 135 = 206
242+ | r <= 255 && g <= 215 && b <= 175 = 207
243+ | r <= 255 && g <= 215 && b <= 215 = 208
244+ | r <= 255 && g <= 215 && b <= 255 = 209
245+ | r <= 255 && g <= 255 && b <= 0 = 210
246+ | r <= 255 && g <= 255 && b <= 95 = 211
247+ | r <= 255 && g <= 255 && b <= 135 = 212
248+ | r <= 255 && g <= 255 && b <= 175 = 213
249+ | r <= 255 && g <= 255 && b <= 215 = 214
250+ | r <= 255 && g <= 255 && b <= 255 = 215
251+ | otherwise = error (printf "RGB color %d %d %d does not map to 240 palette."
252+ (fromIntegral r :: Int)
253+ (fromIntegral g :: Int)
254+ (fromIntegral b :: Int))
255+
256+sameColor :: Int -> Int -> Int -> Bool
257+sameColor r g b = oldRgbColorToColor240 r g b == rgbColorToColor240 r g b
258+
259+genVal :: Gen Int
260+genVal = chooseInt (0, 255)
261+
262+sameColorCheck :: Property
263+sameColorCheck = forAll genVal (\r -> forAll genVal (\g -> forAll genVal (sameColor r g)))
264+
265+tests :: IO [Test]
266+tests = return
267+ [ verify "check rgbColorToColor240 optimization is correct" sameColorCheck
268+ ]
269+
test/VerifyConfig.hsnew file+44
@@ -0,0 +1,44 @@
1+{-# LANGUAGE QuasiQuotes #-}
2+module Main where
3+
4+import Graphics.Vty.Config
5+import Graphics.Vty.Input.Events
6+
7+import Data.String.QQ
8+import qualified Data.ByteString.Char8 as B
9+
10+import Test.Framework
11+import Test.Framework.Providers.HUnit
12+import Test.HUnit hiding (Test)
13+
14+exampleConfig :: B.ByteString
15+exampleConfig = B.pack [s|
16+-- comments should be ignored.
17+map _ "\ESC[B" KUp []
18+askfjla dfasjdflk jasdlkfj asdfj -- lines failing parse should be ignored
19+map _ "\ESC[1;3B" KDown [MAlt]
20+map "xterm" "\ESC[1;3B" KDown [MAlt]
21+map "xterm-256-color" "\ESC[1;3B" KDown [MAlt]
22+debugLog "/tmp/vty-debug.txt"
23+|]
24+
25+exampleConfigConfig :: Config
26+exampleConfigConfig = defaultConfig
27+ { debugLog = Just "/tmp/vty-debug.txt"
28+ , inputMap = [ (Nothing, "\ESC[B", EvKey KUp [])
29+ , (Nothing, "\ESC[1;3B", EvKey KDown [MAlt])
30+ , (Just "xterm", "\ESC[1;3B", EvKey KDown [MAlt])
31+ , (Just "xterm-256-color", "\ESC[1;3B", EvKey KDown [MAlt])
32+ ]
33+ }
34+
35+exampleConfigParses :: IO ()
36+exampleConfigParses =
37+ assertEqual "example config parses as expected"
38+ exampleConfigConfig
39+ (runParseConfig "exampleConfig" exampleConfig)
40+
41+main :: IO ()
42+main = defaultMain
43+ [ testCase "example config parses" $ exampleConfigParses
44+ ]
test/VerifyCropSpanGeneration.hs+55−55
@@ -2,7 +2,8 @@
2{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3module VerifyCropSpanGeneration where
3module VerifyCropSpanGeneration where
4
4
5−import Verify.Graphics.Vty.DisplayRegion
5+import Verify.Graphics.Vty.Prelude
6+
6import Verify.Graphics.Vty.Image
7import Verify.Graphics.Vty.Image
7import Verify.Graphics.Vty.Picture
8import Verify.Graphics.Vty.Picture
8import Verify.Graphics.Vty.Span
9import Verify.Graphics.Vty.Span
@@ -12,81 +13,80 @@ import Graphics.Vty.PictureToSpans
12
13
13import Verify
14import Verify
14
15
15−import qualified Data.Vector as Vector
16+import qualified Data.Vector as Vector
16
17
17−crop_op_display_ops :: (Int -> Image -> Image) ->
18+cropOpDisplayOps :: (Int -> Image -> Image) ->
18− Int -> Image -> (DisplayOps, Image)
19+ Int -> Image -> (DisplayOps, Image)
19−crop_op_display_ops crop_op v i =
20+cropOpDisplayOps cropOp v i =
20− let i_out = crop_op v i
21+ let iOut = cropOp v i
21− p = pic_for_image i_out
22+ p = picForImage iOut
22− w = MockWindow (image_width i_out) (image_height i_out)
23+ w = MockWindow (imageWidth iOut) (imageHeight iOut)
23− in (display_ops_for_pic p (region_for_window w), i_out)
24+ in (displayOpsForPic p (regionForWindow w), iOut)
24
25
25−width_crop_output_columns :: (Int -> Image -> Image) ->
26+widthCropOutputColumns :: (Int -> Image -> Image) ->
26− SingleAttrSingleSpanStack ->
27+ SingleAttrSingleSpanStack ->
27− NonNegative Int ->
28+ NonNegative Int ->
28− Property
29+ Property
29−width_crop_output_columns crop_op s (NonNegative w) = stack_width s > w ==>
30+widthCropOutputColumns cropOp s (NonNegative w) = stackWidth s > w ==>
30− let (ops, i_out) = crop_op_display_ops crop_op w (stack_image s)
31+ let (ops, iOut) = cropOpDisplayOps cropOp w (stackImage s)
31− in verify_all_spans_have_width i_out ops w
32+ in verifyAllSpansHaveWidth iOut ops w
32
33
33−height_crop_output_columns :: (Int -> Image -> Image) ->
34+heightCropOutputColumns :: (Int -> Image -> Image) ->
34− SingleAttrSingleSpanStack ->
35+ SingleAttrSingleSpanStack ->
35− NonNegative Int ->
36+ NonNegative Int ->
36− Property
37+ Property
37−height_crop_output_columns crop_op s (NonNegative h) = stack_height s > h ==>
38+heightCropOutputColumns cropOp s (NonNegative h) = stackHeight s > h ==>
38− let (ops, _) = crop_op_display_ops crop_op h (stack_image s)
39+ let (ops, _) = cropOpDisplayOps cropOp h (stackImage s)
39− in display_ops_rows ops == h
40+ in displayOpsRows ops == h
40
41
41−crop_right_output_columns :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
42+cropRightOutputColumns :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
42−crop_right_output_columns = width_crop_output_columns crop_right
43+cropRightOutputColumns = widthCropOutputColumns cropRight
43
44
44−crop_left_output_columns :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
45+cropLeftOutputColumns :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
45−crop_left_output_columns = width_crop_output_columns crop_left
46+cropLeftOutputColumns = widthCropOutputColumns cropLeft
46
47
47−crop_top_output_rows :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
48+cropTopOutputRows :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
48−crop_top_output_rows = height_crop_output_columns crop_top
49+cropTopOutputRows = heightCropOutputColumns cropTop
49
50
50−crop_bottom_output_rows :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
51+cropBottomOutputRows :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
51−crop_bottom_output_rows = height_crop_output_columns crop_bottom
52+cropBottomOutputRows = heightCropOutputColumns cropBottom
52
53
53-- TODO: known benign failure.
54-- TODO: known benign failure.
54−crop_right_and_left_rejoined_equivalence :: SingleAttrSingleSpanStack -> Property
55+cropRightAndLeftRejoinedEquivalence :: SingleAttrSingleSpanStack -> Property
55−crop_right_and_left_rejoined_equivalence stack = image_width (stack_image stack) `mod` 2 == 0 ==>
56+cropRightAndLeftRejoinedEquivalence stack = imageWidth (stackImage stack) `mod` 2 == 0 ==>
56− let i = stack_image stack
57+ let i = stackImage stack
57 -- the right part is made by cropping the image from the left.
58 -- the right part is made by cropping the image from the left.
58− i_r = crop_left (image_width i `div` 2) i
59+ iR = cropLeft (imageWidth i `div` 2) i
59 -- the left part is made by cropping the image from the right
60 -- the left part is made by cropping the image from the right
60− i_l = crop_right (image_width i `div` 2) i
61+ iL = cropRight (imageWidth i `div` 2) i
61− i_alt = i_l <|> i_r
62+ iAlt = iL <|> iR
62− i_ops = display_ops_for_image i
63+ iOps = displayOpsForImage i
63− i_alt_ops = display_ops_for_image i_alt
64+ iAltOps = displayOpsForImage iAlt
64− in verify_ops_equality i_ops i_alt_ops
65+ in verifyOpsEquality iOps iAltOps
65
66
66−crop_top_and_bottom_rejoined_equivalence :: SingleAttrSingleSpanStack -> Property
67+cropTopAndBottomRejoinedEquivalence :: SingleAttrSingleSpanStack -> Property
67−crop_top_and_bottom_rejoined_equivalence stack = image_height (stack_image stack) `mod` 2 == 0 ==>
68+cropTopAndBottomRejoinedEquivalence stack = imageHeight (stackImage stack) `mod` 2 == 0 ==>
68− let i = stack_image stack
69+ let i = stackImage stack
69 -- the top part is made by cropping the image from the bottom.
70 -- the top part is made by cropping the image from the bottom.
70− i_t = crop_bottom (image_height i `div` 2) i
71+ iT = cropBottom (imageHeight i `div` 2) i
71 -- the bottom part is made by cropping the image from the top.
72 -- the bottom part is made by cropping the image from the top.
72− i_b = crop_top (image_height i `div` 2) i
73+ iB = cropTop (imageHeight i `div` 2) i
73− i_alt = i_t <-> i_b
74+ iAlt = iT <-> iB
74− in display_ops_for_image i == display_ops_for_image i_alt
75+ in displayOpsForImage i == displayOpsForImage iAlt
75
76
76tests :: IO [Test]
77tests :: IO [Test]
77−tests = return
78+tests = return
78 [ verify "cropping from the bottom produces display operations covering the expected rows"
79 [ verify "cropping from the bottom produces display operations covering the expected rows"
79− crop_bottom_output_rows
80+ cropBottomOutputRows
80 , verify "cropping from the top produces display operations covering the expected rows"
81 , verify "cropping from the top produces display operations covering the expected rows"
81− crop_top_output_rows
82+ cropTopOutputRows
82 , verify "cropping from the left produces display operations covering the expected columns"
83 , verify "cropping from the left produces display operations covering the expected columns"
83− crop_left_output_columns
84+ cropLeftOutputColumns
84 , verify "cropping from the right produces display operations covering the expected columns"
85 , verify "cropping from the right produces display operations covering the expected columns"
85− crop_right_output_columns
86+ cropRightOutputColumns
86 -- TODO: known benign failure.
87 -- TODO: known benign failure.
87 -- , verify "the output of a stack is the same as that stack cropped left & right and joined together"
88 -- , verify "the output of a stack is the same as that stack cropped left & right and joined together"
88− -- crop_right_and_left_rejoined_equivalence
89+ -- cropRightAndLeftRejoinedEquivalence
89 , verify "the output of a stack is the same as that stack cropped top & bottom and joined together"
90 , verify "the output of a stack is the same as that stack cropped top & bottom and joined together"
90− crop_top_and_bottom_rejoined_equivalence
91+ cropTopAndBottomRejoinedEquivalence
91 ]
92 ]
92−
test/VerifyEmptyImageProps.hs+3−2
@@ -1,13 +1,14 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
1module VerifyEmptyImageProps where
2module VerifyEmptyImageProps where
2
3
3import Verify
4import Verify
4
5
5-- should be exported by Graphics.Vty.Picture
6-- should be exported by Graphics.Vty.Picture
6−import Graphics.Vty.Picture ( Image, empty_image )
7+import Graphics.Vty.Image ( Image, emptyImage )
7
8
8tests :: IO [Test]
9tests :: IO [Test]
9tests = do
10tests = do
10 -- should provide an image type.
11 -- should provide an image type.
11− let _ :: Image = empty_image
12+ let _ :: Image = emptyImage
12 return []
13 return []
13
14
test/VerifyEvalTerminfoCaps.hs+39−36
@@ -1,17 +1,17 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
3module VerifyEvalTerminfoCaps where
4module VerifyEvalTerminfoCaps where
4
5
5−import Data.Marshalling
6+import Blaze.ByteString.Builder.Internal.Write (runWrite, getBound)
6−
7−import Data.Terminfo.Eval
7+import Data.Terminfo.Eval
8import Data.Terminfo.Parse
8import Data.Terminfo.Parse
9import Control.DeepSeq
9import Control.DeepSeq
10
10
11import qualified System.Console.Terminfo as Terminfo
11import qualified System.Console.Terminfo as Terminfo
12
12
13import Verify
13import Verify
14−import Verify.Graphics.Vty.Terminal
14+import Verify.Graphics.Vty.Output
15
15
16import Control.Applicative ( (<$>) )
16import Control.Applicative ( (<$>) )
17import Control.Exception ( try, SomeException(..) )
17import Control.Exception ( try, SomeException(..) )
@@ -21,10 +21,13 @@ import Control.Monad ( mapM_, forM, forM_ )
21import Data.Maybe ( fromJust )
21import Data.Maybe ( fromJust )
22import Data.Word
22import Data.Word
23
23
24+import Foreign.Marshal.Alloc (mallocBytes)
25+import Foreign.Ptr (Ptr, minusPtr)
24import Numeric
26import Numeric
25
27
26−-- If a terminal defines one of the caps then it's expected to be parsable.
28+-- If a terminal defines one of the caps then it's expected to be
27−caps_of_interest =
29+-- parsable.
30+capsOfInterest =
28 [ "cup"
31 [ "cup"
29 , "sc"
32 , "sc"
30 , "rc"
33 , "rc"
@@ -44,46 +47,46 @@ caps_of_interest =
44 , "sgr0"
47 , "sgr0"
45 ]
48 ]
46
49
47−from_capname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
50+fromCapname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
48
51
49tests :: IO [Test]
52tests :: IO [Test]
50tests = do
53tests = do
51− eval_buffer :: Ptr Word8 <- mallocBytes (1024 * 1024) -- Should be big enough for any termcaps ;-)
54+ -- 1 MB should be big enough for any termcaps ;-)
52− fmap concat $ forM terminals_of_interest $ \term_name -> do
55+ evalBuffer :: Ptr Word8 <- mallocBytes (1024 * 1024)
56+ fmap concat $ forM terminalsOfInterest $ \termName -> do
53− putStrLn $ "adding tests for terminal: " ++ term_name
57+ putStrLn $ "adding tests for terminal: " ++ termName
54− mti <- try $ Terminfo.setupTerm term_name
58+ mti <- try $ Terminfo.setupTerm termName
55 case mti of
59 case mti of
56− Left (_e :: SomeException)
60+ Left (_e :: SomeException)
57 -> return []
61 -> return []
58 Right ti -> do
62 Right ti -> do
59− fmap concat $ forM caps_of_interest $ \cap_name -> do
63+ fmap concat $ forM capsOfInterest $ \capName -> do
60− case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
64+ case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
61− Just cap_def -> do
65+ Just capDef -> do
62− putStrLn $ "\tadding test for cap: " ++ cap_name
66+ putStrLn $ "\tadding test for cap: " ++ capName
63− let test_name = term_name ++ "(" ++ cap_name ++ ")"
67+ let testName = termName ++ "(" ++ capName ++ ")"
64− parse_result <- parse_cap_expression cap_def
68+ case parseCapExpression capDef of
65− case parse_result of
66− Left error -> return [ verify test_name ( failed { reason = "parse error " ++ show error } ) ]
69+ Left error -> return [verify testName (failed {reason = "parse error " ++ show error})]
67− Right !cap_expr -> return [ verify test_name ( verify_eval_cap eval_buffer cap_expr ) ]
70+ Right !cap_expr -> return [verify testName (verifyEvalCap evalBuffer cap_expr)]
68 Nothing -> do
71 Nothing -> do
69 return []
72 return []
70
73
71−{-# NOINLINE verify_eval_cap #-}
74+{-# NOINLINE verifyEvalCap #-}
72−verify_eval_cap :: Ptr Word8 -> CapExpression -> Int -> Property
75+verifyEvalCap :: Ptr Word8 -> CapExpression -> Int -> Property
73−verify_eval_cap eval_buffer expr !junk_int = do
76+verifyEvalCap evalBuffer expr !junkInt = do
74− forAll (vector 9) $ \input_values ->
77+ forAll (vector 9) $ \inputValues ->
75− let !byte_count = cap_expression_required_bytes expr input_values
78+ let write = writeCapExpr expr inputValues
79+ !byteCount = getBound write
76 in liftIOResult $ do
80 in liftIOResult $ do
77− let start_ptr :: Ptr Word8 = eval_buffer
81+ let startPtr :: Ptr Word8 = evalBuffer
78− forM_ [0..100] $ \i -> serialize_cap_expression expr input_values start_ptr
82+ forM_ [0..100] $ \i -> runWrite write startPtr
79− end_ptr <- serialize_cap_expression expr input_values start_ptr
83+ endPtr <- runWrite write startPtr
80− case end_ptr `minusPtr` start_ptr of
84+ case endPtr `minusPtr` startPtr of
81− count | count < 0 ->
85+ count | count < 0 ->
82 return $ failed { reason = "End pointer before start pointer." }
86 return $ failed { reason = "End pointer before start pointer." }
83− | toEnum count > byte_count ->
87+ | toEnum count > byteCount ->
84− return $ failed { reason = "End pointer past end of buffer by "
88+ return $ failed { reason = "End pointer past end of buffer by "
85− ++ show (toEnum count - byte_count)
89+ ++ show (toEnum count - byteCount)
86 }
90 }
87− | otherwise ->
91+ | otherwise ->
88 return succeeded
92 return succeeded
89−
test/VerifyImageOps.hs+126−126
@@ -1,3 +1,4 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
1module VerifyImageOps where
2module VerifyImageOps where
2
3
3import Graphics.Vty.Attributes
4import Graphics.Vty.Attributes
@@ -8,167 +9,166 @@ import Verify
8
9
9import Control.DeepSeq
10import Control.DeepSeq
10
11
11−two_sw_horiz_concat :: SingleColumnChar -> SingleColumnChar -> Bool
12+twoSwHorizConcat :: SingleColumnChar -> SingleColumnChar -> Bool
12−two_sw_horiz_concat (SingleColumnChar c1) (SingleColumnChar c2) =
13+twoSwHorizConcat (SingleColumnChar c1) (SingleColumnChar c2) =
13− image_width (char def_attr c1 <|> char def_attr c2) == 2
14+ imageWidth (char defAttr c1 <|> char defAttr c2) == 2
14
15
15−many_sw_horiz_concat :: [SingleColumnChar] -> Bool
16+manySwHorizConcat :: [SingleColumnChar] -> Bool
16−many_sw_horiz_concat cs =
17+manySwHorizConcat cs =
17 let chars = [ char | SingleColumnChar char <- cs ]
18 let chars = [ char | SingleColumnChar char <- cs ]
18 l = fromIntegral $ length cs
19 l = fromIntegral $ length cs
19− in image_width ( horiz_cat $ map (char def_attr) chars ) == l
20+ in imageWidth ( horizCat $ map (char defAttr) chars ) == l
20
21
21−two_sw_vert_concat :: SingleColumnChar -> SingleColumnChar -> Bool
22+twoSwVertConcat :: SingleColumnChar -> SingleColumnChar -> Bool
22−two_sw_vert_concat (SingleColumnChar c1) (SingleColumnChar c2) =
23+twoSwVertConcat (SingleColumnChar c1) (SingleColumnChar c2) =
23− image_height (char def_attr c1 <-> char def_attr c2) == 2
24+ imageHeight (char defAttr c1 <-> char defAttr c2) == 2
24
25
25−horiz_concat_sw_assoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool
26+horizConcatSwAssoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool
26−horiz_concat_sw_assoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) =
27+horizConcatSwAssoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) =
27− (char def_attr c0 <|> char def_attr c1) <|> char def_attr c2
28+ (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2
28− ==
29+ ==
29− char def_attr c0 <|> (char def_attr c1 <|> char def_attr c2)
30+ char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2)
30
31
31−two_dw_horiz_concat :: DoubleColumnChar -> DoubleColumnChar -> Bool
32+twoDwHorizConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool
32−two_dw_horiz_concat (DoubleColumnChar c1) (DoubleColumnChar c2) =
33+twoDwHorizConcat (DoubleColumnChar c1) (DoubleColumnChar c2) =
33− image_width (char def_attr c1 <|> char def_attr c2) == 4
34+ imageWidth (char defAttr c1 <|> char defAttr c2) == 4
34
35
35−many_dw_horiz_concat :: [DoubleColumnChar] -> Bool
36+manyDwHorizConcat :: [DoubleColumnChar] -> Bool
36−many_dw_horiz_concat cs =
37+manyDwHorizConcat cs =
37 let chars = [ char | DoubleColumnChar char <- cs ]
38 let chars = [ char | DoubleColumnChar char <- cs ]
38 l = fromIntegral $ length cs
39 l = fromIntegral $ length cs
39− in image_width ( horiz_cat $ map (char def_attr) chars ) == l * 2
40+ in imageWidth ( horizCat $ map (char defAttr) chars ) == l * 2
40−
41+
41−two_dw_vert_concat :: DoubleColumnChar -> DoubleColumnChar -> Bool
42+twoDwVertConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool
42−two_dw_vert_concat (DoubleColumnChar c1) (DoubleColumnChar c2) =
43+twoDwVertConcat (DoubleColumnChar c1) (DoubleColumnChar c2) =
43− image_height (char def_attr c1 <-> char def_attr c2) == 2
44+ imageHeight (char defAttr c1 <-> char defAttr c2) == 2
44−
45+
45−horiz_concat_dw_assoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool
46+horizConcatDwAssoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool
46−horiz_concat_dw_assoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) =
47+horizConcatDwAssoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) =
47− (char def_attr c0 <|> char def_attr c1) <|> char def_attr c2
48+ (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2
48− ==
49+ ==
49− char def_attr c0 <|> (char def_attr c1 <|> char def_attr c2)
50+ char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2)
50−
51+
51−vert_contat_single_row :: NonEmptyList SingleRowSingleAttrImage -> Bool
52+vertContatSingleRow :: NonEmptyList SingleRowSingleAttrImage -> Bool
52−vert_contat_single_row (NonEmpty stack) =
53+vertContatSingleRow (NonEmpty stack) =
53− let expected_height :: Int = length stack
54+ let expectedHeight :: Int = length stack
54− stack_image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack ]
55+ stackImage = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack ]
55− in image_height stack_image == expected_height
56+ in imageHeight stackImage == expectedHeight
56−
57+
57−disjoint_height_horiz_join :: NonEmptyList SingleRowSingleAttrImage
58+disjointHeightHorizJoin :: NonEmptyList SingleRowSingleAttrImage
59+ -> NonEmptyList SingleRowSingleAttrImage
60+ -> Bool
61+disjointHeightHorizJoin (NonEmpty stack0) (NonEmpty stack1) =
62+ let expectedHeight :: Int = max (length stack0) (length stack1)
63+ stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
64+ stackImage1 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
65+ in imageHeight (stackImage0 <|> stackImage1) == expectedHeight
66+
67+
68+disjointHeightHorizJoinBgFill :: NonEmptyList SingleRowSingleAttrImage
58 -> NonEmptyList SingleRowSingleAttrImage
69 -> NonEmptyList SingleRowSingleAttrImage
59 -> Bool
70 -> Bool
60−disjoint_height_horiz_join (NonEmpty stack_0) (NonEmpty stack_1) =
71+disjointHeightHorizJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
61− let expected_height :: Int = max (length stack_0) (length stack_1)
62− stack_image_0 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]
63− stack_image_1 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]
64− in image_height (stack_image_0 <|> stack_image_1) == expected_height
65−
66−
67−disjoint_height_horiz_join_bg_fill :: NonEmptyList SingleRowSingleAttrImage
68− -> NonEmptyList SingleRowSingleAttrImage
69− -> Bool
70−disjoint_height_horiz_join_bg_fill (NonEmpty stack_0) (NonEmpty stack_1) =
71− let stack_image_0 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]
72+ let stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
72− stack_image_1 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]
73+ stackImage1 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
73− image = stack_image_0 <|> stack_image_1
74+ image = stackImage0 <|> stackImage1
74− expected_height = image_height image
75+ expectedHeight = imageHeight image
75 in case image of
76 in case image of
76− HorizJoin {} -> ( expected_height == (image_height $ part_left image) )
77+ HorizJoin {} -> ( expectedHeight == (imageHeight $ partLeft image) )
77− &&
78+ &&
78− ( expected_height == (image_height $ part_right image) )
79+ ( expectedHeight == (imageHeight $ partRight image) )
79 _ -> True
80 _ -> True
80
81
81−disjoint_width_vert_join :: NonEmptyList SingleRowSingleAttrImage
82+disjointWidthVertJoin :: NonEmptyList SingleRowSingleAttrImage
83+ -> NonEmptyList SingleRowSingleAttrImage
84+ -> Bool
85+disjointWidthVertJoin (NonEmpty stack0) (NonEmpty stack1) =
86+ let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)
87+ stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
88+ stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
89+ stack0Image = vertCat stack0Images
90+ stack1Image = vertCat stack1Images
91+ image = stack0Image <-> stack1Image
92+ in imageWidth image == expectedWidth
93+
94+disjointWidthVertJoinBgFill :: NonEmptyList SingleRowSingleAttrImage
82 -> NonEmptyList SingleRowSingleAttrImage
95 -> NonEmptyList SingleRowSingleAttrImage
83 -> Bool
96 -> Bool
84−disjoint_width_vert_join (NonEmpty stack_0) (NonEmpty stack_1) =
97+disjointWidthVertJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
85− let expected_width = maximum $ map image_width (stack_0_images ++ stack_1_images)
98+ let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)
86− stack_0_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]
87− stack_1_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]
88− stack_0_image = vert_cat stack_0_images
89− stack_1_image = vert_cat stack_1_images
90− image = stack_0_image <-> stack_1_image
91− in image_width image == expected_width
92−
93−disjoint_width_vert_join_bg_fill :: NonEmptyList SingleRowSingleAttrImage
94− -> NonEmptyList SingleRowSingleAttrImage
95− -> Bool
96−disjoint_width_vert_join_bg_fill (NonEmpty stack_0) (NonEmpty stack_1) =
97− let expected_width = maximum $ map image_width (stack_0_images ++ stack_1_images)
98− stack_0_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]
99+ stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
99− stack_1_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]
100+ stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
100− stack_0_image = vert_cat stack_0_images
101+ stack0Image = vertCat stack0Images
101− stack_1_image = vert_cat stack_1_images
102+ stack1Image = vertCat stack1Images
102− image = stack_0_image <-> stack_1_image
103+ image = stack0Image <-> stack1Image
103 in case image of
104 in case image of
104− VertJoin {} -> ( expected_width == (image_width $ part_top image) )
105+ VertJoin {} -> ( expectedWidth == (imageWidth $ partTop image) )
105 &&
106 &&
106− ( expected_width == (image_width $ part_bottom image) )
107+ ( expectedWidth == (imageWidth $ partBottom image) )
107 _ -> True
108 _ -> True
108
109
109−translation_is_linear_on_out_size :: Translation -> Bool
110+translationIsLinearOnOutSize :: Translation -> Bool
110−translation_is_linear_on_out_size (Translation i (x,y) i') =
111+translationIsLinearOnOutSize (Translation i (x,y) i') =
111− image_width i' == image_width i + x && image_height i' == image_height i + y
112+ imageWidth i' == imageWidth i + x && imageHeight i' == imageHeight i + y
112
113
113−padding_is_linear_on_out_size :: Image -> Gen Bool
114+paddingIsLinearOnOutSize :: Image -> Gen Bool
114−padding_is_linear_on_out_size i = do
115+paddingIsLinearOnOutSize i = do
115 l <- offset
116 l <- offset
116 t <- offset
117 t <- offset
117 r <- offset
118 r <- offset
118 b <- offset
119 b <- offset
119 let i' = pad l t r b i
120 let i' = pad l t r b i
120− return $ image_width i' == image_width i + l + r && image_height i' == image_height i + t + b
121+ return $ imageWidth i' == imageWidth i + l + r && imageHeight i' == imageHeight i + t + b
121 where offset = choose (1,1024)
122 where offset = choose (1,1024)
122
123
123−crop_left_limits_width :: Image -> Int -> Property
124+cropLeftLimitsWidth :: Image -> Int -> Property
124−crop_left_limits_width i v = v >= 0 ==>
125+cropLeftLimitsWidth i v = v >= 0 ==>
125− v >= image_width (crop_left v i)
126+ v >= imageWidth (cropLeft v i)
126
127
127−crop_right_limits_width :: Image -> Int -> Property
128+cropRightLimitsWidth :: Image -> Int -> Property
128−crop_right_limits_width i v = v >= 0 ==>
129+cropRightLimitsWidth i v = v >= 0 ==>
129− v >= image_width (crop_right v i)
130+ v >= imageWidth (cropRight v i)
130
131
131−crop_top_limits_height :: Image -> Int -> Property
132+cropTopLimitsHeight :: Image -> Int -> Property
132−crop_top_limits_height i v = v >= 0 ==>
133+cropTopLimitsHeight i v = v >= 0 ==>
133− v >= image_height (crop_top v i)
134+ v >= imageHeight (cropTop v i)
134
135
135−crop_bottom_limits_height :: Image -> Int -> Property
136+cropBottomLimitsHeight :: Image -> Int -> Property
136−crop_bottom_limits_height i v = v >= 0 ==>
137+cropBottomLimitsHeight i v = v >= 0 ==>
137− v >= image_height (crop_bottom v i)
138+ v >= imageHeight (cropBottom v i)
138
139
139−-- rediculous tests just to satisfy my desire for nice code coverage :-P
140+-- ridiculous tests just to satisfy my desire for nice code coverage :-P
140−can_show_image :: Image -> Bool
141+canShowImage :: Image -> Bool
141−can_show_image i = length (show i) > 0
142+canShowImage i = length (show i) > 0
142
143
143−can_rnf_image :: Image -> Bool
144+canRnfImage :: Image -> Bool
144−can_rnf_image i = rnf i == ()
145+canRnfImage i = rnf i == ()
145
146
146−can_pp_image :: Image -> Bool
147+canPpImage :: Image -> Bool
147−can_pp_image i = length (pp_image_structure i) > 0
148+canPpImage i = length (ppImageStructure i) > 0
148
149
149tests :: IO [Test]
150tests :: IO [Test]
150tests = return
151tests = return
151− [ verify "two_sw_horiz_concat" two_sw_horiz_concat
152+ [ verify "twoSwHorizConcat" twoSwHorizConcat
152− , verify "many_sw_horiz_concat" many_sw_horiz_concat
153+ , verify "manySwHorizConcat" manySwHorizConcat
153− , verify "two_sw_vert_concat" two_sw_vert_concat
154+ , verify "twoSwVertConcat" twoSwVertConcat
154− , verify "horiz_concat_sw_assoc" horiz_concat_sw_assoc
155+ , verify "horizConcatSwAssoc" horizConcatSwAssoc
155− , verify "many_dw_horiz_concat" many_dw_horiz_concat
156+ , verify "manyDwHorizConcat" manyDwHorizConcat
156− , verify "two_dw_horiz_concat" two_dw_horiz_concat
157+ , verify "twoDwHorizConcat" twoDwHorizConcat
157− , verify "two_dw_vert_concat" two_dw_vert_concat
158+ , verify "twoDwVertConcat" twoDwVertConcat
158− , verify "horiz_concat_dw_assoc" horiz_concat_dw_assoc
159+ , verify "horizConcatDwAssoc" horizConcatDwAssoc
159− , verify "single row vert concats to correct height" vert_contat_single_row
160+ , verify "single row vert concats to correct height" vertContatSingleRow
160− , verify "disjoint_height_horiz_join" disjoint_height_horiz_join
161+ , verify "disjointHeightHorizJoin" disjointHeightHorizJoin
161− , verify "disjoint_height_horiz_join BG fill" disjoint_height_horiz_join_bg_fill
162+ , verify "disjointHeightHorizJoin BG fill" disjointHeightHorizJoinBgFill
162− , verify "disjoint_width_vert_join" disjoint_width_vert_join
163+ , verify "disjointWidthVertJoin" disjointWidthVertJoin
163− , verify "disjoint_width_vert_join BG fill" disjoint_width_vert_join_bg_fill
164+ , verify "disjointWidthVertJoin BG fill" disjointWidthVertJoinBgFill
164− , verify "translation effects output dimensions linearly" translation_is_linear_on_out_size
165+ , verify "translation effects output dimensions linearly" translationIsLinearOnOutSize
165− , verify "padding effects output dimensions linearly" padding_is_linear_on_out_size
166+ , verify "padding effects output dimensions linearly" paddingIsLinearOnOutSize
166− , verify "crop left limits width" crop_left_limits_width
167+ , verify "crop left limits width" cropLeftLimitsWidth
167− , verify "crop right limits width" crop_right_limits_width
168+ , verify "crop right limits width" cropRightLimitsWidth
168− , verify "crop top limits height" crop_top_limits_height
169+ , verify "crop top limits height" cropTopLimitsHeight
169− , verify "crop bottom limits height" crop_bottom_limits_height
170+ , verify "crop bottom limits height" cropBottomLimitsHeight
170− , verify "can show image" can_show_image
171+ , verify "can show image" canShowImage
171− , verify "can rnf image" can_rnf_image
172+ , verify "can rnf image" canRnfImage
172− , verify "can pp image" can_pp_image
173+ , verify "can pp image" canPpImage
173 ]
174 ]
174−
test/VerifyImageTrans.hs+18−18
@@ -9,26 +9,26 @@ import Verify
9
9
10import Data.Word
10import Data.Word
11
11
12−is_horiz_text_of_columns :: Image -> Int -> Bool
12+isHorizTextOfColumns :: Image -> Int -> Bool
13−is_horiz_text_of_columns (HorizText { output_width = in_w }) expected_w = in_w == expected_w
13+isHorizTextOfColumns (HorizText { outputWidth = inW }) expectedW = inW == expectedW
14−is_horiz_text_of_columns (BGFill { output_width = in_w }) expected_w = in_w == expected_w
14+isHorizTextOfColumns (BGFill { outputWidth = inW }) expectedW = inW == expectedW
15−is_horiz_text_of_columns _image _expected_w = False
15+isHorizTextOfColumns _image _expectedW = False
16−
16+
17−verify_horiz_contat_wo_attr_change_simplifies :: SingleRowSingleAttrImage -> Bool
17+verifyHorizContatWoAttrChangeSimplifies :: SingleRowSingleAttrImage -> Bool
18−verify_horiz_contat_wo_attr_change_simplifies (SingleRowSingleAttrImage _attr char_count image) =
18+verifyHorizContatWoAttrChangeSimplifies (SingleRowSingleAttrImage _attr charCount image) =
19− is_horiz_text_of_columns image char_count
19+ isHorizTextOfColumns image charCount
20−
20+
21−verify_horiz_contat_w_attr_change_simplifies :: SingleRowTwoAttrImage -> Bool
21+verifyHorizContatWAttrChangeSimplifies :: SingleRowTwoAttrImage -> Bool
22−verify_horiz_contat_w_attr_change_simplifies ( SingleRowTwoAttrImage (SingleRowSingleAttrImage attr0 char_count0 _image0)
22+verifyHorizContatWAttrChangeSimplifies ( SingleRowTwoAttrImage (SingleRowSingleAttrImage attr0 charCount0 _image0)
23− (SingleRowSingleAttrImage attr1 char_count1 _image1)
23+ (SingleRowSingleAttrImage attr1 charCount1 _image1)
24− i
24+ i
25− )
25+ )
26− | char_count0 == 0 || char_count1 == 0 || attr0 == attr1 = is_horiz_text_of_columns i (char_count0 + char_count1)
26+ | charCount0 == 0 || charCount1 == 0 || attr0 == attr1 = isHorizTextOfColumns i (charCount0 + charCount1)
27− | otherwise = False == is_horiz_text_of_columns i (char_count0 + char_count1)
27+ | otherwise = False == isHorizTextOfColumns i (charCount0 + charCount1)
28
28
29tests :: IO [Test]
29tests :: IO [Test]
30tests = return
30tests = return
31− [ verify "verify_horiz_contat_wo_attr_change_simplifies" verify_horiz_contat_wo_attr_change_simplifies
31+ [ verify "verifyHorizContatWoAttrChangeSimplifies" verifyHorizContatWoAttrChangeSimplifies
32− , verify "verify_horiz_contat_w_attr_change_simplifies" verify_horiz_contat_w_attr_change_simplifies
32+ , verify "verifyHorizContatWAttrChangeSimplifies" verifyHorizContatWAttrChangeSimplifies
33 ]
33 ]
34
34
test/VerifyInline.hs+8−8
@@ -1,10 +1,10 @@
1module VerifyInline where
1module VerifyInline where
2
2
3import Graphics.Vty.Inline
3import Graphics.Vty.Inline
4−import Graphics.Vty.Terminal
4+import Graphics.Vty.Output
5−import Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
5+import Graphics.Vty.Output.TerminfoBased as TerminfoBased
6
6
7−import Verify.Graphics.Vty.Terminal
7+import Verify.Graphics.Vty.Output
8
8
9import Verify
9import Verify
10
10
@@ -13,15 +13,15 @@ import Distribution.TestSuite
13import System.IO
13import System.IO
14
14
15tests :: IO [Test]
15tests :: IO [Test]
16−tests = concat <$> forM terminals_of_interest (\term_name -> return $
16+tests = concat <$> forM terminalsOfInterest (\termName -> return $
17 [ Test $ TestInstance
17 [ Test $ TestInstance
18 { name = "verify vty inline"
18 { name = "verify vty inline"
19 , run = do
19 , run = do
20 {- disabled because I cannot get useful output out of cabal why this fails.
20 {- disabled because I cannot get useful output out of cabal why this fails.
21− null_out <- openFile "/dev/null" WriteMode
21+ nullOut <- openFile "/dev/null" WriteMode
22− t <- TerminfoBased.reserve_terminal term_name null_out
22+ t <- TerminfoBased.reserveTerminal termName nullOut
23− put_attr_change t $ default_all
23+ putAttrChange t $ default_all
24− release_terminal t
24+ releaseTerminal t
25 -}
25 -}
26 return $ Finished Pass
26 return $ Finished Pass
27 , tags = []
27 , tags = []
test/VerifyLayersSpanGeneration.hs+98−95
@@ -2,7 +2,9 @@
2{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3module VerifyLayersSpanGeneration where
3module VerifyLayersSpanGeneration where
4
4
5−import Verify.Graphics.Vty.DisplayRegion
5+import Verify.Graphics.Vty.Prelude
6+
7+import Verify.Graphics.Vty.Attributes
6import Verify.Graphics.Vty.Image
8import Verify.Graphics.Vty.Image
7import Verify.Graphics.Vty.Picture
9import Verify.Graphics.Vty.Picture
8import Verify.Graphics.Vty.Span
10import Verify.Graphics.Vty.Span
@@ -12,116 +14,117 @@ import Graphics.Vty.PictureToSpans
12
14
13import Verify
15import Verify
14
16
15−import qualified Data.Vector as Vector
17+import qualified Data.Vector as Vector
16
18
17−larger_horiz_span_occlusion :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
19+largerHorizSpanOcclusion :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
18−larger_horiz_span_occlusion row_0 row_1 =
20+largerHorizSpanOcclusion row0 row1 =
19− let i_0 = row_image row_0
21+ let i0 = rowImage row0
20− i_1 = row_image row_1
22+ i1 = rowImage row1
21− (i_larger, i_smaller) = if image_width i_0 > image_width i_1 then (i_0, i_1) else (i_1, i_0)
23+ (iLarger, iSmaller) = if imageWidth i0 > imageWidth i1 then (i0, i1) else (i1, i0)
22− expected_ops = display_ops_for_image i_larger
24+ expectedOps = displayOpsForImage iLarger
23− p = pic_for_layers [i_larger, i_smaller]
25+ p = picForLayers [iLarger, iSmaller]
24− ops = display_ops_for_pic p $ DisplayRegion (image_width i_larger) (image_height i_larger)
26+ ops = displayOpsForPic p (imageWidth iLarger,imageHeight iLarger)
25− in verify_ops_equality expected_ops ops
27+ in verifyOpsEquality expectedOps ops
26
28
27−-- | Two rows stacked vertical is equivalent to the first row rendered as the top layer and the
29+-- | Two rows stacked vertical is equivalent to the first row rendered
30+-- as the top layer and the second row rendered as a bottom layer with a
28−-- second row rendered as a bottom layer with a background fill where the first row would be.
31+-- background fill where the first row would be.
29−vert_stack_layer_equivalence_0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
32+vertStackLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
30−vert_stack_layer_equivalence_0 row_0 row_1 =
33+vertStackLayerEquivalence0 row0 row1 =
31− let i_0 = row_image row_0
34+ let i0 = rowImage row0
32− i_1 = row_image row_1
35+ i1 = rowImage row1
33− i = i_0 <-> i_1
36+ i = i0 <-> i1
34− p = pic_for_image i
37+ p = picForImage i
35− i_lower = background_fill (image_width i_0) 1 <-> i_1
38+ iLower = backgroundFill (imageWidth i0) 1 <-> i1
36− p_layered = pic_for_layers [i_0, i_lower]
39+ pLayered = picForLayers [i0, iLower]
37− expected_ops = display_ops_for_image i
40+ expectedOps = displayOpsForImage i
38− ops_layered = display_ops_for_pic p_layered $ DisplayRegion (image_width i_lower) (image_height i_lower)
41+ opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
39− in verify_ops_equality expected_ops ops_layered
42+ in verifyOpsEquality expectedOps opsLayered
40
43
41−vert_stack_layer_equivalence_1 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
44+vertStackLayerEquivalence1 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
42−vert_stack_layer_equivalence_1 row_0 row_1 =
45+vertStackLayerEquivalence1 row0 row1 =
43− let i_0 = row_image row_0
46+ let i0 = rowImage row0
44− i_1 = row_image row_1
47+ i1 = rowImage row1
45− i = i_0 <-> i_1
48+ i = i0 <-> i1
46− p = pic_for_image i
49+ p = picForImage i
47− i_lower = i_0 <-> background_fill (image_width i_1) 1
50+ iLower = i0 <-> backgroundFill (imageWidth i1) 1
48− i_upper = background_fill (image_width i_0) 1 <-> i_1
51+ iUpper = backgroundFill (imageWidth i0) 1 <-> i1
49− p_layered = pic_for_layers [i_upper, i_lower]
52+ pLayered = picForLayers [iUpper, iLower]
50− expected_ops = display_ops_for_image i
53+ expectedOps = displayOpsForImage i
51− ops_layered = display_ops_for_pic p_layered $ DisplayRegion (image_width i_lower) (image_height i_lower)
54+ opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
52− in verify_ops_equality expected_ops ops_layered
55+ in verifyOpsEquality expectedOps opsLayered
53
56
54−-- | Two rows horiz joined is equivalent to the first row rendered as the top layer and the
57+-- | Two rows horiz joined is equivalent to the first row rendered as
58+-- the top layer and the second row rendered as a bottom layer with a
55−-- second row rendered as a bottom layer with a background fill where the first row would be.
59+-- background fill where the first row would be.
56−horiz_join_layer_equivalence_0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
60+horizJoinLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
57−horiz_join_layer_equivalence_0 row_0 row_1 =
61+horizJoinLayerEquivalence0 row0 row1 =
58− let i_0 = row_image row_0
62+ let i0 = rowImage row0
59− i_1 = row_image row_1
63+ i1 = rowImage row1
60− i = i_0 <|> i_1
64+ i = i0 <|> i1
61− p = pic_for_image i
65+ p = picForImage i
62− i_lower = background_fill (image_width i_0) 1 <|> i_1
66+ iLower = backgroundFill (imageWidth i0) 1 <|> i1
63− p_layered = pic_for_layers [i_0, i_lower]
67+ pLayered = picForLayers [i0, iLower]
64− expected_ops = display_ops_for_image i
68+ expectedOps = displayOpsForImage i
65− ops_layered = display_ops_for_pic p_layered $ DisplayRegion (image_width i_lower) (image_height i_lower)
69+ opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
66− in verify_ops_equality expected_ops ops_layered
70+ in verifyOpsEquality expectedOps opsLayered
67
71
68−horiz_join_layer_equivalence_1 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
72+horizJoinLayerEquivalence1 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
69−horiz_join_layer_equivalence_1 row_0 row_1 =
73+horizJoinLayerEquivalence1 row0 row1 =
70− let i_0 = row_image row_0
74+ let i0 = rowImage row0
71− i_1 = row_image row_1
75+ i1 = rowImage row1
72− i = i_0 <|> i_1
76+ i = i0 <|> i1
73− p = pic_for_image i
77+ p = picForImage i
74− i_lower = i_0 <|> background_fill (image_width i_1) 1
78+ iLower = i0 <|> backgroundFill (imageWidth i1) 1
75− i_upper = background_fill (image_width i_0) 1 <|> i_1
79+ iUpper = backgroundFill (imageWidth i0) 1 <|> i1
76− p_layered = pic_for_layers [i_upper, i_lower]
80+ pLayered = picForLayers [iUpper, iLower]
77− expected_ops = display_ops_for_image i
81+ expectedOps = displayOpsForImage i
78− ops_layered = display_ops_for_pic p_layered $ DisplayRegion (image_width i_lower) (image_height i_lower)
82+ opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
79− in verify_ops_equality expected_ops ops_layered
83+ in verifyOpsEquality expectedOps opsLayered
80
84
81−horiz_join_alternate_0 :: Result
85+horizJoinAlternate0 :: Result
82−horiz_join_alternate_0 =
86+horizJoinAlternate0 =
83 let size = 4
87 let size = 4
84− str_0 = replicate size 'a'
88+ str0 = replicate size 'a'
85− str_1 = replicate size 'b'
89+ str1 = replicate size 'b'
86− i_0 = string def_attr str_0
90+ i0 = string defAttr str0
87− i_1 = string def_attr str_1
91+ i1 = string defAttr str1
88− i = horiz_cat $ zipWith horiz_join (replicate size i_0) (replicate size i_1)
92+ i = horizCat $ zipWith horizJoin (replicate size i0) (replicate size i1)
89− layer_0 = horiz_cat $ replicate size $ i_0 <|> background_fill size 1
93+ layer0 = horizCat $ replicate size $ i0 <|> backgroundFill size 1
90− layer_1 = horiz_cat $ replicate size $ background_fill size 1 <|> i_1
94+ layer1 = horizCat $ replicate size $ backgroundFill size 1 <|> i1
91− expected_ops = display_ops_for_image i
95+ expectedOps = displayOpsForImage i
92− ops_layered = display_ops_for_pic (pic_for_layers [layer_0, layer_1])
96+ opsLayered = displayOpsForPic (picForLayers [layer0, layer1])
93− (DisplayRegion (image_width i) (image_height i))
97+ (imageWidth i,imageHeight i)
94− in verify_ops_equality expected_ops ops_layered
98+ in verifyOpsEquality expectedOps opsLayered
95
99
96−horiz_join_alternate_1 :: Result
100+horizJoinAlternate1 :: Result
97−horiz_join_alternate_1 =
101+horizJoinAlternate1 =
98 let size = 4
102 let size = 4
99− str_0 = replicate size 'a'
103+ str0 = replicate size 'a'
100− str_1 = replicate size 'b'
104+ str1 = replicate size 'b'
101− i_0 = string def_attr str_0
105+ i0 = string defAttr str0
102− i_1 = string def_attr str_1
106+ i1 = string defAttr str1
103− i = horiz_cat $ zipWith horiz_join (replicate size i_0) (replicate size i_1)
107+ i = horizCat $ zipWith horizJoin (replicate size i0) (replicate size i1)
104− layers = [l | b <- take 4 [0,size*2..], let l = background_fill b 1 <|> i_0 <|> i_1]
108+ layers = [l | b <- take 4 [0,size*2..], let l = backgroundFill b 1 <|> i0 <|> i1]
105− expected_ops = display_ops_for_image i
109+ expectedOps = displayOpsForImage i
106− ops_layered = display_ops_for_pic (pic_for_layers layers)
110+ opsLayered = displayOpsForPic (picForLayers layers)
107− (DisplayRegion (image_width i) (image_height i))
111+ (imageWidth i,imageHeight i)
108− in verify_ops_equality expected_ops ops_layered
112+ in verifyOpsEquality expectedOps opsLayered
109
113
110tests :: IO [Test]
114tests :: IO [Test]
111−tests = return
115+tests = return
112 [ verify "a larger horiz span occludes a smaller span on a lower layer"
116 [ verify "a larger horiz span occludes a smaller span on a lower layer"
113− larger_horiz_span_occlusion
117+ largerHorizSpanOcclusion
114 , verify "two rows stack vertical equiv to first image layered on top of second with padding (0)"
118 , verify "two rows stack vertical equiv to first image layered on top of second with padding (0)"
115− vert_stack_layer_equivalence_0
119+ vertStackLayerEquivalence0
116 , verify "two rows stack vertical equiv to first image layered on top of second with padding (1)"
120 , verify "two rows stack vertical equiv to first image layered on top of second with padding (1)"
117− vert_stack_layer_equivalence_1
121+ vertStackLayerEquivalence1
118 -- , verify "two rows horiz joined equiv to first image layered on top of second with padding (0)"
122 -- , verify "two rows horiz joined equiv to first image layered on top of second with padding (0)"
119− -- horiz_join_layer_equivalence_0
123+ -- horizJoinLayerEquivalence0
120 -- , verify "two rows horiz joined equiv to first image layered on top of second with padding (1)"
124 -- , verify "two rows horiz joined equiv to first image layered on top of second with padding (1)"
121− -- horiz_join_layer_equivalence_1
125+ -- horizJoinLayerEquivalence1
122 -- , verify "alternating images using joins is the same as alternating images using layers (0)"
126 -- , verify "alternating images using joins is the same as alternating images using layers (0)"
123− -- horiz_join_alternate_0
127+ -- horizJoinAlternate0
124 -- , verify "alternating images using joins is the same as alternating images using layers (1)"
128 -- , verify "alternating images using joins is the same as alternating images using layers (1)"
125− -- horiz_join_alternate_1
129+ -- horizJoinAlternate1
126 ]
130 ]
127−
test/VerifyOutput.hsnew file+58
@@ -0,0 +1,58 @@
1+-- We setup the environment to invoke certain terminals of interest.
2+-- This assumes appropriate definitions exist in the current environment
3+-- for the terminals of interest.
4+{-# LANGUAGE ScopedTypeVariables #-}
5+module VerifyOutput where
6+
7+import Verify
8+
9+import Graphics.Vty
10+
11+import Verify.Graphics.Vty.Image
12+import Verify.Graphics.Vty.Output
13+
14+import Control.Monad
15+
16+import qualified System.Console.Terminfo as Terminfo
17+import System.Posix.IO
18+
19+tests :: IO [Test]
20+tests = concat <$> forM terminalsOfInterest (\termName -> do
21+ -- check if that terminfo exists
22+ -- putStrLn $ "testing end to end for terminal: " ++ termName
23+ mti <- try $ Terminfo.setupTerm termName
24+ case mti of
25+ Left (_ :: SomeException) -> return []
26+ Right _ -> return [ verify ("verify " ++ termName ++ " could output a picture")
27+ (smokeTestTermNonMac termName)
28+ ]
29+ )
30+
31+smokeTestTermNonMac :: String -> Image -> Property
32+smokeTestTermNonMac termName i = liftIOResult $ do
33+ smokeTestTerm termName i
34+
35+smokeTestTerm :: String -> Image -> IO Result
36+smokeTestTerm termName i = do
37+ nullOut <- openFd "/dev/null" WriteOnly Nothing defaultFileFlags
38+ t <- outputForConfig $ defaultConfig
39+ { outputFd = Just nullOut
40+ , termName = Just termName
41+ , colorMode = Just NoColor
42+ }
43+ -- putStrLn $ "context color count: " ++ show (contextColorCount t)
44+ reserveDisplay t
45+ dc <- displayContext t (100,100)
46+ -- always show the cursor to produce tests for terminals with no
47+ -- cursor support.
48+ let pic = (picForImage i) { picCursor = Cursor 0 0 }
49+ outputPicture dc pic
50+ setCursorPos t 0 0
51+ when (supportsCursorVisibility t) $ do
52+ hideCursor t
53+ showCursor t
54+ releaseDisplay t
55+ releaseTerminal t
56+ closeFd nullOut
57+ return succeeded
58+
test/VerifyParseTerminfoCaps.hs+55−69
@@ -1,4 +1,5 @@
1{-# LANGUAGE NamedFieldPuns #-}
1{-# LANGUAGE NamedFieldPuns #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
2module VerifyParseTerminfoCaps where
3module VerifyParseTerminfoCaps where
3
4
4import Prelude hiding ( catch )
5import Prelude hiding ( catch )
@@ -6,7 +7,7 @@ import Prelude hiding ( catch )
6import qualified System.Console.Terminfo as Terminfo
7import qualified System.Console.Terminfo as Terminfo
7
8
8import Verify.Data.Terminfo.Parse
9import Verify.Data.Terminfo.Parse
9−import Verify.Graphics.Vty.Terminal
10+import Verify.Graphics.Vty.Output
10import Verify
11import Verify
11
12
12import Data.Maybe ( catMaybes, fromJust )
13import Data.Maybe ( catMaybes, fromJust )
@@ -16,7 +17,7 @@ import Numeric
16
17
17-- If a terminal defines one of the caps then it's expected to be parsable.
18-- If a terminal defines one of the caps then it's expected to be parsable.
18-- TODO: reduce duplication with terminfo terminal implementation.
19-- TODO: reduce duplication with terminfo terminal implementation.
19−caps_of_interest =
20+capsOfInterest =
20 [ "cup"
21 [ "cup"
21 , "sc"
22 , "sc"
22 , "rc"
23 , "rc"
@@ -36,80 +37,65 @@ caps_of_interest =
36 , "sgr0"
37 , "sgr0"
37 ]
38 ]
38
39
39−from_capname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
40+fromCapname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
40
41
41tests :: IO [Test]
42tests :: IO [Test]
42tests = do
43tests = do
43− parse_tests <- concat <$> forM terminals_of_interest ( \term_name -> do
44+ parseTests <- concat <$> forM terminalsOfInterest (\termName ->
44− mti <- liftIO $ try $ Terminfo.setupTerm term_name
45+ liftIO (try $ Terminfo.setupTerm termName)
45− case mti of
46− Left (_e :: SomeException)
46+ >>= either (\(_e :: SomeException) -> return [])
47− -> return []
47+ (\ti -> concat <$> forM capsOfInterest (\capName -> do
48− Right ti -> do
48+ let caseName = "\tparsing cap: " ++ capName
49− concat <$> forM caps_of_interest ( \cap_name -> do
49+ liftIO $ putStrLn caseName
50− liftIO $ putStrLn $ "\tparsing cap: " ++ cap_name
51− case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
50+ return $ case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
52− Just cap_def -> do
51+ Just capDef -> [verify (caseName ++ " -> " ++ show capDef)
53− return [ verify ( "\tparse cap " ++ cap_name ++ " -> " ++ show cap_def )
52+ (verifyParseCap capDef $ const succeeded)]
54− ( verify_parse_cap cap_def $ const (return succeeded) ) ]
55− Nothing -> do
53+ Nothing -> []
56− return []
57 )
54 )
55+ )
58 )
56 )
59− -- The quickcheck tests
57+ return $ [ verify "parse_nonParamaterizedCaps" nonParamaterizedCaps
60− return $ [ verify "parse_non_paramaterized_caps" non_paramaterized_caps
61− , verify "parse cap string with literal %" literal_percent_caps
58+ , verify "parse cap string with literal %" literalPercentCaps
62− , verify "parse cap string with %i op" inc_first_two_caps
59+ , verify "parse cap string with %i op" incFirstTwoCaps
63− , verify "parse cap string with %pN op" push_param_caps
60+ , verify "parse cap string with %pN op" pushParamCaps
64− ] ++ parse_tests
61+ ] ++ parseTests
65−
62+
66−verify_parse_cap cap_string on_parse = liftIOResult $ do
63+verifyParseCap capString onParse =
67− parse_result <- parse_cap_expression cap_string
64+ case parseCapExpression capString of
68− case parse_result of
69− Left error -> return $ failed { reason = "parse error " ++ show error }
65+ Left error -> failed { reason = "parse error " ++ show error }
70− Right e -> on_parse e
66+ Right e -> onParse e
71−
67+
72−non_paramaterized_caps (NonParamCapString cap) = do
68+nonParamaterizedCaps (NonParamCapString cap) = do
73− verify_parse_cap cap $ \e ->
69+ verifyParseCap cap $ \e ->
74− let expected_count :: Word8 = toEnum $ length cap
75− expected_bytes = map (toEnum . fromEnum) cap
70+ let expectedBytes = map (toEnum . fromEnum) cap
76− out_bytes = bytes_for_range e 0 expected_count
71+ outBytes = bytesForRange e 0 (length cap)
77− in return $ verify_bytes_equal out_bytes expected_bytes
72+ in verifyBytesEqual outBytes expectedBytes
78−
73+
79−literal_percent_caps (LiteralPercentCap cap_string expected_bytes) = do
74+literalPercentCaps (LiteralPercentCap capString expectedBytes) = do
80− verify_parse_cap cap_string $ \e ->
75+ verifyParseCap capString $ \e -> verifyBytesEqual (collectBytes e) expectedBytes
81− let expected_count :: Word8 = toEnum $ length expected_bytes
76+
82− out_bytes = collect_bytes e
77+incFirstTwoCaps (IncFirstTwoCap capString expectedBytes) = do
83− in return $ verify_bytes_equal out_bytes expected_bytes
78+ verifyParseCap capString $ \e -> verifyBytesEqual (collectBytes e) expectedBytes
84−
79+
85−inc_first_two_caps (IncFirstTwoCap cap_string expected_bytes) = do
80+pushParamCaps (PushParamCap capString expectedParamCount expectedBytes) = do
86− verify_parse_cap cap_string $ \e ->
81+ verifyParseCap capString $ \e ->
87− let expected_count :: Word8 = toEnum $ length expected_bytes
82+ let outBytes = collectBytes e
88− out_bytes = collect_bytes e
83+ outParamCount = paramCount e
89− in return $ verify_bytes_equal out_bytes expected_bytes
84+ in if outParamCount == expectedParamCount
90−
85+ then verifyBytesEqual outBytes expectedBytes
91−push_param_caps (PushParamCap cap_string expected_param_count expected_bytes) = do
92− verify_parse_cap cap_string $ \e ->
93− let expected_count :: Word8 = toEnum $ length expected_bytes
94− out_bytes = collect_bytes e
95− out_param_count = param_count e
96− in return $ if out_param_count == expected_param_count
97− then verify_bytes_equal out_bytes expected_bytes
98 else failed { reason = "out param count /= expected param count" }
86 else failed { reason = "out param count /= expected param count" }
99
87
100−dec_print_param_caps (DecPrintCap cap_string expected_param_count expected_bytes) = do
88+decPrintParamCaps (DecPrintCap capString expectedParamCount expectedBytes) = do
101− verify_parse_cap cap_string $ \e ->
89+ verifyParseCap capString $ \e ->
102− let expected_count :: Word8 = toEnum $ length expected_bytes
90+ let outBytes = collectBytes e
103− out_bytes = collect_bytes e
91+ outParamCount = paramCount e
104− out_param_count = param_count e
92+ in if outParamCount == expectedParamCount
105− in return $ if out_param_count == expected_param_count
93+ then verifyBytesEqual outBytes expectedBytes
106− then verify_bytes_equal out_bytes expected_bytes
107 else failed { reason = "out param count /= expected param count" }
94 else failed { reason = "out param count /= expected param count" }
108
95
109−print_cap ti cap_name = do
96+printCap ti capName = do
110− putStrLn $ cap_name ++ ": " ++ show (from_capname ti cap_name)
97+ putStrLn $ capName ++ ": " ++ show (fromCapname ti capName)
111−
112−print_expression ti cap_name = do
113− parse_result <- parse_cap_expression $ from_capname ti cap_name
114− putStrLn $ cap_name ++ ": " ++ show parse_result
115
98
99+printExpression ti capName = do
100+ let parseResult = parseCapExpression $ fromCapname ti capName
101+ putStrLn $ capName ++ ": " ++ show parseResult
test/VerifySimpleSpanGeneration.hs+159−158
@@ -2,7 +2,8 @@
2{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3module VerifySimpleSpanGeneration where
3module VerifySimpleSpanGeneration where
4
4
5−import Verify.Graphics.Vty.DisplayRegion
5+import Verify.Graphics.Vty.Prelude
6+
6import Verify.Graphics.Vty.Image
7import Verify.Graphics.Vty.Image
7import Verify.Graphics.Vty.Picture
8import Verify.Graphics.Vty.Picture
8import Verify.Graphics.Vty.Span
9import Verify.Graphics.Vty.Span
@@ -12,176 +13,176 @@ import Graphics.Vty.PictureToSpans
12
13
13import Verify
14import Verify
14
15
15−import qualified Data.Vector as Vector
16+import qualified Data.Vector as Vector
16−
17+
17−unit_image_and_zero_window_0 :: UnitImage -> EmptyWindow -> Bool
18+unitImageAndZeroWindow0 :: UnitImage -> EmptyWindow -> Bool
18−unit_image_and_zero_window_0 (UnitImage _ i) (EmptyWindow w) =
19+unitImageAndZeroWindow0 (UnitImage _ i) (EmptyWindow w) =
19− let p = pic_for_image i
20+ let p = picForImage i
20− ops = display_ops_for_pic p (region_for_window w)
21+ ops = displayOpsForPic p (regionForWindow w)
21− in display_ops_columns ops == 0 && display_ops_rows ops == 0
22+ in displayOpsColumns ops == 0 && displayOpsRows ops == 0
22−
23+
23−unit_image_and_zero_window_1 :: UnitImage -> EmptyWindow -> Bool
24+unitImageAndZeroWindow1 :: UnitImage -> EmptyWindow -> Bool
24−unit_image_and_zero_window_1 (UnitImage _ i) (EmptyWindow w) =
25+unitImageAndZeroWindow1 (UnitImage _ i) (EmptyWindow w) =
25− let p = pic_for_image i
26+ let p = picForImage i
26− ops = display_ops_for_pic p (region_for_window w)
27+ ops = displayOpsForPic p (regionForWindow w)
27− in ( span_ops_effected_rows ops == 0 ) && ( all_spans_have_width ops 0 )
28+ in ( spanOpsAffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )
28−
29+
29−horiz_span_image_and_zero_window_0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
30+horizSpanImageAndZeroWindow0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
30−horiz_span_image_and_zero_window_0 (SingleRowSingleAttrImage { row_image = i }) (EmptyWindow w) =
31+horizSpanImageAndZeroWindow0 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) =
31− let p = pic_for_image i
32+ let p = picForImage i
32− ops = display_ops_for_pic p (region_for_window w)
33+ ops = displayOpsForPic p (regionForWindow w)
33− in display_ops_columns ops == 0 && display_ops_rows ops == 0
34+ in displayOpsColumns ops == 0 && displayOpsRows ops == 0
34−
35+
35−horiz_span_image_and_zero_window_1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
36+horizSpanImageAndZeroWindow1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
36−horiz_span_image_and_zero_window_1 (SingleRowSingleAttrImage { row_image = i }) (EmptyWindow w) =
37+horizSpanImageAndZeroWindow1 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) =
37− let p = pic_for_image i
38+ let p = picForImage i
38− ops = display_ops_for_pic p (region_for_window w)
39+ ops = displayOpsForPic p (regionForWindow w)
39− in ( span_ops_effected_rows ops == 0 ) && ( all_spans_have_width ops 0 )
40+ in ( spanOpsAffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )
40−
41+
41−horiz_span_image_and_equal_window_0 :: SingleRowSingleAttrImage -> Result
42+horizSpanImageAndEqualWindow0 :: SingleRowSingleAttrImage -> Result
42−horiz_span_image_and_equal_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
43+horizSpanImageAndEqualWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
43− let p = pic_for_image i
44+ let p = picForImage i
44 w = MockWindow c 1
45 w = MockWindow c 1
45− ops = display_ops_for_pic p (region_for_window w)
46+ ops = displayOpsForPic p (regionForWindow w)
46− in verify_all_spans_have_width i ops c
47+ in verifyAllSpansHaveWidth i ops c
47
48
48−horiz_span_image_and_equal_window_1 :: SingleRowSingleAttrImage -> Bool
49+horizSpanImageAndEqualWindow1 :: SingleRowSingleAttrImage -> Bool
49−horiz_span_image_and_equal_window_1 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
50+horizSpanImageAndEqualWindow1 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
50− let p = pic_for_image i
51+ let p = picForImage i
51 w = MockWindow c 1
52 w = MockWindow c 1
52− ops = display_ops_for_pic p (region_for_window w)
53+ ops = displayOpsForPic p (regionForWindow w)
53− in span_ops_effected_rows ops == 1
54+ in spanOpsAffectedRows ops == 1
54−
55+
55−horiz_span_image_and_lesser_window_0 :: SingleRowSingleAttrImage -> Result
56+horizSpanImageAndLesserWindow0 :: SingleRowSingleAttrImage -> Result
56−horiz_span_image_and_lesser_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
57+horizSpanImageAndLesserWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
57− let p = pic_for_image i
58+ let p = picForImage i
58− lesser_width = c `div` 2
59+ lesserWidth = c `div` 2
59− w = MockWindow lesser_width 1
60+ w = MockWindow lesserWidth 1
60− ops = display_ops_for_pic p (region_for_window w)
61+ ops = displayOpsForPic p (regionForWindow w)
61− in verify_all_spans_have_width i ops lesser_width
62+ in verifyAllSpansHaveWidth i ops lesserWidth
62−
63+
63−single_attr_single_span_stack_cropped_0 :: SingleAttrSingleSpanStack -> Result
64+singleAttrSingleSpanStackCropped0 :: SingleAttrSingleSpanStack -> Result
64−single_attr_single_span_stack_cropped_0 stack =
65+singleAttrSingleSpanStackCropped0 stack =
65− let p = pic_for_image (stack_image stack)
66+ let p = picForImage (stackImage stack)
66− w = MockWindow (stack_width stack `div` 2) (stack_height stack)
67+ w = MockWindow (stackWidth stack `div` 2) (stackHeight stack)
67− ops = display_ops_for_pic p (region_for_window w)
68+ ops = displayOpsForPic p (regionForWindow w)
68− in verify_all_spans_have_width (stack_image stack) ops (stack_width stack `div` 2)
69+ in verifyAllSpansHaveWidth (stackImage stack) ops (stackWidth stack `div` 2)
69−
70+
70−single_attr_single_span_stack_cropped_1 :: SingleAttrSingleSpanStack -> Bool
71+singleAttrSingleSpanStackCropped1 :: SingleAttrSingleSpanStack -> Bool
71−single_attr_single_span_stack_cropped_1 stack =
72+singleAttrSingleSpanStackCropped1 stack =
72− let p = pic_for_image (stack_image stack)
73+ let p = picForImage (stackImage stack)
73− expected_row_count = stack_height stack `div` 2
74+ expectedRowCount = stackHeight stack `div` 2
74− w = MockWindow (stack_width stack) expected_row_count
75+ w = MockWindow (stackWidth stack) expectedRowCount
75− ops = display_ops_for_pic p (region_for_window w)
76+ ops = displayOpsForPic p (regionForWindow w)
76− actual_row_count = span_ops_effected_rows ops
77+ actualRowCount = spanOpsAffectedRows ops
77− in expected_row_count == actual_row_count
78+ in expectedRowCount == actualRowCount
78−
79+
79−single_attr_single_span_stack_cropped_2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
80+singleAttrSingleSpanStackCropped2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
80−single_attr_single_span_stack_cropped_2 stack_0 stack_1 =
81+singleAttrSingleSpanStackCropped2 stack0 stack1 =
81− let p = pic_for_image (stack_image stack_0 <|> stack_image stack_1)
82+ let p = picForImage (stackImage stack0 <|> stackImage stack1)
82− w = MockWindow (stack_width stack_0) (image_height (pic_image p))
83+ w = MockWindow (stackWidth stack0) (imageHeight (picImage p))
83− ops = display_ops_for_pic p (region_for_window w)
84+ ops = displayOpsForPic p (regionForWindow w)
84− in verify_all_spans_have_width (pic_image p) ops (stack_width stack_0)
85+ in verifyAllSpansHaveWidth (picImage p) ops (stackWidth stack0)
85−
86+
86−single_attr_single_span_stack_cropped_3 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
87+singleAttrSingleSpanStackCropped3 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
87−single_attr_single_span_stack_cropped_3 stack_0 stack_1 =
88+singleAttrSingleSpanStackCropped3 stack0 stack1 =
88− let p = pic_for_image (stack_image stack_0 <|> stack_image stack_1)
89+ let p = picForImage (stackImage stack0 <|> stackImage stack1)
89− w = MockWindow (image_width (pic_image p)) expected_row_count
90+ w = MockWindow (imageWidth (picImage p)) expectedRowCount
90− ops = display_ops_for_pic p (region_for_window w)
91+ ops = displayOpsForPic p (regionForWindow w)
91− expected_row_count = image_height (pic_image p) `div` 2
92+ expectedRowCount = imageHeight (picImage p) `div` 2
92− actual_row_count = span_ops_effected_rows ops
93+ actualRowCount = spanOpsAffectedRows ops
93− in expected_row_count == actual_row_count
94+ in expectedRowCount == actualRowCount
94−
95+
95−single_attr_single_span_stack_cropped_4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
96+singleAttrSingleSpanStackCropped4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
96−single_attr_single_span_stack_cropped_4 stack_0 stack_1 =
97+singleAttrSingleSpanStackCropped4 stack0 stack1 =
97− let p = pic_for_image (stack_image stack_0 <-> stack_image stack_1)
98+ let p = picForImage (stackImage stack0 <-> stackImage stack1)
98− w = MockWindow expected_width (image_height (pic_image p))
99+ w = MockWindow expectedWidth (imageHeight (picImage p))
99− ops = display_ops_for_pic p (region_for_window w)
100+ ops = displayOpsForPic p (regionForWindow w)
100− expected_width = image_width (pic_image p) `div` 2
101+ expectedWidth = imageWidth (picImage p) `div` 2
101− in verify_all_spans_have_width (pic_image p) ops expected_width
102+ in verifyAllSpansHaveWidth (picImage p) ops expectedWidth
102−
103+
103−single_attr_single_span_stack_cropped_5 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
104+singleAttrSingleSpanStackCropped5 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
104−single_attr_single_span_stack_cropped_5 stack_0 stack_1 =
105+singleAttrSingleSpanStackCropped5 stack0 stack1 =
105− let p = pic_for_image (stack_image stack_0 <-> stack_image stack_1)
106+ let p = picForImage (stackImage stack0 <-> stackImage stack1)
106− w = MockWindow (image_width (pic_image p)) (stack_height stack_0)
107+ w = MockWindow (imageWidth (picImage p)) (stackHeight stack0)
107− ops = display_ops_for_pic p (region_for_window w)
108+ ops = displayOpsForPic p (regionForWindow w)
108− expected_row_count = stack_height stack_0
109+ expectedRowCount = stackHeight stack0
109− actual_row_count = span_ops_effected_rows ops
110+ actualRowCount = spanOpsAffectedRows ops
110− in expected_row_count == actual_row_count
111+ in expectedRowCount == actualRowCount
111−
112+
112−horiz_span_image_and_greater_window_0 :: SingleRowSingleAttrImage -> Result
113+horizSpanImageAndGreaterWindow0 :: SingleRowSingleAttrImage -> Result
113−horiz_span_image_and_greater_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
114+horizSpanImageAndGreaterWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
114− let p = pic_for_image i
115+ let p = picForImage i
115 -- SingleRowSingleAttrImage always has width >= 1
116 -- SingleRowSingleAttrImage always has width >= 1
116− greater_width = c * 2
117+ greaterWidth = c * 2
117− w = MockWindow greater_width 1
118+ w = MockWindow greaterWidth 1
118− ops = display_ops_for_pic p (region_for_window w)
119+ ops = displayOpsForPic p (regionForWindow w)
119− in verify_all_spans_have_width i ops greater_width
120+ in verifyAllSpansHaveWidth i ops greaterWidth
120−
121+
121−arb_image_is_cropped :: DefaultImage -> MockWindow -> Bool
122+arbImageIsCropped :: DefaultImage -> MockWindow -> Bool
122−arb_image_is_cropped (DefaultImage image) win@(MockWindow w h) =
123+arbImageIsCropped (DefaultImage image) win@(MockWindow w h) =
123− let pic = pic_for_image image
124+ let pic = picForImage image
124− ops = display_ops_for_pic pic (region_for_window win)
125+ ops = displayOpsForPic pic (regionForWindow win)
125− in ( span_ops_effected_rows ops == h ) && ( all_spans_have_width ops w )
126+ in ( spanOpsAffectedRows ops == h ) && ( allSpansHaveWidth ops w )
126−
127+
127−span_ops_actually_fill_rows :: DefaultPic -> Bool
128+spanOpsActuallyFillRows :: DefaultPic -> Bool
128−span_ops_actually_fill_rows (DefaultPic pic win) =
129+spanOpsActuallyFillRows (DefaultPic pic win) =
129− let ops = display_ops_for_pic pic (region_for_window win)
130+ let ops = displayOpsForPic pic (regionForWindow win)
130− expected_row_count = region_height (region_for_window win)
131+ expectedRowCount = regionHeight (regionForWindow win)
131− actual_row_count = span_ops_effected_rows ops
132+ actualRowCount = spanOpsAffectedRows ops
132− in expected_row_count == actual_row_count
133+ in expectedRowCount == actualRowCount
133−
134+
134−span_ops_actually_fill_columns :: DefaultPic -> Bool
135+spanOpsActuallyFillColumns :: DefaultPic -> Bool
135−span_ops_actually_fill_columns (DefaultPic pic win) =
136+spanOpsActuallyFillColumns (DefaultPic pic win) =
136− let ops = display_ops_for_pic pic (region_for_window win)
137+ let ops = displayOpsForPic pic (regionForWindow win)
137− expected_column_count = region_width (region_for_window win)
138+ expectedColumnCount = regionWidth (regionForWindow win)
138− in all_spans_have_width ops expected_column_count
139+ in allSpansHaveWidth ops expectedColumnCount
139−
140+
140−first_span_op_sets_attr :: DefaultPic -> Bool
141+firstSpanOpSetsAttr :: DefaultPic -> Bool
141−first_span_op_sets_attr DefaultPic { default_pic = pic, default_win = win } =
142+firstSpanOpSetsAttr DefaultPic { defaultPic = pic, defaultWin = win } =
142− let ops = display_ops_for_pic pic (region_for_window win)
143+ let ops = displayOpsForPic pic (regionForWindow win)
143− in all ( is_attr_span_op . Vector.head ) ( Vector.toList ops )
144+ in all ( isAttrSpanOp . Vector.head ) ( Vector.toList ops )
144−
145+
145−single_attr_single_span_stack_op_coverage :: SingleAttrSingleSpanStack -> Result
146+singleAttrSingleSpanStackOpCoverage :: SingleAttrSingleSpanStack -> Result
146−single_attr_single_span_stack_op_coverage stack =
147+singleAttrSingleSpanStackOpCoverage stack =
147− let p = pic_for_image (stack_image stack)
148+ let p = picForImage (stackImage stack)
148− w = MockWindow (stack_width stack) (stack_height stack)
149+ w = MockWindow (stackWidth stack) (stackHeight stack)
149− ops = display_ops_for_pic p (region_for_window w)
150+ ops = displayOpsForPic p (regionForWindow w)
150− in verify_all_spans_have_width (stack_image stack) ops (stack_width stack)
151+ in verifyAllSpansHaveWidth (stackImage stack) ops (stackWidth stack)
151−
152+
152−image_coverage_matches_bounds :: Image -> Result
153+imageCoverageMatchesBounds :: Image -> Result
153−image_coverage_matches_bounds i =
154+imageCoverageMatchesBounds i =
154− let p = pic_for_image i
155+ let p = picForImage i
155− r = DisplayRegion (image_width i) (image_height i)
156+ r = (imageWidth i,imageHeight i)
156− ops = display_ops_for_pic p r
157+ ops = displayOpsForPic p r
157− in verify_all_spans_have_width i ops (image_width i)
158+ in verifyAllSpansHaveWidth i ops (imageWidth i)
158
159
159tests :: IO [Test]
160tests :: IO [Test]
160−tests = return
161+tests = return
161− [ verify "unit image is cropped when window size == (0,0) [0]" unit_image_and_zero_window_0
162+ [ verify "unit image is cropped when window size == (0,0) [0]" unitImageAndZeroWindow0
162− , verify "unit image is cropped when window size == (0,0) [1]" unit_image_and_zero_window_1
163+ , verify "unit image is cropped when window size == (0,0) [1]" unitImageAndZeroWindow1
163− , verify "horiz span image is cropped when window size == (0,0) [0]" horiz_span_image_and_zero_window_0
164+ , verify "horiz span image is cropped when window size == (0,0) [0]" horizSpanImageAndZeroWindow0
164− , verify "horiz span image is cropped when window size == (0,0) [1]" horiz_span_image_and_zero_window_1
165+ , verify "horiz span image is cropped when window size == (0,0) [1]" horizSpanImageAndZeroWindow1
165− , verify "horiz span image is not cropped when window size == size of image [width]" horiz_span_image_and_equal_window_0
166+ , verify "horiz span image is not cropped when window size == size of image [width]" horizSpanImageAndEqualWindow0
166− , verify "horiz span image is not cropped when window size == size of image [height]" horiz_span_image_and_equal_window_1
167+ , verify "horiz span image is not cropped when window size == size of image [height]" horizSpanImageAndEqualWindow1
167− , verify "horiz span image is not cropped when window size < size of image [width]" horiz_span_image_and_lesser_window_0
168+ , verify "horiz span image is not cropped when window size < size of image [width]" horizSpanImageAndLesserWindow0
168− , verify "horiz span image is not cropped when window size > size of image [width]" horiz_span_image_and_greater_window_0
169+ , verify "horiz span image is not cropped when window size > size of image [width]" horizSpanImageAndGreaterWindow0
169− , verify "first span op is always to set the text attribute" first_span_op_sets_attr
170+ , verify "first span op is always to set the text attribute" firstSpanOpSetsAttr
170 , verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"
171 , verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"
171− single_attr_single_span_stack_op_coverage
172+ singleAttrSingleSpanStackOpCoverage
172 , verify "a single attr text span is cropped when window size < size of stack image [width]"
173 , verify "a single attr text span is cropped when window size < size of stack image [width]"
173− single_attr_single_span_stack_cropped_0
174+ singleAttrSingleSpanStackCropped0
174 , verify "a single attr text span is cropped when window size < size of stack image [height]"
175 , verify "a single attr text span is cropped when window size < size of stack image [height]"
175− single_attr_single_span_stack_cropped_1
176+ singleAttrSingleSpanStackCropped1
176 , verify "single attr text span <|> single attr text span display cropped. [width]"
177 , verify "single attr text span <|> single attr text span display cropped. [width]"
177− single_attr_single_span_stack_cropped_2
178+ singleAttrSingleSpanStackCropped2
178 , verify "single attr text span <|> single attr text span display cropped. [height]"
179 , verify "single attr text span <|> single attr text span display cropped. [height]"
179− single_attr_single_span_stack_cropped_3
180+ singleAttrSingleSpanStackCropped3
180 , verify "single attr text span <-> single attr text span display cropped. [width]"
181 , verify "single attr text span <-> single attr text span display cropped. [width]"
181− single_attr_single_span_stack_cropped_4
182+ singleAttrSingleSpanStackCropped4
182 , verify "single attr text span <-> single attr text span display cropped. [height]"
183 , verify "single attr text span <-> single attr text span display cropped. [height]"
183− single_attr_single_span_stack_cropped_5
184+ singleAttrSingleSpanStackCropped5
184 , verify "an arbitrary image when rendered to a window of the same size will cover the entire window"
185 , verify "an arbitrary image when rendered to a window of the same size will cover the entire window"
185− image_coverage_matches_bounds
186+ imageCoverageMatchesBounds
186 ]
187 ]
187
188
test/VerifyTerminal.hsdeleted−63
@@ -1,63 +0,0 @@
1−{- We setup the environment to envoke certain terminals of interest.
2− - This assumes appropriate definitions exist in the current environment for the terminals of
3− - interest.
4− -}
5−module VerifyTerminal where
6−import Verify
7−
8−import Graphics.Vty
9−
10−import Verify.Graphics.Vty.Image
11−import Verify.Graphics.Vty.Terminal
12−
13−import Control.Monad
14−
15−import qualified System.Console.Terminfo as Terminfo
16−import System.Posix.Env
17−import System.IO
18−
19−tests :: IO [Test]
20−tests = concat <$> forM terminals_of_interest (\term_name -> do
21− -- check if that terminfo exists
22− putStrLn $ "testing end to end for terminal: " ++ term_name
23− mti <- try $ Terminfo.setupTerm term_name
24− case mti of
25− Left (_ :: SomeException) -> return []
26− Right _ -> return [ verify ("verify " ++ term_name ++ " could output a picture")
27− (smoke_test_term_non_mac term_name)
28− -- this is excessive.
29− , verify ("verify " ++ term_name ++ " could output a picture on a Mac.")
30− (smoke_test_term_mac term_name)
31− ]
32− )
33−
34−smoke_test_term_non_mac :: String -> Image -> Property
35−smoke_test_term_non_mac term_name i = liftIOResult $ do
36− -- unset the TERM_PROGRAM environment variable if set.
37− -- Required to execute regression test for #42 on a mac
38− unsetEnv "TERM_PROGRAM"
39− smoke_test_term term_name i
40−
41−smoke_test_term_mac :: String -> Image -> Property
42−smoke_test_term_mac term_name i = liftIOResult $ do
43− setEnv "TERM_PROGRAM" "Apple_Terminal" True
44− smoke_test_term term_name i
45−
46−smoke_test_term :: String -> Image -> IO Result
47−smoke_test_term term_name i = do
48− null_out <- openFile "/dev/null" WriteMode
49− t <- terminal_with_name_and_io term_name null_out
50− putStrLn $ "context color count: " ++ show (context_color_count t)
51− reserve_display t
52− dc <- display_context t (DisplayRegion 100 100)
53− -- always show the cursor to produce tests for terminals with no cursor support.
54− let pic = (pic_for_image i) { pic_cursor = Cursor 0 0 }
55− output_picture dc pic
56− set_cursor_pos t 0 0
57− when (supports_cursor_visibility t) $ do
58− hide_cursor t
59− show_cursor t
60− release_display t
61− release_terminal t
62− return succeeded
63−
test/VerifyUsingMockInput.hs+164−135
@@ -1,29 +1,29 @@
1{-# LANGUAGE MultiParamTypeClasses #-}
1{-# LANGUAGE MultiParamTypeClasses #-}
2{-# LANGUAGE FlexibleInstances #-}
2{-# LANGUAGE FlexibleInstances #-}
3−{- We setup the environment to envoke certain terminals of interest.
3+{-# LANGUAGE ScopedTypeVariables #-}
4− - This assumes appropriate definitions exist in the current environment for the terminals of
4+-- Generate some input bytes and delays between blocks of input bytes.
5− - interest.
5+-- Verify the events produced are as expected.
6− -}
7module Main where
6module Main where
8
7
9−import Verify.Graphics.Vty.Terminal
8+import Verify.Graphics.Vty.Output
10−
11−import Data.List (intersperse, permutations)
12
9
13import Graphics.Vty hiding (resize)
10import Graphics.Vty hiding (resize)
14−import Graphics.Vty.Input.Data
11+import Graphics.Vty.Input.Events
15−import Graphics.Vty.Input.Internal
12+import Graphics.Vty.Input.Loop
16import Graphics.Vty.Input.Terminfo
13import Graphics.Vty.Input.Terminfo
17
14
18−import Control.Applicative
19import Control.Concurrent
15import Control.Concurrent
16+import Control.Concurrent.STM
20import Control.Exception
17import Control.Exception
18+import Lens.Micro ((^.))
21import Control.Monad
19import Control.Monad
22
20
23import Data.IORef
21import Data.IORef
22+import Data.List (intersperse, reverse, nubBy)
24
23
25import System.Console.Terminfo
24import System.Console.Terminfo
26import System.Posix.IO
25import System.Posix.IO
26+import System.Posix.Terminal (openPseudoTerminal)
27import System.Posix.Types
27import System.Posix.Types
28import System.Timeout
28import System.Timeout
29
29
@@ -32,104 +32,113 @@ import Test.Framework
32import Test.SmallCheck
32import Test.SmallCheck
33import Test.SmallCheck.Series
33import Test.SmallCheck.Series
34
34
35+import Text.Printf
36+
35−-- processing a block of 16 chars is the largest I can do without taking too long to run the test.
37+-- processing a block of 16 chars is the largest I can do without taking
36−max_block_size :: Int
38+-- too long to run the test.
37−max_block_size = 16
39+maxBlockSize :: Int
40+maxBlockSize = 16
41+
42+maxTableSize :: Int
43+maxTableSize = 28
38
44
39forEachOf :: (Show a, Testable m b) => [a] -> (a -> b) -> Property m
45forEachOf :: (Show a, Testable m b) => [a] -> (a -> b) -> Property m
40forEachOf l = over (generate (\n -> take n l))
46forEachOf l = over (generate (\n -> take n l))
41
47
42data InputEvent
48data InputEvent
49+ = Bytes String
43− = Bytes String -- | input sequence encoded as a string. Regardless, the input is read a byte at a time.
50+ -- ^ Input sequence encoded as a string. Regardless, the input is
51+ -- read a byte at a time.
44− | Delay Int -- | microsecond delay
52+ | Delay Int
53+ -- ^ Microsecond delay
45 deriving Show
54 deriving Show
46
55
47type InputSpec = [InputEvent]
56type InputSpec = [InputEvent]
48
57
49type ExpectedSpec = [Event]
58type ExpectedSpec = [Event]
50
59
51−synthesize_input :: InputSpec -> Fd -> IO ()
60+synthesizeInput :: InputSpec -> Fd -> IO ()
52−synthesize_input input out_handle = forM_ input f >> (void $ fdWrite out_handle "\xFFFD")
61+synthesizeInput input outHandle = forM_ input f >> (void $ fdWrite outHandle "\xFFFD")
53 where
62 where
54− f (Bytes str) = void $ fdWrite out_handle str
63+ f (Bytes str) = void $ fdWrite outHandle str
55 f (Delay t) = threadDelay t
64 f (Delay t) = threadDelay t
56
65
57−min_detectable_delay :: Int
66+minDetectableDelay :: Int
58−min_detectable_delay = 1000
67+minDetectableDelay = 4000
59
68
60−min_timout :: Int
69+minTimout :: Int
61−min_timout = 4000000
70+minTimout = 4000000
62
71
63−test_key_delay :: Int
72+testKeyDelay :: Int
64−test_key_delay = min_detectable_delay * 4
73+testKeyDelay = minDetectableDelay * 4
65
74
66−test_esc_sample_delay :: Int
75+testEscSampleDelay :: Int
67−test_esc_sample_delay = min_detectable_delay * 2
76+testEscSampleDelay = minDetectableDelay * 2
68
77
69−gen_events_using_io_actions :: Int -> IO () -> IO () -> IO ()
78+genEventsUsingIoActions :: Int -> IO () -> IO () -> IO ()
70−gen_events_using_io_actions max_duration input_action output_action = do
79+genEventsUsingIoActions maxDuration inputAction outputAction = do
71− let max_duration' = max min_timout max_duration
80+ let maxDuration' = max minTimout maxDuration
72− read_complete <- newEmptyMVar
81+ readComplete <- newEmptyMVar
73− write_complete <- newEmptyMVar
82+ writeComplete <- newEmptyMVar
74− _ <- forkIO $ input_action `finally` putMVar write_complete ()
83+ _ <- forkOS $ inputAction `finally` putMVar writeComplete ()
75− _ <- forkIO $ output_action `finally` putMVar read_complete ()
84+ _ <- forkOS $ outputAction `finally` putMVar readComplete ()
76− Just () <- timeout max_duration' $ takeMVar write_complete
85+ Just () <- timeout maxDuration' $ takeMVar writeComplete
77− Just () <- timeout max_duration' $ takeMVar read_complete
86+ Just () <- timeout maxDuration' $ takeMVar readComplete
78 return ()
87 return ()
79
88
89+compareEvents :: (Show a1, Show a, Eq a1) => a -> [a1] -> [a1] -> IO Bool
90+compareEvents inputSpec expectedEvents outEvents = compareEvents' expectedEvents outEvents
91+ where
92+ compareEvents' [] [] = return True
93+ compareEvents' [] outEvents' = do
94+ printf "extra events %s\n" (show outEvents') :: IO ()
95+ return False
96+ compareEvents' expectedEvents' [] = do
97+ printf "events %s were not produced for input %s\n" (show expectedEvents') (show inputSpec) :: IO ()
98+ printf "expected events %s\n" (show expectedEvents) :: IO ()
99+ printf "received events %s\n" (show outEvents) :: IO ()
100+ return False
101+ compareEvents' (e : expectedEvents') (o : outEvents')
102+ | e == o = compareEvents' expectedEvents' outEvents'
103+ | otherwise = do
104+ printf "%s expected not %s for input %s\n" (show e) (show o) (show inputSpec) :: IO ()
105+ printf "expected events %s\n" (show expectedEvents) :: IO ()
106+ printf "received events %s\n" (show outEvents) :: IO ()
107+ return False
108+
80−assert_events_from_syn_input :: ClassifyTable -> InputSpec -> ExpectedSpec -> IO Bool
109+assertEventsFromSynInput :: ClassifyMap -> InputSpec -> ExpectedSpec -> IO Bool
81−assert_events_from_syn_input table input_spec expected_events = do
110+assertEventsFromSynInput table inputSpec expectedEvents = do
82− let max_duration = sum [t | Delay t <- input_spec] + min_detectable_delay
111+ let maxDuration = sum [t | Delay t <- inputSpec] + minDetectableDelay
83− event_count = length expected_events
112+ eventCount = length expectedEvents
84− -- create pipe
113+ (writeFd, readFd) <- openPseudoTerminal
85− (output_fd, input_fd) <- createPipe
114+ (setTermAttr,_) <- attributeControl readFd
86− (output, shutdown_input) <- initInputForFd test_esc_sample_delay table output_fd
115+ setTermAttr
116+ let testConfig = defaultConfig { inputFd = Just readFd
117+ , termName = Just "dummy"
118+ , vmin = Just 1
119+ , vtime = Just 100
120+ }
121+ input <- initInput testConfig table
87− events_ref <- newIORef []
122+ eventsRef <- newIORef []
88− let write_wait_close = do
123+ let writeWaitClose = do
89− synthesize_input input_spec input_fd
124+ synthesizeInput inputSpec writeFd
90− threadDelay min_detectable_delay
125+ threadDelay minDetectableDelay
91− shutdown_input
126+ shutdownInput input
92− threadDelay min_detectable_delay
127+ threadDelay minDetectableDelay
93− closeFd input_fd
128+ closeFd writeFd
94− closeFd output_fd
129+ closeFd readFd
95 -- drain output pipe
130 -- drain output pipe
96− let read_all_events = read_loop event_count
131+ let readEvents = readLoop eventCount
97− read_loop 0 = return ()
132+ readLoop 0 = return ()
98− read_loop n = do
133+ readLoop n = do
99− e <- readChan output
134+ e <- atomically $ readTChan $ input^.eventChannel
100− modifyIORef events_ref ((:) e)
135+ case e of
101− read_loop (n - 1)
136+ InputEvent ev -> modifyIORef eventsRef ((:) ev)
102− gen_events_using_io_actions max_duration write_wait_close read_all_events
137+ ResumeAfterSignal -> return ()
138+ readLoop (n - 1)
139+ genEventsUsingIoActions maxDuration writeWaitClose readEvents
103− out_events <- reverse <$> readIORef events_ref
140+ outEvents <- reverse <$> readIORef eventsRef
104− print (input_spec, expected_events, out_events)
141+ compareEvents inputSpec expectedEvents outEvents
105− return $ out_events == expected_events
106−
107−assert_events_from_input_block :: ClassifyTable -> InputSpec -> ExpectedSpec -> IO Bool
108−assert_events_from_input_block table input_spec expected_events = do
109− let classifier = classify table
110− max_duration = sum [t | Delay t <- input_spec] + min_detectable_delay
111− input <- newChan
112− output <- newChan
113− gen_events_using_io_actions
114− max_duration
115− (write_input_spec_to_chan input_spec input)
116− (inputToEventThread classifier input output)
117− -- TODO: use STM TChan?
118− -- assures reading "length expected_events" from the channel does not block.
119− let min_event_count = length expected_events
120− writeList2Chan output $ replicate min_event_count undefined
121− out_events <- take min_event_count <$> getChanContents output
122− return $ out_events == expected_events
123−
124−write_input_spec_to_chan :: InputSpec -> Chan Char -> IO ()
125−write_input_spec_to_chan [] chan = do
126− writeChan chan '\xFFFD'
127−write_input_spec_to_chan (Bytes str : input_spec') chan = do
128− writeList2Chan chan str
129− write_input_spec_to_chan input_spec' chan
130−write_input_spec_to_chan (Delay _t : input_spec') chan = do
131− writeChan chan '\xFFFE'
132− write_input_spec_to_chan input_spec' chan
133
142
134newtype InputBlocksUsingTable event
143newtype InputBlocksUsingTable event
135 = InputBlocksUsingTable ([(String,event)] -> [(String, event)])
144 = InputBlocksUsingTable ([(String,event)] -> [(String, event)])
@@ -139,56 +148,76 @@ instance Show (InputBlocksUsingTable event) where
139
148
140instance Monad m => Serial m (InputBlocksUsingTable event) where
149instance Monad m => Serial m (InputBlocksUsingTable event) where
141 series = do
150 series = do
142− n :: Int <- localDepth (max max_block_size) series -- what elements to select from the table
151+ n :: Int <- localDepth (const maxTableSize) series
143− return $ InputBlocksUsingTable $ \table -> concat (take n (permutations table))
152+ return $ InputBlocksUsingTable $ \raw_table ->
144−
153+ let table = reverse $ nubBy (\(s0,_) (s1,_) -> s0 == s1) $ reverse raw_table
145−verify_simple_input_block_to_event :: Property IO
154+ in concat (take n (selections table))
155+ where
156+ selections [] = []
157+ selections (x:xs) = let z = selections xs in [x] : (z ++ map ((:) x) z)
158+
159+verifyVisibleSynInputToEvent :: Property IO
146−verify_simple_input_block_to_event = forAll $ \(InputBlocksUsingTable gen) -> do
160+verifyVisibleSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) -> monadic $ do
147− let input_seq = gen simple_chars
161+ let table = visibleChars
148− input = Bytes $ concat [s | (s,_) <- input_seq]
162+ inputSeq = gen table
149− events = [e | (_,(k,ms)) <- input_seq, let e = EvKey k ms]
163+ events = map snd inputSeq
150− monadic $ assert_events_from_input_block simple_chars [input] events
164+ keydowns = map (Bytes . fst) inputSeq
151−
165+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
152−verify_keys_from_caps_table_block_to_event :: Property IO
166+ assertEventsFromSynInput universalTable input events
167+
168+verifyCapsSynInputToEvent :: Property IO
153−verify_keys_from_caps_table_block_to_event = forAll $ \(InputBlocksUsingTable gen) ->
169+verifyCapsSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
154− forEachOf terminals_of_interest $ \term_name -> monadic $ do
170+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
155− term <- setupTerm term_name
171+ term <- setupTerm terminalName
156− let table = caps_classify_table term keys_from_caps_table
172+ let table = capsClassifyMap term keysFromCapsTable
157− input_seq = gen table
173+ inputSeq = gen table
158− events = [e | (_,e) <- input_seq]
174+ events = map snd inputSeq
159− keydowns = [Bytes s | (s,_) <- input_seq]
175+ keydowns = map (Bytes . fst) inputSeq
160− input = intersperse (Delay test_key_delay) keydowns
176+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
161− assert_events_from_input_block (map_to_legacy_table table) input events
177+ assertEventsFromSynInput table input events
162−
178+
163−verify_simple_syn_input_to_event :: Property IO
179+verifySpecialSynInputToEvent :: Property IO
180+verifySpecialSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) -> monadic $ do
181+ let table = specialSupportKeys
182+ inputSeq = gen table
183+ events = map snd inputSeq
184+ keydowns = map (Bytes . fst) inputSeq
185+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
186+ assertEventsFromSynInput universalTable input events
187+
188+verifyFullSynInputToEvent :: Property IO
164−verify_simple_syn_input_to_event = forAll $ \(InputBlocksUsingTable gen) ->
189+verifyFullSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
165− forEachOf terminals_of_interest $ \term_name -> monadic $ do
190+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
166− term <- setupTerm term_name
191+ term <- setupTerm terminalName
167− let table = caps_classify_table term keys_from_caps_table
192+ let table = classifyMapForTerm terminalName term
168− input_seq = gen table
193+ inputSeq = gen table
169− events = [e | (_,e) <- input_seq]
194+ events = map snd inputSeq
170− keydowns = [Bytes s | (s,_) <- input_seq]
195+ keydowns = map (Bytes . fst) inputSeq
171− input = intersperse (Delay test_key_delay) keydowns
196+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
172− assert_events_from_syn_input (map_to_legacy_table table) input events
197+ assertEventsFromSynInput table input events
173−
198+
174−verify_fkeys_syn_input_to_event :: Property IO
199+verifyFullSynInputToEvent_2x :: Property IO
175−verify_fkeys_syn_input_to_event = forAll $ \(InputBlocksUsingTable gen) -> monadic $ do
200+verifyFullSynInputToEvent_2x = forAll $ \(InputBlocksUsingTable gen) ->
176− let table = function_keys_0
201+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
202+ term <- setupTerm terminalName
203+ let table = classifyMapForTerm terminalName term
177− input_seq = gen table
204+ inputSeq = gen table
178− events = [EvKey k ms | (_,(k,ms)) <- input_seq]
205+ events = concatMap ((\s -> [s,s]) . snd) inputSeq
179− keydowns = [Bytes s | (s,_) <- input_seq]
206+ keydowns = map (Bytes . (\s -> s ++ s) . fst) inputSeq
180− input = intersperse (Delay test_key_delay) keydowns
207+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
181− assert_events_from_syn_input (concat ansi_classify_table) input events
208+ assertEventsFromSynInput table input events
182
209
183main :: IO ()
210main :: IO ()
184main = defaultMain
211main = defaultMain
185− [ testProperty "basic block generated from a single ansi chars to event translation"
212+ [ testProperty "synthesized typing of single visible chars translates to expected events"
186− verify_simple_input_block_to_event
213+ verifyVisibleSynInputToEvent
187− , testProperty "key sequences read from caps table map to expected events"
214+ , testProperty "synthesized typing of keys from capabilities tables translates to expected events"
188− verify_keys_from_caps_table_block_to_event
215+ verifyCapsSynInputToEvent
189− , testProperty "synthesized typing from basic blocks translates to expected events"
216+ , testProperty "synthesized typing of hard coded special keys translates to expected events"
190− verify_simple_syn_input_to_event
217+ verifySpecialSynInputToEvent
218+ , testProperty "synthesized typing of any key in the table translates to its paired event"
219+ verifyFullSynInputToEvent
191− , testProperty "synthesized typing from function keys translates to expected events"
220+ , testProperty "synthesized typing of 2x any key in the table translates to 2x paired event"
192− verify_fkeys_syn_input_to_event
221+ verifyFullSynInputToEvent_2x
193 ]
222 ]
194
223
test/VerifyUsingMockTerminal.hs+83−74
@@ -1,12 +1,15 @@
1module VerifyUsingMockTerminal where
1module VerifyUsingMockTerminal where
2
2
3−import Verify.Graphics.Vty.DisplayRegion
3+import Verify.Graphics.Vty.Prelude
4+
4import Verify.Graphics.Vty.Picture
5import Verify.Graphics.Vty.Picture
5import Verify.Graphics.Vty.Image
6import Verify.Graphics.Vty.Image
7+import Verify.Graphics.Vty.Attributes
6import Verify.Graphics.Vty.Span
8import Verify.Graphics.Vty.Span
7−import Verify.Graphics.Vty.Terminal
9+import Verify.Graphics.Vty.Output
10+import Graphics.Vty.Output
8−import Graphics.Vty.Terminal
11+import Graphics.Vty.Output.Interface
9−import Graphics.Vty.Terminal.Mock
12+import Graphics.Vty.Output.Mock
10
13
11import Graphics.Vty.Debug
14import Graphics.Vty.Debug
12
15
@@ -18,82 +21,88 @@ import qualified Data.String.UTF8 as UTF8
18
21
19import System.IO
22import System.IO
20
23
21−unit_image_unit_bounds :: UnitImage -> Property
24+unitImageUnitBounds :: UnitImage -> Property
22−unit_image_unit_bounds (UnitImage _ i) = liftIOResult $ do
25+unitImageUnitBounds (UnitImage _ i) = liftIOResult $ do
23− (_,t) <- mock_terminal (DisplayRegion 1 1)
26+ (_,t) <- mockTerminal (1,1)
24− dc <- display_bounds t >>= display_context t
27+ dc <- displayBounds t >>= displayContext t
25− let pic = pic_for_image i
28+ let pic = picForImage i
26− output_picture dc pic
29+ outputPicture dc pic
27 return succeeded
30 return succeeded
28
31
29−unit_image_arb_bounds :: UnitImage -> MockWindow -> Property
32+unitImageArbBounds :: UnitImage -> MockWindow -> Property
30−unit_image_arb_bounds (UnitImage _ i) (MockWindow w h) = liftIOResult $ do
33+unitImageArbBounds (UnitImage _ i) (MockWindow w h) = liftIOResult $ do
31− (_,t) <- mock_terminal (DisplayRegion w h)
34+ (_,t) <- mockTerminal (w,h)
32− dc <- display_bounds t >>= display_context t
35+ dc <- displayBounds t >>= displayContext t
33− let pic = pic_for_image i
36+ let pic = picForImage i
34− output_picture dc pic
37+ outputPicture dc pic
35 return succeeded
38 return succeeded
36
39
37−single_T_row :: MockWindow -> Property
40+singleTRow :: MockWindow -> Property
38−single_T_row (MockWindow w h) = liftIOResult $ do
41+singleTRow (MockWindow w h) = liftIOResult $ do
39− (mock_data,t) <- mock_terminal (DisplayRegion w h)
42+ (mockData,t) <- mockTerminal (w,h)
40− dc <- display_bounds t >>= display_context t
43+ dc <- displayBounds t >>= displayContext t
41− -- create an image that contains just the character T repeated for a single row
44+ -- create an image that contains just the character T repeated for a
45+ -- single row
42− let i = horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
46+ let i = horizCat $ replicate (fromEnum w) (char defAttr 'T')
43− pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
47+ pic = (picForImage i) { picBackground = Background 'B' defAttr }
44− output_picture dc pic
48+ outputPicture dc pic
45− -- The mock output string that represents the output bytes a single line containing the T
49+ -- The mock output string that represents the output bytes a single
50+ -- line containing the T string: Followed by h - 1 lines of a change
46− -- string: Followed by h - 1 lines of a change to the background attribute and then the
51+ -- to the background attribute and then the background character
47− -- background character
48− let expected = "HD" ++ "MA" ++ replicate (fromEnum w) 'T'
52+ let expected = "H" ++ "MDA" ++ replicate (fromEnum w) 'T'
49− ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')
53+ ++ concat (replicate (fromEnum h - 1) $ "MDA" ++ replicate (fromEnum w) 'B')
50− compare_mock_output mock_data expected
54+ compareMockOutput mockData expected
51−
55+
52−many_T_rows :: MockWindow -> Property
56+manyTRows :: MockWindow -> Property
53−many_T_rows (MockWindow w h) = liftIOResult $ do
57+manyTRows (MockWindow w h) = liftIOResult $ do
54− (mock_data, t) <- mock_terminal (DisplayRegion w h)
58+ (mockData, t) <- mockTerminal (w,h)
55− dc <- display_bounds t >>= display_context t
59+ dc <- displayBounds t >>= displayContext t
56− -- create an image that contains the character 'T' repeated for all the rows
60+ -- create an image that contains the character 'T' repeated for all
61+ -- the rows
57− let i = vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
62+ let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')
58− pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
63+ pic = (picForImage i) { picBackground = Background 'B' defAttr }
59− output_picture dc pic
64+ outputPicture dc pic
60− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
65+ -- The UTF8 string that represents the output bytes is h repeats of
61− -- attribute change. 'A', followed by w 'T's
66+ -- a move, 'M', followed by an attribute change. 'A', followed by w
67+ -- 'T's
62− let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
68+ let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
63− compare_mock_output mock_data expected
69+ compareMockOutput mockData expected
64
70
65−many_T_rows_cropped_width :: MockWindow -> Property
71+manyTRowsCroppedWidth :: MockWindow -> Property
66−many_T_rows_cropped_width (MockWindow w h) = liftIOResult $ do
72+manyTRowsCroppedWidth (MockWindow w h) = liftIOResult $ do
67− (mock_data,t) <- mock_terminal (DisplayRegion w h)
73+ (mockData,t) <- mockTerminal (w,h)
68− dc <- display_bounds t >>= display_context t
74+ dc <- displayBounds t >>= displayContext t
69− -- create an image that contains the character 'T' repeated for all the rows
75+ -- create an image that contains the character 'T' repeated for all
76+ -- the rows
70− let i = vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w * 2) (char def_attr 'T')
77+ let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w * 2) (char defAttr 'T')
71− pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
78+ pic = (picForImage i) { picBackground = Background 'B' defAttr }
72− output_picture dc pic
79+ outputPicture dc pic
73− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
80+ -- The UTF8 string that represents the output bytes is h repeats of
74− -- attribute change. 'A', followed by w 'T's
81+ -- a move, 'M', followed by an attribute change. 'A', followed by w
82+ -- 'T's
75− let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
83+ let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
76− compare_mock_output mock_data expected
84+ compareMockOutput mockData expected
77
85
78−many_T_rows_cropped_height :: MockWindow -> Property
86+manyTRowsCroppedHeight :: MockWindow -> Property
79−many_T_rows_cropped_height (MockWindow w h) = liftIOResult $ do
87+manyTRowsCroppedHeight (MockWindow w h) = liftIOResult $ do
80− (mock_data,t) <- mock_terminal (DisplayRegion w h)
88+ (mockData,t) <- mockTerminal (w,h)
81− dc <- display_bounds t >>= display_context t
89+ dc <- displayBounds t >>= displayContext t
82− -- create an image that contains the character 'T' repeated for all the rows
90+ -- create an image that contains the character 'T' repeated for all
91+ -- the rows
83− let i = vert_cat $ replicate (fromEnum h * 2) $ horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
92+ let i = vertCat $ replicate (fromEnum h * 2) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')
84− pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
93+ pic = (picForImage i) { picBackground = Background 'B' defAttr }
85− output_picture dc pic
94+ outputPicture dc pic
86− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
95+ -- The UTF8 string that represents the output bytes is h repeats of
87− -- attribute change. 'A', followed by w count 'T's
96+ -- a move, 'M', followed by an attribute change. 'A', followed by w
97+ -- count 'T's
88− let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
98+ let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
89− compare_mock_output mock_data expected
99+ compareMockOutput mockData expected
90
100
91tests :: IO [Test]
101tests :: IO [Test]
92−tests = return [ verify "unit_image_unit_bounds" unit_image_unit_bounds
102+tests = return [ verify "unitImageUnitBounds" unitImageUnitBounds
93− , verify "unit_image_arb_bounds" unit_image_arb_bounds
103+ , verify "unitImageArbBounds" unitImageArbBounds
94− , verify "single_T_row" single_T_row
104+ , verify "singleTRow" singleTRow
95− , verify "many_T_rows" many_T_rows
105+ , verify "manyTRows" manyTRows
96− , verify "many_T_rows_cropped_width" many_T_rows_cropped_width
106+ , verify "manyTRowsCroppedWidth" manyTRowsCroppedWidth
97− , verify "many_T_rows_cropped_height" many_T_rows_cropped_height
107+ , verify "manyTRowsCroppedHeight" manyTRowsCroppedHeight
98 ]
108 ]
99−
test/VerifyUtf8Width.hs+15−13
@@ -1,28 +1,30 @@
1module VerifyUtf8Width where
1module VerifyUtf8Width where
2+
2import Verify
3import Verify
3
4
4import Graphics.Text.Width
5import Graphics.Text.Width
5import Graphics.Vty.Attributes
6import Graphics.Vty.Attributes
6import Graphics.Vty.Picture
7import Graphics.Vty.Picture
8+import Graphics.Vty.Image
7
9
8−sw_is_1_column :: SingleColumnChar -> Bool
10+swIs1Column :: SingleColumnChar -> Bool
9−sw_is_1_column (SingleColumnChar c) = image_width (char def_attr c) == 1
11+swIs1Column (SingleColumnChar c) = imageWidth (char defAttr c) == 1
10
12
11−dw_is_2_column :: DoubleColumnChar -> Bool
13+dwIs2Column :: DoubleColumnChar -> Bool
12−dw_is_2_column (DoubleColumnChar c) = image_width (char def_attr c) == 2
14+dwIs2Column (DoubleColumnChar c) = imageWidth (char defAttr c) == 2
13
15
14−dc_string_is_even :: NonEmptyList DoubleColumnChar -> Bool
16+dcStringIsEven :: NonEmptyList DoubleColumnChar -> Bool
15−dc_string_is_even (NonEmpty dw_list) =
17+dcStringIsEven (NonEmpty dw_list) =
16− even $ safe_wcswidth [ c | DoubleColumnChar c <- dw_list ]
18+ even $ safeWcswidth [ c | DoubleColumnChar c <- dw_list ]
17
19
18−safe_wcwidth_for_control_chars :: Bool
20+safeWcwidthForControlChars :: Bool
19−safe_wcwidth_for_control_chars = 0 == safe_wcwidth '\NUL'
21+safeWcwidthForControlChars = 0 == safeWcwidth '\NUL'
20
22
21tests :: IO [Test]
23tests :: IO [Test]
22tests = return
24tests = return
23− [ verify "sw_is_1_column" sw_is_1_column
25+ [ verify "swIs1Column" swIs1Column
24− , verify "dw_is_2_column" dw_is_2_column
26+ , verify "dwIs2Column" dwIs2Column
25− , verify "a string of double characters is an even width" dc_string_is_even
27+ , verify "a string of double characters is an even width" dcStringIsEven
26− , verify "safe_wcwidth provides a width of 0 for chars without widths" safe_wcwidth_for_control_chars
28+ , verify "safeWcwidth provides a width of 0 for chars without widths" safeWcwidthForControlChars
27 ]
29 ]
28
30
test/yi_issue_264.hs → test/YiIssue264.hsrenamed+1
@@ -1,4 +1,5 @@
1module Main where
1module Main where
2+
2import Graphics.Vty
3import Graphics.Vty
3import Control.Exception
4import Control.Exception
4
5
test/benchmark.hs+21−21
@@ -20,11 +20,11 @@ import Verify
20
20
21main = do
21main = do
22 args <- getArgs
22 args <- getArgs
23− let benches = [ ("no-diff-opt-0", BenchNoDiffOpt.bench_0)
23+ let benches = [ ("no-diff-opt-0", BenchNoDiffOpt.bench0)
24− , ("render-char-0", BenchRenderChar.bench_0)
24+ , ("render-char-0", BenchRenderChar.bench0)
25− , ("render-char-1", BenchRenderChar.bench_1)
25+ , ("render-char-1", BenchRenderChar.bench1)
26− , ("vertical-scroll-0", BenchVerticalScroll.bench_0)
26+ , ("vertical-scroll-0", BenchVerticalScroll.bench0)
27− , ("image-fuzz-0", BenchImageFuzz.bench_0) ]
27+ , ("image-fuzz-0", BenchImageFuzz.bench0) ]
28 help = forM_ benches $ \(b,_) -> putStrLn $ "--" ++ b
28 help = forM_ benches $ \(b,_) -> putStrLn $ "--" ++ b
29 case args of
29 case args of
30 ["--help"] -> help
30 ["--help"] -> help
@@ -34,23 +34,23 @@ main = do
34 then map (drop 2) args
34 then map (drop 2) args
35 else map fst benches
35 else map fst benches
36 -- drop the dash-dash "--"
36 -- drop the dash-dash "--"
37− results <- forM args' $ \b_name -> do
37+ results <- forM args' $ \bName -> do
38− case lookup b_name benches of
38+ case lookup bName benches of
39− Just b -> bench b_name b
39+ Just b -> bench bName b
40− Nothing -> fail $ "No benchmark named " ++ b_name
40+ Nothing -> fail $ "No benchmark named " ++ bName
41 print results
41 print results
42 return ()
42 return ()
43
43
44−bench b_name b = do
44+bench bName b = do
45− putStrLn $ "starting " ++ b_name
45+ putStrLn $ "starting " ++ bName
46− Bench b_data_gen b_proc <- b
46+ Bench bDataGen bProc <- b
47− b_data <- b_data_gen
47+ bData <- bDataGen
48− start_times <- b_data `deepseq` getProcessTimes
48+ startTimes <- bData `deepseq` getProcessTimes
49− b_proc b_data
49+ bProc bData
50− end_times <- getProcessTimes
50+ endTimes <- getProcessTimes
51− let user_time = userTime end_times - userTime start_times
51+ let ut = userTime endTimes - userTime startTimes
52− system_time = systemTime end_times - systemTime start_times
52+ st = systemTime endTimes - systemTime startTimes
53− putStrLn $ "user time: " ++ show user_time
53+ putStrLn $ "user time: " ++ show ut
54− putStrLn $ "system time: " ++ show system_time
54+ putStrLn $ "system time: " ++ show st
55− return (b_name, user_time, system_time)
55+ return (bName, ut, st)
56
56
test/current_benchmark_results.txt+28−4
@@ -1,7 +1,30 @@
1+post-blaze-builder
2+Phenom II X4, GHC 7.6.3 on linux, Konsole
3+ [("no-diff-opt-0",81,6),("render-char-0",318,10),("render-char-1",126,9),("vertical-scroll-0",745,39),("image-fuzz-0",182,7)]
4+ [("no-diff-opt-0",94,6),("render-char-0",322,10),("render-char-1",137,7),("vertical-scroll-0",770,36),("image-fuzz-0",186,8)]
5+
6+2 ghz core i7, GHC 7.6.3 on Mac using iTerm
7+ [("no-diff-opt-0",30,3),("render-char-0",118,8),("render-char-1",49,5),("vertical-scroll-0",307,27),("image-fuzz-0",194,13)]
8+ [("no-diff-opt-0",29,3),("render-char-0",119,9),("render-char-1",49,5),("vertical-scroll-0",307,27),("image-fuzz-0",192,13)]
9+
10+2 ghz core i7. GHC 7.6.3 on NixOS over screen + ssh using Mac iTerm
11+ [("no-diff-opt-0",18,26),("render-char-0",93,140),("render-char-1",26,46),("vertical-scroll-0",190,279),("image-fuzz-0",117,107)]
12+
13+pre-blaze-builder
14+
15+Phenom II X4, GHC 7.6.3 on linux, Konsole
16+ [("no-diff-opt-0",79,6),("render-char-0",474,10),("render-char-1",154,12),("vertical-scroll-0",702,37),("image-fuzz-0",144,6)]
17+ [("no-diff-opt-0",76,6),("render-char-0",485,11),("render-char-1",152,13),("vertical-scroll-0",701,37),("image-fuzz-0",149,7)]
18+
19+2.4 ghz intel core duo. Mac iTerm
20+ [("no-diff-opt-0",43,9),("render-char-0",345,28),("render-char-1",76,14),("vertical-scroll-0",431,62),("image-fuzz-0",169,21)]
21+2 ghz core i7. NixOS over screen + ssh using Mac iTerm
22+ [("no-diff-opt-0",16,23),("render-char-0",119,182),("render-char-1",32,44),("vertical-scroll-0",164,245),("image-fuzz-0",94,75)]
23+
1no unclipped opt 5.0
24no unclipped opt 5.0
2on toast
25on toast
3−[("no-diff-opt-0",147,8),("render-char-0",332,18),("render-char-1",277,16),("vertical-scroll-0",1503,61),("image-fuzz-0",46,3)]
26+ [("no-diff-opt-0",147,8),("render-char-0",332,18),("render-char-1",277,16),("vertical-scroll-0",1503,61),("image-fuzz-0",46,3)]
4−[("no-diff-opt-0",137,11),("render-char-0",470,18),("render-char-1",254,18),("vertical-scroll-0",1240,56),("image-fuzz-0",46,3)]
27+ [("no-diff-opt-0",137,11),("render-char-0",470,18),("render-char-1",254,18),("vertical-scroll-0",1240,56),("image-fuzz-0",46,3)]
5
28
6on ooyala, no screen, iTerm:
29on ooyala, no screen, iTerm:
7
30
@@ -12,5 +35,6 @@ on ooyala, no screen, iTerm:
12 * [("no-diff-opt-0",26,3),("render-char-0",109,8),("render-char-1",44,5),("vertical-scroll-0",244,26),("image-fuzz-0",17,2)]
35 * [("no-diff-opt-0",26,3),("render-char-0",109,8),("render-char-1",44,5),("vertical-scroll-0",244,26),("image-fuzz-0",17,2)]
13 * [("no-diff-opt-0",25,4),("render-char-0",110,7),("render-char-1",43,6),("vertical-scroll-0",239,24),("image-fuzz-0",17,2)]
36 * [("no-diff-opt-0",25,4),("render-char-0",110,7),("render-char-1",43,6),("vertical-scroll-0",239,24),("image-fuzz-0",17,2)]
14
37
15−release 4.7 on toast
38+release 4.7
39+on toast
16−[("no-diff-opt-0",226,13),("render-char-0",411,22),("render-char-1",386,21),("vertical-scroll-0",1930,96),("image-fuzz-0",1225,52)]
40+ [("no-diff-opt-0",226,13),("render-char-0",411,22),("render-char-1",386,21),("vertical-scroll-0",1930,96),("image-fuzz-0",1225,52)]
test/interactive_terminal_test.hs+601−594
@@ -1,12 +1,12 @@
1{-# LANGUAGE QuasiQuotes #-}
1{-# LANGUAGE QuasiQuotes #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
2module Main where
3module Main where
3
4
4import Graphics.Vty
5import Graphics.Vty
5import Graphics.Vty.Attributes
6import Graphics.Vty.Attributes
6import Graphics.Vty.Inline
7import Graphics.Vty.Inline
7import Graphics.Vty.Picture
8import Graphics.Vty.Picture
8−import Graphics.Vty.Terminal
9+import Graphics.Vty.Output
9−import Graphics.Vty.DisplayRegion
10
10
11import Control.Concurrent (threadDelay)
11import Control.Concurrent (threadDelay)
12import Control.Exception
12import Control.Exception
@@ -18,18 +18,18 @@ import Data.Monoid
18import Data.String.QQ
18import Data.String.QQ
19import Data.Word
19import Data.Word
20
20
21−import Foreign.Marshal.Array
21+import Foreign.Marshal.Array
22
22
23import qualified System.Environment as Env
23import qualified System.Environment as Env
24
24
25import System.IO ( hFlush, hPutStr, hPutBuf, stdout )
25import System.IO ( hFlush, hPutStr, hPutBuf, stdout )
26
26
27main = do
27main = do
28− print_intro
28+ printIntro
29
29
30−output_file_path = "test_results.list"
30+outputFilePath = "test_results.list"
31
31
32−print_intro = do
32+printIntro = do
33 putStr $ [s|
33 putStr $ [s|
34This is an interactive verification program for the terminal input and output
34This is an interactive verification program for the terminal input and output
35support of the VTY library. This will ask a series of questions about what you
35support of the VTY library. This will ask a series of questions about what you
@@ -37,7 +37,7 @@ see on screen. The goal is to verify that VTY's output and input support
37performs as expected with your terminal.
37performs as expected with your terminal.
38
38
39This program produces a file named
39This program produces a file named
40− |] ++ output_file_path ++ [s|
40+ |] ++ outputFilePath ++ [s|
41in the current directory that contains the results for each test assertion. This
41in the current directory that contains the results for each test assertion. This
42can be emailed to coreyoconnor@gmail.com and used by the VTY authors to improve
42can be emailed to coreyoconnor@gmail.com and used by the VTY authors to improve
43support for your terminal. No personal information is contained in the report.
43support for your terminal. No personal information is contained in the report.
@@ -66,41 +66,40 @@ All the tests assume the following about the terminal display:
66Thanks for the help! :-D
66Thanks for the help! :-D
67To exit the test early enter "q" anytime at the following menu screen.
67To exit the test early enter "q" anytime at the following menu screen.
68
68
69−If any test failed then please post an issue to
69+If any test fails then please post an issue to
70 https://github.com/coreyoconnor/vty/issues
70 https://github.com/coreyoconnor/vty/issues
71−with the test_results.list file pasted into the issue. The issue summary can
71+with the test_results.list file pasted into the issue. A suitable summary is:
72−mention the specific tests that failed or just say "interactive terminal test
72+"interactive terminal test failure".
73−failure".
74|]
73|]
75− wait_for_return
74+ waitForReturn
76− results <- do_test_menu 1
75+ results <- doTestMenu 1
77− env_attributes <- mapM ( \env_name -> Control.Exception.catch ( Env.getEnv env_name >>= return . (,) env_name )
76+ envAttributes <- mapM ( \envName -> Control.Exception.catch ( Env.getEnv envName >>= return . (,) envName )
78− ( \ (_ :: SomeException) -> return (env_name, "") )
77+ ( \ (_ :: SomeException) -> return (envName, "") )
79− )
78+ )
80− [ "TERM", "COLORTERM", "LANG", "TERM_PROGRAM", "XTERM_VERSION" ]
79+ [ "TERM", "COLORTERM", "LANG", "TERM_PROGRAM", "XTERM_VERSION" ]
81− t <- current_terminal
80+ t <- standardIOConfig >>= outputForConfig
82− let results_txt = show env_attributes ++ "\n"
81+ let resultsTxt = show envAttributes ++ "\n"
83− ++ terminal_ID t ++ "\n"
82+ ++ terminalID t ++ "\n"
84− ++ show results ++ "\n"
83+ ++ show results ++ "\n"
85− release_terminal t
84+ releaseTerminal t
86− writeFile output_file_path results_txt
85+ writeFile outputFilePath resultsTxt
87−
86+
88−wait_for_return = do
87+waitForReturn = do
89 putStr "\n(press return to continue)"
88 putStr "\n(press return to continue)"
90 hFlush stdout
89 hFlush stdout
91 getLine
90 getLine
92
91
93−test_menu :: [(String, Test)]
92+testMenu :: [(String, Test)]
94−test_menu = zip (map show [1..]) all_tests
93+testMenu = zip (map show [1..]) allTests
95
94
96−do_test_menu :: Int -> IO [(String, Bool)]
95+doTestMenu :: Int -> IO [(String, Bool)]
97−do_test_menu next_ID
96+doTestMenu nextID
98− | next_ID > length all_tests = do
97+ | nextID > length allTests = do
99− putStrLn $ "Done! Please email the " ++ output_file_path ++ " file to coreyoconnor@gmail.com"
98+ putStrLn $ "Done! Please email the " ++ outputFilePath ++ " file to coreyoconnor@gmail.com"
100 return []
99 return []
101 | otherwise = do
100 | otherwise = do
102− display_test_menu
101+ displayTestMenu
103− putStrLn $ "Press return to start with #" ++ show next_ID ++ "."
102+ putStrLn $ "Press return to start with #" ++ show nextID ++ "."
104 putStrLn "Enter a test number to perform only that test."
103 putStrLn "Enter a test number to perform only that test."
105 putStrLn "q (or control-C) to quit."
104 putStrLn "q (or control-C) to quit."
106 putStr "> "
105 putStr "> "
@@ -108,102 +107,102 @@ do_test_menu next_ID
108 s <- getLine >>= return . filter (/= '\n')
107 s <- getLine >>= return . filter (/= '\n')
109 case s of
108 case s of
110 "q" -> return mempty
109 "q" -> return mempty
111− "" -> do
110+ "" -> do
112− r <- run_test $ show next_ID
111+ r <- runTest $ show nextID
113− rs <- do_test_menu ( next_ID + 1 )
112+ rs <- doTestMenu ( nextID + 1 )
114 return $ r : rs
113 return $ r : rs
115− i | isJust ( lookup i test_menu ) -> do
114+ i | isJust ( lookup i testMenu ) -> do
116− r <- run_test i
115+ r <- runTest i
117− rs <- do_test_menu ( read i + 1 )
116+ rs <- doTestMenu ( read i + 1 )
118 return $ r : rs
117 return $ r : rs
119 where
118 where
120− display_test_menu
119+ displayTestMenu
121− = mapM_ display_test_menu' test_menu
120+ = mapM_ displayTestMenu' testMenu
122− display_test_menu' ( i, t )
121+ displayTestMenu' ( i, t )
123− = putStrLn $ ( if i == show next_ID
122+ = putStrLn $ ( if i == show nextID
124− then "> "
123+ then "> "
125 else " "
124 else " "
126− ) ++ i ++ ". " ++ test_name t
125+ ) ++ i ++ ". " ++ testName t
127−
126+
128−run_test :: String -> IO (String, Bool)
127+runTest :: String -> IO (String, Bool)
129−run_test i = do
128+runTest i = do
130− let t = fromJust $ lookup i test_menu
129+ let t = fromJust $ lookup i testMenu
131− print_summary t
130+ printSummary t
132− wait_for_return
131+ waitForReturn
133− test_action t
132+ testAction t
134− r <- confirm_results t
133+ r <- confirmResults t
135− return (test_ID t, r)
134+ return (testID t, r)
136−
135+
137−default_success_confirm_results = do
136+defaultSuccessConfirmResults = do
138 putStr "\n"
137 putStr "\n"
139 putStr "[Y/n] "
138 putStr "[Y/n] "
140 hFlush stdout
139 hFlush stdout
141 r <- getLine
140 r <- getLine
142 case r of
141 case r of
143− "" -> return True
142+ "" -> return True
144 "y" -> return True
143 "y" -> return True
145 "Y" -> return True
144 "Y" -> return True
146 "n" -> return False
145 "n" -> return False
147
146
148data Test = Test
147data Test = Test
149− { test_name :: String
148+ { testName :: String
150− , test_ID :: String
149+ , testID :: String
151− , test_action :: IO ()
150+ , testAction :: IO ()
152− , print_summary :: IO ()
151+ , printSummary :: IO ()
153− , confirm_results :: IO Bool
152+ , confirmResults :: IO Bool
154 }
153 }
155
154
156−all_tests
155+allTests
157− = [ reserve_output_test
156+ = [ reserveOutputTest
158− , display_bounds_test_0
157+ , displayBoundsTest0
159− , display_bounds_test_1
158+ , displayBoundsTest1
160− , display_bounds_test_2
159+ , displayBoundsTest2
161− , display_bounds_test_3
160+ , displayBoundsTest3
162− , unicode_single_width_0
161+ , unicodeSingleWidth0
163− , unicode_single_width_1
162+ , unicodeSingleWidth1
164− , unicode_double_width_0
163+ , unicodeDoubleWidth0
165− , unicode_double_width_1
164+ , unicodeDoubleWidth1
166− , attributes_test_0
165+ , attributesTest0
167− , attributes_test_1
166+ , attributesTest1
168− , attributes_test_2
167+ , attributesTest2
169− , attributes_test_3
168+ , attributesTest3
170− , attributes_test_4
169+ , attributesTest4
171− , attributes_test_5
170+ , attributesTest5
172− , inline_test_0
171+ , inlineTest0
173− , inline_test_1
172+ , inlineTest1
174− , inline_test_2
173+ , inlineTest2
175− , cursor_hide_test_0
174+ , cursorHideTest0
176− , vert_crop_test_0
175+ , vertCropTest0
177− , vert_crop_test_1
176+ , vertCropTest1
178− , vert_crop_test_2
177+ , vertCropTest2
179− , vert_crop_test_3
178+ , vertCropTest3
180− , horiz_crop_test_0
179+ , horizCropTest0
181− , horiz_crop_test_1
180+ , horizCropTest1
182− , horiz_crop_test_2
181+ , horizCropTest2
183− , horiz_crop_test_3
182+ , horizCropTest3
184− , layer_0
183+ , layer0
185− , layer_1
184+ , layer1
186 ]
185 ]
187
186
188−reserve_output_test = Test
187+reserveOutputTest = Test
189− { test_name = "Initialize and reserve terminal output then restore previous state."
188+ { testName = "Initialize and reserve terminal output then restore previous state."
190− , test_ID = "reserve_output_test"
189+ , testID = "reserveOutputTest"
191− , test_action = do
190+ , testAction = do
192− t <- current_terminal
191+ t <- standardIOConfig >>= outputForConfig
193− reserve_display t
192+ reserveDisplay t
194 putStrLn "Line 1"
193 putStrLn "Line 1"
195 putStrLn "Line 2"
194 putStrLn "Line 2"
196 putStrLn "Line 3"
195 putStrLn "Line 3"
197 putStrLn "Line 4 (press return)"
196 putStrLn "Line 4 (press return)"
198 hFlush stdout
197 hFlush stdout
199 getLine
198 getLine
200− release_display t
199+ releaseDisplay t
201− release_terminal t
200+ releaseTerminal t
202 return ()
201 return ()
203− , print_summary = do
202+ , printSummary = do
204 putStr $ [s|
203 putStr $ [s|
205Once return is pressed:
204Once return is pressed:
206− 0. The screen will be cleared.
205+ 0. The screen will be cleared.
207 1. Four lines of text should be visible.
206 1. Four lines of text should be visible.
208 1. The cursor should be visible and at the start of the fifth line.
207 1. The cursor should be visible and at the start of the fifth line.
209
208
@@ -211,123 +210,123 @@ After return is pressed for the second time this test then:
211 * The screen containing the test summary should be restored;
210 * The screen containing the test summary should be restored;
212 * The cursor is visible.
211 * The cursor is visible.
213|]
212|]
214− , confirm_results = do
213+ , confirmResults = do
215 putStr $ [s|
214 putStr $ [s|
216Did the test output match the description?
215Did the test output match the description?
217|]
216|]
218− default_success_confirm_results
217+ defaultSuccessConfirmResults
219 }
218 }
220
219
221−display_bounds_test_0 = Test
220+displayBoundsTest0 = Test
222− { test_name = "Verify display bounds are correct test 0: Using spaces."
221+ { testName = "Verify display bounds are correct test 0: Using spaces."
223− , test_ID = "display_bounds_test_0"
222+ , testID = "displayBoundsTest0"
224− , test_action = do
223+ , testAction = do
225− t <- current_terminal
224+ t <- standardIOConfig >>= outputForConfig
226− reserve_display t
225+ reserveDisplay t
227− DisplayRegion w h <- display_bounds t
226+ (w,h) <- displayBounds t
228− let row_0 = replicate (fromEnum w) 'X' ++ "\n"
227+ let row0 = replicate (fromEnum w) 'X' ++ "\n"
229− row_h = replicate (fromEnum w - 1) 'X'
228+ rowH = replicate (fromEnum w - 1) 'X'
230− row_n = "X" ++ replicate (fromEnum w - 2) ' ' ++ "X\n"
229+ rowN = "X" ++ replicate (fromEnum w - 2) ' ' ++ "X\n"
231− image = row_0 ++ (concat $ replicate (fromEnum h - 2) row_n) ++ row_h
230+ image = row0 ++ (concat $ replicate (fromEnum h - 2) rowN) ++ rowH
232 putStr image
231 putStr image
233 hFlush stdout
232 hFlush stdout
234 getLine
233 getLine
235− release_display t
234+ releaseDisplay t
236− release_terminal t
235+ releaseTerminal t
237 return ()
236 return ()
238− , print_summary = display_bounds_test_summary True
237+ , printSummary = displayBoundsTestSummary True
239− , confirm_results = generic_output_match_confirm
238+ , confirmResults = genericOutputMatchConfirm
240 }
239 }
241
240
242−display_bounds_test_1 = Test
241+displayBoundsTest1 = Test
243− { test_name = "Verify display bounds are correct test 0: Using cursor movement."
242+ { testName = "Verify display bounds are correct test 0: Using cursor movement."
244− , test_ID = "display_bounds_test_1"
243+ , testID = "displayBoundsTest1"
245− , test_action = do
244+ , testAction = do
246− t <- current_terminal
245+ t <- standardIOConfig >>= outputForConfig
247− reserve_display t
246+ reserveDisplay t
248− DisplayRegion w h <- display_bounds t
247+ (w,h) <- displayBounds t
249− set_cursor_pos t 0 0
248+ setCursorPos t 0 0
250− let row_0 = replicate (fromEnum w) 'X' ++ "\n"
249+ let row0 = replicate (fromEnum w) 'X' ++ "\n"
251− putStr row_0
250+ putStr row0
252 forM_ [1 .. h - 2] $ \y -> do
251 forM_ [1 .. h - 2] $ \y -> do
253− set_cursor_pos t 0 y
252+ setCursorPos t 0 y
254 putStr "X"
253 putStr "X"
255 hFlush stdout
254 hFlush stdout
256− set_cursor_pos t (w - 1) y
255+ setCursorPos t (w - 1) y
257 putStr "X"
256 putStr "X"
258 hFlush stdout
257 hFlush stdout
259− set_cursor_pos t 0 (h - 1)
258+ setCursorPos t 0 (h - 1)
260− let row_h = replicate (fromEnum w - 1) 'X'
259+ let rowH = replicate (fromEnum w - 1) 'X'
261− putStr row_h
260+ putStr rowH
262 hFlush stdout
261 hFlush stdout
263 getLine
262 getLine
264− release_display t
263+ releaseDisplay t
265− release_terminal t
264+ releaseTerminal t
266 return ()
265 return ()
267− , print_summary = display_bounds_test_summary True
266+ , printSummary = displayBoundsTestSummary True
268− , confirm_results = generic_output_match_confirm
267+ , confirmResults = genericOutputMatchConfirm
269 }
268 }
270
269
271−display_bounds_test_2 = Test
270+displayBoundsTest2 = Test
272− { test_name = "Verify display bounds are correct test 0: Using Image ops."
271+ { testName = "Verify display bounds are correct test 0: Using Image ops."
273− , test_ID = "display_bounds_test_2"
272+ , testID = "displayBoundsTest2"
274− , test_action = do
273+ , testAction = do
275− t <- current_terminal
274+ t <- standardIOConfig >>= outputForConfig
276− reserve_display t
275+ reserveDisplay t
277− bounds@(DisplayRegion w h) <- display_bounds t
276+ bounds@(w,h) <- displayBounds t
278− let first_row = horiz_cat $ replicate (fromEnum w) (char def_attr 'X')
277+ let firstRow = horizCat $ replicate (fromEnum w) (char defAttr 'X')
279− middle_rows = vert_cat $ replicate (fromEnum h - 2) middle_row
278+ middleRows = vertCat $ replicate (fromEnum h - 2) middleRow
280− middle_row = (char def_attr 'X') <|> background_fill (w - 2) 1 <|> (char def_attr 'X')
279+ middleRow = (char defAttr 'X') <|> backgroundFill (w - 2) 1 <|> (char defAttr 'X')
281− end_row = first_row
280+ endRow = firstRow
282− image = first_row <-> middle_rows <-> end_row
281+ image = firstRow <-> middleRows <-> endRow
283− pic = (pic_for_image image) { pic_cursor = Cursor (w - 1) (h - 1) }
282+ pic = (picForImage image) { picCursor = Cursor (w - 1) (h - 1) }
284− d <- display_context t bounds
283+ d <- displayContext t bounds
285− output_picture d pic
284+ outputPicture d pic
286 getLine
285 getLine
287− release_display t
286+ releaseDisplay t
288− release_terminal t
287+ releaseTerminal t
289 return ()
288 return ()
290− , print_summary = display_bounds_test_summary True
289+ , printSummary = displayBoundsTestSummary True
291− , confirm_results = generic_output_match_confirm
290+ , confirmResults = genericOutputMatchConfirm
292 }
291 }
293
292
294−display_bounds_test_3 = Test
293+displayBoundsTest3 = Test
295− { test_name = "Verify display bounds are correct test 0: Hide cursor; Set cursor pos."
294+ { testName = "Verify display bounds are correct test 0: Hide cursor; Set cursor pos."
296− , test_ID = "display_bounds_test_3"
295+ , testID = "displayBoundsTest3"
297− , test_action = do
296+ , testAction = do
298− t <- current_terminal
297+ t <- standardIOConfig >>= outputForConfig
299− reserve_display t
298+ reserveDisplay t
300− DisplayRegion w h <- display_bounds t
299+ (w,h) <- displayBounds t
301− hide_cursor t
300+ hideCursor t
302− set_cursor_pos t 0 0
301+ setCursorPos t 0 0
303− let row_0 = replicate (fromEnum w) 'X'
302+ let row0 = replicate (fromEnum w) 'X'
304− putStrLn row_0
303+ putStrLn row0
305 forM_ [1 .. h - 2] $ \y -> do
304 forM_ [1 .. h - 2] $ \y -> do
306− set_cursor_pos t 0 y
305+ setCursorPos t 0 y
307 putStr "X"
306 putStr "X"
308 hFlush stdout
307 hFlush stdout
309− set_cursor_pos t (w - 1) y
308+ setCursorPos t (w - 1) y
310 putStr "X"
309 putStr "X"
311 hFlush stdout
310 hFlush stdout
312− set_cursor_pos t 0 (h - 1)
311+ setCursorPos t 0 (h - 1)
313− let row_h = row_0
312+ let rowH = row0
314− putStr row_h
313+ putStr rowH
315 hFlush stdout
314 hFlush stdout
316 getLine
315 getLine
317− show_cursor t
316+ showCursor t
318− release_display t
317+ releaseDisplay t
319− release_terminal t
318+ releaseTerminal t
320 return ()
319 return ()
321− , print_summary = display_bounds_test_summary False
320+ , printSummary = displayBoundsTestSummary False
322− , confirm_results = generic_output_match_confirm
321+ , confirmResults = genericOutputMatchConfirm
323 }
322 }
324
323
325−display_bounds_test_summary has_cursor = do
324+displayBoundsTestSummary hasCursor = do
326 putStr $ [s|
325 putStr $ [s|
327Once return is pressed:
326Once return is pressed:
328 0. The screen will be cleared.
327 0. The screen will be cleared.
329|]
328|]
330− if has_cursor
329+ if hasCursor
331 then putStr " 1. The cursor will be visible."
330 then putStr " 1. The cursor will be visible."
332 else putStr " 1. The cursor will NOT be visible."
331 else putStr " 1. The cursor will NOT be visible."
333 putStr [s|
332 putStr [s|
@@ -338,7 +337,7 @@ Once return is pressed:
338 |XXXXXXXXXXXXX|
337 |XXXXXXXXXXXXX|
339 |X X||]
338 |X X||]
340
339
341− if has_cursor
340+ if hasCursor
342 then putStr $ [s|
341 then putStr $ [s|
343
342
344 |XXXXXXXXXXXXC| |]
343 |XXXXXXXXXXXXC| |]
@@ -359,13 +358,13 @@ After return is pressed for the second time:
359 1. The cursor should be visible.
358 1. The cursor should be visible.
360|]
359|]
361
360
362−generic_output_match_confirm = do
361+genericOutputMatchConfirm = do
363 putStr $ [s|
362 putStr $ [s|
364Did the test output match the description?
363Did the test output match the description?
365|]
364|]
366− default_success_confirm_results
365+ defaultSuccessConfirmResults
367
366
368−-- Explicitely definethe bytes that encode each example text.
367+-- Explicitly define the bytes that encode each example text.
369-- This avoids any issues with how the compiler represents string literals.
368-- This avoids any issues with how the compiler represents string literals.
370--
369--
371-- This document is UTF-8 encoded so the UTF-8 string is still included for
370-- This document is UTF-8 encoded so the UTF-8 string is still included for
@@ -374,24 +373,24 @@ Did the test output match the description?
374-- It's assumed the compiler will at least not barf on UTF-8 encoded text in
373-- It's assumed the compiler will at least not barf on UTF-8 encoded text in
375-- comments ;-)
374-- comments ;-)
376--
375--
377−-- txt_0 = ↑↑↓↓←→←→BA
376+-- txt0 = ↑↑↓↓←→←→BA
378−
377+
379−utf8_txt_0 :: [[Word8]]
378+utf8Txt0 :: [[Word8]]
380−utf8_txt_0 = [ [ 0xe2 , 0x86 , 0x91 ]
379+utf8Txt0 = [ [ 0xe2 , 0x86 , 0x91 ]
381− , [ 0xe2 , 0x86 , 0x91 ]
380+ , [ 0xe2 , 0x86 , 0x91 ]
382− , [ 0xe2 , 0x86 , 0x93 ]
381+ , [ 0xe2 , 0x86 , 0x93 ]
383− , [ 0xe2 , 0x86 , 0x93 ]
382+ , [ 0xe2 , 0x86 , 0x93 ]
384− , [ 0xe2 , 0x86 , 0x90 ]
383+ , [ 0xe2 , 0x86 , 0x90 ]
385− , [ 0xe2 , 0x86 , 0x92 ]
384+ , [ 0xe2 , 0x86 , 0x92 ]
386− , [ 0xe2 , 0x86 , 0x90 ]
385+ , [ 0xe2 , 0x86 , 0x90 ]
387− , [ 0xe2 , 0x86 , 0x92 ]
386+ , [ 0xe2 , 0x86 , 0x92 ]
388− , [ 0x42 ]
387+ , [ 0x42 ]
389− , [ 0x41 ]
388+ , [ 0x41 ]
390− ]
389+ ]
391−
390+
392−iso_10646_txt_0 :: String
391+iso10646Txt0 :: String
393−iso_10646_txt_0 = map toEnum
392+iso10646Txt0 = map toEnum
394− [ 8593
393+ [ 8593
395 , 8593
394 , 8593
396 , 8595
395 , 8595
397 , 8595
396 , 8595
@@ -403,46 +402,46 @@ iso_10646_txt_0 = map toEnum
403 , 65
402 , 65
404 ]
403 ]
405
404
406−unicode_single_width_0 = Test
405+unicodeSingleWidth0 = Test
407− { test_name = "Verify terminal can display unicode single-width characters. (Direct UTF-8)"
406+ { testName = "Verify terminal can display unicode single-width characters. (Direct UTF-8)"
408− , test_ID = "unicode_single_width_0"
407+ , testID = "unicodeSingleWidth0"
409− , test_action = do
408+ , testAction = do
410− t <- current_terminal
409+ t <- standardIOConfig >>= outputForConfig
411− reserve_display t
410+ reserveDisplay t
412− hide_cursor t
411+ hideCursor t
413− withArrayLen (concat utf8_txt_0) (flip $ hPutBuf stdout)
412+ withArrayLen (concat utf8Txt0) (flip $ hPutBuf stdout)
414 hPutStr stdout "\n"
413 hPutStr stdout "\n"
415 hPutStr stdout "0123456789\n"
414 hPutStr stdout "0123456789\n"
416 hFlush stdout
415 hFlush stdout
417 getLine
416 getLine
418− release_display t
417+ releaseDisplay t
419− release_terminal t
418+ releaseTerminal t
420 return ()
419 return ()
421− , print_summary = unicode_single_width_summary
420+ , printSummary = unicodeSingleWidthSummary
422− , confirm_results = generic_output_match_confirm
421+ , confirmResults = genericOutputMatchConfirm
423 }
422 }
424
423
425−unicode_single_width_1 = Test
424+unicodeSingleWidth1 = Test
426− { test_name = "Verify terminal can display unicode single-width characters. (Image ops)"
425+ { testName = "Verify terminal can display unicode single-width characters. (Image ops)"
427− , test_ID = "unicode_single_width_1"
426+ , testID = "unicodeSingleWidth1"
428− , test_action = do
427+ , testAction = do
429− t <- current_terminal
428+ t <- standardIOConfig >>= outputForConfig
430− reserve_display t
429+ reserveDisplay t
431− let pic = pic_for_image image
430+ let pic = picForImage image
432− image = line_0 <-> line_1
431+ image = line0 <-> line1
433− line_0 = iso_10646_string def_attr iso_10646_txt_0
432+ line0 = iso10646String defAttr iso10646Txt0
434− line_1 = string def_attr "0123456789"
433+ line1 = string defAttr "0123456789"
435− d <- display_bounds t >>= display_context t
434+ d <- displayBounds t >>= displayContext t
436− output_picture d pic
435+ outputPicture d pic
437 getLine
436 getLine
438− release_display t
437+ releaseDisplay t
439− release_terminal t
438+ releaseTerminal t
440 return ()
439 return ()
441− , print_summary = unicode_single_width_summary
440+ , printSummary = unicodeSingleWidthSummary
442− , confirm_results = generic_output_match_confirm
441+ , confirmResults = genericOutputMatchConfirm
443 }
442 }
444
443
445−unicode_single_width_summary = putStr [s|
444+unicodeSingleWidthSummary = putStr [s|
446Once return is pressed:
445Once return is pressed:
447 0. The screen will be cleared.
446 0. The screen will be cleared.
448 1. The cursor will be hidden.
447 1. The cursor will be hidden.
@@ -476,55 +475,55 @@ After return is pressed for the second time:
476
475
477-- The second example is a unicode string containing double-width glyphs
476-- The second example is a unicode string containing double-width glyphs
478-- 你好吗
477-- 你好吗
479−utf8_txt_1 :: [[Word8]]
478+utf8Txt1 :: [[Word8]]
480−utf8_txt_1 = [ [0xe4,0xbd,0xa0]
479+utf8Txt1 = [ [0xe4,0xbd,0xa0]
481− , [0xe5,0xa5,0xbd]
480+ , [0xe5,0xa5,0xbd]
482− , [0xe5,0x90,0x97]
481+ , [0xe5,0x90,0x97]
483− ]
482+ ]
484−
483+
485−iso_10646_txt_1 :: String
484+iso10646Txt1 :: String
486−iso_10646_txt_1 = map toEnum [20320,22909,21527]
485+iso10646Txt1 = map toEnum [20320,22909,21527]
487−
486+
488−unicode_double_width_0 = Test
487+unicodeDoubleWidth0 = Test
489− { test_name = "Verify terminal can display unicode double-width characters. (Direct UTF-8)"
488+ { testName = "Verify terminal can display unicode double-width characters. (Direct UTF-8)"
490− , test_ID = "unicode_double_width_0"
489+ , testID = "unicodeDoubleWidth0"
491− , test_action = do
490+ , testAction = do
492− t <- current_terminal
491+ t <- standardIOConfig >>= outputForConfig
493− reserve_display t
492+ reserveDisplay t
494− hide_cursor t
493+ hideCursor t
495− withArrayLen (concat utf8_txt_1) (flip $ hPutBuf stdout)
494+ withArrayLen (concat utf8Txt1) (flip $ hPutBuf stdout)
496 hPutStr stdout "\n"
495 hPutStr stdout "\n"
497 hPutStr stdout "012345\n"
496 hPutStr stdout "012345\n"
498 hFlush stdout
497 hFlush stdout
499 getLine
498 getLine
500− release_display t
499+ releaseDisplay t
501− release_terminal t
500+ releaseTerminal t
502 return ()
501 return ()
503− , print_summary = unicode_double_width_summary
502+ , printSummary = unicodeDoubleWidthSummary
504− , confirm_results = generic_output_match_confirm
503+ , confirmResults = genericOutputMatchConfirm
505 }
504 }
506
505
507−unicode_double_width_1 = Test
506+unicodeDoubleWidth1 = Test
508− { test_name = "Verify terminal can display unicode double-width characters. (Image ops)"
507+ { testName = "Verify terminal can display unicode double-width characters. (Image ops)"
509− , test_ID = "unicode_double_width_1"
508+ , testID = "unicodeDoubleWidth1"
510− , test_action = do
509+ , testAction = do
511− t <- current_terminal
510+ t <- standardIOConfig >>= outputForConfig
512− reserve_display t
511+ reserveDisplay t
513− let pic = pic_for_image image
512+ let pic = picForImage image
514− image = line_0 <-> line_1
513+ image = line0 <-> line1
515− line_0 = iso_10646_string def_attr iso_10646_txt_1
514+ line0 = iso10646String defAttr iso10646Txt1
516− line_1 = string def_attr "012345"
515+ line1 = string defAttr "012345"
517− d <- display_bounds t >>= display_context t
516+ d <- displayBounds t >>= displayContext t
518− output_picture d pic
517+ outputPicture d pic
519 getLine
518 getLine
520− release_display t
519+ releaseDisplay t
521− release_terminal t
520+ releaseTerminal t
522 return ()
521 return ()
523− , print_summary = unicode_double_width_summary
522+ , printSummary = unicodeDoubleWidthSummary
524− , confirm_results = generic_output_match_confirm
523+ , confirmResults = genericOutputMatchConfirm
525 }
524 }
526
525
527−unicode_double_width_summary = putStr [s|
526+unicodeDoubleWidthSummary = putStr [s|
528Once return is pressed:
527Once return is pressed:
529 0. The screen will be cleared.
528 0. The screen will be cleared.
530 1. The cursor will be hidden.
529 1. The cursor will be hidden.
@@ -551,32 +550,32 @@ After return is pressed for the second time:
551 1. The cursor should be visible.
550 1. The cursor should be visible.
552|]
551|]
553
552
554−all_colors = zip [ black, red, green, yellow, blue, magenta, cyan, white ]
553+allColors = zip [ black, red, green, yellow, blue, magenta, cyan, white ]
555− [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]
554+ [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]
556
555
557−all_bright_colors
556+allBrightColors
558− = zip [ bright_black, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white ]
557+ = zip [ brightBlack, brightRed, brightGreen, brightYellow, brightBlue, brightMagenta, brightCyan, brightWhite ]
559 [ "bright black", "bright red", "bright green", "bright yellow", "bright blue", "bright magenta", "bright cyan", "bright white" ]
558 [ "bright black", "bright red", "bright green", "bright yellow", "bright blue", "bright magenta", "bright cyan", "bright white" ]
560
559
561−attributes_test_0 = Test
560+attributesTest0 = Test
562− { test_name = "Character attributes: foreground colors."
561+ { testName = "Character attributes: foreground colors."
563− , test_ID = "attributes_test_0"
562+ , testID = "attributesTest0"
564− , test_action = do
563+ , testAction = do
565− t <- current_terminal
564+ t <- standardIOConfig >>= outputForConfig
566− reserve_display t
565+ reserveDisplay t
567− let pic = pic_for_image image
566+ let pic = picForImage image
568− image = border <|> column_0 <|> border <|> column_1 <|> border
567+ image = border <|> column0 <|> border <|> column1 <|> border
569− column_0 = vert_cat $ map line_with_color all_colors
568+ column0 = vertCat $ map lineWithColor allColors
570− border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
569+ border = vertCat $ replicate (length allColors) $ string defAttr " | "
571− column_1 = vert_cat $ map (string def_attr . snd) all_colors
570+ column1 = vertCat $ map (string defAttr . snd) allColors
572− line_with_color (c, c_name) = string (def_attr `with_fore_color` c) c_name
571+ lineWithColor (c, cName) = string (defAttr `withForeColor` c) cName
573− d <- display_bounds t >>= display_context t
572+ d <- displayBounds t >>= displayContext t
574− output_picture d pic
573+ outputPicture d pic
575 getLine
574 getLine
576− release_display t
575+ releaseDisplay t
577− release_terminal t
576+ releaseTerminal t
578 return ()
577 return ()
579− , print_summary = do
578+ , printSummary = do
580 putStr $ [s|
579 putStr $ [s|
581Once return is pressed:
580Once return is pressed:
582 0. The screen will be cleared.
581 0. The screen will be cleared.
@@ -597,32 +596,32 @@ After return is pressed for the second time:
597 0. The screen containing the test summary should be restored.
596 0. The screen containing the test summary should be restored.
598 1. The cursor should be visible.
597 1. The cursor should be visible.
599|]
598|]
600− , confirm_results = do
599+ , confirmResults = do
601 putStr $ [s|
600 putStr $ [s|
602Did the test output match the description?
601Did the test output match the description?
603|]
602|]
604− default_success_confirm_results
603+ defaultSuccessConfirmResults
605 }
604 }
606
605
607−attributes_test_1 = Test
606+attributesTest1 = Test
608− { test_name = "Character attributes: background colors."
607+ { testName = "Character attributes: background colors."
609− , test_ID = "attributes_test_1"
608+ , testID = "attributesTest1"
610− , test_action = do
609+ , testAction = do
611− t <- current_terminal
610+ t <- standardIOConfig >>= outputForConfig
612− reserve_display t
611+ reserveDisplay t
613− let pic = pic_for_image image
612+ let pic = picForImage image
614− image = border <|> column_0 <|> border <|> column_1 <|> border
613+ image = border <|> column0 <|> border <|> column1 <|> border
615− column_0 = vert_cat $ map line_with_color all_colors
614+ column0 = vertCat $ map lineWithColor allColors
616− border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
615+ border = vertCat $ replicate (length allColors) $ string defAttr " | "
617− column_1 = vert_cat $ map (string def_attr . snd) all_colors
616+ column1 = vertCat $ map (string defAttr . snd) allColors
618− line_with_color (c, c_name) = string (def_attr `with_back_color` c) c_name
617+ lineWithColor (c, cName) = string (defAttr `withBackColor` c) cName
619− d <- display_bounds t >>= display_context t
618+ d <- displayBounds t >>= displayContext t
620− output_picture d pic
619+ outputPicture d pic
621 getLine
620 getLine
622− release_display t
621+ releaseDisplay t
623− release_terminal t
622+ releaseTerminal t
624 return ()
623 return ()
625− , print_summary = do
624+ , printSummary = do
626 putStr $ [s|
625 putStr $ [s|
627Once return is pressed:
626Once return is pressed:
628 0. The screen will be cleared.
627 0. The screen will be cleared.
@@ -652,57 +651,57 @@ After return is pressed for the second time:
652 0. The screen containing the test summary should be restored.
651 0. The screen containing the test summary should be restored.
653 1. The cursor should be visible.
652 1. The cursor should be visible.
654|]
653|]
655− , confirm_results = do
654+ , confirmResults = do
656 putStr $ [s|
655 putStr $ [s|
657Did the test output match the description?
656Did the test output match the description?
658|]
657|]
659− default_success_confirm_results
658+ defaultSuccessConfirmResults
660 }
659 }
661
660
662−attributes_test_2 = Test
661+attributesTest2 = Test
663− { test_name = "Character attributes: Vivid foreground colors."
662+ { testName = "Character attributes: Vivid foreground colors."
664− , test_ID = "attributes_test_2"
663+ , testID = "attributesTest2"
665− , test_action = do
664+ , testAction = do
666− t <- current_terminal
665+ t <- standardIOConfig >>= outputForConfig
667− reserve_display t
666+ reserveDisplay t
668− let pic = pic_for_image image
667+ let pic = picForImage image
669− image = horiz_cat [border, column_0, border, column_1, border, column_2, border]
668+ image = horizCat [border, column0, border, column1, border, column2, border]
670− border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
669+ border = vertCat $ replicate (length allColors) $ string defAttr " | "
671− column_0 = vert_cat $ map line_with_color_0 all_colors
670+ column0 = vertCat $ map lineWithColor0 allColors
672− column_1 = vert_cat $ map line_with_color_1 all_bright_colors
671+ column1 = vertCat $ map lineWithColor1 allBrightColors
673− column_2 = vert_cat $ map (string def_attr . snd) all_colors
672+ column2 = vertCat $ map (string defAttr . snd) allColors
674− line_with_color_0 (c, c_name) = string (def_attr `with_fore_color` c) c_name
673+ lineWithColor0 (c, cName) = string (defAttr `withForeColor` c) cName
675− line_with_color_1 (c, c_name) = string (def_attr `with_fore_color` c) c_name
674+ lineWithColor1 (c, cName) = string (defAttr `withForeColor` c) cName
676− d <- display_bounds t >>= display_context t
675+ d <- displayBounds t >>= displayContext t
677− output_picture d pic
676+ outputPicture d pic
678 getLine
677 getLine
679− release_display t
678+ releaseDisplay t
680− release_terminal t
679+ releaseTerminal t
681 return ()
680 return ()
682− , print_summary = do
681+ , printSummary = do
683 putStr $ [s|
682 putStr $ [s|
684Once return is pressed:
683Once return is pressed:
685 0. The screen will be cleared.
684 0. The screen will be cleared.
686 1. The cursor will be hidden.
685 1. The cursor will be hidden.
687 2. 9 lines of text in three columns will be drawn:
686 2. 9 lines of text in three columns will be drawn:
688 a. The first column will be a name of a standard color (for an 8 color
687 a. The first column will be a name of a standard color (for an 8 color
689− terminal) rendered with that color as the foreground color.
688+ terminal) rendered with that color as the foreground color.
690 b. The next column will be also be the name of a standard color rendered
689 b. The next column will be also be the name of a standard color rendered
691 with that color as the foreground color but the shade used should be
690 with that color as the foreground color but the shade used should be
692− more vivid than the shade used in the first column.
691+ more vivid than the shade used in the first column.
693 c. The final column will be the name of a color rendered with the
692 c. The final column will be the name of a color rendered with the
694 default attributes.
693 default attributes.
695
694
696For instance, one line will be the word "magenta" and that word should be
695For instance, one line will be the word "magenta" and that word should be
697−rendered in the magenta color.
696+rendered in the magenta color.
698
697
699I'm not actually sure exactly what "vivid" means in this context. For xterm the
698I'm not actually sure exactly what "vivid" means in this context. For xterm the
700−vivid colors are brighter.
699+vivid colors are brighter.
701
700
702−Verify:
701+Verify:
703 * The first column: The foreground color matches the named color.
702 * The first column: The foreground color matches the named color.
704 * The second column: The foreground color matches the named color but is
703 * The second column: The foreground color matches the named color but is
705− more vivid than the color used in the first column.
704+ more vivid than the color used in the first column.
706 * The third column: All text is rendered with the default attributes.
705 * The third column: All text is rendered with the default attributes.
707 * The vertical bars used in each line to mark the border of a column are
706 * The vertical bars used in each line to mark the border of a column are
708 lined up.
707 lined up.
@@ -711,34 +710,34 @@ After return is pressed for the second time:
711 0. The screen containing the test summary should be restored.
710 0. The screen containing the test summary should be restored.
712 1. The cursor should be visible.
711 1. The cursor should be visible.
713|]
712|]
714− , confirm_results = do
713+ , confirmResults = do
715 putStr $ [s|
714 putStr $ [s|
716Did the test output match the description?
715Did the test output match the description?
717|]
716|]
718− default_success_confirm_results
717+ defaultSuccessConfirmResults
719 }
718 }
720
719
721−attributes_test_3 = Test
720+attributesTest3 = Test
722− { test_name = "Character attributes: Vivid background colors."
721+ { testName = "Character attributes: Vivid background colors."
723− , test_ID = "attributes_test_3"
722+ , testID = "attributesTest3"
724− , test_action = do
723+ , testAction = do
725− t <- current_terminal
724+ t <- standardIOConfig >>= outputForConfig
726− reserve_display t
725+ reserveDisplay t
727− let pic = pic_for_image image
726+ let pic = picForImage image
728− image = horiz_cat [border, column_0, border, column_1, border, column_2, border]
727+ image = horizCat [border, column0, border, column1, border, column2, border]
729− border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
728+ border = vertCat $ replicate (length allColors) $ string defAttr " | "
730− column_0 = vert_cat $ map line_with_color_0 all_colors
729+ column0 = vertCat $ map lineWithColor0 allColors
731− column_1 = vert_cat $ map line_with_color_1 all_bright_colors
730+ column1 = vertCat $ map lineWithColor1 allBrightColors
732− column_2 = vert_cat $ map (string def_attr . snd) all_colors
731+ column2 = vertCat $ map (string defAttr . snd) allColors
733− line_with_color_0 (c, c_name) = string (def_attr `with_back_color` c) c_name
732+ lineWithColor0 (c, cName) = string (defAttr `withBackColor` c) cName
734− line_with_color_1 (c, c_name) = string (def_attr `with_back_color` c) c_name
733+ lineWithColor1 (c, cName) = string (defAttr `withBackColor` c) cName
735− d <- display_bounds t >>= display_context t
734+ d <- displayBounds t >>= displayContext t
736− output_picture d pic
735+ outputPicture d pic
737 getLine
736 getLine
738− release_display t
737+ releaseDisplay t
739− release_terminal t
738+ releaseTerminal t
740 return ()
739 return ()
741− , print_summary = do
740+ , printSummary = do
742 putStr $ [s|
741 putStr $ [s|
743Once return is pressed:
742Once return is pressed:
744 0. The screen will be cleared.
743 0. The screen will be cleared.
@@ -746,23 +745,23 @@ Once return is pressed:
746 2. 9 lines of text in three columns will be drawn:
745 2. 9 lines of text in three columns will be drawn:
747 a. The first column will contain be a name of a standard color for an 8
746 a. The first column will contain be a name of a standard color for an 8
748 color terminal rendered with the default foreground color with a
747 color terminal rendered with the default foreground color with a
749− background the named color.
748+ background the named color.
750 b. The first column will contain be a name of a standard color for an 8
749 b. The first column will contain be a name of a standard color for an 8
751 color terminal rendered with the default foreground color with the
750 color terminal rendered with the default foreground color with the
752− background a vivid version of the named color.
751+ background a vivid version of the named color.
753 c. The third column will be the name of a standard color rendered with
752 c. The third column will be the name of a standard color rendered with
754 the default attributes.
753 the default attributes.
755−
754+
756For instance, one line will contain be the word "magenta" and the word should
755For instance, one line will contain be the word "magenta" and the word should
757−be rendered in the default foreground color over a magenta background.
756+be rendered in the default foreground color over a magenta background.
758
757
759I'm not actually sure exactly what "vivid" means in this context. For xterm the
758I'm not actually sure exactly what "vivid" means in this context. For xterm the
760vivid colors are brighter.
759vivid colors are brighter.
761
760
762−Verify:
761+Verify:
763 * The first column: The background color matches the named color.
762 * The first column: The background color matches the named color.
764 * The second column: The background color matches the named color and is
763 * The second column: The background color matches the named color and is
765− more vivid than the color used in the first column.
764+ more vivid than the color used in the first column.
766 * The third column column: All text is rendered with the default attributes.
765 * The third column column: All text is rendered with the default attributes.
767 * The vertical bars used in each line to mark the border of a column are
766 * The vertical bars used in each line to mark the border of a column are
768 lined up.
767 lined up.
@@ -778,51 +777,51 @@ After return is pressed for the second time:
778 0. The screen containing the test summary should be restored.
777 0. The screen containing the test summary should be restored.
779 1. The cursor should be visible.
778 1. The cursor should be visible.
780|]
779|]
781− , confirm_results = do
780+ , confirmResults = do
782 putStr $ [s|
781 putStr $ [s|
783Did the test output match the description?
782Did the test output match the description?
784|]
783|]
785− default_success_confirm_results
784+ defaultSuccessConfirmResults
786 }
785 }
787
786
788−attr_combos =
787+attrCombos =
789 [ ( "default", id )
788 [ ( "default", id )
790− , ( "bold", flip with_style bold )
789+ , ( "bold", flip withStyle bold )
791− , ( "blink", flip with_style blink )
790+ , ( "blink", flip withStyle blink )
792− , ( "underline", flip with_style underline )
791+ , ( "underline", flip withStyle underline )
793− , ( "bold + blink", flip with_style (bold + blink) )
792+ , ( "bold + blink", flip withStyle (bold + blink) )
794− , ( "bold + underline", flip with_style (bold + underline) )
793+ , ( "bold + underline", flip withStyle (bold + underline) )
795− , ( "underline + blink", flip with_style (underline + blink) )
794+ , ( "underline + blink", flip withStyle (underline + blink) )
796− , ( "bold + blink + underline", flip with_style (bold + blink + underline) )
795+ , ( "bold + blink + underline", flip withStyle (bold + blink + underline) )
797 ]
796 ]
798
797
799−attributes_test_4 = Test
798+attributesTest4 = Test
800− { test_name = "Character attributes: Bold; Blink; Underline."
799+ { testName = "Character attributes: Bold; Blink; Underline."
801− , test_ID = "attributes_test_4"
800+ , testID = "attributesTest4"
802− , test_action = do
801+ , testAction = do
803− t <- current_terminal
802+ t <- standardIOConfig >>= outputForConfig
804− reserve_display t
803+ reserveDisplay t
805− let pic = pic_for_image image
804+ let pic = picForImage image
806− image = horiz_cat [border, column_0, border, column_1, border]
805+ image = horizCat [border, column0, border, column1, border]
807− border = vert_cat $ replicate (length attr_combos) $ string def_attr " | "
806+ border = vertCat $ replicate (length attrCombos) $ string defAttr " | "
808− column_0 = vert_cat $ map line_with_attrs attr_combos
807+ column0 = vertCat $ map lineWithAttrs attrCombos
809− column_1 = vert_cat $ map (string def_attr . fst) attr_combos
808+ column1 = vertCat $ map (string defAttr . fst) attrCombos
810− line_with_attrs (desc, attr_f) = string (attr_f def_attr) desc
809+ lineWithAttrs (desc, attrF) = string (attrF defAttr) desc
811− d <- display_bounds t >>= display_context t
810+ d <- displayBounds t >>= displayContext t
812− output_picture d pic
811+ outputPicture d pic
813 getLine
812 getLine
814− release_display t
813+ releaseDisplay t
815− release_terminal t
814+ releaseTerminal t
816 return ()
815 return ()
817− , print_summary = do
816+ , printSummary = do
818 putStr $ [s|
817 putStr $ [s|
819Once return is pressed:
818Once return is pressed:
820 0. The screen will be cleared.
819 0. The screen will be cleared.
821 1. The cursor will be hidden.
820 1. The cursor will be hidden.
822− 2. 8 rows of text in two columns.
821+ 2. 8 rows of text in two columns.
823 The rows will contain the following text:
822 The rows will contain the following text:
824 default
823 default
825− bold
824+ bold
826 blink
825 blink
827 underline
826 underline
828 bold + blink
827 bold + blink
@@ -831,8 +830,8 @@ Once return is pressed:
831 bold + blink + underline
830 bold + blink + underline
832 The first column will be rendered with the described attributes. The second
831 The first column will be rendered with the described attributes. The second
833 column will be rendered with the default attributes.
832 column will be rendered with the default attributes.
834−
833+
835−Verify:
834+Verify:
836 * The vertical bars used in each line to mark the border of a column are
835 * The vertical bars used in each line to mark the border of a column are
837 lined up.
836 lined up.
838 * The text in the first column is rendered as described.
837 * The text in the first column is rendered as described.
@@ -841,31 +840,31 @@ After return is pressed for the second time:
841 0. The screen containing the test summary should be restored.
840 0. The screen containing the test summary should be restored.
842 1. The cursor should be visible.
841 1. The cursor should be visible.
843|]
842|]
844− , confirm_results = do
843+ , confirmResults = do
845 putStr $ [s|
844 putStr $ [s|
846Did the test output match the description?
845Did the test output match the description?
847|]
846|]
848− default_success_confirm_results
847+ defaultSuccessConfirmResults
849 }
848 }
850
849
851−attributes_test_5 = Test
850+attributesTest5 = Test
852− { test_name = "Character attributes: 240 color palette"
851+ { testName = "Character attributes: 240 color palette"
853− , test_ID = "attributes_test_5"
852+ , testID = "attributesTest5"
854− , test_action = do
853+ , testAction = do
855− t <- current_terminal
854+ t <- standardIOConfig >>= outputForConfig
856− reserve_display t
855+ reserveDisplay t
857− let pic = pic_for_image image
856+ let pic = picForImage image
858− image = vert_cat $ map horiz_cat $ split_color_images color_images
857+ image = vertCat $ map horizCat $ splitColorImages colorImages
859− color_images = map (\i -> string (current_attr `with_back_color` Color240 i) " ") [0..239]
858+ colorImages = map (\i -> string (currentAttr `withBackColor` Color240 i) " ") [0..239]
860− split_color_images [] = []
859+ splitColorImages [] = []
861− split_color_images is = (take 20 is ++ [string def_attr " "]) : (split_color_images (drop 20 is))
860+ splitColorImages is = (take 20 is ++ [string defAttr " "]) : (splitColorImages (drop 20 is))
862− d <- display_bounds t >>= display_context t
861+ d <- displayBounds t >>= displayContext t
863− output_picture d pic
862+ outputPicture d pic
864 getLine
863 getLine
865− release_display t
864+ releaseDisplay t
866− release_terminal t
865+ releaseTerminal t
867 return ()
866 return ()
868− , print_summary = do
867+ , printSummary = do
869 putStr $ [s|
868 putStr $ [s|
870Once return is pressed:
869Once return is pressed:
871 0. The screen will be cleared.
870 0. The screen will be cleared.
@@ -873,296 +872,304 @@ Once return is pressed:
873 2. A 20 character wide and 12 row high block of color squares. This should look like a palette
872 2. A 20 character wide and 12 row high block of color squares. This should look like a palette
874 of some sort. I'm not exactly sure if all color terminals use the same palette. I doubt it...
873 of some sort. I'm not exactly sure if all color terminals use the same palette. I doubt it...
875
874
876−Verify:
875+Verify:
877
876
878After return is pressed for the second time:
877After return is pressed for the second time:
879 0. The screen containing the test summary should be restored.
878 0. The screen containing the test summary should be restored.
880 1. The cursor should be visible.
879 1. The cursor should be visible.
881|]
880|]
882− , confirm_results = do
881+ , confirmResults = do
883 putStr $ [s|
882 putStr $ [s|
884Did the test output match the description?
883Did the test output match the description?
885|]
884|]
886− default_success_confirm_results
885+ defaultSuccessConfirmResults
887 }
886 }
888
887
889−inline_test_0 = Test
888+inlineTest0 = Test
890− { test_name = "Verify styled output can be performed without clearing the screen."
889+ { testName = "Verify styled output can be performed without clearing the screen."
891− , test_ID = "inline_test_0"
890+ , testID = "inlineTest0"
892− , test_action = do
891+ , testAction = do
893 putStrLn "line 1."
892 putStrLn "line 1."
894− put_attr_change_ $ back_color red >> apply_style underline
893+ putAttrChange_ $ backColor red >> applyStyle underline
895 putStrLn "line 2."
894 putStrLn "line 2."
896− put_attr_change_ $ default_all
895+ putAttrChange_ $ defaultAll
897 putStrLn "line 3."
896 putStrLn "line 3."
898− , print_summary = putStr $ [s|
897+ , printSummary = putStr $ [s|
899lines are in order.
898lines are in order.
900The second line "line 2" should have a red background and the text underline.
899The second line "line 2" should have a red background and the text underline.
901The third line "line 3" should be drawn in the same style as the first line.
900The third line "line 3" should be drawn in the same style as the first line.
902|]
901|]
903
902
904− , confirm_results = generic_output_match_confirm
903+ , confirmResults = genericOutputMatchConfirm
905 }
904 }
906
905
907−inline_test_1 = Test
906+inlineTest1 = Test
908− { test_name = "Verify styled output can be performed without clearing the screen."
907+ { testName = "Verify styled output can be performed without clearing the screen."
909− , test_ID = "inline_test_1"
908+ , testID = "inlineTest1"
910− , test_action = do
909+ , testAction = do
911 putStr "Not styled. "
910 putStr "Not styled. "
912− put_attr_change_ $ back_color red >> apply_style underline
911+ putAttrChange_ $ backColor red >> applyStyle underline
913 putStr " Styled! "
912 putStr " Styled! "
914− put_attr_change_ $ default_all
913+ putAttrChange_ $ defaultAll
915 putStrLn "Not styled."
914 putStrLn "Not styled."
916− , print_summary = putStr $ [s|
915+ , printSummary = putStr $ [s|
917|]
916|]
918
917
919− , confirm_results = generic_output_match_confirm
918+ , confirmResults = genericOutputMatchConfirm
920 }
919 }
921
920
922−inline_test_2 = Test
921+inlineTest2 = Test
923− { test_name = "Verify styled output can be performed without clearing the screen."
922+ { testName = "Verify styled output can be performed without clearing the screen."
924− , test_ID = "inline_test_1"
923+ , testID = "inlineTest1"
925− , test_action = do
924+ , testAction = do
926 putStr "Not styled. "
925 putStr "Not styled. "
927− put_attr_change_ $ back_color red >> apply_style underline
926+ putAttrChange_ $ backColor red >> applyStyle underline
928 putStr " Styled! "
927 putStr " Styled! "
929− put_attr_change_ $ default_all
928+ putAttrChange_ $ defaultAll
930 putStr "Not styled.\n"
929 putStr "Not styled.\n"
931− , print_summary = putStr $ [s|
930+ , printSummary = putStr $ [s|
932|]
931|]
933− , confirm_results = generic_output_match_confirm
932+ , confirmResults = genericOutputMatchConfirm
934 }
933 }
935
934
936−cursor_hide_test_0 :: Test
935+cursorHideTest0 :: Test
937−cursor_hide_test_0 = Test
936+cursorHideTest0 = Test
938− { test_name = "Verify the cursor is hid and re-shown. issue #7"
937+ { testName = "Verify the cursor is hid and re-shown. issue #7"
939− , test_ID = "cursor_hide_test_0"
938+ , testID = "cursorHideTest0"
940− , test_action = do
939+ , testAction = do
941− vty <- mkVty
940+ vty <- mkVty defaultConfig
942− show_cursor $ terminal vty
941+ showCursor $ outputIface vty
943− set_cursor_pos (terminal vty) 5 5
942+ setCursorPos (outputIface vty) 5 5
944− next_event vty
943+ nextEvent vty
945− hide_cursor $ terminal vty
944+ hideCursor $ outputIface vty
946− next_event vty
945+ nextEvent vty
947 shutdown vty
946 shutdown vty
948 return ()
947 return ()
949− , print_summary = putStr $ [s|
948+ , printSummary = putStr $ [s|
950 1. verify the cursor is displayed.
949 1. verify the cursor is displayed.
951 2. press enter
950 2. press enter
952 3. verify the cursor is hid.
951 3. verify the cursor is hid.
953 4. press enter.
952 4. press enter.
954 5. the display should return to the state before the test.
953 5. the display should return to the state before the test.
955|]
954|]
956− , confirm_results = generic_output_match_confirm
955+ , confirmResults = genericOutputMatchConfirm
957 }
956 }
958
957
959−output_image_and_wait :: Image -> IO ()
958+outputImageAndWait :: Image -> IO ()
960−output_image_and_wait image = do
959+outputImageAndWait image = do
961− let pic = pic_for_image image
960+ let pic = picForImage image
962− output_pic_and_wait pic
961+ outputPicAndWait pic
963−
962+
964−output_pic_and_wait :: Picture -> IO ()
963+outputPicAndWait :: Picture -> IO ()
965−output_pic_and_wait pic = do
964+outputPicAndWait pic = do
966− t <- current_terminal
965+ t <- standardIOConfig >>= outputForConfig
967− reserve_display t
966+ reserveDisplay t
968− d <- display_bounds t >>= display_context t
967+ d <- displayBounds t >>= displayContext t
969− output_picture d pic
968+ outputPicture d pic
970 getLine
969 getLine
971− release_display t
970+ releaseDisplay t
972− release_terminal t
971+ releaseTerminal t
973 return ()
972 return ()
974−
973+
975−vert_crop_test_0 :: Test
974+vertCropTest0 :: Test
976−vert_crop_test_0 = Test
975+vertCropTest0 = Test
977− { test_name = "Verify bottom cropping works as expected with single column chars"
976+ { testName = "Verify bottom cropping works as expected with single column chars"
978− , test_ID = "crop_test_0"
977+ , testID = "cropTest0"
979− , test_action = do
978+ , testAction = do
980− let block_0 = crop_bottom 2 $ vert_cat $ map (string def_attr) lorum_ipsum
979+ let block0 = cropBottom 2 $ vertCat $ map (string defAttr) lorumIpsum
981− block_1 = vert_cat $ map (string def_attr) $ take 2 lorum_ipsum
980+ block1 = vertCat $ map (string defAttr) $ take 2 lorumIpsum
982− image = block_0 <-> background_fill 10 2 <-> block_1
981+ image = block0 <-> backgroundFill 10 2 <-> block1
983− output_image_and_wait image
982+ outputImageAndWait image
984− , print_summary = putStr $ [s|
983+ , printSummary = putStr $ [s|
985 1. Verify the two text blocks are identical.
984 1. Verify the two text blocks are identical.
986 2. press enter.
985 2. press enter.
987 3. the display should return to the state before the test.
986 3. the display should return to the state before the test.
988|]
987|]
989− , confirm_results = generic_output_match_confirm
988+ , confirmResults = genericOutputMatchConfirm
990 }
989 }
991
990
992−vert_crop_test_1 :: Test
991+vertCropTest1 :: Test
993−vert_crop_test_1 = Test
992+vertCropTest1 = Test
994− { test_name = "Verify bottom cropping works as expected with double column chars"
993+ { testName = "Verify bottom cropping works as expected with double column chars"
995− , test_ID = "crop_test_0"
994+ , testID = "cropTest0"
996− , test_action = do
995+ , testAction = do
997− let block_0 = crop_bottom 2 $ vert_cat $ map (string def_attr) lorum_ipsum_chinese
996+ let block0 = cropBottom 2 $ vertCat $ map (string defAttr) lorumIpsumChinese
998− block_1 = vert_cat $ map (string def_attr) $ take 2 lorum_ipsum_chinese
997+ block1 = vertCat $ map (string defAttr) $ take 2 lorumIpsumChinese
999− image = block_0 <-> background_fill 10 2 <-> block_1
998+ image = block0 <-> backgroundFill 10 2 <-> block1
1000− output_image_and_wait image
999+ outputImageAndWait image
1001− , print_summary = putStr $ [s|
1000+ , printSummary = putStr $ [s|
1002 1. Verify the two text blocks are identical.
1001 1. Verify the two text blocks are identical.
1003 2. press enter.
1002 2. press enter.
1004 3. the display should return to the state before the test.
1003 3. the display should return to the state before the test.
1005|]
1004|]
1006− , confirm_results = generic_output_match_confirm
1005+ , confirmResults = genericOutputMatchConfirm
1007 }
1006 }
1008
1007
1009−vert_crop_test_2 :: Test
1008+vertCropTest2 :: Test
1010−vert_crop_test_2 = Test
1009+vertCropTest2 = Test
1011− { test_name = "Verify top cropping works as expected with single column chars"
1010+ { testName = "Verify top cropping works as expected with single column chars"
1012− , test_ID = "crop_test_0"
1011+ , testID = "cropTest2"
1013− , test_action = do
1012+ , testAction = do
1014− let block_0 = crop_top 2 $ vert_cat $ map (string def_attr) lorum_ipsum
1013+ let block0 = cropTop 2 $ vertCat $ map (string defAttr) lorumIpsum
1015− block_1 = vert_cat $ map (string def_attr) $ drop (length lorum_ipsum - 2) lorum_ipsum
1014+ block1 = vertCat $ map (string defAttr) $ drop (length lorumIpsum - 2) lorumIpsum
1016− image = block_0 <-> background_fill 10 2 <-> block_1
1015+ image = block0 <-> backgroundFill 10 2 <-> block1
1017− output_image_and_wait image
1016+ outputImageAndWait image
1018− , print_summary = putStr $ [s|
1017+ , printSummary = putStr $ [s|
1019 1. Verify the two text blocks are identical.
1018 1. Verify the two text blocks are identical.
1020 2. press enter.
1019 2. press enter.
1021 3. the display should return to the state before the test.
1020 3. the display should return to the state before the test.
1022|]
1021|]
1023− , confirm_results = generic_output_match_confirm
1022+ , confirmResults = genericOutputMatchConfirm
1024 }
1023 }
1025
1024
1026−vert_crop_test_3 :: Test
1025+vertCropTest3 :: Test
1027−vert_crop_test_3 = Test
1026+vertCropTest3 = Test
1028− { test_name = "Verify top cropping works as expected with double column chars"
1027+ { testName = "Verify top cropping works as expected with double column chars"
1029− , test_ID = "crop_test_0"
1028+ , testID = "cropTest0"
1030− , test_action = do
1029+ , testAction = do
1031− let block_0 = crop_top 2 $ vert_cat $ map (string def_attr) lorum_ipsum_chinese
1030+ let block0 = cropTop 2 $ vertCat $ map (string defAttr) lorumIpsumChinese
1032− block_1 = vert_cat $ map (string def_attr) $ drop (length lorum_ipsum_chinese - 2 ) lorum_ipsum_chinese
1031+ block1 = vertCat $ map (string defAttr) $ drop (length lorumIpsumChinese - 2 ) lorumIpsumChinese
1033− image = block_0 <-> background_fill 10 2 <-> block_1
1032+ image = block0 <-> backgroundFill 10 2 <-> block1
1034− output_image_and_wait image
1033+ outputImageAndWait image
1035− , print_summary = putStr $ [s|
1034+ , printSummary = putStr $ [s|
1036 1. Verify the two text blocks are identical.
1035 1. Verify the two text blocks are identical.
1037 2. press enter.
1036 2. press enter.
1038 3. the display should return to the state before the test.
1037 3. the display should return to the state before the test.
1039|]
1038|]
1040− , confirm_results = generic_output_match_confirm
1039+ , confirmResults = genericOutputMatchConfirm
1041 }
1040 }
1042
1041
1043−horiz_crop_test_0 :: Test
1042+horizCropTest0 :: Test
1044−horiz_crop_test_0 = Test
1043+horizCropTest0 = Test
1045− { test_name = "Verify right cropping works as expected with single column chars"
1044+ { testName = "Verify right cropping works as expected with single column chars"
1046− , test_ID = "crop_test_0"
1045+ , testID = "cropTest0"
1047− , test_action = do
1046+ , testAction = do
1048− let base_image = vert_cat $ map (string def_attr) lorum_ipsum
1047+ let baseImage = vertCat $ map (string defAttr) lorumIpsum
1049− cropped_image = crop_right (image_width base_image `div` 2) base_image
1048+ croppedImage = cropRight (imageWidth baseImage `div` 2) baseImage
1050− image = base_image <-> background_fill 10 2 <-> cropped_image
1049+ image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1051− output_image_and_wait image
1050+ outputImageAndWait image
1052− , print_summary = putStr $ [s|
1051+ , printSummary = putStr $ [s|
1053 1. Verify the bottom text block is about half the width of the top text block.
1052 1. Verify the bottom text block is about half the width of the top text block.
1054 2. press enter.
1053 2. press enter.
1055 3. the display should return to the state before the test.
1054 3. the display should return to the state before the test.
1056|]
1055|]
1057− , confirm_results = generic_output_match_confirm
1056+ , confirmResults = genericOutputMatchConfirm
1058 }
1057 }
1059
1058
1060−horiz_crop_test_1 :: Test
1059+horizCropTest1 :: Test
1061−horiz_crop_test_1 = Test
1060+horizCropTest1 = Test
1062− { test_name = "Verify right cropping works as expected with double column chars"
1061+ { testName = "Verify right cropping works as expected with double column chars"
1063− , test_ID = "crop_test_0"
1062+ , testID = "cropTest0"
1064− , test_action = do
1063+ , testAction = do
1065− let base_image = vert_cat $ map (string def_attr) lorum_ipsum_chinese
1064+ let baseImage = vertCat $ map (string defAttr) lorumIpsumChinese
1066− cropped_image = crop_right (image_width base_image `div` 2) base_image
1065+ croppedImage = cropRight (imageWidth baseImage `div` 2) baseImage
1067− image = base_image <-> background_fill 10 2 <-> cropped_image
1066+ image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1068− output_image_and_wait image
1067+ outputImageAndWait image
1069− , print_summary = putStr $ [s|
1068+ , printSummary = putStr $ [s|
1070 1. Verify the bottom text block is the left half of the top block. Ellipses on the right edge are OK.
1069 1. Verify the bottom text block is the left half of the top block. Ellipses on the right edge are OK.
1071 2. press enter.
1070 2. press enter.
1072 3. the display should return to the state before the test.
1071 3. the display should return to the state before the test.
1073|]
1072|]
1074− , confirm_results = generic_output_match_confirm
1073+ , confirmResults = genericOutputMatchConfirm
1075 }
1074 }
1076
1075
1077−horiz_crop_test_2 :: Test
1076+horizCropTest2 :: Test
1078−horiz_crop_test_2 = Test
1077+horizCropTest2 = Test
1079− { test_name = "Verify left cropping works as expected with single column chars"
1078+ { testName = "Verify left cropping works as expected with single column chars"
1080− , test_ID = "crop_test_0"
1079+ , testID = "cropTest0"
1081− , test_action = do
1080+ , testAction = do
1082− let base_image = vert_cat $ map (string def_attr) lorum_ipsum
1081+ let baseImage = vertCat $ map (string defAttr) lorumIpsum
1083− cropped_image = crop_left (image_width base_image `div` 2) base_image
1082+ croppedImage = cropLeft (imageWidth baseImage `div` 2) baseImage
1084− image = base_image <-> background_fill 10 2 <-> cropped_image
1083+ image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1085− output_image_and_wait image
1084+ outputImageAndWait image
1086− , print_summary = putStr $ [s|
1085+ , printSummary = putStr $ [s|
1087 1. Verify the bottom text block is the right half of the top text block.
1086 1. Verify the bottom text block is the right half of the top text block.
1088 2. press enter.
1087 2. press enter.
1089 3. the display should return to the state before the test.
1088 3. the display should return to the state before the test.
1090|]
1089|]
1091− , confirm_results = generic_output_match_confirm
1090+ , confirmResults = genericOutputMatchConfirm
1092 }
1091 }
1093
1092
1094−horiz_crop_test_3 :: Test
1093+horizCropTest3 :: Test
1095−horiz_crop_test_3 = Test
1094+horizCropTest3 = Test
1096− { test_name = "Verify right cropping works as expected with double column chars"
1095+ { testName = "Verify right cropping works as expected with double column chars"
1097− , test_ID = "crop_test_0"
1096+ , testID = "cropTest0"
1098− , test_action = do
1097+ , testAction = do
1099− let base_image = vert_cat $ map (string def_attr) lorum_ipsum_chinese
1098+ let baseImage = vertCat $ map (string defAttr) lorumIpsumChinese
1100− cropped_image = crop_left (image_width base_image `div` 2) base_image
1099+ croppedImage = cropLeft (imageWidth baseImage `div` 2) baseImage
1101− image = base_image <-> background_fill 10 2 <-> cropped_image
1100+ image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1102− output_image_and_wait image
1101+ outputImageAndWait image
1103− , print_summary = putStr $ [s|
1102+ , printSummary = putStr $ [s|
1104 1. Verify the bottom text block is the right half of the top block. Ellipses on the left edge are OK.
1103 1. Verify the bottom text block is the right half of the top block. Ellipses on the left edge are OK.
1105 2. press enter.
1104 2. press enter.
1106 3. the display should return to the state before the test.
1105 3. the display should return to the state before the test.
1107|]
1106|]
1108− , confirm_results = generic_output_match_confirm
1107+ , confirmResults = genericOutputMatchConfirm
1109 }
1108 }
1110
1109
1111−layer_0 :: Test
1110+layer0 :: Test
1112−layer_0 = Test
1111+layer0 = Test
1113− { test_name = "verify layer 0"
1112+ { testName = "verify layer 0"
1114− , test_ID = "layer_0"
1113+ , testID = "layer0"
1115− , test_action = do
1114+ , testAction = do
1116− let upper_image = vert_cat $ map (string def_attr) lorum_ipsum_chinese
1115+ let upperImage = vertCat $ map (string defAttr) lorumIpsumChinese
1117− lower_image = vert_cat $ map (string def_attr) lorum_ipsum
1116+ lowerImage = vertCat $ map (string defAttr) lorumIpsum
1118− p = pic_for_layers [upper_image, lower_image]
1117+ p = picForLayers [upperImage, lowerImage]
1119− output_pic_and_wait p
1118+ outputPicAndWait p
1120− , print_summary = putStr $ [s|
1119+ , printSummary = putStr $ [s|
1121 1. Verify the text block appears to be Chinese text placed on top Latin text.
1120 1. Verify the text block appears to be Chinese text placed on top Latin text.
1122 2. press enter.
1121 2. press enter.
1123 3. the display should return to the state before the test.
1122 3. the display should return to the state before the test.
1124|]
1123|]
1125− , confirm_results = generic_output_match_confirm
1124+ , confirmResults = genericOutputMatchConfirm
1126 }
1125 }
1127
1126
1128−layer_1 :: Test
1127+layer1 :: Test
1129−layer_1 = Test
1128+layer1 = Test
1130− { test_name = "verify layer 1"
1129+ { testName = "verify layer 1"
1131− , test_ID = "layer_1"
1130+ , testID = "layer1"
1132− , test_action = do
1131+ , testAction = do
1133− let upper_image = vert_cat $ map (string def_attr) lorum_ipsum_chinese
1132+ let upperImage = vertCat $ map (string defAttr) lorumIpsumChinese
1134− block = resize 10 10 upper_image
1133+ block = resize 10 10 upperImage
1135− layer_0 = vert_cat $ map (string def_attr) lorum_ipsum
1134+ layer0 = vertCat $ map (string defAttr) lorumIpsum
1136− layer_1 = char_fill (def_attr `with_back_color` blue) '#' 1000 1000
1135+ layer1 = charFill (defAttr `withBackColor` blue) '#' 1000 1000
1137− cheesy_anim_0 block [layer_0, layer_1]
1136+ cheesyAnim0 block [layer0, layer1]
1138− , print_summary = putStr $ [s|
1137+ , printSummary = putStr $ [s|
1139 1. Verify the text block appears to be Chinese text moving on top a Latin text.
1138 1. Verify the text block appears to be Chinese text moving on top a Latin text.
1140 Which is all on a background of '#' characters over blue.
1139 Which is all on a background of '#' characters over blue.
1141 2. press enter.
1140 2. press enter.
1142 3. the display should return to the state before the test.
1141 3. the display should return to the state before the test.
1143|]
1142|]
1144− , confirm_results = generic_output_match_confirm
1143+ , confirmResults = genericOutputMatchConfirm
1145 }
1144 }
1146
1145
1147−cheesy_anim_0 :: Image -> [Image] -> IO ()
1146+cheesyAnim0 :: Image -> [Image] -> IO ()
1148−cheesy_anim_0 i background = do
1147+cheesyAnim0 i background = do
1149− t <- current_terminal
1148+ t <- standardIOConfig >>= outputForConfig
1150− reserve_display t
1149+ reserveDisplay t
1151− bounds <- display_bounds t
1150+ bounds <- displayBounds t
1152− d <- display_context t bounds
1151+ d <- displayContext t bounds
1153− forM_ [0..100] $ \t -> do
1152+ forM_ [0..2] $ \t -> do
1153+ forM_ [0..100] $ \t -> do
1154+ let i_offset = translate (t `mod` fst bounds)
1155+ (t `div` 2 `mod` snd bounds)
1156+ i
1157+ let pic = picForLayers $ i_offset : background
1158+ outputPicture d pic
1159+ threadDelay 50000
1160+ forM_ [0..100] $ \t -> do
1154− let i_offset = translate (t `mod` region_width bounds)
1161+ let i_offset = translate (t * (-1) `mod` fst bounds)
1155− (t `div` 2 `mod` region_height bounds)
1162+ (t * (-1) `div` 2 `mod` snd bounds)
1156 i
1163 i
1157− let pic = pic_for_layers $ i_offset : background
1164+ let pic = picForLayers $ i_offset : background
1158− output_picture d pic
1165+ outputPicture d pic
1159 threadDelay 50000
1166 threadDelay 50000
1160− release_display t
1167+ releaseDisplay t
1161− release_terminal t
1168+ releaseTerminal t
1162 return ()
1169 return ()
1163
1170
1164−lorum_ipsum :: [String]
1171+lorumIpsum :: [String]
1165−lorum_ipsum = lines [s|
1172+lorumIpsum = lines [s|
1166Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
1173Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
1167totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
1174totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
1168dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
1175dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
@@ -1174,8 +1181,8 @@ commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate v
1174molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
1181molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
1175|]
1182|]
1176
1183
1177−lorum_ipsum_chinese :: [String]
1184+lorumIpsumChinese :: [String]
1178−lorum_ipsum_chinese = lines [s|
1185+lorumIpsumChinese = lines [s|
1179輐銛 螷蟞覮 裌覅詵 暕 鴅噮 槶 惝掭掝 婸媥媕 耏胠臿, 汫汭沎 忕汌卣 蚡袀 僣 蒮 瀁瀎瀊 渮湸湤 緌翢,
1186輐銛 螷蟞覮 裌覅詵 暕 鴅噮 槶 惝掭掝 婸媥媕 耏胠臿, 汫汭沎 忕汌卣 蚡袀 僣 蒮 瀁瀎瀊 渮湸湤 緌翢,
1180腠腶舝 糲蘥蠩 樏殣氀 蒮 蹢鎒 滍 鸄齴 櫧櫋瀩 鬄鵊鵙 莃荶衒, 毸溠 橀 簎艜薤 莃荶衒 翣聜蒢
1187腠腶舝 糲蘥蠩 樏殣氀 蒮 蹢鎒 滍 鸄齴 櫧櫋瀩 鬄鵊鵙 莃荶衒, 毸溠 橀 簎艜薤 莃荶衒 翣聜蒢
1181斔櫅檷 晛桼桾 拻敁柧 犿玒 膣, 墐 笓粊紒 bacon 鼀齕, 蔝蓶蓨 顊顃餭 姴怤 骱 暕 蹢鎒鎛 藒襓謥 鄻鎟霣
1188斔櫅檷 晛桼桾 拻敁柧 犿玒 膣, 墐 笓粊紒 bacon 鼀齕, 蔝蓶蓨 顊顃餭 姴怤 骱 暕 蹢鎒鎛 藒襓謥 鄻鎟霣
test/vty-examples.cabalnew file+138
@@ -0,0 +1,138 @@
1+name: vty-examples
2+version: 5.7
3+license: BSD3
4+license-file: LICENSE
5+author: AUTHORS
6+maintainer: Jonathan Daugherty (cygnus@foobox.com)
7+homepage: https://github.com/jtdaugherty/vty
8+category: User Interfaces
9+synopsis: Examples programs using the vty library.
10+description:
11+ vty is terminal GUI library in the niche of ncurses. It is intended to
12+ be easy to use, have no confusing corner cases, and good support for
13+ common terminal types.
14+ .
15+ vty-interactive-terminal-test - interactive test. Useful for building
16+ a bug report for vtys author.
17+ .
18+ vty-event-echo - view a input event log for vty. Example of
19+ interacting with user.
20+ .
21+ vty-rogue - A bad rogue-like game. Go from the entrance to exit.
22+ Example of layers
23+ .
24+ vty-benchmark - benchmarks vty. A series of tests that push random
25+ pictures to the terminal.
26+ .
27+ &#169; Corey O'Connor; BSD3 license.
28+ .
29+ &#169; Jonathan Daugherty; BSD3 license.
30+cabal-version: >= 1.18.0
31+build-type: Simple
32+
33+source-repository head
34+ type: git
35+ location: https://github.com/jtdaugherty/vty.git
36+
37+executable vty-interactive-terminal-test
38+ main-is: interactive_terminal_test.hs
39+
40+ default-language: Haskell2010
41+ default-extensions: ScopedTypeVariables
42+ ghc-options: -threaded
43+
44+ build-depends: vty >= 5.15,
45+ base >= 4 && < 5,
46+ bytestring,
47+ containers,
48+ data-default,
49+ deepseq,
50+ lens,
51+ mtl,
52+ parallel,
53+ parsec,
54+ string-qq,
55+ terminfo,
56+ text,
57+ unix,
58+ utf8-string,
59+ vector
60+
61+ other-modules: Verify.Data.Terminfo.Parse
62+ Verify
63+
64+executable vty-event-echo
65+ main-is: EventEcho.hs
66+
67+ default-language: Haskell2010
68+ default-extensions: ScopedTypeVariables
69+ FlexibleContexts
70+ ghc-options: -threaded
71+
72+ build-depends: vty >= 5.15,
73+ base >= 4 && < 5,
74+ array,
75+ bytestring,
76+ containers,
77+ data-default,
78+ lens,
79+ mtl,
80+ parallel,
81+ text,
82+ utf8-string,
83+ vector
84+
85+executable vty-rogue
86+ main-is: Rogue.hs
87+
88+ default-language: Haskell2010
89+ default-extensions: ScopedTypeVariables
90+ ghc-options: -threaded
91+
92+ build-depends: vty >= 5.15,
93+ base >= 4 && < 5,
94+ array,
95+ bytestring,
96+ containers,
97+ data-default,
98+ lens,
99+ mtl,
100+ parallel,
101+ random,
102+ text,
103+ utf8-string,
104+ vector
105+
106+executable vty-benchmark
107+ main-is: benchmark.hs
108+
109+ default-language: Haskell2010
110+ default-extensions: ScopedTypeVariables
111+ ghc-options: -threaded
112+
113+ build-depends: vty >= 5.15,
114+ base >= 4 && < 5,
115+ bytestring,
116+ Cabal,
117+ containers,
118+ data-default,
119+ deepseq,
120+ lens,
121+ mtl,
122+ parallel,
123+ parsec,
124+ QuickCheck,
125+ random,
126+ string-qq,
127+ terminfo,
128+ text,
129+ unix,
130+ utf8-string,
131+ vector
132+
133+ other-modules: BenchImageFuzz
134+ BenchNoDiffOpt
135+ BenchRenderChar
136+ BenchVerticalScroll
137+ Verify.Graphics.Vty.Image
138+ Verify.Graphics.Vty.Attributes
test/vty_inline_example.hs+2−2
@@ -3,7 +3,7 @@ import Graphics.Vty.Inline
3
3
4main = do
4main = do
5 putStr "Not styled. "
5 putStr "Not styled. "
6− put_attr_change_ t $ back_color red >> apply_style underline
6+ putAttrChange_ t $ backColor red >> applyStyle underline
7 putStr " Styled! "
7 putStr " Styled! "
8− put_attr_change_ t $ default_all
8+ putAttrChange_ t $ defaultAll
9 putStrLn "Not styled."
9 putStrLn "Not styled."
tools/BuildWidthTable.hsnew file+144
@@ -0,0 +1,144 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+{-# LANGUAGE CPP #-}
3+module Main where
4+
5+import qualified Control.Exception as E
6+import Control.Monad (when)
7+import Data.Maybe (fromMaybe)
8+#if !(MIN_VERSION_base(4,11,0))
9+import Data.Semigroup ((<>))
10+#endif
11+import System.Directory (createDirectoryIfMissing)
12+import System.Environment (getArgs, getProgName)
13+import System.FilePath (takeDirectory)
14+import System.Exit (exitFailure)
15+import System.Console.GetOpt
16+import Text.Read (readMaybe)
17+
18+import Graphics.Vty.Config ( terminalWidthTablePath, currentTerminalName
19+ , vtyConfigPath, addConfigWidthMap
20+ , ConfigUpdateResult(..)
21+ )
22+import Graphics.Vty.UnicodeWidthTable.IO
23+import Graphics.Vty.UnicodeWidthTable.Query
24+
25+data Arg = Help
26+ | OutputPath String
27+ | TableUpperBound String
28+ | UpdateConfig
29+ | VtyConfigPath String
30+ deriving (Eq, Show)
31+
32+options :: Config -> [OptDescr Arg]
33+options config =
34+ [ Option "h" ["help"] (NoArg Help)
35+ "This help output"
36+ , Option "b" ["bound"] (ReqArg TableUpperBound "MAX_CHAR")
37+ ("The maximum Unicode code point to test when building the table " <>
38+ "(default: " <> (show $ fromEnum $ configBound config) <> ")")
39+ , Option "p" ["path"] (ReqArg OutputPath "PATH")
40+ ("The output path to write to (default: " <>
41+ fromMaybe "<none>" (configOutputPath config) <> ")")
42+ , Option "u" ["update-config"] (NoArg UpdateConfig)
43+ "Create or update the Vty configuration file to use the new table (default: no)"
44+ , Option "c" ["config-path"] (ReqArg VtyConfigPath "PATH")
45+ ("Update the specified Vty configuration file path when -u is set (default: " <>
46+ configPath config <> ")")
47+ ]
48+
49+data Config =
50+ Config { configOutputPath :: Maybe FilePath
51+ , configBound :: Char
52+ , configUpdate :: Bool
53+ , configPath :: FilePath
54+ }
55+ deriving (Show)
56+
57+mkDefaultConfig :: IO Config
58+mkDefaultConfig = do
59+ Config <$> terminalWidthTablePath
60+ <*> pure defaultUnicodeTableUpperBound
61+ <*> pure False
62+ <*> vtyConfigPath
63+
64+usage :: IO ()
65+usage = do
66+ config <- mkDefaultConfig
67+ pn <- getProgName
68+ putStrLn $ "Usage: " <> pn <> " [options]"
69+ putStrLn ""
70+ putStrLn "This tool queries the terminal on stdout to determine the widths"
71+ putStrLn "of Unicode characters rendered to the terminal. The resulting data"
72+ putStrLn "is written to a table at the specified output path for later"
73+ putStrLn "loading by Vty-based applications."
74+ putStrLn ""
75+
76+ putStrLn $ usageInfo pn (options config)
77+
78+updateConfigFromArg :: Arg -> Config -> Config
79+updateConfigFromArg Help c =
80+ c
81+updateConfigFromArg UpdateConfig c =
82+ c { configUpdate = True }
83+updateConfigFromArg (VtyConfigPath p) c =
84+ c { configPath = p }
85+updateConfigFromArg (TableUpperBound s) c =
86+ case readMaybe s of
87+ Nothing -> error $ "Invalid table upper bound: " <> show s
88+ Just v -> c { configBound = toEnum v }
89+updateConfigFromArg (OutputPath p) c =
90+ c { configOutputPath = Just p }
91+
92+main :: IO ()
93+main = do
94+ defConfig <- mkDefaultConfig
95+ strArgs <- getArgs
96+ let (args, unused, errors) = getOpt Permute (options defConfig) strArgs
97+
98+ when (not $ null errors) $ do
99+ mapM_ putStrLn errors
100+ exitFailure
101+
102+ when ((not $ null unused) || (Help `elem` args)) $ do
103+ usage
104+ exitFailure
105+
106+ let config = foldr updateConfigFromArg defConfig args
107+
108+ outputPath <- case configOutputPath config of
109+ Nothing -> do
110+ putStrLn "Error: could not obtain terminal width table path"
111+ exitFailure
112+ Just path -> return path
113+
114+ putStrLn "Querying terminal:"
115+ builtTable <- buildUnicodeWidthTable $ configBound config
116+
117+ let dir = takeDirectory outputPath
118+ createDirectoryIfMissing True dir
119+ writeUnicodeWidthTable outputPath builtTable
120+
121+ putStrLn $ "\nOutput table written to " <> outputPath
122+
123+ when (configUpdate config) $ do
124+ let cPath = configPath config
125+ Just tName <- currentTerminalName
126+
127+ result <- E.try $ addConfigWidthMap cPath tName outputPath
128+
129+ case result of
130+ Left (e::E.SomeException) -> do
131+ putStrLn $ "Error updating Vty configuration at " <> cPath <> ": " <>
132+ show e
133+ exitFailure
134+ Right ConfigurationCreated -> do
135+ putStrLn $ "Configuration file created: " <> cPath
136+ Right ConfigurationModified -> do
137+ putStrLn $ "Configuration file updated: " <> cPath
138+ Right (ConfigurationConflict other) -> do
139+ putStrLn $ "Configuration file not updated: uses a different table " <>
140+ "for TERM=" <> tName <> ": " <> other
141+ Right ConfigurationRedundant -> do
142+ putStrLn $ "Configuration file not updated: configuration " <>
143+ cPath <> " already uses table " <> outputPath <>
144+ " for TERM=" <> tName
vty.cabal+278−294
@@ -1,133 +1,171 @@
1name: vty
1name: vty
2−version: 5.0.0
2+version: 5.35.1
3license: BSD3
3license: BSD3
4license-file: LICENSE
4license-file: LICENSE
5author: AUTHORS
5author: AUTHORS
6−maintainer: Corey O'Connor (coreyoconnor@gmail.com)
6+maintainer: Jonathan Daugherty (cygnus@foobox.com)
7−homepage: https://github.com/coreyoconnor/vty
7+homepage: https://github.com/jtdaugherty/vty
8category: User Interfaces
8category: User Interfaces
9synopsis: A simple terminal UI library
9synopsis: A simple terminal UI library
10description:
10description:
11− vty is terminal GUI library in the niche of ncurses. It is intended to be easy to use, have no
11+ vty is terminal GUI library in the niche of ncurses. It is intended to
12− confusing corner cases, and good support for common terminal types.
12+ be easy to use, have no confusing corner cases, and good support for
13+ common terminal types.
13 .
14 .
15+ See the @vty-examples@ package as well as the program
14− Included in the source distribution is a program test/interactive_terminal_test.hs that
16+ @test/interactive_terminal_test.hs@ included in the @vty@ package for
15− demonstrates the various features.
17+ examples on how to use the library.
16 .
18 .
17− If your terminal is not behaving as expected the results of the vty-interactive-terminal-test
19+ Import the "Graphics.Vty" convenience module to get access to the core
18− executable should be sent to the Vty maintainter to aid in debugging the issue.
20+ parts of the library.
19− .
20− Notable infelicities: Sometimes poor efficiency; Assumes UTF-8 character encoding support by the
21− terminal;
22− .
23− Project is hosted on github.com: https:\/\/github.com\/coreyoconnor\/vty
24− .
25− git clone git:\/\/github.com\/coreyoconnor\/vty.git
26 .
21 .
27 &#169; 2006-2007 Stefan O'Rear; BSD3 license.
22 &#169; 2006-2007 Stefan O'Rear; BSD3 license.
28 .
23 .
29 &#169; Corey O'Connor; BSD3 license.
24 &#169; Corey O'Connor; BSD3 license.
25+ .
26+ &#169; Jonathan Daugherty; BSD3 license.
30−cabal-version: >= 1.18.0
27+cabal-version: 1.18
31−build-type: Simple
28+build-type: Simple
32−data-files: README.md,
29+extra-doc-files: README.md,
33− TODO,
34 AUTHORS,
30 AUTHORS,
35− CHANGELOG,
31+ CHANGELOG.md,
36 LICENSE
32 LICENSE
33+tested-with: GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3, GHC==8.6.5
34+
35+source-repository head
36+ type: git
37+ location: https://github.com/jtdaugherty/vty.git
37
38
38library
39library
39 default-language: Haskell2010
40 default-language: Haskell2010
40− default-extensions: ScopedTypeVariables
41− build-depends: base >= 4 && < 5,
41+ build-depends: base >= 4.8 && < 5,
42+ blaze-builder >= 0.3.3.2 && < 0.5,
42 bytestring,
43 bytestring,
43 containers,
44 containers,
44− deepseq >= 1.1 && < 1.4,
45+ deepseq >= 1.1 && < 1.5,
45− ghc-prim,
46+ directory,
46− lens >= 3.9.0.2,
47+ filepath >= 1.0 && < 2.0,
47− -- required for nice installation with yi
48+ microlens < 0.4.13,
49+ microlens-mtl,
50+ microlens-th,
48 hashable >= 1.2,
51 hashable >= 1.2,
49− mtl >= 1.1.1.0 && < 2.2,
52+ mtl >= 1.1.1.0 && < 2.3,
50 parallel >= 2.2 && < 3.3,
53 parallel >= 2.2 && < 3.3,
51 parsec >= 2 && < 4,
54 parsec >= 2 && < 4,
55+ stm,
52− terminfo >= 0.3 && < 0.4,
56+ terminfo >= 0.3 && < 0.5,
57+ transformers >= 0.3.0.0,
53 text >= 0.11.3,
58 text >= 0.11.3,
54 unix,
59 unix,
55− utf8-string >= 0.3 && < 0.4,
60+ utf8-string >= 0.3 && < 1.1,
56− vector >= 0.7
61+ vector >= 0.7,
62+ binary,
63+ ansi-terminal >= 0.10.3
64+
65+ if !impl(ghc >= 8.0)
66+ build-depends: semigroups >= 0.16,
67+ fail
57
68
58 exposed-modules: Graphics.Vty
69 exposed-modules: Graphics.Vty
59 Graphics.Vty.Attributes
70 Graphics.Vty.Attributes
71+ Graphics.Vty.Attributes.Color
72+ Graphics.Vty.Attributes.Color240
73+ Graphics.Vty.Config
60 Graphics.Vty.Error
74 Graphics.Vty.Error
61 Graphics.Vty.Image
75 Graphics.Vty.Image
62 Graphics.Vty.Inline
76 Graphics.Vty.Inline
63 Graphics.Vty.Inline.Unsafe
77 Graphics.Vty.Inline.Unsafe
64 Graphics.Vty.Input
78 Graphics.Vty.Input
65− Graphics.Vty.Input.Data
79+ Graphics.Vty.Input.Events
66− Graphics.Vty.Input.Internal
67− Graphics.Vty.Input.Terminfo
68 Graphics.Vty.Picture
80 Graphics.Vty.Picture
69− Graphics.Vty.Terminal
81+ Graphics.Vty.Output
70 Graphics.Text.Width
82 Graphics.Text.Width
71− -- the modules below are only meant to be used by the tests.
72 Codec.Binary.UTF8.Debug
83 Codec.Binary.UTF8.Debug
73 Data.Terminfo.Parse
84 Data.Terminfo.Parse
74 Data.Terminfo.Eval
85 Data.Terminfo.Eval
75− Data.Marshalling
76 Graphics.Vty.Debug
86 Graphics.Vty.Debug
77− Graphics.Vty.DisplayRegion
78 Graphics.Vty.DisplayAttributes
87 Graphics.Vty.DisplayAttributes
79 Graphics.Vty.Image.Internal
88 Graphics.Vty.Image.Internal
89+ Graphics.Vty.Input.Classify
90+ Graphics.Vty.Input.Classify.Types
91+ Graphics.Vty.Input.Classify.Parse
92+ Graphics.Vty.Input.Loop
93+ Graphics.Vty.Input.Mouse
94+ Graphics.Vty.Input.Focus
95+ Graphics.Vty.Input.Paste
96+ Graphics.Vty.Input.Terminfo
80 Graphics.Vty.PictureToSpans
97 Graphics.Vty.PictureToSpans
81 Graphics.Vty.Span
98 Graphics.Vty.Span
82− Graphics.Vty.Terminal.Mock
99+ Graphics.Vty.Output.Mock
83− Graphics.Vty.Terminal.Interface
100+ Graphics.Vty.Output.Interface
84− Graphics.Vty.Terminal.MacOSX
101+ Graphics.Vty.Output.XTermColor
85− Graphics.Vty.Terminal.XTermColor
86− Graphics.Vty.Terminal.TerminfoBased
102+ Graphics.Vty.Output.TerminfoBased
87−
103+ Graphics.Vty.UnicodeWidthTable.Types
104+ Graphics.Vty.UnicodeWidthTable.IO
105+ Graphics.Vty.UnicodeWidthTable.Query
106+ Graphics.Vty.UnicodeWidthTable.Install
107+
88− other-modules: Graphics.Vty.Attributes.Color
108+ other-modules: Graphics.Vty.Debug.Image
89− Graphics.Vty.Attributes.Color240
109+ Graphics.Vty.Input.Terminfo.ANSIVT
90
110
91 c-sources: cbits/gwinsz.c
111 c-sources: cbits/gwinsz.c
92 cbits/set_term_timing.c
112 cbits/set_term_timing.c
113+ cbits/get_tty_erase.c
93 cbits/mk_wcwidth.c
114 cbits/mk_wcwidth.c
94
115
95 include-dirs: cbits
116 include-dirs: cbits
96
117
97 hs-source-dirs: src
118 hs-source-dirs: src
98
119
99− default-extensions: ScopedTypeVariables
120+ ghc-options: -O2 -funbox-strict-fields -Wall -fspec-constr -fspec-constr-count=10
100− ForeignFunctionInterface
101
121
102− ghc-options: -O2 -funbox-strict-fields -threaded -Wall -fspec-constr -fspec-constr-count=10
122+ ghc-prof-options: -O2 -funbox-strict-fields -caf-all -Wall -fspec-constr -fspec-constr-count=10
103
123
104− ghc-prof-options: -O2 -funbox-strict-fields -threaded -caf-all -Wall -fspec-constr -fspec-constr-count=10
124+executable vty-build-width-table
125+ main-is: BuildWidthTable.hs
126+ hs-source-dirs: tools
105
127
128+ default-language: Haskell2010
106− cc-options: -O2
129+ ghc-options: -threaded -Wall
130+
131+ if !impl(ghc >= 8.0)
132+ build-depends: semigroups >= 0.16
133+
134+ build-depends: vty,
135+ directory,
136+ filepath,
137+ base >= 4.8 && < 5
138+
139+executable vty-mode-demo
140+ main-is: ModeDemo.hs
141+ hs-source-dirs: demos
107
142
108−test-suite verify-attribute-ops
109 default-language: Haskell2010
143 default-language: Haskell2010
110− default-extensions: ScopedTypeVariables
144+ ghc-options: -threaded
111− type: detailed-0.9
145+
112− hs-source-dirs: test
113− test-module: VerifyAttributeOps
114 build-depends: vty,
146 build-depends: vty,
115− Cabal == 1.18.*,
116− QuickCheck >= 2.4,
117− random == 1.0.*,
118− base >= 4 && < 5,
147+ base >= 4.8 && < 5,
119− bytestring,
120 containers,
148 containers,
121− deepseq >= 1.1 && < 1.4,
149+ microlens,
150+ microlens-mtl,
122− mtl >= 1.1.1.0 && < 2.2,
151+ mtl >= 1.1.1.0 && < 2.3
123− text >= 0.11.3,
152+
124− unix,
153+executable vty-demo
125− utf8-string >= 0.3 && < 0.4,
154+ main-is: Demo.hs
126− vector >= 0.7
155+ hs-source-dirs: demos
156+
157+ default-language: Haskell2010
158+ ghc-options: -threaded
159+
160+ build-depends: vty,
161+ base >= 4.8 && < 5,
162+ containers,
163+ microlens,
164+ microlens-mtl,
165+ mtl >= 1.1.1.0 && < 2.3
127
166
128test-suite verify-using-mock-terminal
167test-suite verify-using-mock-terminal
129 default-language: Haskell2010
168 default-language: Haskell2010
130− default-extensions: ScopedTypeVariables
131
169
132 type: detailed-0.9
170 type: detailed-0.9
133
171
@@ -137,63 +175,61 @@ test-suite verify-using-mock-terminal
137
175
138 other-modules: Verify
176 other-modules: Verify
139 Verify.Graphics.Vty.Attributes
177 Verify.Graphics.Vty.Attributes
140− Verify.Graphics.Vty.DisplayRegion
178+ Verify.Graphics.Vty.Prelude
141 Verify.Graphics.Vty.Picture
179 Verify.Graphics.Vty.Picture
142 Verify.Graphics.Vty.Image
180 Verify.Graphics.Vty.Image
143 Verify.Graphics.Vty.Span
181 Verify.Graphics.Vty.Span
144− Verify.Graphics.Vty.Terminal
182+ Verify.Graphics.Vty.Output
145
183
146 build-depends: vty,
184 build-depends: vty,
147− Cabal == 1.18.*,
185+ Cabal >= 1.20,
148− QuickCheck >= 2.4,
186+ QuickCheck >= 2.7,
149− random == 1.0.*,
187+ random >= 1.0 && < 1.3,
150− base >= 4 && < 5,
188+ base >= 4.8 && < 5,
151 bytestring,
189 bytestring,
152 containers,
190 containers,
153− deepseq >= 1.1 && < 1.4,
191+ deepseq >= 1.1 && < 1.5,
154− mtl >= 1.1.1.0 && < 2.2,
192+ mtl >= 1.1.1.0 && < 2.3,
155 text >= 0.11.3,
193 text >= 0.11.3,
156− terminfo,
194+ terminfo >= 0.3 && < 0.5,
157 unix,
195 unix,
158− utf8-string >= 0.3 && < 0.4,
196+ utf8-string >= 0.3 && < 1.1,
159 vector >= 0.7
197 vector >= 0.7
160−
198+
161test-suite verify-terminal
199test-suite verify-terminal
162 default-language: Haskell2010
200 default-language: Haskell2010
163− default-extensions: ScopedTypeVariables
164
201
165 type: detailed-0.9
202 type: detailed-0.9
166
203
167 hs-source-dirs: test
204 hs-source-dirs: test
168
205
169− test-module: VerifyTerminal
206+ test-module: VerifyOutput
170
207
171 other-modules: Verify
208 other-modules: Verify
172 Verify.Graphics.Vty.Attributes
209 Verify.Graphics.Vty.Attributes
173− Verify.Graphics.Vty.DisplayRegion
210+ Verify.Graphics.Vty.Prelude
174 Verify.Graphics.Vty.Picture
211 Verify.Graphics.Vty.Picture
175 Verify.Graphics.Vty.Image
212 Verify.Graphics.Vty.Image
176 Verify.Graphics.Vty.Span
213 Verify.Graphics.Vty.Span
177− Verify.Graphics.Vty.Terminal
214+ Verify.Graphics.Vty.Output
178
215
179 build-depends: vty,
216 build-depends: vty,
180− Cabal == 1.18.*,
217+ Cabal >= 1.20,
181− QuickCheck >= 2.4,
218+ QuickCheck >= 2.7,
182− random == 1.0.*,
219+ random >= 1.0 && < 1.3,
183− base >= 4 && < 5,
220+ base >= 4.8 && < 5,
184 bytestring,
221 bytestring,
185 containers,
222 containers,
186− deepseq >= 1.1 && < 1.4,
223+ deepseq >= 1.1 && < 1.5,
187− mtl >= 1.1.1.0 && < 2.2,
224+ mtl >= 1.1.1.0 && < 2.3,
188− terminfo >= 0.3 && < 0.4,
225+ terminfo >= 0.3 && < 0.5,
189 text >= 0.11.3,
226 text >= 0.11.3,
190 unix,
227 unix,
191− utf8-string >= 0.3 && < 0.4,
228+ utf8-string >= 0.3 && < 1.1,
192 vector >= 0.7
229 vector >= 0.7
193
230
194test-suite verify-display-attributes
231test-suite verify-display-attributes
195 default-language: Haskell2010
232 default-language: Haskell2010
196− default-extensions: ScopedTypeVariables
197
233
198 type: detailed-0.9
234 type: detailed-0.9
199
235
@@ -203,28 +239,28 @@ test-suite verify-display-attributes
203
239
204 other-modules: Verify
240 other-modules: Verify
205 Verify.Graphics.Vty.Attributes
241 Verify.Graphics.Vty.Attributes
206− Verify.Graphics.Vty.DisplayRegion
242+ Verify.Graphics.Vty.DisplayAttributes
243+ Verify.Graphics.Vty.Prelude
207 Verify.Graphics.Vty.Picture
244 Verify.Graphics.Vty.Picture
208 Verify.Graphics.Vty.Image
245 Verify.Graphics.Vty.Image
209 Verify.Graphics.Vty.Span
246 Verify.Graphics.Vty.Span
210
247
211 build-depends: vty,
248 build-depends: vty,
212− Cabal == 1.18.*,
249+ Cabal >= 1.20,
213− QuickCheck >= 2.4,
250+ QuickCheck >= 2.7,
214− random == 1.0.*,
251+ random >= 1.0 && < 1.3,
215− base >= 4 && < 5,
252+ base >= 4.8 && < 5,
216 bytestring,
253 bytestring,
217 containers,
254 containers,
218− deepseq >= 1.1 && < 1.4,
255+ deepseq >= 1.1 && < 1.5,
219− mtl >= 1.1.1.0 && < 2.2,
256+ mtl >= 1.1.1.0 && < 2.3,
220 text >= 0.11.3,
257 text >= 0.11.3,
221 unix,
258 unix,
222− utf8-string >= 0.3 && < 0.4,
259+ utf8-string >= 0.3 && < 1.1,
223 vector >= 0.7
260 vector >= 0.7
224
261
225test-suite verify-empty-image-props
262test-suite verify-empty-image-props
226 default-language: Haskell2010
263 default-language: Haskell2010
227− default-extensions: ScopedTypeVariables
228
264
229 type: detailed-0.9
265 type: detailed-0.9
230
266
@@ -235,22 +271,21 @@ test-suite verify-empty-image-props
235 other-modules: Verify
271 other-modules: Verify
236
272
237 build-depends: vty,
273 build-depends: vty,
238− Cabal == 1.18.*,
274+ Cabal >= 1.20,
239− QuickCheck >= 2.4,
275+ QuickCheck >= 2.7,
240− random == 1.0.*,
276+ random >= 1.0 && < 1.3,
241− base >= 4 && < 5,
277+ base >= 4.8 && < 5,
242 bytestring,
278 bytestring,
243 containers,
279 containers,
244− deepseq >= 1.1 && < 1.4,
280+ deepseq >= 1.1 && < 1.5,
245− mtl >= 1.1.1.0 && < 2.2,
281+ mtl >= 1.1.1.0 && < 2.3,
246 text >= 0.11.3,
282 text >= 0.11.3,
247 unix,
283 unix,
248− utf8-string >= 0.3 && < 0.4,
284+ utf8-string >= 0.3 && < 1.1,
249 vector >= 0.7
285 vector >= 0.7
250
286
251test-suite verify-eval-terminfo-caps
287test-suite verify-eval-terminfo-caps
252 default-language: Haskell2010
288 default-language: Haskell2010
253− default-extensions: ScopedTypeVariables
254
289
255 type: detailed-0.9
290 type: detailed-0.9
256
291
@@ -259,26 +294,26 @@ test-suite verify-eval-terminfo-caps
259 test-module: VerifyEvalTerminfoCaps
294 test-module: VerifyEvalTerminfoCaps
260
295
261 other-modules: Verify
296 other-modules: Verify
262− Verify.Graphics.Vty.Terminal
297+ Verify.Graphics.Vty.Output
263
298
264 build-depends: vty,
299 build-depends: vty,
265− Cabal == 1.18.*,
300+ Cabal >= 1.20,
266− QuickCheck >= 2.4,
301+ QuickCheck >= 2.7,
267− random == 1.0.*,
302+ random >= 1.0 && < 1.3,
268− base >= 4 && < 5,
303+ base >= 4.8 && < 5,
304+ blaze-builder >= 0.3.3.2 && < 0.5,
269 bytestring,
305 bytestring,
270 containers,
306 containers,
271− deepseq >= 1.1 && < 1.4,
307+ deepseq >= 1.1 && < 1.5,
272− mtl >= 1.1.1.0 && < 2.2,
308+ mtl >= 1.1.1.0 && < 2.3,
273− terminfo >= 0.3 && < 0.4,
309+ terminfo >= 0.3 && < 0.5,
274 text >= 0.11.3,
310 text >= 0.11.3,
275 unix,
311 unix,
276− utf8-string >= 0.3 && < 0.4,
312+ utf8-string >= 0.3 && < 1.1,
277 vector >= 0.7
313 vector >= 0.7
278
314
279test-suite verify-image-ops
315test-suite verify-image-ops
280 default-language: Haskell2010
316 default-language: Haskell2010
281− default-extensions: ScopedTypeVariables
282
317
283 type: detailed-0.9
318 type: detailed-0.9
284
319
@@ -291,22 +326,21 @@ test-suite verify-image-ops
291 Verify.Graphics.Vty.Image
326 Verify.Graphics.Vty.Image
292
327
293 build-depends: vty,
328 build-depends: vty,
294− Cabal == 1.18.*,
329+ Cabal >= 1.20,
295− QuickCheck >= 2.4,
330+ QuickCheck >= 2.7,
296− random == 1.0.*,
331+ random >= 1.0 && < 1.3,
297− base >= 4 && < 5,
332+ base >= 4.8 && < 5,
298 bytestring,
333 bytestring,
299 containers,
334 containers,
300− deepseq >= 1.1 && < 1.4,
335+ deepseq >= 1.1 && < 1.5,
301− mtl >= 1.1.1.0 && < 2.2,
336+ mtl >= 1.1.1.0 && < 2.3,
302 text >= 0.11.3,
337 text >= 0.11.3,
303 unix,
338 unix,
304− utf8-string >= 0.3 && < 0.4,
339+ utf8-string >= 0.3 && < 1.1,
305 vector >= 0.7
340 vector >= 0.7
306
341
307test-suite verify-image-trans
342test-suite verify-image-trans
308 default-language: Haskell2010
343 default-language: Haskell2010
309− default-extensions: ScopedTypeVariables
310
344
311 type: detailed-0.9
345 type: detailed-0.9
312
346
@@ -319,22 +353,21 @@ test-suite verify-image-trans
319 Verify.Graphics.Vty.Image
353 Verify.Graphics.Vty.Image
320
354
321 build-depends: vty,
355 build-depends: vty,
322− Cabal == 1.18.*,
356+ Cabal >= 1.20,
323− QuickCheck >= 2.4,
357+ QuickCheck >= 2.7,
324− random == 1.0.*,
358+ random >= 1.0 && < 1.3,
325− base >= 4 && < 5,
359+ base >= 4.8 && < 5,
326 bytestring,
360 bytestring,
327 containers,
361 containers,
328− deepseq >= 1.1 && < 1.4,
362+ deepseq >= 1.1 && < 1.5,
329− mtl >= 1.1.1.0 && < 2.2,
363+ mtl >= 1.1.1.0 && < 2.3,
330 text >= 0.11.3,
364 text >= 0.11.3,
331 unix,
365 unix,
332− utf8-string >= 0.3 && < 0.4,
366+ utf8-string >= 0.3 && < 1.1,
333 vector >= 0.7
367 vector >= 0.7
334
368
335test-suite verify-inline
369test-suite verify-inline
336 default-language: Haskell2010
370 default-language: Haskell2010
337− default-extensions: ScopedTypeVariables
338
371
339 type: detailed-0.9
372 type: detailed-0.9
340
373
@@ -343,25 +376,24 @@ test-suite verify-inline
343 test-module: VerifyInline
376 test-module: VerifyInline
344
377
345 other-modules: Verify
378 other-modules: Verify
346− Verify.Graphics.Vty.Terminal
379+ Verify.Graphics.Vty.Output
347
380
348 build-depends: vty,
381 build-depends: vty,
349− Cabal == 1.18.*,
382+ Cabal >= 1.20,
350− QuickCheck >= 2.4,
383+ QuickCheck >= 2.7,
351− random == 1.0.*,
384+ random >= 1.0 && < 1.3,
352− base >= 4 && < 5,
385+ base >= 4.8 && < 5,
353 bytestring,
386 bytestring,
354 containers,
387 containers,
355− deepseq >= 1.1 && < 1.4,
388+ deepseq >= 1.1 && < 1.5,
356− mtl >= 1.1.1.0 && < 2.2,
389+ mtl >= 1.1.1.0 && < 2.3,
357 text >= 0.11.3,
390 text >= 0.11.3,
358 unix,
391 unix,
359− utf8-string >= 0.3 && < 0.4,
392+ utf8-string >= 0.3 && < 1.1,
360 vector >= 0.7
393 vector >= 0.7
361
394
362test-suite verify-parse-terminfo-caps
395test-suite verify-parse-terminfo-caps
363 default-language: Haskell2010
396 default-language: Haskell2010
364− default-extensions: ScopedTypeVariables
365
397
366 type: detailed-0.9
398 type: detailed-0.9
367
399
@@ -371,26 +403,25 @@ test-suite verify-parse-terminfo-caps
371
403
372 other-modules: Verify
404 other-modules: Verify
373 Verify.Data.Terminfo.Parse
405 Verify.Data.Terminfo.Parse
374− Verify.Graphics.Vty.Terminal
406+ Verify.Graphics.Vty.Output
375
407
376 build-depends: vty,
408 build-depends: vty,
377− Cabal == 1.18.*,
409+ Cabal >= 1.20,
378− QuickCheck >= 2.4,
410+ QuickCheck >= 2.7,
379− random == 1.0.*,
411+ random >= 1.0 && < 1.3,
380− base >= 4 && < 5,
412+ base >= 4.8 && < 5,
381 bytestring,
413 bytestring,
382 containers,
414 containers,
383− deepseq >= 1.1 && < 1.4,
415+ deepseq >= 1.1 && < 1.5,
384− mtl >= 1.1.1.0 && < 2.2,
416+ mtl >= 1.1.1.0 && < 2.3,
385− terminfo >= 0.3 && < 0.4,
417+ terminfo >= 0.3 && < 0.5,
386 text >= 0.11.3,
418 text >= 0.11.3,
387 unix,
419 unix,
388− utf8-string >= 0.3 && < 0.4,
420+ utf8-string >= 0.3 && < 1.1,
389 vector >= 0.7
421 vector >= 0.7
390−
422+
391test-suite verify-simple-span-generation
423test-suite verify-simple-span-generation
392 default-language: Haskell2010
424 default-language: Haskell2010
393− default-extensions: ScopedTypeVariables
394
425
395 type: detailed-0.9
426 type: detailed-0.9
396
427
@@ -400,29 +431,28 @@ test-suite verify-simple-span-generation
400
431
401 other-modules: Verify
432 other-modules: Verify
402 Verify.Graphics.Vty.Attributes
433 Verify.Graphics.Vty.Attributes
403− Verify.Graphics.Vty.DisplayRegion
434+ Verify.Graphics.Vty.Prelude
404 Verify.Graphics.Vty.Picture
435 Verify.Graphics.Vty.Picture
405 Verify.Graphics.Vty.Image
436 Verify.Graphics.Vty.Image
406 Verify.Graphics.Vty.Span
437 Verify.Graphics.Vty.Span
407
438
408 build-depends: vty,
439 build-depends: vty,
409− Cabal == 1.18.*,
440+ Cabal >= 1.20,
410− QuickCheck >= 2.4,
441+ QuickCheck >= 2.7,
411− random == 1.0.*,
442+ random >= 1.0 && < 1.3,
412− base >= 4 && < 5,
443+ base >= 4.8 && < 5,
413 bytestring,
444 bytestring,
414 containers,
445 containers,
415− deepseq >= 1.1 && < 1.4,
446+ deepseq >= 1.1 && < 1.5,
416− mtl >= 1.1.1.0 && < 2.2,
447+ mtl >= 1.1.1.0 && < 2.3,
417 text >= 0.11.3,
448 text >= 0.11.3,
418 unix,
449 unix,
419− utf8-string >= 0.3 && < 0.4,
450+ utf8-string >= 0.3 && < 1.1,
420 vector >= 0.7
451 vector >= 0.7
421−
452+
422−
453+
423test-suite verify-crop-span-generation
454test-suite verify-crop-span-generation
424 default-language: Haskell2010
455 default-language: Haskell2010
425− default-extensions: ScopedTypeVariables
426
456
427 type: detailed-0.9
457 type: detailed-0.9
428
458
@@ -432,29 +462,28 @@ test-suite verify-crop-span-generation
432
462
433 other-modules: Verify
463 other-modules: Verify
434 Verify.Graphics.Vty.Attributes
464 Verify.Graphics.Vty.Attributes
435− Verify.Graphics.Vty.DisplayRegion
465+ Verify.Graphics.Vty.Prelude
436 Verify.Graphics.Vty.Picture
466 Verify.Graphics.Vty.Picture
437 Verify.Graphics.Vty.Image
467 Verify.Graphics.Vty.Image
438 Verify.Graphics.Vty.Span
468 Verify.Graphics.Vty.Span
439
469
440 build-depends: vty,
470 build-depends: vty,
441− Cabal == 1.18.*,
471+ Cabal >= 1.20,
442− QuickCheck >= 2.4,
472+ QuickCheck >= 2.7,
443− random == 1.0.*,
473+ random >= 1.0 && < 1.3,
444− base >= 4 && < 5,
474+ base >= 4.8 && < 5,
445 bytestring,
475 bytestring,
446 containers,
476 containers,
447− deepseq >= 1.1 && < 1.4,
477+ deepseq >= 1.1 && < 1.5,
448− mtl >= 1.1.1.0 && < 2.2,
478+ mtl >= 1.1.1.0 && < 2.3,
449 text >= 0.11.3,
479 text >= 0.11.3,
450 unix,
480 unix,
451− utf8-string >= 0.3 && < 0.4,
481+ utf8-string >= 0.3 && < 1.1,
452 vector >= 0.7
482 vector >= 0.7
453−
483+
454
484
455test-suite verify-layers-span-generation
485test-suite verify-layers-span-generation
456 default-language: Haskell2010
486 default-language: Haskell2010
457− default-extensions: ScopedTypeVariables
458
487
459 type: detailed-0.9
488 type: detailed-0.9
460
489
@@ -464,186 +493,141 @@ test-suite verify-layers-span-generation
464
493
465 other-modules: Verify
494 other-modules: Verify
466 Verify.Graphics.Vty.Attributes
495 Verify.Graphics.Vty.Attributes
467− Verify.Graphics.Vty.DisplayRegion
496+ Verify.Graphics.Vty.Prelude
468 Verify.Graphics.Vty.Picture
497 Verify.Graphics.Vty.Picture
469 Verify.Graphics.Vty.Image
498 Verify.Graphics.Vty.Image
470 Verify.Graphics.Vty.Span
499 Verify.Graphics.Vty.Span
471
500
472 build-depends: vty,
501 build-depends: vty,
473− Cabal == 1.18.*,
502+ Cabal >= 1.20,
474− QuickCheck >= 2.4,
503+ QuickCheck >= 2.7,
475− random == 1.0.*,
504+ random >= 1.0 && < 1.3,
476− base >= 4 && < 5,
505+ base >= 4.8 && < 5,
477 bytestring,
506 bytestring,
478 containers,
507 containers,
479− deepseq >= 1.1 && < 1.4,
508+ deepseq >= 1.1 && < 1.5,
480− mtl >= 1.1.1.0 && < 2.2,
509+ mtl >= 1.1.1.0 && < 2.3,
481 text >= 0.11.3,
510 text >= 0.11.3,
482 unix,
511 unix,
483− utf8-string >= 0.3 && < 0.4,
512+ utf8-string >= 0.3 && < 1.1,
484 vector >= 0.7
513 vector >= 0.7
485
514
486−test-suite verify-utf8-width
515+test-suite verify-color-mapping
487 default-language: Haskell2010
516 default-language: Haskell2010
488− default-extensions: ScopedTypeVariables
489
517
490 type: detailed-0.9
518 type: detailed-0.9
491
519
492 hs-source-dirs: test
520 hs-source-dirs: test
493
521
494− test-module: VerifyUtf8Width
522+ test-module: VerifyColor240
495
523
496 other-modules: Verify
524 other-modules: Verify
497
525
498 build-depends: vty,
526 build-depends: vty,
499− Cabal == 1.18.*,
527+ Cabal >= 1.20,
500− QuickCheck >= 2.4,
528+ QuickCheck >= 2.7,
501− random == 1.0.*,
529+ random >= 1.0 && < 1.3,
502− base >= 4 && < 5,
530+ base >= 4.8 && < 5,
503 bytestring,
531 bytestring,
504 containers,
532 containers,
505− deepseq >= 1.1 && < 1.4,
533+ deepseq >= 1.1 && < 1.5,
506− mtl >= 1.1.1.0 && < 2.2,
534+ mtl >= 1.1.1.0 && < 2.3,
507 text >= 0.11.3,
535 text >= 0.11.3,
508 unix,
536 unix,
509− utf8-string >= 0.3 && < 0.4,
537+ utf8-string >= 0.3 && < 1.1,
510 vector >= 0.7
538 vector >= 0.7
511−
539+
512−test-suite verify-using-mock-input
540+test-suite verify-utf8-width
513 default-language: Haskell2010
541 default-language: Haskell2010
514− default-extensions: ScopedTypeVariables
515
542
516− type: exitcode-stdio-1.0
543+ type: detailed-0.9
517
544
518 hs-source-dirs: test
545 hs-source-dirs: test
519
546
520− main-is: VerifyUsingMockInput.hs
547+ test-module: VerifyUtf8Width
521−
522− build-depends: vty,
523− Cabal == 1.18.*,
524− QuickCheck >= 2.4,
525− smallcheck == 1.*,
526− quickcheck-assertions >= 0.1.1,
527− test-framework == 0.8.*,
528− test-framework-smallcheck == 0.2.*,
529− random == 1.0.*,
530− base >= 4 && < 5,
531− bytestring,
532− containers,
533− deepseq >= 1.1 && < 1.4,
534− mtl >= 1.1.1.0 && < 2.2,
535− terminfo >= 0.3 && < 0.4,
536− text >= 0.11.3,
537− unix,
538− utf8-string >= 0.3 && < 0.4,
539− vector >= 0.7
540−
541− ghc-options: -threaded -Wall
542−
543−executable vty-interactive-terminal-test
544− main-is: interactive_terminal_test.hs
545−
546− default-language: Haskell2010
547− default-extensions: ScopedTypeVariables
548
548
549− hs-source-dirs: test
549+ other-modules: Verify
550
550
551 build-depends: vty,
551 build-depends: vty,
552+ Cabal >= 1.20,
553+ QuickCheck >= 2.7,
554+ random >= 1.0 && < 1.3,
552− base >= 4 && < 5,
555+ base >= 4.8 && < 5,
553 bytestring,
556 bytestring,
554 containers,
557 containers,
555− deepseq >= 1.1 && < 1.4,
558+ deepseq >= 1.1 && < 1.5,
556− ghc-prim,
557− lens,
558− mtl >= 1.1.1.0 && < 2.2,
559+ mtl >= 1.1.1.0 && < 2.3,
559− parallel >= 2.2 && < 3.3,
560− parsec >= 2 && < 4,
561− string-qq,
562− terminfo >= 0.3 && < 0.4,
563 text >= 0.11.3,
560 text >= 0.11.3,
564 unix,
561 unix,
565− utf8-string >= 0.3 && < 0.4,
562+ utf8-string >= 0.3 && < 1.1,
566 vector >= 0.7
563 vector >= 0.7
567
564
568−executable vty-event-echo
565+test-suite verify-using-mock-input
569− main-is: EventEcho.hs
570− hs-source-dirs: test
571−
572 default-language: Haskell2010
566 default-language: Haskell2010
573− default-extensions: ScopedTypeVariables
574
567
575− build-depends: vty,
568+ type: exitcode-stdio-1.0
576− base >= 4 && < 5,
577− array >= 0.4 && < 2.0,
578− bytestring,
579− containers,
580− lens,
581− mtl >= 1.1.1.0 && < 2.2,
582− parallel >= 2.2 && < 3.3,
583− text >= 0.11.3,
584− utf8-string >= 0.3 && < 0.4,
585− vector >= 0.7
586
569
587−executable vty-rouge
588− main-is: Rouge.hs
589 hs-source-dirs: test
570 hs-source-dirs: test
590
571
591− default-language: Haskell2010
572+ main-is: VerifyUsingMockInput.hs
592− default-extensions: ScopedTypeVariables
573+
574+ other-modules: Verify.Graphics.Vty.Output
593
575
594 build-depends: vty,
576 build-depends: vty,
577+ Cabal >= 1.20,
578+ QuickCheck >= 2.7,
579+ smallcheck == 1.*,
580+ quickcheck-assertions >= 0.1.1,
581+ test-framework == 0.8.*,
582+ test-framework-smallcheck == 0.2.*,
583+ random >= 1.0 && < 1.3,
595− base >= 4 && < 5,
584+ base >= 4.8 && < 5,
596− array >= 0.4 && < 2.0,
597 bytestring,
585 bytestring,
598 containers,
586 containers,
599− lens,
587+ deepseq >= 1.1 && < 1.5,
600− mtl >= 1.1.1.0 && < 2.2,
588+ microlens,
601− parallel >= 2.2 && < 3.3,
589+ microlens-mtl,
602− random >= 1.0 && < 2.0,
590+ mtl >= 1.1.1.0 && < 2.3,
591+ stm,
592+ terminfo >= 0.3 && < 0.5,
603 text >= 0.11.3,
593 text >= 0.11.3,
594+ unix,
604− utf8-string >= 0.3 && < 0.4,
595+ utf8-string >= 0.3 && < 1.1,
605 vector >= 0.7
596 vector >= 0.7
606
597
607−executable vty-benchmark
598+ ghc-options: -threaded -Wall
608− main-is: benchmark.hs
609
599
600+test-suite verify-config
610 default-language: Haskell2010
601 default-language: Haskell2010
611− default-extensions: ScopedTypeVariables
602+
603+ type: exitcode-stdio-1.0
612
604
613 hs-source-dirs: test
605 hs-source-dirs: test
614
606
607+ main-is: VerifyConfig.hs
608+
615 build-depends: vty,
609 build-depends: vty,
610+ Cabal >= 1.20,
611+ HUnit,
612+ QuickCheck >= 2.7,
613+ smallcheck == 1.*,
614+ quickcheck-assertions >= 0.1.1,
615+ test-framework == 0.8.*,
616+ test-framework-smallcheck == 0.2.*,
617+ test-framework-hunit,
618+ random >= 1.0 && < 1.3,
616− base >= 4 && < 5,
619+ base >= 4.8 && < 5,
617 bytestring,
620 bytestring,
618− Cabal == 1.18.*,
619 containers,
621 containers,
620− deepseq >= 1.1 && < 1.4,
622+ deepseq >= 1.1 && < 1.5,
621− ghc-prim,
623+ microlens,
622− lens,
624+ microlens-mtl,
623− mtl >= 1.1.1.0 && < 2.2,
625+ mtl >= 1.1.1.0 && < 2.3,
624− parallel >= 2.2 && < 3.3,
625− parsec >= 2 && < 4,
626− QuickCheck >= 2.4,
627− random == 1.0.*,
628 string-qq,
626 string-qq,
629− terminfo >= 0.3 && < 0.4,
627+ terminfo >= 0.3 && < 0.5,
630 text >= 0.11.3,
628 text >= 0.11.3,
631 unix,
629 unix,
632− utf8-string >= 0.3 && < 0.4,
630+ utf8-string >= 0.3 && < 1.1,
633 vector >= 0.7
631 vector >= 0.7
634
632
635−-- ControlTable.hs
633+ ghc-options: -threaded -Wall
636−-- HereDoc.hs
637−-- Test.hs
638−-- Test2.hs
639−-- Verify.hs
640−-- Verify/Data/Terminfo/Parse.hs
641−-- Verify/Graphics/Vty/Attributes.hs
642−-- Verify/Graphics/Vty/DisplayRegion.hs
643−-- Verify/Graphics/Vty/Image.hs
644−-- Verify/Graphics/Vty/Picture.hs
645−-- Verify/Graphics/Vty/Span.hs
646−-- vty_inline_example.hs
647−-- vty_issue_18.hs
648−-- yi_issue_264.hs
649−