on master help sign in
136 files changed+12287−5131
since splitsnapshots
unifiedsplit
.gitignore+15 .travis.yml+109 AUTHORS+22 CHANGELOG−77 CHANGELOG.md+861 LICENSE+2 README−48 README.md+201 TODO−15 cbits/get_tty_erase.c+18 cbits/gwinsz.c+15−6 cbits/gwinsz.h+2−1 cbits/mk_wcwidth.c+128−99 cbits/set_term_timing.c+5−6 ci/build-and-test+44 ci/travis_ci_install+12 ci/travis_ci_script+11 demos/Demo.hs+90 demos/ModeDemo.hs+55 DESIGN → docs/DESIGN scripts/256colres.pl+98 src/Codec/Binary/UTF8/Debug.hs+8−8 src/Codec/Binary/UTF8/Width.hs−40 src/Data/Marshalling.hs−31 src/Data/Sequence/Extra.hs+3−2 src/Data/Terminfo/Eval.hs+112−224 src/Data/Terminfo/Parse.hs+276−248 src/Graphics/Text/Width.hs+64 src/Graphics/Vty.hs+246−124 src/Graphics/Vty/Attributes.hs+165−162 src/Graphics/Vty/Attributes/Color.hs+191 src/Graphics/Vty/Attributes/Color240.hs+298 src/Graphics/Vty/Config.hs+488 src/Graphics/Vty/Debug.hs+24−64 src/Graphics/Vty/Debug/Image.hs+32 src/Graphics/Vty/DisplayAttributes.hs+124−65 src/Graphics/Vty/DisplayRegion.hs−11 src/Graphics/Vty/Error.hs+8 src/Graphics/Vty/Image.hs+353−301 src/Graphics/Vty/Image/Internal.hs+285 src/Graphics/Vty/Inline.hs+108−40 src/Graphics/Vty/Inline/Unsafe.hs+59 src/Graphics/Vty/Input.hs+176 src/Graphics/Vty/Input/Classify.hs+110 src/Graphics/Vty/Input/Classify/Parse.hs+61 src/Graphics/Vty/Input/Classify/Types.hs+25 src/Graphics/Vty/Input/Events.hs+83 src/Graphics/Vty/Input/Focus.hs+49 src/Graphics/Vty/Input/Loop.hs+286 src/Graphics/Vty/Input/Mouse.hs+163 src/Graphics/Vty/Input/Paste.hs+41 src/Graphics/Vty/Input/Terminfo.hs+186 src/Graphics/Vty/Input/Terminfo/ANSIVT.hs+88 src/Graphics/Vty/LLInput.hs−226 src/Graphics/Vty/Output.hs+107 src/Graphics/Vty/Output/Interface.hs+331 src/Graphics/Vty/Output/Mock.hs+90 src/Graphics/Vty/Output/TerminfoBased.hs+606 src/Graphics/Vty/Output/XTermColor.hs+131 src/Graphics/Vty/Picture.hs+116−57 src/Graphics/Vty/PictureToSpans.hs+344−3 src/Graphics/Vty/Span.hs+119−213 src/Graphics/Vty/Terminal.hs−129 src/Graphics/Vty/Terminal/Debug.hs−117 src/Graphics/Vty/Terminal/Generic.hs−405 src/Graphics/Vty/Terminal/MacOSX.hs−95 src/Graphics/Vty/Terminal/TerminfoBased.hs−412 src/Graphics/Vty/Terminal/XTermColor.hs−94 src/Graphics/Vty/UnicodeWidthTable/IO.hs+105 src/Graphics/Vty/UnicodeWidthTable/Install.hs+129 src/Graphics/Vty/UnicodeWidthTable/Query.hs+79 src/Graphics/Vty/UnicodeWidthTable/Types.hs+29 test/Bench.hs−62 test/Bench2.hs−30 test/BenchImageFuzz.hs+29 test/BenchNoDiffOpt.hs+38 test/BenchRenderChar.hs+32−26 test/BenchVerticalScroll.hs+59 test/ControlTable.hs+4−4 test/EventEcho.hs+40 test/HereDoc.hs+3−5 test/vty_issue_18.hs → test/Issue18.hs+4−6 test/Issue76.hs+17 test/LICENSE+31 test/Makefile+20−8 test/MinimalExample.hs+14 test/Rogue.hs+175 test/Setup.lhs+3 test/Test.hs−44 test/Test2.hs+1 test/Verify.hs+59−55 test/Verify/Data/Terminfo/Parse.hs+61−63 test/Verify/Graphics/Vty/Attributes.hs+52−19 test/Verify/Graphics/Vty/DisplayAttributes.hs+5−5 test/Verify/Graphics/Vty/DisplayRegion.hs−27 test/Verify/Graphics/Vty/Image.hs+178−51 test/Verify/Graphics/Vty/Output.hs+59 test/Verify/Graphics/Vty/Picture.hs+30−28 test/Verify/Graphics/Vty/Prelude.hs+24 test/Verify/Graphics/Vty/Span.hs+25−17 test/VerifyColor240.hs+269 test/VerifyConfig.hs+44 test/VerifyCropSpanGeneration.hs+92 test/verify_display_attributes.hs → test/VerifyDisplayAttributes.hs+3−3 test/VerifyEmptyImageProps.hs+14 test/VerifyEvalTerminfoCaps.hs+92 test/VerifyImageOps.hs+174 test/VerifyImageTrans.hs+34 test/VerifyInline.hs+31 test/VerifyLayersSpanGeneration.hs+130 test/VerifyOutput.hs+58 test/VerifyParseTerminfoCaps.hs+101 test/VerifySimpleSpanGeneration.hs+188 test/VerifyUsingMockInput.hs+223 test/VerifyUsingMockTerminal.hs+108 test/VerifyUtf8Width.hs+30 test/yi_issue_264.hs → test/YiIssue264.hs+1 test/benchmark.hs+56 test/current_benchmark_results.txt+40 test/interactive_terminal_test.hs+739−450 test/verify_attribute_ops.hs−8 test/verify_debug_terminal.hs−115 test/verify_empty_image_props.hs−13 test/verify_eval_terminfo_caps.hs−129 test/verify_image_ops.hs−124 test/verify_image_trans.hs−31 test/verify_inline.hs−11 test/verify_parse_terminfo_caps.hs−158 test/verify_picture_ops.hs−9 test/verify_picture_to_span.hs−10 test/verify_span_ops.hs−179 test/verify_utf8_width.hs−17 test/vty-examples.cabal+138 test/vty_inline_example.hs+2−5 tools/BuildWidthTable.hs+144 vty.cabal+627−96
.gitignorenew file+15
@@ -0,0 +1,15 @@
1+*.o
2+*.swp
3+*.hi
4+*.prof
5+Setup
6+dist
7+dist-newstyle
8+cabal.project.local
9+.ghc.environment.*
10+test/benchmark
11+.hpc
12+.cabal-sandbox
13+cabal.sandbox.config
14+test/cabal.sandbox.config
15+.stack-work
.travis.ymlnew file+109
@@ -0,0 +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+#
7+language: c
8+sudo: 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
AUTHORSnew file+22
@@ -0,0 +1,22 @@
1+The following people should be thanked for contributing to the vty
2+library:
3+
4+ * Andrea Vezzosi
5+ * Corey O'Connor
6+ * Emily Backes
7+ * Josef Svenningsson
8+ * Nicolas Pouillard
9+ * Roman Cheplyaka
10+ * Stefan O'Rear
11+ * Yusaku Hashimoto
12+ * allan.clark
13+ * gwern0
14+ * jeanphilippe.bernardy
15+ * m.niloc
16+ * mikesteele81
17+ * Mikolaj Konarski
18+ * Eyal Lotem
19+ * Yoshikuni Jujo
20+ * Dmitry Ivanov
21+
22+Plus others.. Check the git log and CHANGELOG.md for a full list.
CHANGELOGdeleted−77
@@ -1,77 +0,0 @@
1−4.2.1.0
2− * API changes:
3− * Attr record accessor fore_color changed to attr_fore_color
4− * Attr record accessor back_color changed to attr_back_color
5− * Attr record accessor style changed to attr_style
6− * Added an "inline" display attribute changing DSL:
7− * put_attr_change applies a display attribute change immediately to a terminal
8− * For instance, can be used to change the display attrbiutes of text output via putStrLn
9− and putStr. EX: "put_attr_change $ back_color red" will set the
10− background color to red.
11− * Changes do not apply to a Picture output via output_picture.
12− * See Graphics.Vty.Inline
13− * Moved all IO actions into any monad an instance of MonadIO
14−
15−4.0.0.1
16− * binding for mk_wcswidth was incorrect. Most platforms just magically worked due to
17− coincidence.
18−
19−4.0.0
20− * API changes:
21− * "getSize" has been removed. Use "terminal vty >>= display_bounds" where "vty" is an
22− instance of the Vty data structure.
23− * added a "terminal" field to the Vty data structure. Accesses the TerminalHandle associated
24− with the Vty instance.
25− * Graphics.Vty.Types has undergone a number of changes. Summary:
26− * Partitioned into Graphics.Vty.Attributes for display attributes. Graphics.Vty.Image for
27− image combinators. Graphics.Vty.Picture for final picture construction.
28− * Graphics.Vty.Attributes:
29− * "setFG" and "setBG" are now "with_fore_color" and "with_back_color"
30− * All other "set.." equations similarly replaced.
31− * "attr" is now "def_attr", short for "default display attributes" Also added a
32− "current_attr" for "currently applied display attributes"
33− * Graphics.Vty.Image:
34− * "horzcat" is now "horiz_cat"
35− * "vertcat" is now "vert_cat"
36− * "renderBS" is now "utf8_bytestring"
37− * "renderChar" is now "char"
38− * "renderFill" is now "char_fill"
39− * added a "utf8_string" and "string" (AKA "iso_10464_string") for UTF-8 encoded Strings
40− and ISO-10464 encoded Strings. String literals in GHC have an ISO-10464 runtime
41− representation.
42− * Graphics.Vty.Picture:
43− * exports Graphics.Vty.Image
44− * "pic" is now "pic_for_image"
45− * added API for setting background fill pattern.
46− * Completely rewritten output backend.
47− * Efficient, scanline style output span generator. Has not been fully optimized, but good
48− enough.
49− * The details required to display the desired picture on a terminal are well encapsulated.
50− * Terminfo based display terminal implementation. With specialized derivitives for xterm,
51− Terminal.app, and iTerm.app.
52− * Attempts to robustly handle even terminals that don't support all display attributes.
53− * I've tested the following terminals with success: iTerm.app, Terminal.app, xterm,
54− rxvt, mlterm, Eterm, gnome-terminal, konsole, screen, linux vty. Hopefully you will be
55− as successfull.
56− * Improved unicode support. Double wide characters will display as expected.
57− * 256 color support. See Graphics.Vty.Attributes.Color240. The actual output color is adjusted
58− according to the number of colors the terminal supports.
59− * The Graphics.Vty.Image combinators no longer require matching dimensions to arguments.
60− Unspecified areas are filled in with a user-customizable background pattern. See
61− Graphics.Vty.Picture.
62− * output images are always cropped to display size.
63− * Significant code coverage by QuickCheck tests. An interactive test for those final properties
64− that couldn't be automatically verified.
65−
66− issues resolved:
67− * "gnome terminal displays non-basic attributes as strikethrough"
68− * http://trac.haskell.org/vty/ticket/14
69− * "Multi-byte characters are not displayed correctly on update"
70− * http://trac.haskell.org/vty/ticket/10
71− * "Redraw does not handle rendering a line that extends beyond screen width characters"
72− * http://trac.haskell.org/vty/ticket/13
73− * "The <|> and <-> combinators should be more forgiving of mismatched dimensions"
74− * http://trac.haskell.org/vty/ticket/9
75− * "256-color support"
76− * http://trac.haskell.org/vty/ticket/19
77−
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+
13Copyright Stefan O'Rear 2006, Corey O'Connor 2008, Corey O'Connor 2009
24
35All rights reserved.
READMEdeleted−48
@@ -1,48 +0,0 @@
1−vty is a terminal interface library.
2−
3−Vty currently provides:
4−
5−* Automatic handling of window resizes.
6−
7−* Supports Unicode characters on output, automatically setting and
8− resetting UTF-8 mode for xterm. Other terminals are assumed to support
9−
10−* Efficient output.
11−
12−* Minimizes repaint area, thus virtually eliminating the flicker
13− problem that plagues ncurses programs.
14−
15−* A pure, compositional interface for efficiently constructing display
16− images.
17−
18−* Automatically decodes keyboard keys into (key,[modifier]) tuples.
19−
20−* Automatically supports refresh on Ctrl-L.
21−
22−* Automatically supports timeout after 50ms for lone ESC (a barely
23− noticable delay)
24−
25−* Interface is designed for relatively easy compatible extension.
26−
27−* Supports all ANSI SGR-modes (defined in console_codes(4)) with
28− a type-safe interface.
29−
30−* Properly handles cleanup.
31−
32−Current disadvantages:
33−
34−* The character encoding of the output terminal is assumed to be UTF-8.
35−
36−* Minimal support for special keys on terminals other than the
37− linux-console. (F1-5 and arrow keys should work, but anything
38− shifted isn't likely to.)
39−
40−* Uses the TIOCGWINSZ ioctl to find the current window size, which
41− appears to be limited to Linux and *BSD.
42−
43−darcs get --tag=vty-4.0.0.1 http://code.haskell.org/vty/
44−
45−To compile the demonstration program: ghc --make test/Test.hs gwinsz.c
46−
47−The main documentation consists of the haddock-comments and the demonstration
48−program
README.mdnew file+201
@@ -0,0 +1,201 @@
1+[![Build Status](https://travis-ci.org/jtdaugherty/vty.png)](https://travis-ci.org/jtdaugherty/vty)
2+
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.
6+
7+Install via `git` with:
8+
9+```
10+git clone git://github.com/jtdaugherty/vty.git
11+```
12+
13+Install via `cabal` with:
14+
15+```
16+cabal install vty
17+```
18+
19+# Features
20+
21+* Supports a large number of terminals, i.e., vt100, ansi, hurd, linux,
22+ `screen`, etc., or anything with a sufficient terminfo entry.
23+
24+* Automatically handles window resizes.
25+
26+* Supports Unicode output on terminals with UTF-8 support.
27+
28+* Provides an efficient output algorithm. Output buffering and terminal
29+ state changes are minimized.
30+
31+* Minimizes repaint area, which virtually eliminates the flicker
32+ problems that plague ncurses programs.
33+
34+* Provides a pure, compositional interface for efficiently constructing
35+ display images.
36+
37+* Automatically decodes keyboard keys into (key,[modifier]) tuples.
38+
39+* Automatically supports refresh on Ctrl-L.
40+
41+* Supports a keypress timeout after for lone ESC. The timeout is
42+ customizable.
43+
44+* Provides extensible input and output interfaces.
45+
46+* Supports ANSI graphics modes (SGR as defined in `console_codes(4)`)
47+ with a type-safe interface and graceful fallback for terminals
48+ with limited or nonexistent support for such modes.
49+
50+* Properly handles cleanup (but not due to signals).
51+
52+* Provides a comprehensive test suite.
53+
54+* Supports "normal" and "extended" (SGR) mouse modes as described at
55+ http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
56+
57+* Supports bracketed paste mode as described at
58+ http://cirw.in/blog/bracketed-paste
59+
60+* Supports multi-column Unicode characters such as emoji characters. In
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.
70+
71+# Platform Support
72+
73+## Posix Terminals
74+
75+For the most part, Vty uses `terminfo` to determine terminal protocol
76+with some special rules to handle some omissions from `terminfo`.
77+
78+## Windows
79+
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.
172+
173+# Known Issues
174+
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.
178+
179+* STOP, TERM and INT signals are not handled.
180+
181+* The character encoding of the terminal is assumed to be UTF-8 if
182+ unicode is used.
183+
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.
188+
189+* Vty uses the `TIOCGWINSZ` ioctl to find the current window size, which
190+ appears to be limited to Linux and BSD.
191+
192+# Further Reading
193+
194+Good sources of documentation for terminal programming are:
195+
196+* https://github.com/b4winckler/vim/blob/master/src/term.c
197+* http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
198+* http://ulisse.elettra.trieste.it/services/doc/serial/config.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 @@
11#include <sys/ioctl.h>
22
3−unsigned long c_get_window_size(void) {
4− struct winsize w;
5− if (ioctl (0, TIOCGWINSZ, &w) >= 0)
6− return (w.ws_row << 16) + w.ws_col;
7− else
8− return 0x190050;
3+unsigned long vty_c_get_window_size(int fd) {
4+ struct winsize w;
5+ if (ioctl (fd, TIOCGWINSZ, &w) >= 0)
6+ return (w.ws_row << 16) + w.ws_col;
7+ else
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+ }
918}
cbits/gwinsz.h+2−1
@@ -1 +1,2 @@
1−unsigned long 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+128−99
@@ -59,7 +59,29 @@
5959 * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
6060 */
6161
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;
6385
6486struct interval {
6587 int first;
@@ -67,7 +89,7 @@ struct interval {
6789};
6890
6991/* auxiliary function for binary search in interval table */
70−static int bisearch(wchar_t ucs, const struct interval *table, int max) {
92+static int vty_bisearch(HsChar ucs, const struct interval *table, int max) {
7193 int min = 0;
7294 int mid;
7395
@@ -119,7 +141,7 @@ static int bisearch(wchar_t ucs, const struct interval *table, int max) {
119141 * in ISO 10646.
120142 */
121143
122−int mk_wcwidth(wchar_t ucs)
144+static HsInt builtin_wcwidth(HsChar ucs)
123145{
124146 /* sorted list of non-overlapping intervals of non-spacing characters */
125147 /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
@@ -181,13 +203,13 @@ int mk_wcwidth(wchar_t ucs)
181203 return -1;
182204
183205 /* binary search in table of non-spacing characters */
184− if (bisearch(ucs, combining,
185− sizeof(combining) / sizeof(struct interval) - 1))
206+ if (vty_bisearch(ucs, combining,
207+ sizeof(combining) / sizeof(struct interval) - 1))
186208 return 0;
187209
188210 /* if we arrive here, ucs is not a combining or C0/C1 control character */
189211
190− return 1 +
212+ return 1 +
191213 (ucs >= 0x1100 &&
192214 (ucs <= 0x115f || /* Hangul Jamo init. consonants */
193215 ucs == 0x2329 || ucs == 0x232a ||
@@ -203,107 +225,114 @@ int mk_wcwidth(wchar_t ucs)
203225 (ucs >= 0x30000 && ucs <= 0x3fffd)));
204226}
205227
206−
207−int mk_wcswidth(const wchar_t *pwcs, size_t n)
228+// Return the width, in terminal cells, of the specified character.
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)
208235{
209− int w, width = 0;
210−
211− for (;*pwcs && n-- > 0; pwcs++)
212− if ((w = mk_wcwidth(*pwcs)) < 0)
213− return -1;
214− else
215− width += w;
216−
217− return width;
236+ if (custom_table_ready) {
237+ if ((ch >= 0) && (ch < custom_table_size)) {
238+ return custom_table[ch];
239+ } else {
240+ return -1;
241+ }
242+ } else {
243+ return builtin_wcwidth(ch);
244+ }
218245}
219246
220−
221−/*
222− * The following functions are the same as mk_wcwidth() and
223− * mk_wcswidth(), except that spacing characters in the East Asian
224− * Ambiguous (A) category as defined in Unicode Technical Report #11
225− * have a column width of 2. This variant might be useful for users of
226− * CJK legacy encodings who want to migrate to UCS without changing
227− * the traditional terminal character-width behaviour. It is not
228− * otherwise recommended for general use.
229− */
230−int mk_wcwidth_cjk(wchar_t ucs)
247+// Initialize a custom character width table.
248+//
249+// This allocates a new character width table of the specified size
250+// (in characters). If a custom table has already been allocated, this
251+// returns 1. Otherwise it allocates a new table, initializes all of its
252+// entries to 1, and returns zero.
253+//
254+// Note that this does *not* mark the table as ready for use. Until the
255+// table is marked ready, it will not be used by vty_mk_wcwidth. To mark
256+// the table as ready, call vty_activate_custom_table() after the table
257+// has been set up with calls to vty_set_custom_table_range.
258+int vty_init_custom_table(int size)
231259{
232− /* sorted list of non-overlapping intervals of East Asian Ambiguous
233− * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
234− static const struct interval ambiguous[] = {
235− { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
236− { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
237− { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
238− { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
239− { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
240− { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
241− { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
242− { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
243− { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
244− { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
245− { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
246− { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
247− { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
248− { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
249− { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
250− { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
251− { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
252− { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
253− { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
254− { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
255− { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
256− { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
257− { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
258− { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
259− { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
260− { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
261− { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
262− { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
263− { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
264− { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
265− { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
266− { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
267− { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
268− { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
269− { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
270− { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
271− { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
272− { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
273− { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
274− { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
275− { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
276− { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
277− { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
278− { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
279− { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
280− { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
281− { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
282− { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
283− { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
284− { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
285− { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
286− { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
287− };
288−
289− /* binary search in table of non-spacing characters */
290− if (bisearch(ucs, ambiguous,
291− sizeof(ambiguous) / sizeof(struct interval) - 1))
292− return 2;
293−
294− return mk_wcwidth(ucs);
260+ if (custom_table == NULL) {
261+ if (size > 0 && size <= MAX_CUSTOM_TABLE_SIZE) {
262+ custom_table_ready = 0;
263+ custom_table = malloc(size);
264+ memset(custom_table, 1, size);
265+ custom_table_size = size;
266+ return 0;
267+ } else {
268+ return 1;
269+ }
270+ } else {
271+ return 1;
272+ }
295273}
296274
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+}
297299
298−int 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()
299309{
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+}
301317
302− for (;*pwcs && n-- > 0; pwcs++)
303− if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
304− return -1;
305− else
306− width += w;
318+// Returns whether a custom character width table has been marked ready.
319+int vty_custom_table_ready()
320+{
321+ return custom_table_ready;
322+}
307323
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+ }
309338}
cbits/set_term_timing.c+5−6
@@ -3,12 +3,11 @@
33#include <unistd.h>
44#include <stdlib.h>
55
6−void set_term_timing(void)
6+void vty_set_term_timing(int fd, int vmin, int vtime)
77{
88 struct termios trm;
9− tcgetattr(STDIN_FILENO, &trm);
10− trm.c_cc[VMIN] = 0;
11− trm.c_cc[VTIME] = 0;
12− tcsetattr(STDIN_FILENO, TCSANOW, &trm);
9+ tcgetattr(fd, &trm);
10+ trm.c_cc[VMIN] = vmin;
11+ trm.c_cc[VTIME] = vtime;
12+ tcsetattr(fd, TCSANOW, &trm);
1313}
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.plnew file+98
@@ -0,0 +1,98 @@
1+#! /usr/bin/perl
2+# $XTermId: 256colres.pl,v 1.16 2007/06/08 23:58:37 tom Exp $
3+# -----------------------------------------------------------------------------
4+# this file is part of xterm
5+#
6+# Copyright 1999-2002,2007 by Thomas E. Dickey
7+#
8+# All Rights Reserved
9+#
10+# Permission is hereby granted, free of charge, to any person obtaining a
11+# copy of this software and associated documentation files (the
12+# "Software"), to deal in the Software without restriction, including
13+# without limitation the rights to use, copy, modify, merge, publish,
14+# distribute, sublicense, and/or sell copies of the Software, and to
15+# permit persons to whom the Software is furnished to do so, subject to
16+# the following conditions:
17+#
18+# The above copyright notice and this permission notice shall be included
19+# in all copies or substantial portions of the Software.
20+#
21+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24+# IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
25+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28+#
29+# Except as contained in this notice, the name(s) of the above copyright
30+# holders shall not be used in advertising or otherwise to promote the
31+# sale, use or other dealings in this Software without prior written
32+# authorization.
33+# -----------------------------------------------------------------------------
34+
35+# Construct a header file defining default resources for the 256-color model
36+# of xterm. This is modeled after the 256colors2.pl script.
37+
38+# use the resources for colors 0-15 - usually more-or-less a
39+# reproduction of the standard ANSI colors, but possibly more
40+# pleasing shades
41+
42+use strict;
43+
44+our ( $line1, $line2, $line3 );
45+our ( $red, $green, $blue, $gray );
46+our ( $level, $code, @steps );
47+
48+print <<EOF;
49+{-
50+ - This header file was generated by $0
51+ -}
52+module Graphics.Vty.Attributes.Color240 where
53+
54+import Graphics.Vty.Attributes.Color
55+
56+import Text.Printf
57+
58+-- | RGB color to 240 color palette.
59+--
60+-- generated from 256colres.pl which is forked from xterm 256colres.pl
61+-- todo: all values get clamped high.
62+rgbColor :: Integral i => i -> i -> i -> Color
63+rgbColor r g b
64+ | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"
65+EOF
66+
67+my $grey_line=" | r == %d && g == %d && b == %d = Color240 %d\n";
68+my $rgb_line=" | r <= %d && g <= %d && b <= %d = Color240 %d\n";
69+
70+# colors 216-239 are a grayscale ramp, intentionally leaving out
71+# black and white
72+for ($gray = 0; $gray < 24; $gray++) {
73+ $level = ($gray * 10) + 8;
74+ $code = 216 + $gray;
75+ printf($grey_line, $level, $level, $level, $code);
76+}
77+
78+# colors 16-231 are a 6x6x6 color cube
79+for ($red = 0; $red < 6; $red++) {
80+ for ($green = 0; $green < 6; $green++) {
81+ for ($blue = 0; $blue < 6; $blue++) {
82+ $code = ($red * 36) + ($green * 6) + $blue;
83+ printf($rgb_line,
84+ ($red ? ($red * 40 + 55) : 0),
85+ ($green ? ($green * 40 + 55) : 0),
86+ ($blue ? ($blue * 40 + 55) : 0),
87+ $code);
88+ }
89+ }
90+}
91+
92+print <<EOF;
93+ | otherwise = error (printf "RGB color %d %d %d does not map to 240 palette."
94+ (fromIntegral r :: Int)
95+ (fromIntegral g :: Int)
96+ (fromIntegral b :: Int))
97+
98+EOF
src/Codec/Binary/UTF8/Debug.hs+8−8
@@ -1,6 +1,6 @@
1+{-# OPTIONS_HADDOCK hide #-}
12-- Copyright 2009 Corey O'Connor
2−module Codec.Binary.UTF8.Debug
3− where
3+module Codec.Binary.UTF8.Debug where
44
55import Codec.Binary.UTF8.String ( encode )
66
@@ -8,10 +8,10 @@ import Data.Word
88
99import Numeric
1010
11−-- Converts an array of ISO-10646 characters (Char type) to an array of Word8 bytes that is the
12−-- corresponding UTF8 byte sequence
13−utf8_from_iso :: Integral i => [i] -> [Word8]
14−utf8_from_iso = encode . map toEnum
15−
16−pp_utf8 = print . map (\f -> f "") . map showHex . utf8_from_iso
11+-- | Converts an array of ISO-10646 characters (Char type) to an array
12+-- of Word8 bytes that is the corresponding UTF8 byte sequence
13+utf8FromIso :: [Int] -> [Word8]
14+utf8FromIso = encode . map toEnum
1715
16+ppUtf8 :: [Int] -> IO ()
17+ppUtf8 = print . map (`showHex` "") . utf8FromIso
src/Codec/Binary/UTF8/Width.hsdeleted−40
@@ -1,40 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# OPTIONS_GHC -D_XOPEN_SOURCE -fno-cse #-}
3−{-# LANGUAGE ForeignFunctionInterface, BangPatterns #-}
4−{-# INCLUDE <wchar.h> #-}
5−module Codec.Binary.UTF8.Width ( wcwidth
6− , wcswidth
7− )
8− where
9−
10−import Foreign.C.Types
11−import Foreign.C.String
12−import Foreign.Storable
13−import Foreign.Ptr
14−
15−-- import Numeric ( showHex )
16−
17−import System.IO.Unsafe
18−
19−wcwidth :: Char -> Int
20−wcwidth c = unsafePerformIO (withCWString [c] $! \ws -> do
21− wc <- peek ws
22− -- putStr $ "wcwidth(0x" ++ showHex (fromEnum wc) "" ++ ")"
23− let !w = fromIntegral $! wcwidth' wc
24− -- putStrLn $ " -> " ++ show w
25− return w
26− )
27−{-# NOINLINE wcwidth #-}
28−
29−foreign import ccall unsafe "mk_wcwidth" wcwidth' :: CWchar -> CInt
30−
31−wcswidth :: String -> Int
32−wcswidth str = unsafePerformIO (withCWStringLen str $! \(ws, ws_len) -> do
33− -- putStr $ "wcswidth(...)"
34− let !w = fromIntegral $! wcswidth' ws (fromIntegral ws_len)
35− -- putStrLn $ " -> " ++ show w
36− return w
37− )
38−{-# NOINLINE wcswidth #-}
39−
40−foreign import ccall unsafe "mk_wcswidth" wcswidth' :: Ptr CWchar -> CSize -> CInt
src/Data/Marshalling.hsdeleted−31
@@ -1,31 +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.Marshal.Array
20−import Foreign.Storable
21−
22−type OutputBuffer = Ptr Word8
23−
24−string_to_bytes :: String -> [Word8]
25−string_to_bytes str = map (toEnum . fromEnum) str
26−
27−serialize_bytes :: MonadIO m => [Word8] -> OutputBuffer -> m OutputBuffer
28−serialize_bytes bytes !out_ptr = do
29− liftIO $! pokeArray out_ptr bytes
30− return $! out_ptr `plusPtr` ( length bytes )
31−
src/Data/Sequence/Extra.hs+3−2
@@ -1,3 +1,4 @@
1+{-# OPTIONS_HADDOCK hide #-}
12-- Copyright 2009 Corey O'Connor
23module Data.Sequence.Extra where
34
@@ -6,7 +7,7 @@ import Data.Sequence
67import Control.Parallel.Strategies
78
89instance NFData a => NFData (Seq a) where
9− rnf = \v -> rnf' (viewl v)
10− where
10+ rnf = rnf' . viewl
11+ where
1112 rnf' EmptyL = ()
1213 rnf' (a :< r) = rnf a >| rnf' (viewl r)
src/Data/Terminfo/Eval.hs+112−224
@@ -1,244 +1,132 @@
1−{-# LANGUAGE MagicHash #-}
21{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE ScopedTypeVariables #-}
4−{-# LANGUAGE RecordWildCards #-}
5−{-# LANGUAGE FlexibleContexts #-}
62{-# LANGUAGE NoMonomorphismRestriction #-}
7−{-# LANGUAGE NamedFieldPuns #-}
8−{-# OPTIONS_GHC -funbox-strict-fields -O #-}
9−{- Evaluates the paramaterized terminfo string capability with the given parameters.
10− -
11− - todo: This can be greatly simplified.
12− -}
13−module Data.Terminfo.Eval ( cap_expression_required_bytes
14− , serialize_cap_expression
15− , bytes_for_range
16− )
17− where
18−
19−import Data.Marshalling
3+{-# OPTIONS_GHC -funbox-strict-fields #-}
4+{-# OPTIONS_HADDOCK hide #-}
5+
6+-- | Evaluates the paramaterized terminfo string capability with the
7+-- given parameters.
8+module Data.Terminfo.Eval
9+ ( writeCapExpr
10+ )
11+where
12+
13+import Blaze.ByteString.Builder.Word
14+import Blaze.ByteString.Builder
2015import Data.Terminfo.Parse
2116
2217import Control.Monad.Identity
23−import Control.Monad.Reader
2418import Control.Monad.State.Strict
25−import Control.Monad.Trans
19+import Control.Monad.Writer
20+
21+import Data.Bits ((.|.), (.&.), xor)
22+import Data.List
2623
27−import Data.Array.Unboxed
28−import Data.Bits ( (.|.), (.&.), xor )
29−import Data.List
24+import qualified Data.Vector.Unboxed as Vector
3025
31−import GHC.Prim
32−import GHC.Word
26+-- | capability evaluator state
27+data EvalState = EvalState
28+ { evalStack :: ![CapParam]
29+ , evalExpression :: !CapExpression
30+ , evalParams :: ![CapParam]
31+ }
3332
34−type EvalT m a = ReaderT (CapExpression,[CapParam]) (StateT [CapParam] m) a
35−type Eval a = EvalT Identity a
33+type Eval a = StateT EvalState (Writer Write) a
3634
37−pop :: MonadState [CapParam] m => m CapParam
35+pop :: Eval CapParam
3836pop = do
39− v : stack <- get
40− put stack
37+ s <- get
38+ let v : stack' = evalStack s
39+ s' = s { evalStack = stack' }
40+ put s'
4141 return v
4242
43−read_param :: MonadReader (CapExpression, [CapParam]) m => Word -> m CapParam
44−read_param pn = do
45− (_,params) <- ask
43+readParam :: Word -> Eval CapParam
44+readParam pn = do
45+ !params <- evalParams <$> get
4646 return $! genericIndex params pn
4747
48−push :: MonadState [CapParam] m => CapParam -> m ()
48+push :: CapParam -> Eval ()
4949push !v = do
50− stack <- get
51− put (v : stack)
52−
53−apply_param_ops :: CapExpression -> [CapParam] -> [CapParam]
54−apply_param_ops cap params = foldl apply_param_op params (param_ops cap)
55−
56−apply_param_op :: [CapParam] -> ParamOp -> [CapParam]
57−apply_param_op params IncFirstTwo = map (+ 1) params
58−
59−-- | range is 0-based offset into cap_bytes and count
60−--
61−-- todo: The returned list is not assured to have a length st. length == count
62−bytes_for_range :: CapExpression -> Word8 -> Word8 -> [Word8]
63−bytes_for_range cap !offset !count
64− = take (fromEnum count)
65− $ drop (fromEnum offset)
66− $ elems
67− $ cap_bytes cap
68−
69−cap_expression_required_bytes :: CapExpression -> [CapParam] -> Word
70−cap_expression_required_bytes cap params =
71− let params' = apply_param_ops cap params
72− in fst $! runIdentity $ runStateT (runReaderT (cap_ops_required_bytes $ cap_ops cap) (cap, params')) []
73−
74−cap_ops_required_bytes :: CapOps -> Eval Word
75−cap_ops_required_bytes ops = do
76− counts <- mapM cap_op_required_bytes ops
77− return $ sum counts
78−
79−cap_op_required_bytes :: CapOp -> Eval Word
80−cap_op_required_bytes (Bytes _ c) = return $ toEnum $ fromEnum c
81−cap_op_required_bytes DecOut = do
50+ s <- get
51+ let s' = s { evalStack = v : evalStack s }
52+ put s'
53+
54+applyParamOps :: CapExpression -> [CapParam] -> [CapParam]
55+applyParamOps cap params = foldl applyParamOp params (paramOps cap)
56+
57+applyParamOp :: [CapParam] -> ParamOp -> [CapParam]
58+applyParamOp params IncFirstTwo = map (+ 1) params
59+
60+writeCapExpr :: CapExpression -> [CapParam] -> Write
61+writeCapExpr cap params =
62+ let params' = applyParamOps cap params
63+ s0 = EvalState [] cap params'
64+ in snd $ runWriter (runStateT (writeCapOps (capOps cap)) s0)
65+
66+writeCapOps :: CapOps -> Eval ()
67+writeCapOps = mapM_ writeCapOp
68+
69+writeCapOp :: CapOp -> Eval ()
70+writeCapOp (Bytes !offset !count) = do
71+ !cap <- evalExpression <$> get
72+ let bytes = Vector.take count $ Vector.drop offset (capBytes cap)
73+ Vector.forM_ bytes $ tell.writeWord8
74+writeCapOp DecOut = do
8275 p <- pop
83− return $ toEnum $ length $ show p
84−cap_op_required_bytes CharOut = 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
76+ forM_ (show p) $ tell.writeWord8.toEnum.fromEnum
77+writeCapOp CharOut = do
78+ pop >>= tell.writeWord8.toEnum.fromEnum
79+writeCapOp (PushParam pn) = do
80+ readParam pn >>= push
81+writeCapOp (PushValue v) = do
9182 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 :: MonadIO m => CapExpression -> [CapParam] -> OutputBuffer -> m OutputBuffer
154−serialize_cap_expression cap params out_ptr = do
155− let params' = apply_param_ops cap params
156− (!out_ptr', _) <- runStateT (runReaderT (serialize_cap_ops out_ptr (cap_ops cap)) (cap, params')) []
157− return out_ptr'
158−
159−serialize_cap_ops :: MonadIO m => OutputBuffer -> CapOps -> EvalT m OutputBuffer
160−serialize_cap_ops out_ptr ops = foldM serialize_cap_op out_ptr ops
161−
162−serialize_cap_op :: MonadIO m => OutputBuffer -> CapOp -> EvalT m OutputBuffer
163−serialize_cap_op out_ptr (Bytes offset c) = do
164− (cap, _) <- ask
165− let out_bytes = bytes_for_range cap offset c
166− serialize_bytes out_bytes out_ptr
167−serialize_cap_op out_ptr DecOut = do
168− p <- pop
169− let out_str = show p
170− out_bytes = string_to_bytes out_str
171− serialize_bytes out_bytes out_ptr
172−serialize_cap_op out_ptr CharOut = do
173− W# p <- pop
174− -- todo? Truncate the character value to a single byte?
175− let !out_byte = W8# (and# p 0xFF##)
176− !out_ptr' = out_ptr `plusPtr` 1
177− liftIO $ poke out_ptr out_byte
178− return out_ptr'
179−serialize_cap_op out_ptr (PushParam pn) = do
180− read_param pn >>= push
181− return out_ptr
182−serialize_cap_op out_ptr (PushValue v) = do
183− push v
184− return out_ptr
185−serialize_cap_op out_ptr (Conditional expr parts) = do
186− out_ptr' <- serialize_cap_ops out_ptr expr
187− out_ptr'' <- serialize_cond_parts out_ptr' parts
188− return out_ptr''
189− where
190− serialize_cond_parts ptr [] = return ptr
191− serialize_cond_parts ptr ( (true_ops, false_ops) : false_parts ) = do
83+writeCapOp (Conditional expr parts) = do
84+ writeCapOps expr
85+ writeContitionalParts parts
86+ where
87+ writeContitionalParts [] = return ()
88+ writeContitionalParts ((trueOps, falseOps) : falseParts) = do
19289 -- (man 5 terminfo)
193− -- Usually the %? expr part pushes a value onto the stack, and %t pops it from the
194− -- stack, testing if it is nonzero (true). If it is zero (false), control
195− -- passes to the %e (else) part.
90+ -- Usually the %? expr part pushes a value onto the stack,
91+ -- and %t pops it from the stack, testing if it is nonzero
92+ -- (true). If it is zero (false), control passes to the %e
93+ -- (else) part.
19694 v <- pop
197− ptr'' <- if v /= 0
198− then serialize_cap_ops ptr true_ops
199− else do
200− ptr' <- serialize_cap_ops ptr false_ops
201− serialize_cond_parts ptr' false_parts
202− return ptr''
203−
204−serialize_cap_op out_ptr BitwiseOr = do
205− v_0 <- pop
206− v_1 <- pop
207− push $ v_0 .|. v_1
208− return out_ptr
209−serialize_cap_op out_ptr BitwiseAnd = do
210− v_0 <- pop
211− v_1 <- pop
212− push $ v_0 .&. v_1
213− return out_ptr
214−serialize_cap_op out_ptr BitwiseXOr = do
215− v_1 <- pop
216− v_0 <- pop
217− push $ v_0 `xor` v_1
218− return out_ptr
219−serialize_cap_op out_ptr ArithPlus = do
220− v_1 <- pop
221− v_0 <- pop
222− push $ v_0 + v_1
223− return out_ptr
224−serialize_cap_op out_ptr ArithMinus = do
225− v_1 <- pop
226− v_0 <- pop
227− push $ v_0 - v_1
228− return out_ptr
229−serialize_cap_op out_ptr CompareEq = do
230− v_1 <- pop
231− v_0 <- pop
232− push $ if v_0 == v_1 then 1 else 0
233− return out_ptr
234−serialize_cap_op out_ptr CompareLt = do
235− v_1 <- pop
236− v_0 <- pop
237− push $ if v_0 < v_1 then 1 else 0
238− return out_ptr
239−serialize_cap_op out_ptr CompareGt = do
240− v_1 <- pop
241− v_0 <- pop
242− push $ if v_0 > v_1 then 1 else 0
243− return out_ptr
244−
95+ if v /= 0
96+ then writeCapOps trueOps
97+ else do
98+ writeCapOps falseOps
99+ writeContitionalParts falseParts
100+
101+writeCapOp BitwiseOr = do
102+ v0 <- pop
103+ v1 <- pop
104+ push $ v0 .|. v1
105+writeCapOp BitwiseAnd = do
106+ v0 <- pop
107+ v1 <- pop
108+ push $ v0 .&. v1
109+writeCapOp BitwiseXOr = do
110+ v1 <- pop
111+ v0 <- pop
112+ push $ v0 `xor` v1
113+writeCapOp ArithPlus = do
114+ v1 <- pop
115+ v0 <- pop
116+ push $ v0 + v1
117+writeCapOp ArithMinus = do
118+ v1 <- pop
119+ v0 <- pop
120+ push $ v0 - v1
121+writeCapOp CompareEq = do
122+ v1 <- pop
123+ v0 <- pop
124+ push $ if v0 == v1 then 1 else 0
125+writeCapOp CompareLt = do
126+ v1 <- pop
127+ v0 <- pop
128+ push $ if v0 < v1 then 1 else 0
129+writeCapOp CompareGt = do
130+ v1 <- pop
131+ v0 <- pop
132+ push $ if v0 > v1 then 1 else 0
src/Data/Terminfo/Parse.hs+276−248
@@ -1,320 +1,348 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
21{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE CPP #-}
43{-# LANGUAGE NoMonomorphismRestriction #-}
5−{-# LANGUAGE NamedFieldPuns #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
65{-# OPTIONS_GHC -funbox-strict-fields -O #-}
7−module Data.Terminfo.Parse ( module Data.Terminfo.Parse
8− , Text.ParserCombinators.Parsec.ParseError
9− )
10− where
6+{-# OPTIONS_HADDOCK hide #-}
7+
8+module Data.Terminfo.Parse
9+ ( module Data.Terminfo.Parse
10+ , Text.Parsec.ParseError
11+ )
12+where
1113
1214import Control.Monad ( liftM )
13−import Control.Parallel.Strategies
15+import Control.DeepSeq
1416
15−import Data.Array.Unboxed
16−import Data.Monoid
17+#if !(MIN_VERSION_base(4,11,0))
18+import Data.Semigroup (Semigroup(..))
19+#endif
1720import Data.Word
21+import qualified Data.Vector.Unboxed as Vector
1822
19−import Text.ParserCombinators.Parsec
23+import Numeric (showHex)
2024
21−type BytesLength = Word8
22−type BytesOffset = Word8
23−type CapBytes = UArray Word8 Word8
25+import Text.Parsec
2426
2527data CapExpression = CapExpression
26− { cap_ops :: !CapOps
27− , cap_bytes :: !CapBytes
28− , source_string :: !String
29− , param_count :: !Word
30− , param_ops :: !ParamOps
31− }
28+ { capOps :: !CapOps
29+ , capBytes :: !(Vector.Vector Word8)
30+ , sourceString :: !String
31+ , paramCount :: !Int
32+ , paramOps :: !ParamOps
33+ } deriving (Eq)
34+
35+instance Show CapExpression where
36+ show c
37+ = "CapExpression { " ++ show (capOps c) ++ " }"
38+ ++ " <- [" ++ hexDump ( map ( toEnum . fromEnum ) $! sourceString c ) ++ "]"
39+ ++ " <= " ++ show (sourceString c)
40+ where
41+ hexDump :: [Word8] -> String
42+ hexDump = foldr showHex ""
3243
3344instance NFData CapExpression where
34− rnf (CapExpression ops !_bytes !_str !_c !_p_ops)
35− = rnf ops
45+ rnf (CapExpression ops !_bytes !str !c !pOps)
46+ = rnf ops `seq` rnf str `seq` rnf c `seq` rnf pOps
3647
3748type CapParam = Word
3849
3950type CapOps = [CapOp]
40−data CapOp =
41− Bytes !BytesOffset !BytesLength
51+data CapOp =
52+ Bytes !Int !Int -- offset count
4253 | DecOut | CharOut
43− -- This stores a 0-based index to the parameter. However the operation that implies this op is
44− -- 1-based
54+ -- This stores a 0-based index to the parameter. However the
55+ -- operation that implies this op is 1-based
4556 | PushParam !Word | PushValue !Word
46− -- The conditional parts are the sequence of (%t expression, %e expression) pairs.
47− -- The %e expression may be NOP
48− | Conditional
49− { conditional_expr :: !CapOps
50− , conditional_parts :: ![(CapOps, CapOps)]
57+ -- The conditional parts are the sequence of (%t expression, %e
58+ -- The expression) pairs. %e expression may be NOP
59+ | Conditional
60+ { conditionalExpr :: !CapOps
61+ , conditionalParts :: ![(CapOps, CapOps)]
5162 }
5263 | BitwiseOr | BitwiseXOr | BitwiseAnd
5364 | ArithPlus | ArithMinus
5465 | CompareEq | CompareLt | CompareGt
55− deriving ( Show )
66+ deriving (Show, Eq)
5667
5768instance NFData CapOp where
58− rnf (Bytes offset c) = rnf offset >| rnf c
59− rnf (PushParam !_pn) = ()
60− rnf (PushValue !_v) = ()
61− rnf (Conditional c_expr c_parts) = rnf c_expr >| rnf c_parts
62− rnf _ = ()
69+ rnf (Bytes offset byteCount ) = rnf offset `seq` rnf byteCount
70+ rnf (PushParam pn) = rnf pn
71+ rnf (PushValue v) = rnf v
72+ rnf (Conditional cExpr cParts) = rnf cExpr `seq` rnf cParts
73+ rnf BitwiseOr = ()
74+ rnf BitwiseXOr = ()
75+ rnf BitwiseAnd = ()
76+ rnf ArithPlus = ()
77+ rnf ArithMinus = ()
78+ rnf CompareEq = ()
79+ rnf CompareLt = ()
80+ rnf CompareGt = ()
81+ rnf DecOut = ()
82+ rnf CharOut = ()
6383
6484type ParamOps = [ParamOp]
6585data ParamOp =
6686 IncFirstTwo
67− deriving ( Show )
68−
69−parse_cap_expression :: String -> Either ParseError CapExpression
70−parse_cap_expression cap_string =
71− let v = runParser cap_expression_parser
72− initial_build_state
73− "terminfo cap"
74− cap_string
87+ deriving (Show, Eq)
88+
89+instance NFData ParamOp where
90+ rnf IncFirstTwo = ()
91+
92+parseCapExpression :: String -> Either ParseError CapExpression
93+parseCapExpression capString =
94+ let v = runParser capExpressionParser
95+ initialBuildState
96+ "terminfo cap"
97+ capString
7598 in case v of
7699 Left e -> Left e
77− Right build_results ->
78− Right $! ( CapExpression
79− { cap_ops = out_cap_ops build_results
80− -- The cap bytes are the lower 8 bits of the input string's characters.
81− -- \todo Verify the input string actually contains an 8bit byte per character.
82− , cap_bytes = listArray (0, toEnum $ length cap_string - 1)
83− $ map (toEnum . fromEnum) cap_string
84− , source_string = cap_string
85− , param_count = out_param_count build_results
86− , param_ops = out_param_ops build_results
87− }
88− `using` rnf
89− )
90−
91−type CapParser a = GenParser Char BuildState a
92−
93−cap_expression_parser :: CapParser BuildResults
94−cap_expression_parser = do
95− rs <- many $ param_escape_parser <|> bytes_op_parser
100+ Right buildResults -> Right $ constructCapExpression capString buildResults
101+
102+constructCapExpression :: String -> BuildResults -> CapExpression
103+constructCapExpression capString buildResults =
104+ let expr = CapExpression
105+ { capOps = outCapOps buildResults
106+ -- The cap bytes are the lower 8 bits of the input
107+ -- string's characters.
108+ , capBytes = Vector.fromList $ map (toEnum.fromEnum) capString
109+ , sourceString = capString
110+ , paramCount = outParamCount buildResults
111+ , paramOps = outParamOps buildResults
112+ }
113+ in rnf expr `seq` expr
114+
115+type CapParser a = Parsec String BuildState a
116+
117+capExpressionParser :: CapParser BuildResults
118+capExpressionParser = do
119+ rs <- many $ paramEscapeParser <|> bytesOpParser
96120 return $ mconcat rs
97121
98−param_escape_parser :: CapParser BuildResults
99−param_escape_parser = do
100− char '%'
101− inc_offset 1
102− literal_percent_parser <|> param_op_parser
103−
104−literal_percent_parser :: CapParser BuildResults
105−literal_percent_parser = do
106− char '%'
107− start_offset <- getState >>= return . next_offset
108− inc_offset 1
109− return $ BuildResults 0 [Bytes start_offset 1] []
110−
111−param_op_parser :: CapParser BuildResults
112−param_op_parser
113− = increment_op_parser
114− <|> push_op_parser
115− <|> dec_out_parser
116− <|> char_out_parser
117− <|> conditional_op_parser
118− <|> bitwise_op_parser
119− <|> arith_op_parser
120− <|> literal_int_op_parser
121− <|> compare_op_parser
122− <|> char_const_parser
123−
124−increment_op_parser :: CapParser BuildResults
125−increment_op_parser = do
126− char 'i'
127− inc_offset 1
122+paramEscapeParser :: CapParser BuildResults
123+paramEscapeParser = do
124+ _ <- char '%'
125+ incOffset 1
126+ literalPercentParser <|> paramOpParser
127+
128+literalPercentParser :: CapParser BuildResults
129+literalPercentParser = do
130+ _ <- char '%'
131+ startOffset <- nextOffset <$> getState
132+ incOffset 1
133+ return $ BuildResults 0 [Bytes startOffset 1] []
134+
135+paramOpParser :: CapParser BuildResults
136+paramOpParser
137+ = incrementOpParser
138+ <|> pushOpParser
139+ <|> decOutParser
140+ <|> charOutParser
141+ <|> conditionalOpParser
142+ <|> bitwiseOpParser
143+ <|> arithOpParser
144+ <|> literalIntOpParser
145+ <|> compareOpParser
146+ <|> charConstParser
147+
148+incrementOpParser :: CapParser BuildResults
149+incrementOpParser = do
150+ _ <- char 'i'
151+ incOffset 1
128152 return $ BuildResults 0 [] [ IncFirstTwo ]
129153
130−push_op_parser :: CapParser BuildResults
131−push_op_parser = do
132− char 'p'
133− param_n <- digit >>= return . (\d -> read [d])
134− inc_offset 2
135− return $ BuildResults param_n [ PushParam $ param_n - 1 ] []
136−
137−dec_out_parser :: CapParser BuildResults
138−dec_out_parser = do
139− char 'd'
140− inc_offset 1
154+pushOpParser :: CapParser BuildResults
155+pushOpParser = do
156+ _ <- char 'p'
157+ paramN <- read . pure <$> digit
158+ incOffset 2
159+ return $ BuildResults (fromEnum paramN) [PushParam $ paramN - 1] []
160+
161+decOutParser :: CapParser BuildResults
162+decOutParser = do
163+ _ <- char 'd'
164+ incOffset 1
141165 return $ BuildResults 0 [ DecOut ] []
142166
143−char_out_parser :: CapParser BuildResults
144−char_out_parser = do
145− char 'c'
146− inc_offset 1
167+charOutParser :: CapParser BuildResults
168+charOutParser = do
169+ _ <- char 'c'
170+ incOffset 1
147171 return $ BuildResults 0 [ CharOut ] []
148172
149−conditional_op_parser :: CapParser BuildResults
150−conditional_op_parser = do
151− char '?'
152− inc_offset 1
153− cond_part <- many_expr conditional_true_parser
154− parts <- many_p
155− ( do
156− true_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser
157− , conditional_false_parser
158− ]
159− false_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser
160− , conditional_true_parser
161− ]
162− return ( true_part, false_part )
163− )
164− conditional_end_parser
165−
166− let true_parts = map fst parts
167− false_parts = map snd parts
168− BuildResults n cond cond_param_ops = cond_part
169−
170− let n' = maximum $ n : map out_param_count true_parts
171− n'' = maximum $ n' : map out_param_count false_parts
172−
173− let true_ops = map out_cap_ops true_parts
174− false_ops = map out_cap_ops false_parts
175− cond_parts = zip true_ops false_ops
176−
177− let true_param_ops = mconcat $ map out_param_ops true_parts
178− false_param_ops = mconcat $ map out_param_ops false_parts
179− p_ops = mconcat [cond_param_ops, true_param_ops, false_param_ops]
180−
181− return $ BuildResults n'' [ Conditional cond cond_parts ] p_ops
182−
183− where
184− many_p !p !end = choice
173+conditionalOpParser :: CapParser BuildResults
174+conditionalOpParser = do
175+ _ <- char '?'
176+ incOffset 1
177+ condPart <- manyExpr conditionalTrueParser
178+ parts <- manyP
179+ ( do
180+ truePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser
181+ , conditionalFalseParser
182+ ]
183+ falsePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser
184+ , conditionalTrueParser
185+ ]
186+ return ( truePart, falsePart )
187+ )
188+ conditionalEndParser
189+
190+ let trueParts = map fst parts
191+ falseParts = map snd parts
192+ BuildResults n cond condParamOps = condPart
193+
194+ let n' = maximum $ n : map outParamCount trueParts
195+ n'' = maximum $ n' : map outParamCount falseParts
196+
197+ let trueOps = map outCapOps trueParts
198+ falseOps = map outCapOps falseParts
199+ condParts = zip trueOps falseOps
200+
201+ let trueParamOps = mconcat $ map outParamOps trueParts
202+ falseParamOps = mconcat $ map outParamOps falseParts
203+ pOps = mconcat [condParamOps, trueParamOps, falseParamOps]
204+
205+ return $ BuildResults n'' [ Conditional cond condParts ] pOps
206+
207+ where
208+ manyP !p !end = choice
185209 [ try end >> return []
186− , do !v <- p
187− !vs <- many_p p end
210+ , do !v <- p
211+ !vs <- manyP p end
188212 return $! v : vs
189213 ]
190− many_expr end = liftM mconcat $ many_p ( param_escape_parser <|> bytes_op_parser ) end
191−
192−conditional_true_parser :: CapParser ()
193−conditional_true_parser = do
194− string "%t"
195− inc_offset 2
196−
197−conditional_false_parser :: CapParser ()
198−conditional_false_parser = do
199− string "%e"
200− inc_offset 2
201−
202−conditional_end_parser :: CapParser ()
203−conditional_end_parser = do
204− string "%;"
205− inc_offset 2
206−
207−bitwise_op_parser :: CapParser BuildResults
208−bitwise_op_parser
209− = bitwise_or_parser
210− <|> bitwise_and_parser
211− <|> bitwise_xor_parser
212−
213−bitwise_or_parser :: CapParser BuildResults
214−bitwise_or_parser = do
215− char '|'
216− inc_offset 1
214+ manyExpr end = liftM mconcat $ manyP ( paramEscapeParser <|> bytesOpParser ) end
215+
216+conditionalTrueParser :: CapParser ()
217+conditionalTrueParser = do
218+ _ <- string "%t"
219+ incOffset 2
220+
221+conditionalFalseParser :: CapParser ()
222+conditionalFalseParser = do
223+ _ <- string "%e"
224+ incOffset 2
225+
226+conditionalEndParser :: CapParser ()
227+conditionalEndParser = do
228+ _ <- string "%;"
229+ incOffset 2
230+
231+bitwiseOpParser :: CapParser BuildResults
232+bitwiseOpParser
233+ = bitwiseOrParser
234+ <|> bitwiseAndParser
235+ <|> bitwiseXorParser
236+
237+bitwiseOrParser :: CapParser BuildResults
238+bitwiseOrParser = do
239+ _ <- char '|'
240+ incOffset 1
217241 return $ BuildResults 0 [ BitwiseOr ] [ ]
218242
219−bitwise_and_parser :: CapParser BuildResults
220−bitwise_and_parser = do
221− char '&'
222− inc_offset 1
243+bitwiseAndParser :: CapParser BuildResults
244+bitwiseAndParser = do
245+ _ <- char '&'
246+ incOffset 1
223247 return $ BuildResults 0 [ BitwiseAnd ] [ ]
224248
225−bitwise_xor_parser :: CapParser BuildResults
226−bitwise_xor_parser = do
227− char '^'
228− inc_offset 1
249+bitwiseXorParser :: CapParser BuildResults
250+bitwiseXorParser = do
251+ _ <- char '^'
252+ incOffset 1
229253 return $ BuildResults 0 [ BitwiseXOr ] [ ]
230254
231−arith_op_parser :: CapParser BuildResults
232−arith_op_parser
233− = plus_op
234− <|> minus_op
255+arithOpParser :: CapParser BuildResults
256+arithOpParser
257+ = plusOp
258+ <|> minusOp
235259 where
236− plus_op = do
237− char '+'
238− inc_offset 1
260+ plusOp = do
261+ _ <- char '+'
262+ incOffset 1
239263 return $ BuildResults 0 [ ArithPlus ] [ ]
240− minus_op = do
241− char '-'
242− inc_offset 1
264+ minusOp = do
265+ _ <- char '-'
266+ incOffset 1
243267 return $ BuildResults 0 [ ArithMinus ] [ ]
244268
245−literal_int_op_parser :: CapParser BuildResults
246−literal_int_op_parser = do
247− char '{'
248− inc_offset 1
249− n_str <- many1 digit
250− inc_offset $ toEnum $ length n_str
251− let n :: Word = read n_str
252− char '}'
253− inc_offset 1
269+literalIntOpParser :: CapParser BuildResults
270+literalIntOpParser = do
271+ _ <- char '{'
272+ incOffset 1
273+ nStr <- many1 digit
274+ incOffset $ toEnum $ length nStr
275+ let n :: Word = read nStr
276+ _ <- char '}'
277+ incOffset 1
254278 return $ BuildResults 0 [ PushValue n ] [ ]
255279
256−compare_op_parser :: CapParser BuildResults
257−compare_op_parser
258− = compare_eq_op
259− <|> compare_lt_op
260− <|> compare_gt_op
280+compareOpParser :: CapParser BuildResults
281+compareOpParser
282+ = compareEqOp
283+ <|> compareLtOp
284+ <|> compareGtOp
261285 where
262− compare_eq_op = do
263− char '='
264− inc_offset 1
286+ compareEqOp = do
287+ _ <- char '='
288+ incOffset 1
265289 return $ BuildResults 0 [ CompareEq ] [ ]
266− compare_lt_op = do
267− char '<'
268− inc_offset 1
290+ compareLtOp = do
291+ _ <- char '<'
292+ incOffset 1
269293 return $ BuildResults 0 [ CompareLt ] [ ]
270− compare_gt_op = do
271− char '>'
272− inc_offset 1
294+ compareGtOp = do
295+ _ <- char '>'
296+ incOffset 1
273297 return $ BuildResults 0 [ CompareGt ] [ ]
274298
275−bytes_op_parser :: CapParser BuildResults
276−bytes_op_parser = do
299+bytesOpParser :: CapParser BuildResults
300+bytesOpParser = do
277301 bytes <- many1 $ satisfy (/= '%')
278− start_offset <- getState >>= return . next_offset
279− let !c = toEnum $ length bytes
302+ startOffset <- nextOffset <$> getState
303+ let !c = length bytes
280304 !s <- getState
281− let s' = s { next_offset = start_offset + c }
305+ let s' = s { nextOffset = startOffset + c }
282306 setState s'
283− return $ BuildResults 0 [Bytes start_offset c] []
284−
285−char_const_parser :: CapParser BuildResults
286−char_const_parser = do
287− char '\''
288− char_value <- liftM (toEnum . fromEnum) anyChar
289− char '\''
290− inc_offset 3
291− return $ BuildResults 0 [ PushValue char_value ] [ ]
292−
293−data BuildState = BuildState
294− { next_offset :: Word8
295− }
296−
297−inc_offset :: Word8 -> CapParser ()
298−inc_offset n = do
307+ return $ BuildResults 0 [Bytes startOffset c] []
308+
309+charConstParser :: CapParser BuildResults
310+charConstParser = do
311+ _ <- char '\''
312+ charValue <- liftM (toEnum . fromEnum) anyChar
313+ _ <- char '\''
314+ incOffset 3
315+ return $ BuildResults 0 [ PushValue charValue ] [ ]
316+
317+data BuildState = BuildState
318+ { nextOffset :: Int
319+ }
320+
321+incOffset :: Int -> CapParser ()
322+incOffset n = do
299323 s <- getState
300− let s' = s { next_offset = next_offset s + n }
324+ let s' = s { nextOffset = nextOffset s + n }
301325 setState s'
302326
303−initial_build_state :: BuildState
304−initial_build_state = BuildState 0
327+initialBuildState :: BuildState
328+initialBuildState = BuildState 0
305329
306330data BuildResults = BuildResults
307− { out_param_count :: !Word
308− , out_cap_ops :: !CapOps
309− , out_param_ops :: !ParamOps
331+ { outParamCount :: !Int
332+ , outCapOps :: !CapOps
333+ , outParamOps :: !ParamOps
310334 }
311335
312−instance Monoid BuildResults where
313− mempty = BuildResults 0 [] []
314− v0 `mappend` v1
336+instance Semigroup BuildResults where
337+ v0 <> v1
315338 = BuildResults
316− { out_param_count = (out_param_count v0) `max` (out_param_count v1)
317− , out_cap_ops = (out_cap_ops v0) `mappend` (out_cap_ops v1)
318− , out_param_ops = (out_param_ops v0) `mappend` (out_param_ops v1)
339+ { outParamCount = outParamCount v0 `max` outParamCount v1
340+ , outCapOps = outCapOps v0 <> outCapOps v1
341+ , outParamOps = outParamOps v0 <> outParamOps v1
319342 }
320343
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.hsnew file+64
@@ -0,0 +1,64 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# OPTIONS_GHC -D_XOPEN_SOURCE #-}
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@.
13+module Graphics.Text.Width
14+ ( wcwidth
15+ , wcswidth
16+ , wctwidth
17+ , wctlwidth
18+ , safeWcwidth
19+ , safeWcswidth
20+ , safeWctwidth
21+ , safeWctlwidth
22+ )
23+where
24+
25+import Data.List (foldl')
26+import qualified Data.Text as T
27+import qualified Data.Text.Lazy as TL
28+
29+foreign import ccall unsafe "vty_mk_wcwidth" wcwidth :: Char -> Int
30+
31+wcswidth :: String -> Int
32+wcswidth = foldl' (\l c -> wcwidth c + l) 0
33+{-# INLINE [1] wcswidth #-}
34+
35+wctwidth :: T.Text -> Int
36+wctwidth = T.foldl' (\l c -> wcwidth c + l) 0
37+
38+wctlwidth :: TL.Text -> Int
39+wctlwidth = TL.foldl' (\l c -> wcwidth c + l) 0
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+
46+-- | Returns the display width of a character. Assumes all characters
47+-- with unknown widths are 0 width.
48+safeWcwidth :: Char -> Int
49+safeWcwidth = max 0 . wcwidth
50+
51+-- | Returns the display width of a string. Assumes all characters with
52+-- unknown widths are 0 width.
53+safeWcswidth :: String -> Int
54+safeWcswidth = foldl' (\l c -> safeWcwidth c + l) 0
55+
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
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+246−124
@@ -1,133 +1,255 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# LANGUAGE ForeignFunctionInterface, BangPatterns, UnboxedTuples #-}
3−{-# CFILES gwinsz.c #-}
4−
5−-- Good sources of documentation for terminal programming are:
6−-- vt100 control sequences: http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.3
7−-- Xterm control sequences: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
8−
9−module Graphics.Vty ( Vty(..)
10− , mkVty
11− , mkVtyEscDelay
12− , module Graphics.Vty.Terminal
13− , module Graphics.Vty.Picture
14− , module Graphics.Vty.DisplayRegion
15− , Key(..)
16− , Modifier(..)
17− , Button(..)
18− , Event(..)
19− )
20− where
21−
22−
23−import Graphics.Vty.Terminal
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+
4+-- | Vty provides interfaces for both terminal input and terminal
5+-- output.
6+--
7+-- - Input to the terminal is provided to the Vty application as a
8+-- sequence of 'Event's.
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+ )
47+where
48+
49+import Graphics.Vty.Config
50+import Graphics.Vty.Input
51+import Graphics.Vty.Input.Events
52+import Graphics.Vty.Output
53+import Graphics.Vty.Output.Interface
2454import Graphics.Vty.Picture
25−import Graphics.Vty.DisplayRegion
26−import Graphics.Vty.LLInput
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
2766
2867import Data.IORef
68+#if !(MIN_VERSION_base(4,11,0))
69+import Data.Semigroup ((<>))
70+#endif
2971
30−import Data.Maybe ( maybe )
31−
32−import qualified System.Console.Terminfo as Terminfo
33−import System.IO
34−
35−-- | The main object. At most one should be created.
36−-- An alternative is to use unsafePerformIO to automatically create a singleton Vty instance when
37−-- required.
38−--
39−-- This does not assure any thread safety. In theory, as long as an update action is not executed
40−-- when another update action is already then it's safe to call this on multiple threads.
41−--
42−-- todo: Once the Terminal interface encompasses input this interface will be deprecated.
43−-- Currently, just using the Terminal interface there is no support for input events.
44−data Vty = Vty
45− { -- | Outputs the given Picture. Equivalent to output_picture applied to a display context
46− -- implicitly managed by Vty.
47− update :: Picture -> IO ()
48− -- | Get one Event object, blocking if necessary.
49− , next_event :: IO Event
50− -- | Handle to the terminal interface. See `Terminal`
51− --
52− -- The use of Vty typically follows this process:
53− --
54− -- 0. initialize vty
55− --
56− -- 1. use the update equation of Vty to display a picture
57− --
58− -- 2. repeat
59− --
60− -- 3. shutdown vty.
61− --
62− -- todo: provide a similar abstraction to Graphics.Vty.Terminal for input. Use haskeline's
63− -- input backend for implementation.
64− --
65− -- todo: remove explicit `shutdown` requirement.
66− , terminal :: TerminalHandle
67− -- | Refresh the display. Normally the library takes care of refreshing. Nonetheless, some
68− -- other program might output to the terminal and mess the display. In that case the user
69− -- might want to force a refresh.
70− , refresh :: IO ()
71− -- | Clean up after vty.
72− , shutdown :: IO ()
73− }
74−
75−-- | Set up the state object for using vty. At most one state object should be
76−-- created at a time.
77−mkVty :: IO Vty
78−mkVty = mkVtyEscDelay 0
79−
80−mkVtyEscDelay :: Int -> IO Vty
81−mkVtyEscDelay escDelay = do
82− term_info <- Terminfo.setupTermFromEnv
83− t <- terminal_handle
84− reserve_display t
85− (kvar, endi) <- initTermInput escDelay term_info
86− intMkVty kvar ( endi >> release_display t >> release_terminal t ) t
87−
88−intMkVty :: IO Event -> IO () -> TerminalHandle -> IO Vty
89−intMkVty kvar fend t = do
90− last_pic_ref <- newIORef undefined
91− last_update_ref <- newIORef Nothing
92−
93− let inner_update in_pic = do
94− b <- display_bounds t
95− mlast_update <- readIORef last_update_ref
96− update_data <- case mlast_update of
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+ }
112+
113+-- | Create a Vty handle. At most one handle should be created at a time
114+-- for a given terminal device.
115+--
116+-- The specified configuration is added to the the configuration
117+-- loaded by 'userConfig' with the 'userConfig' configuration taking
118+-- precedence. See "Graphics.Vty.Config".
119+--
120+-- For most applications @mkVty defaultConfig@ is sufficient.
121+mkVty :: Config -> IO Vty
122+mkVty appConfig = do
123+ config <- (<> appConfig) <$> userConfig
124+
125+ when (allowCustomUnicodeWidthTables config /= Just False) $
126+ installCustomWidthTable config
127+
128+ input <- inputForConfig config
129+ out <- outputForConfig config
130+ internalMkVty input out
131+
132+installCustomWidthTable :: Config -> IO ()
133+installCustomWidthTable c = do
134+ let doLog s = case debugLog c of
135+ Nothing -> return ()
136+ Just path -> appendFile path $ "installWidthTable: " <> s <> "\n"
137+
138+ customInstalled <- isCustomTableReady
139+ when (not customInstalled) $ do
140+ mTerm <- currentTerminalName
141+ case mTerm of
142+ Nothing ->
143+ doLog "No current terminal name available"
144+ Just currentTerm ->
145+ case lookup currentTerm (termWidthMaps c) of
146+ Nothing ->
147+ doLog "Current terminal not found in custom character width mapping list"
148+ Just path -> do
149+ tableResult <- E.try $ readUnicodeWidthTable path
150+ case tableResult of
151+ Left (e::E.SomeException) ->
152+ doLog $ "Error reading custom character width table " <>
153+ "at " <> show path <> ": " <> show e
154+ Right (Left msg) ->
155+ doLog $ "Error reading custom character width table " <>
156+ "at " <> show path <> ": " <> msg
157+ Right (Right table) -> do
158+ installResult <- E.try $ installUnicodeWidthTable table
159+ case installResult of
160+ Left (e::E.SomeException) ->
161+ doLog $ "Error installing unicode table (" <>
162+ show path <> ": " <> show e
163+ Right () ->
164+ doLog $ "Successfully installed Unicode width table " <>
165+ " from " <> show path
166+
167+internalMkVty :: Input -> Output -> IO Vty
168+internalMkVty input out = do
169+ reserveDisplay out
170+
171+ shutdownVar <- newTVarIO False
172+ let shutdownIo = do
173+ alreadyShutdown <- atomically $ swapTVar shutdownVar True
174+ when (not alreadyShutdown) $ do
175+ shutdownInput input
176+ releaseDisplay out
177+ releaseTerminal out
178+
179+ let shutdownStatus = readTVarIO shutdownVar
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
97188 Nothing -> do
98− d <- display_context t b
99− output_picture d in_pic
100− return (b, d)
101− Just (last_bounds, last_context) -> do
102− if b /= last_bounds
189+ dc <- displayContext out b
190+ outputPicture dc inPic
191+ return (b, dc)
192+ Just (lastBounds, lastContext) -> do
193+ if b /= lastBounds
103194 then do
104− d <- display_context t b
105− output_picture d in_pic
106− return (b, d)
195+ dc <- displayContext out b
196+ outputPicture dc inPic
197+ return (b, dc)
107198 else do
108− output_picture last_context in_pic
109− return (b, last_context)
110− writeIORef last_update_ref $ Just update_data
111− writeIORef last_pic_ref $ Just in_pic
112−
113− let inner_refresh
114− = writeIORef last_update_ref Nothing
115− >> readIORef last_pic_ref
116− >>= maybe ( return () ) ( \pic -> inner_update pic )
117−
118− let gkey = do k <- kvar
119− case k of
120− (EvResize _ _) -> inner_refresh
121− >> display_bounds t
122− >>= return . ( \(DisplayRegion w h)
123− -> EvResize (fromEnum w) (fromEnum h)
124− )
125− _ -> return k
126−
127− return $ Vty { update = inner_update
128− , next_event = gkey
129− , terminal = t
130− , refresh = inner_refresh
131− , shutdown = fend
199+ outputPicture lastContext inPic
200+ return (b, lastContext)
201+ writeIORef lastUpdateRef $ Just updateData
202+ writeIORef lastPicRef $ Just inPic
203+
204+ let innerRefresh = do
205+ writeIORef lastUpdateRef Nothing
206+ bounds <- displayBounds out
207+ dc <- displayContext out bounds
208+ writeIORef (assumedStateRef $ contextDevice dc) initialAssumedState
209+ mPic <- readIORef lastPicRef
210+ maybe (return ()) innerUpdate mPic
211+
212+ let mkResize = uncurry EvResize <$> displayBounds out
213+
214+ translateInternalEvent ResumeAfterSignal = mkResize
215+ translateInternalEvent (InputEvent e) = return e
216+
217+ gkey = do
218+ e <- atomically $ readTChan $ _eventChannel input
219+ translateInternalEvent e
220+ gkey' = do
221+ mEv <- atomically $ tryReadTChan $ _eventChannel input
222+ case mEv of
223+ Just e -> Just <$> translateInternalEvent e
224+ Nothing -> return Nothing
225+
226+ return $ Vty { update = innerUpdate
227+ , nextEvent = gkey
228+ , nextEventNonblocking = gkey'
229+ , inputIface = input
230+ , outputIface = out
231+ , refresh = innerRefresh
232+ , shutdown = shutdownIo
233+ , isShutdown = shutdownStatus
132234 }
133235
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+165−162
@@ -1,9 +1,89 @@
1−-- Copyright 2009 Corey O'Connor
2−-- Display attributes
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE DeriveAnyClass #-}
4+
5+-- | Display attributes
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.,
318--
4−-- For efficiency this can be, in the future, encoded into a single 32 bit word. The 32 bit word is
5−-- first divided into 4 groups of 8 bits where:
6−-- The first group codes what action should be taken with regards to the other groups.
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+
84+-- This could be encoded into a single 32 bit word. The 32 bit word is
85+-- first divided into 4 groups of 8 bits where: The first group codes
86+-- what action should be taken with regards to the other groups.
787-- XXYYZZ__
888-- XX - style action
989-- 00 => reset to default
@@ -20,151 +100,53 @@
20100-- __ - unused
21101--
22102-- Next is the style flags
23−-- SURBDO__
103+-- SURBDOI_
24104-- S - standout
25105-- U - underline
26106-- R - reverse video
27107-- B - blink
28108-- D - dim
29109-- O - bold
30−-- __ - unused
110+-- I - italic
111+-- _ - unused
31112--
32113-- Then the foreground color encoded into 8 bits.
33114-- Then the background color encoded into 8 bits.
34−--
35−module Graphics.Vty.Attributes
36− where
37−
38−import Data.Bits
39−import Data.Monoid
40−import Data.Word
41115
42−-- | A display attribute defines the Color and Style of all the characters rendered after the
43−-- attribute is applied.
44−--
45−-- At most 256 colors, picked from a 240 and 16 color palette, are possible for the background and
46−-- foreground. The 240 colors and 16 colors are points in different palettes. See Color for more
47−-- information.
48−data Attr = Attr
49− { attr_style :: !(MaybeDefault Style)
50− , attr_fore_color :: !(MaybeDefault Color)
51− , attr_back_color :: !(MaybeDefault Color)
116+-- | Specifies the display attributes such that the final style and
117+-- color values do not depend on the previously applied display
118+-- attribute. The display attributes can still depend on the terminal's
119+-- default colors (unfortunately).
120+data FixedAttr = FixedAttr
121+ { fixedStyle :: !Style
122+ , fixedForeColor :: !(Maybe Color)
123+ , fixedBackColor :: !(Maybe Color)
124+ , fixedURL :: !(Maybe Text)
52125 } deriving ( Eq, Show )
53126
54−instance Monoid Attr where
55− mempty = Attr mempty mempty mempty
56− mappend attr_0 attr_1 =
57− Attr ( attr_style attr_0 `mappend` attr_style attr_1 )
58− ( attr_fore_color attr_0 `mappend` attr_fore_color attr_1 )
59− ( attr_back_color attr_0 `mappend` attr_back_color attr_1 )
127+-- | The style and color attributes can either be the terminal defaults.
128+-- Or be equivalent to the previously applied style. Or be a specific
129+-- value.
130+data MaybeDefault v = Default | KeepCurrent | SetTo !v
131+ deriving (Eq, Read, Show)
60132
61−-- | Specifies the display attributes such that the final style and color values do not depend on
62−-- the previously applied display attribute. The display attributes can still depend on the
63−-- terminal's default colors (unfortunately).
64−data FixedAttr = FixedAttr
65− { fixed_style :: !Style
66− , fixed_fore_color :: !(Maybe Color)
67− , fixed_back_color :: !(Maybe Color)
68− } deriving ( Eq, Show )
133+instance (NFData v) => NFData (MaybeDefault v) where
134+ rnf Default = ()
135+ rnf KeepCurrent = ()
136+ rnf (SetTo v) = rnf v
69137
70−-- | The style and color attributes can either be the terminal defaults. Or be equivalent to the
71−-- previously applied style. Or be a specific value.
72−data Eq v => MaybeDefault v = Default | KeepCurrent | SetTo !v
73− deriving ( Eq, Show )
74−
75−instance Eq v => Monoid ( MaybeDefault v ) where
76− mempty = KeepCurrent
77− mappend Default Default = Default
78− mappend Default KeepCurrent = Default
79− mappend Default ( SetTo v ) = SetTo v
80− mappend KeepCurrent Default = Default
81− mappend KeepCurrent KeepCurrent = KeepCurrent
82− mappend KeepCurrent ( SetTo v ) = SetTo v
83− mappend ( SetTo _v ) Default = Default
84− mappend ( SetTo v ) KeepCurrent = SetTo v
85− mappend ( SetTo _ ) ( SetTo v ) = SetTo v
86−
87−-- | Abstract data type representing a color.
88−--
89−-- Currently the foreground and background color are specified as points in either a:
90−--
91−-- * 16 color palette. Where the first 8 colors are equal to the 8 colors of the ISO 6429 (ANSI) 8
92−-- color palette and the second 8 colors are bright/vivid versions of the first 8 colors.
93−--
94−-- * 240 color palette. This palette is a regular sampling of the full RGB colorspace.
95−--
96−-- The 8 ISO 6429 (ANSI) colors are as follows:
97−--
98−-- 0. black
99−--
100−-- 1. red
101−--
102−-- 2. green
103−--
104−-- 3. yellow
105−--
106−-- 4. blue
107−--
108−-- 5. magenta
109−--
110−-- 6. cyan
111−--
112−-- 7. white
113−--
114−-- The mapping from points in the 240 color palette to colors actually displayable by the terminal
115−-- depends on the number of colors the terminal claims to support. Which is usually determined by
116−-- the terminfo "colors" property. If this property is not being accurately reported then the color
117−-- reproduction will be incorrect.
118−--
119−-- If the terminal reports <= 16 colors then the 240 color palette points are only mapped to the 8
120−-- color pallete. I'm not sure of the RGB points for the "bright" colors which is why they are not
121−-- addressable via the 240 color palette.
122−--
123−-- If the terminal reports > 16 colors then the 240 color palette points are mapped to the nearest
124−-- points in a ("color count" - 16) subsampling of the 240 color palette.
125−--
126−-- All of this assumes the terminals are behaving similarly to xterm and rxvt when handling colors.
127−-- And that the individual colors have not been remapped by the user. There may be a way to verify
128−-- this through terminfo but I don't know it.
129−--
130−-- Seriously, terminal color support is INSANE.
131−data Color = ISOColor !Word8 | Color240 !Word8
132− deriving ( Eq, Show )
133−
134−-- | Standard 8-color ANSI terminal color codes.
135−black, red, green, yellow, blue, magenta, cyan, white :: Color
136−black = ISOColor 0
137−red = ISOColor 1
138−green = ISOColor 2
139−yellow = ISOColor 3
140−blue = ISOColor 4
141−magenta= ISOColor 5
142−cyan = ISOColor 6
143−white = ISOColor 7
144−
145−-- | Bright/Vivid variants of the standard 8-color ANSI
146−bright_black, bright_red, bright_green, bright_yellow :: Color
147−bright_blue, bright_magenta, bright_cyan, bright_white :: Color
148−bright_black = ISOColor 8
149−bright_red = ISOColor 9
150−bright_green = ISOColor 10
151−bright_yellow = ISOColor 11
152−bright_blue = ISOColor 12
153−bright_magenta= ISOColor 13
154−bright_cyan = ISOColor 14
155−bright_white = ISOColor 15
156−
157−-- | Styles are represented as an 8 bit word. Each bit in the word is 1 if the style attribute
158−-- assigned to that bit should be applied and 0 if the style attribute should not be applied.
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
140+-- if the style attribute should not be applied.
159141type Style = Word8
160142
161−-- | The 6 possible style attributes:
143+-- | Valid style attributes include:
162144--
163145-- * standout
164146--
165147-- * underline
166148--
167−-- * reverse_video
149+-- * reverseVideo
168150--
169151-- * blink
170152--
@@ -172,54 +154,75 @@ type Style = Word8
172154--
173155-- * bold/bright
174156--
175−-- ( The invisible, protect, and altcharset display attributes some terminals support are not
176−-- supported via VTY.)
177−standout, underline, reverse_video, blink, dim, bold :: Style
157+-- * italic
158+--
159+-- * strikethrough (via the smxx/rmxx terminfo capabilities)
160+--
161+-- (The invisible, protect, and altcharset display attributes some
162+-- terminals support are not supported via VTY.)
163+standout, underline, reverseVideo, blink, dim, bold, italic, strikethrough :: Style
178164standout = 0x01
179165underline = 0x02
180−reverse_video = 0x04
166+reverseVideo = 0x04
181167blink = 0x08
182168dim = 0x10
183169bold = 0x20
170+italic = 0x40
171+strikethrough = 0x80
184172
185−default_style_mask :: Style
186−default_style_mask = 0x00
173+defaultStyleMask :: Style
174+defaultStyleMask = 0x00
187175
188−style_mask :: Attr -> Word8
189−style_mask attr
190− = case attr_style attr of
176+styleMask :: Attr -> Word8
177+styleMask attr
178+ = case attrStyle attr of
191179 Default -> 0
192180 KeepCurrent -> 0
193181 SetTo v -> v
194182
195183-- | true if the given Style value has the specified Style set.
196−has_style :: Style -> Style -> Bool
197−has_style s bit_mask = ( s .&. bit_mask ) /= 0
184+hasStyle :: Style -> Style -> Bool
185+hasStyle s bitMask = ( s .&. bitMask ) /= 0
198186
199187-- | Set the foreground color of an `Attr'.
200−with_fore_color :: Attr -> Color -> Attr
201−with_fore_color attr c = attr { attr_fore_color = SetTo c }
188+withForeColor :: Attr -> Color -> Attr
189+withForeColor attr c = attr { attrForeColor = SetTo c }
202190
203191-- | Set the background color of an `Attr'.
204−with_back_color :: Attr -> Color -> Attr
205−with_back_color attr c = attr { attr_back_color = SetTo c }
192+withBackColor :: Attr -> Color -> Attr
193+withBackColor attr c = attr { attrBackColor = SetTo c }
206194
207195-- | Add the given style attribute
208−with_style :: Attr -> Style -> Attr
209−with_style attr style_flag = attr { attr_style = SetTo $ style_mask attr .|. style_flag }
210−
211−-- | Sets the style, background color and foreground color to the default values for the terminal.
212−-- There is no easy way to determine what the default background and foreground colors are.
213−def_attr :: Attr
214−def_attr = Attr Default Default Default
215−
216−-- | Keeps the style, background color and foreground color that was previously set. Used to
217−-- override some part of the previous style.
218−--
219−-- EG: current_style `with_fore_color` bright_magenta
220−--
221−-- Would be the currently applied style (be it underline, bold, etc) but with the foreground color
222−-- set to bright_magenta.
223−current_attr :: Attr
224−current_attr = Attr KeepCurrent KeepCurrent KeepCurrent
225−
196+withStyle :: Attr -> Style -> Attr
197+withStyle attr 0 = attr
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+
214+-- | Sets the style, background color and foreground color to the
215+-- default values for the terminal. There is no easy way to determine
216+-- what the default background and foreground colors are.
217+defAttr :: Attr
218+defAttr = Attr Default Default Default Default
219+
220+-- | Keeps the style, background color and foreground color that was
221+-- previously set. Used to override some part of the previous style.
222+--
223+-- EG: current_style `withForeColor` brightMagenta
224+--
225+-- Would be the currently applied style (be it underline, bold, etc) but
226+-- with the foreground color set to brightMagenta.
227+currentAttr :: Attr
228+currentAttr = Attr KeepCurrent KeepCurrent KeepCurrent KeepCurrent
src/Graphics/Vty/Attributes/Color.hsnew file+191
@@ -0,0 +1,191 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE DeriveAnyClass #-}
3+{-# LANGUAGE MultiWayIf #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+
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+
49+import 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
59+
60+-- | Abstract data type representing a color.
61+--
62+-- Currently the foreground and background color are specified as points
63+-- in either a:
64+--
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
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.
72+--
73+-- The 8 ISO 6429 (ANSI) colors are as follows:
74+--
75+-- 0. black
76+--
77+-- 1. red
78+--
79+-- 2. green
80+--
81+-- 3. yellow
82+--
83+-- 4. blue
84+--
85+-- 5. magenta
86+--
87+-- 6. cyan
88+--
89+-- 7. white
90+--
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
93+-- terminal claims to support. Which is usually determined by the
94+-- terminfo "colors" property. If this property is not being accurately
95+-- reported then the color reproduction will be incorrect.
96+--
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
99+-- the RGB points for the "bright" colors which is why they are not
100+-- addressable via the 240 color palette.
101+--
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)
104+-- subsampling of the 240 color palette.
105+--
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
108+-- been remapped by the user. There may be a way to verify this through
109+-- terminfo but I don't know it.
110+--
111+-- Seriously, terminal color support is INSANE.
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
121+ deriving ( Eq, Show )
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.hsnew file+298
@@ -0,0 +1,298 @@
1+-- This header file was generated by ./256colres.pl
2+module Graphics.Vty.Attributes.Color240
3+ ( rgbColorToColor240
4+ , color240CodeToRGB
5+ )
6+where
7+
8+import Data.Word (Word8)
9+import Text.Printf
10+
11+-- Note: rgbColor's mapping from RGB to 240 colors was generated from
12+-- 256colres.pl which is forked from xterm 256colres.pl.
13+
14+-- | Create a value in the Color240 set from an RGB triple
15+rgbColorToColor240 :: Integral i => i -> i -> i -> Word8
16+rgbColorToColor240 r g b
17+ | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"
18+ | r == 8 && g == 8 && b == 8 = 216
19+ | r == 18 && g == 18 && b == 18 = 217
20+ | r == 28 && g == 28 && b == 28 = 218
21+ | r == 38 && g == 38 && b == 38 = 219
22+ | r == 48 && g == 48 && b == 48 = 220
23+ | r == 58 && g == 58 && b == 58 = 221
24+ | r == 68 && g == 68 && b == 68 = 222
25+ | r == 78 && g == 78 && b == 78 = 223
26+ | r == 88 && g == 88 && b == 88 = 224
27+ | r == 98 && g == 98 && b == 98 = 225
28+ | r == 108 && g == 108 && b == 108 = 226
29+ | r == 118 && g == 118 && b == 118 = 227
30+ | r == 128 && g == 128 && b == 128 = 228
31+ | r == 138 && g == 138 && b == 138 = 229
32+ | r == 148 && g == 148 && b == 148 = 230
33+ | r == 158 && g == 158 && b == 158 = 231
34+ | r == 168 && g == 168 && b == 168 = 232
35+ | r == 178 && g == 178 && b == 178 = 233
36+ | r == 188 && g == 188 && b == 188 = 234
37+ | r == 198 && g == 198 && b == 198 = 235
38+ | r == 208 && g == 208 && b == 208 = 236
39+ | r == 218 && g == 218 && b == 218 = 237
40+ | r == 228 && g == 228 && b == 228 = 238
41+ | r == 238 && g == 238 && b == 238 = 239
42+ | otherwise = 36 * go r + 6 * go g + go b
43+ where go = simpleColor_ (error (printf "RGB color %d %d %d does not map to 240 palette."
44+ (fromIntegral r :: Int)
45+ (fromIntegral g :: 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
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+24−64
@@ -1,79 +1,39 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# LANGUAGE ScopedTypeVariables #-}
3−module Graphics.Vty.Debug ( module Graphics.Vty.Debug
4− )
1+-- Copyright 2009-2010 Corey O'Connor
2+module Graphics.Vty.Debug
3+ ( module Graphics.Vty.Debug
4+ , module Graphics.Vty.Debug.Image
5+ )
56where
67
78import Graphics.Vty.Attributes
8−import Graphics.Vty.Image
9−import Graphics.Vty.Picture
9+import Graphics.Vty.Image (DisplayRegion)
10+import Graphics.Vty.Debug.Image
1011import Graphics.Vty.Span
11−import Graphics.Vty.DisplayRegion
1212
13−import Control.Monad
14−import Data.Array
15−import Data.Word
13+import qualified Data.Vector as Vector
1614
17−instance Show SpanOpSequence where
18− show (SpanOpSequence _ row_ops)
19− = concat $ ["{ "] ++ map (\ops -> show ops ++ "; " ) (elems row_ops) ++ [" }"]
15+rowOpsAffectedColumns :: DisplayOps -> [Int]
16+rowOpsAffectedColumns ops
17+ = Vector.toList $ Vector.map spanOpsAffectedColumns ops
2018
21−instance Show SpanOp where
22− show (AttributeChange attr) = show attr
23− show (TextSpan ow cw _) = "TextSpan " ++ show ow ++ " " ++ show cw
19+allSpansHaveWidth :: DisplayOps -> Int -> Bool
20+allSpansHaveWidth ops expected
21+ = all (== expected) $ Vector.toList $ Vector.map spanOpsAffectedColumns ops
2422
25−row_ops_effected_columns :: SpanOpSequence -> [Word]
26−row_ops_effected_columns spans
27− = map span_ops_effected_columns (elems $ row_ops spans)
23+spanOpsAffectedRows :: DisplayOps -> Int
24+spanOpsAffectedRows ops
25+ = toEnum $ length (filter (not . null . Vector.toList) (Vector.toList ops))
2826
29−all_spans_have_width spans expected
30− = all (== expected) ( map span_ops_effected_columns (elems $ row_ops spans) )
31−
32−span_ops_effected_rows :: SpanOpSequence -> Word
33−span_ops_effected_rows (SpanOpSequence _ row_ops)
34− = toEnum $ length (filter (not . null) (elems row_ops))
35−
3627type SpanConstructLog = [SpanConstructEvent]
3728data SpanConstructEvent = SpanSetAttr Attr
3829
39−is_set_attr :: Attr -> SpanConstructEvent -> Bool
40−is_set_attr expected_attr (SpanSetAttr in_attr)
41− | in_attr == expected_attr = True
42−is_set_attr _attr _event = False
30+isSetAttr :: Attr -> SpanConstructEvent -> Bool
31+isSetAttr expectedAttr (SpanSetAttr inAttr)
32+ | inAttr == expectedAttr = True
33+isSetAttr _attr _event = False
4334
44−data DebugWindow = DebugWindow Word Word
35+data MockWindow = MockWindow Int Int
4536 deriving (Show, Eq)
4637
47−region_for_window :: DebugWindow -> DisplayRegion
48−region_for_window (DebugWindow w h) = DisplayRegion w h
49−
50−type TestWindow = DebugWindow
51−
52−type ImageConstructLog = [ImageConstructEvent]
53−data ImageConstructEvent = ImageConstructEvent
54− deriving ( Show, Eq )
55−
56−forward_image_ops = map forward_transform debug_image_ops
57−
58−forward_transform, reverse_transform :: ImageOp -> (Image -> Image)
59−
60−forward_transform (ImageOp f _) = f
61−reverse_transform (ImageOp _ r) = r
62−
63−data ImageOp = ImageOp ImageEndo ImageEndo
64−type ImageEndo = Image -> Image
65−
66−debug_image_ops =
67− [ id_image_op
68− -- , render_single_column_char_op
69− -- , render_double_column_char_op
70− ]
71−
72−id_image_op :: ImageOp
73−id_image_op = ImageOp id id
74−
75−-- render_char_op :: ImageOp
76−-- render_char_op = ImageOp id id
77−
78−instance Show Picture where
79− show (Picture _ image _ ) = "Picture ?? " ++ show image ++ " ??"
38+regionForWindow :: MockWindow -> DisplayRegion
39+regionForWindow (MockWindow w h) = (w,h)
src/Graphics/Vty/Debug/Image.hsnew file+32
@@ -0,0 +1,32 @@
1+{-# OPTIONS_HADDOCK hide #-}
2+module Graphics.Vty.Debug.Image where
3+
4+import Graphics.Vty.Image
5+
6+type ImageConstructLog = [ImageConstructEvent]
7+data ImageConstructEvent = ImageConstructEvent
8+ deriving ( Show, Eq )
9+
10+forwardImageOps :: [Image -> Image]
11+forwardImageOps = map forwardTransform debugImageOps
12+
13+forwardTransform, reverseTransform :: ImageOp -> (Image -> Image)
14+
15+forwardTransform (ImageOp f _) = f
16+reverseTransform (ImageOp _ r) = r
17+
18+data ImageOp = ImageOp ImageEndo ImageEndo
19+type ImageEndo = Image -> Image
20+
21+debugImageOps :: [ImageOp]
22+debugImageOps =
23+ [ idImageOp
24+ -- , renderSingleColumnCharOp
25+ -- , renderDoubleColumnCharOp
26+ ]
27+
28+idImageOp :: ImageOp
29+idImageOp = ImageOp id id
30+
31+-- renderCharOp :: ImageOp
32+-- renderCharOp = ImageOp id id
src/Graphics/Vty/DisplayAttributes.hs+124−65
@@ -1,60 +1,103 @@
1+-- Copyright 2009-2010 Corey O'Connor
12{-# LANGUAGE BangPatterns #-}
2−module Graphics.Vty.DisplayAttributes
3− where
3+{-# LANGUAGE CPP #-}
4+
5+module Graphics.Vty.DisplayAttributes where
46
57import Graphics.Vty.Attributes
68
7−import Data.Bits ( (.&.) )
8−import Data.Monoid ( Monoid(..), mconcat )
9+import Data.Bits ((.&.))
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)
916
10−-- | Given the previously applied display attributes as a FixedAttr and the current display
11−-- attributes as an Attr produces a FixedAttr that represents the current display attributes. This
12−-- is done by using the previously applied display attributes to remove the "KeepCurrent"
17+-- | Given the previously applied display attributes as a FixedAttr and
18+-- the current display attributes as an Attr produces a FixedAttr that
19+-- represents the current display attributes. This is done by using the
20+-- previously applied display attributes to remove the "KeepCurrent"
1321-- abstraction.
14−fix_display_attr :: FixedAttr -> Attr -> FixedAttr
15−fix_display_attr fattr attr
16− = FixedAttr ( fix_style (fixed_style fattr) (attr_style attr) )
17− ( fix_color (fixed_fore_color fattr) (attr_fore_color attr) )
18− ( fix_color (fixed_back_color fattr) (attr_back_color attr) )
22+fixDisplayAttr :: FixedAttr -> Attr -> FixedAttr
23+fixDisplayAttr fattr attr
24+ = FixedAttr (fixStyle (fixedStyle fattr) (attrStyle attr))
25+ (fixColor (fixedForeColor fattr) (attrForeColor attr))
26+ (fixColor (fixedBackColor fattr) (attrBackColor attr))
27+ (fixURL (fixedURL fattr) (attrURL attr))
1928 where
20− fix_style _s Default = default_style_mask
21− fix_style s KeepCurrent = s
22− fix_style _s (SetTo new_style) = new_style
23− fix_color _c Default = Nothing
24− fix_color c KeepCurrent = c
25− fix_color _c (SetTo c) = Just c
29+ fixStyle _s Default = defaultStyleMask
30+ fixStyle s KeepCurrent = s
31+ fixStyle _s (SetTo newStyle) = newStyle
32+ fixColor _c Default = Nothing
33+ fixColor c KeepCurrent = c
34+ fixColor _c (SetTo c) = Just c
35+ fixURL c KeepCurrent = c
36+ fixURL _c (SetTo n) = Just n
37+ fixURL _c Default = Nothing
2638
39+-- | difference between two display attributes. Used in the calculation
40+-- of the operations required to go from one display attribute to the
41+-- next.
42+--
43+-- Previously, vty would reset display attributes to default then apply
44+-- the new display attributes. This turned out to be very expensive: A
45+-- *lot* more data would be sent to the terminal than required.
2746data DisplayAttrDiff = DisplayAttrDiff
28− { style_diffs :: [ StyleStateChange ]
29− , fore_color_diff :: DisplayColorDiff
30− , back_color_diff :: DisplayColorDiff
47+ { styleDiffs :: [StyleStateChange]
48+ , foreColorDiff :: DisplayColorDiff
49+ , backColorDiff :: DisplayColorDiff
50+ , urlDiff :: URLDiff
3151 }
32− 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
3361
3462instance Monoid DisplayAttrDiff where
35− mempty = DisplayAttrDiff [] NoColorChange NoColorChange
36− mappend d_0 d_1 =
37− let ds = simplify_style_diffs ( style_diffs d_0 ) ( style_diffs d_1 )
38− fcd = simplify_color_diffs ( fore_color_diff d_0 ) ( fore_color_diff d_1 )
39− bcd = simplify_color_diffs ( back_color_diff d_0 ) ( back_color_diff d_1 )
40− in DisplayAttrDiff ds fcd bcd
63+ mempty = DisplayAttrDiff [] NoColorChange NoColorChange NoLinkChange
64+#if !(MIN_VERSION_base(4,11,0))
65+ mappend = (<>)
66+#endif
4167
42−simplify_style_diffs :: [ StyleStateChange ] -> [ StyleStateChange ] -> [ StyleStateChange ]
43−simplify_style_diffs cs_0 cs_1 = cs_0 `mappend` cs_1
68+-- | Used in the computation of a final style attribute change.
69+simplifyStyleDiffs :: [StyleStateChange] -> [StyleStateChange] -> [StyleStateChange]
70+simplifyStyleDiffs cs0 cs1 = cs0 `mappend` cs1
4471
45−simplify_color_diffs _cd ColorToDefault = ColorToDefault
46−simplify_color_diffs cd NoColorChange = cd
47−simplify_color_diffs _cd ( SetColor !c ) = SetColor c
72+-- | Consider two display color attributes diffs. What display color
73+-- attribute diff are these equivalent to?
74+simplifyColorDiffs :: DisplayColorDiff -> DisplayColorDiff -> DisplayColorDiff
75+simplifyColorDiffs _cd ColorToDefault = ColorToDefault
76+simplifyColorDiffs cd NoColorChange = cd
77+simplifyColorDiffs _cd (SetColor !c) = SetColor c
4878
49−data DisplayColorDiff
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
84+
85+-- | Difference between two display color attribute changes.
86+data DisplayColorDiff
5087 = ColorToDefault
5188 | NoColorChange
5289 | SetColor !Color
53− deriving ( Show, Eq )
90+ deriving (Show, Eq)
5491
55−data StyleStateChange
92+-- | Style attribute changes are transformed into a sequence of
93+-- apply/removes of the individual attributes.
94+data StyleStateChange
5695 = ApplyStandout
5796 | RemoveStandout
97+ | ApplyItalic
98+ | RemoveItalic
99+ | ApplyStrikethrough
100+ | RemoveStrikethrough
58101 | ApplyUnderline
59102 | RemoveUnderline
60103 | ApplyReverseVideo
@@ -65,42 +108,58 @@ data StyleStateChange
65108 | RemoveDim
66109 | ApplyBold
67110 | RemoveBold
68− deriving ( Show, Eq )
111+ deriving (Show, Eq)
112+
113+-- Setting and unsetting hyperlinks
114+data URLDiff
115+ = LinkTo !ByteString
116+ | NoLinkChange
117+ | EndLink
118+ deriving (Show, Eq)
69119
70−display_attr_diffs :: FixedAttr -> FixedAttr -> DisplayAttrDiff
71−display_attr_diffs attr attr' = DisplayAttrDiff
72− { style_diffs = diff_styles ( fixed_style attr ) ( fixed_style attr' )
73− , fore_color_diff = diff_color ( fixed_fore_color attr ) ( fixed_fore_color attr' )
74− , back_color_diff = diff_color ( fixed_back_color attr ) ( fixed_back_color attr' )
120+-- | Determines the diff between two display&color attributes. This diff
121+-- determines the operations that actually get output to the terminal.
122+displayAttrDiffs :: FixedAttr -> FixedAttr -> DisplayAttrDiff
123+displayAttrDiffs attr attr' = DisplayAttrDiff
124+ { styleDiffs = diffStyles (fixedStyle attr) (fixedStyle attr')
125+ , foreColorDiff = diffColor (fixedForeColor attr) (fixedForeColor attr')
126+ , backColorDiff = diffColor (fixedBackColor attr) (fixedBackColor attr')
127+ , urlDiff = diffURL (fixedURL attr) (fixedURL attr')
75128 }
76129
77−diff_color :: Maybe Color -> Maybe Color -> DisplayColorDiff
78−diff_color Nothing (Just c') = SetColor c'
79−diff_color (Just c) (Just c')
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+
135+diffColor :: Maybe Color -> Maybe Color -> DisplayColorDiff
136+diffColor Nothing (Just c') = SetColor c'
137+diffColor (Just c) (Just c')
80138 | c == c' = NoColorChange
81139 | otherwise = SetColor c'
82−diff_color Nothing Nothing = NoColorChange
83−diff_color (Just _) Nothing = ColorToDefault
84−
85−diff_styles :: Style -> Style -> [StyleStateChange]
86−diff_styles prev cur
87− = mconcat
88− [ style_diff standout ApplyStandout RemoveStandout
89− , style_diff underline ApplyUnderline RemoveUnderline
90− , style_diff reverse_video ApplyReverseVideo RemoveReverseVideo
91− , style_diff blink ApplyBlink RemoveBlink
92− , style_diff dim ApplyDim RemoveDim
93− , style_diff bold ApplyBold RemoveBold
140+diffColor Nothing Nothing = NoColorChange
141+diffColor (Just _) Nothing = ColorToDefault
142+
143+diffStyles :: Style -> Style -> [StyleStateChange]
144+diffStyles prev cur
145+ = mconcat
146+ [ styleDiff standout ApplyStandout RemoveStandout
147+ , styleDiff underline ApplyUnderline RemoveUnderline
148+ , styleDiff italic ApplyItalic RemoveItalic
149+ , styleDiff strikethrough ApplyStrikethrough RemoveStrikethrough
150+ , styleDiff reverseVideo ApplyReverseVideo RemoveReverseVideo
151+ , styleDiff blink ApplyBlink RemoveBlink
152+ , styleDiff dim ApplyDim RemoveDim
153+ , styleDiff bold ApplyBold RemoveBold
94154 ]
95− where
96− style_diff s sm rm
97− = case ( 0 == prev .&. s, 0 == cur .&. s ) of
155+ where
156+ styleDiff s sm rm
157+ = case (0 == prev .&. s, 0 == cur .&. s) of
98158 -- not set in either
99− ( True, True ) -> []
159+ (True, True) -> []
100160 -- set in both
101− ( False, False ) -> []
161+ (False, False) -> []
102162 -- now set
103− ( True, False) -> [ sm ]
163+ (True, False) -> [sm]
104164 -- now unset
105− ( False, True) -> [ rm ]
106−
165+ (False, True) -> [rm]
src/Graphics/Vty/DisplayRegion.hsdeleted−11
@@ -1,11 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−module Graphics.Vty.DisplayRegion
3− where
4−
5−import Data.Word
6−
7−data DisplayRegion = DisplayRegion
8− { region_width :: !Word
9− , region_height :: !Word
10− } deriving ( Show, Eq )
11−
src/Graphics/Vty/Error.hsnew file+8
@@ -0,0 +1,8 @@
1+module Graphics.Vty.Error where
2+
3+-- | The type of exceptions specific to vty.
4+--
5+-- These have fully qualified names by default since, IMO, exception
6+-- handling requires this.
7+data VtyException
8+ = VtyFailure String -- ^ Uncategorized failure specific to vty.
src/Graphics/Vty/Image.hs+353−301
@@ -1,329 +1,381 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# LANGUAGE BangPatterns #-}
1+-- Copyright 2009-2010 Corey O'Connor
32{-# LANGUAGE NamedFieldPuns #-}
43{-# LANGUAGE DisambiguateRecordFields #-}
5−{-# OPTIONS_GHC -funbox-strict-fields #-}
6−module Graphics.Vty.Image ( Image(..)
7− , image_width
8− , image_height
9− , (<|>)
10− , (<->)
11− , horiz_cat
12− , vert_cat
13− , background_fill
14− , char
15− , string
16− , iso_10646_string
17− , utf8_string
18− , utf8_bytestring
19− , char_fill
20− , empty_image
21− , translate
22− -- | The possible display attributes used in constructing an `Image`.
23− , module Graphics.Vty.Attributes
24− )
25− where
4+-- | A Vty program makes 'Picture's from 'Image's. This module provides
5+-- the core constructors for creating, combining, and modifying
6+-- 'Image's.
7+module Graphics.Vty.Image
8+ (
9+ -- * Images
10+ Image
11+ , imageWidth
12+ , imageHeight
13+ -- * Image constructors
14+ , emptyImage
15+ , char
16+ , string
17+ , iso10646String
18+ , utf8String
19+ , text
20+ , text'
21+ , backgroundFill
22+ , utf8Bytestring
23+ , utf8Bytestring'
24+ , charFill
25+ -- * Combinators
26+ , horizJoin
27+ , (<|>)
28+ , vertJoin
29+ , (<->)
30+ , horizCat
31+ , vertCat
32+ -- * Image modifications
33+ , crop
34+ , cropRight
35+ , cropLeft
36+ , cropBottom
37+ , cropTop
38+ , pad
39+ , resize
40+ , resizeWidth
41+ , resizeHeight
42+ , translate
43+ , translateX
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
59+ )
60+where
2661
2762import Graphics.Vty.Attributes
63+import Graphics.Vty.Image.Internal
64+import Graphics.Text.Width
65+
66+import qualified Data.ByteString as B
67+import qualified Data.ByteString.Lazy as BL
68+import qualified Data.Text as T
69+import qualified Data.Text.Encoding as T
70+import qualified Data.Text.Lazy as TL
71+import qualified Data.Text.Lazy.Encoding as TL
72+import Data.Word
2873
29−import Codec.Binary.UTF8.Width
74+-- | A region of the display (first width, then height)
75+type DisplayRegion = (Int,Int)
3076
31−import Codec.Binary.UTF8.String ( decode )
77+regionWidth :: DisplayRegion -> Int
78+regionWidth = fst
3279
33−import Data.AffineSpace
34−import qualified Data.ByteString as BS
35−import Data.Monoid
36−import qualified Data.Sequence as Seq
37−import qualified Data.String.UTF8 as UTF8
38−import Data.Word
80+regionHeight :: DisplayRegion -> Int
81+regionHeight = snd
3982
4083infixr 5 <|>
4184infixr 4 <->
4285
43−type StringSeq = Seq.Seq Char
44−
45−-- | An image in VTY defines:
46−--
47−-- * properties required to display the image. These are properties that effect the output image
48−-- but are independent of position
49−--
50−-- * A set of position-dependent text and attribute regions. The possible regions are:
51−--
52−-- * a point. ( char )
53−--
54−-- * a horizontal line of characters with a single attribute. (string, utf8_string,
55−-- utf8_bytestring )
56−--
57−-- * a fill of a single character. (char_fill)
58−--
59−-- * a fill of the picture's background. (background_fill)
60−--
61−-- todo: increase the number of encoded bytestring formats supported.
62−data Image =
63− -- A horizontal text span is always >= 1 column and has a row height of 1.
64− HorizText
65− { attr :: !Attr
66− -- All character data is stored as Char sequences with the ISO-10646 encoding.
67− , text :: StringSeq
68− -- in columns
69− , output_width :: !Word -- >= 0
70− , char_width :: !Word -- >= 1
71− }
72− -- A horizontal join can be constructed between any two images. However a HorizJoin instance is
73− -- required to be between two images of equal height. The horiz_join constructor adds background
74− -- filles to the provided images that assure this is true for the HorizJoin value produced.
75− | HorizJoin
76− { part_left :: Image
77− , part_right :: Image
78− , output_width :: !Word -- >= 1
79− , output_height :: !Word -- >= 1
80− }
81− -- A veritical join can be constructed between any two images. However a VertJoin instance is
82− -- required to be between two images of equal width. The vert_join constructor adds background
83− -- fills to the provides images that assure this is true for the VertJoin value produced.
84− | VertJoin
85− { part_top :: Image
86− , part_bottom :: Image
87− , output_width :: !Word -- >= 1
88− , output_height :: !Word -- >= 1
89− }
90− -- A background fill will be filled with the background pattern. The background pattern is
91− -- defined as a property of the Picture this Image is used to form.
92− | BGFill
93− { output_width :: !Word -- >= 1
94− , output_height :: !Word -- >= 1
95− }
96− -- The combining operators identity constant.
97− -- EmptyImage <|> a = a
98− -- EmptyImage <-> a = a
99− --
100− -- Any image of zero size equals the empty image.
101− | EmptyImage
102− | Translation (Int, Int) Image
103− deriving Eq
104−
105−instance Show Image where
106− show ( HorizText { output_width = ow, text = txt } )
107− = "HorizText [" ++ show ow ++ "] (" ++ show txt ++ ")"
108− show ( BGFill { output_width = c, output_height = r } )
109− = "BGFill (" ++ show c ++ "," ++ show r ++ ")"
110− show ( HorizJoin { part_left = l, part_right = r, output_width = c } )
111− = "HorizJoin " ++ show c ++ " ( " ++ show l ++ " <|> " ++ show r ++ " )"
112− show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } )
113− = "VertJoin (" ++ show c ++ ", " ++ show r ++ ") ( " ++ show t ++ " ) <-> ( " ++ show b ++ " )"
114− show ( EmptyImage ) = "EmptyImage"
115−
116−-- | Currently append in the Monoid instance is equivalent to <->. Future versions will just stack
117−-- the images.
118−instance Monoid Image where
119− mempty = empty_image
120− mappend = (<->)
121−
122−-- A horizontal text image of 0 characters in width simplifies to the EmptyImage
123−horiz_text :: Attr -> StringSeq -> Word -> Image
124−horiz_text a txt ow
125− | ow == 0 = EmptyImage
126− | otherwise = HorizText a txt ow (toEnum $ Seq.length txt)
127−
128−horiz_join :: Image -> Image -> Word -> Word -> Image
129−horiz_join i_0 i_1 w h
130− -- A horiz join of two 0 width images simplifies to the EmptyImage
131− | w == 0 = EmptyImage
132− -- A horizontal join where either part is 0 columns in width simplifies to the other part.
133− -- This covers the case where one part is the EmptyImage.
134− | image_width i_0 == 0 = i_1
135− | image_width i_1 == 0 = i_0
136− -- If the images are of the same height then no BG padding is required
137− | image_height i_0 == image_height i_1 = HorizJoin i_0 i_1 w h
138− -- otherwise one of the imagess needs to be padded to the right size.
139− | image_height i_0 < image_height i_1 -- Pad i_0
140− = let pad_amount = image_height i_1 - image_height i_0
141− in horiz_join ( vert_join i_0
142− ( BGFill ( image_width i_0 ) pad_amount )
143− ( image_width i_0 )
144− ( image_height i_1 )
145− )
146− i_1
147− w h
148− | image_height i_0 > image_height i_1 -- Pad i_1
149− = let pad_amount = image_height i_0 - image_height i_1
150− in horiz_join i_0
151− ( vert_join i_1
152− ( BGFill ( image_width i_1 ) pad_amount )
153− ( image_width i_1 )
154− ( image_height i_0 )
155− )
156− w h
157−horiz_join _ _ _ _ = error "horiz_join applied to undefined values."
158−
159−vert_join :: Image -> Image -> Word -> Word -> Image
160−vert_join i_0 i_1 w h
161− -- A vertical join of two 0 height images simplifies to the EmptyImage
162− | h == 0 = EmptyImage
163− -- A vertical join where either part is 0 rows in height simplifies to the other part.
164− -- This covers the case where one part is the EmptyImage
165− | image_height i_0 == 0 = i_1
166− | image_height i_1 == 0 = i_0
167− -- If the images are of the same height then no background padding is required
168− | image_width i_0 == image_width i_1 = VertJoin i_0 i_1 w h
169− -- Otherwise one of the images needs to be padded to the size of the other image.
170− | image_width i_0 < image_width i_1
171− = let pad_amount = image_width i_1 - image_width i_0
172− in vert_join ( horiz_join i_0
173− ( BGFill pad_amount ( image_height i_0 ) )
174− ( image_width i_1 )
175− ( image_height i_0 )
176− )
177− i_1
178− w h
179− | image_width i_0 > image_width i_1
180− = let pad_amount = image_width i_0 - image_width i_1
181− in vert_join i_0
182− ( horiz_join i_1
183− ( BGFill pad_amount ( image_height i_1 ) )
184− ( image_width i_0 )
185− ( image_height i_1 )
186− )
187− w h
188−vert_join _ _ _ _ = error "vert_join applied to undefined values."
189−
190−-- | An area of the picture's bacground (See Background) of w columns and h rows.
191−background_fill :: Word -> Word -> Image
192−background_fill w h
86+-- | An area of the picture's background (See 'Background').
87+backgroundFill :: Int
88+ -- ^ Fill width in columns
89+ -> Int
90+ -- ^ Fill height in rows
91+ -> Image
92+backgroundFill w h
19393 | w == 0 = EmptyImage
19494 | h == 0 = EmptyImage
19595 | otherwise = BGFill w h
19696
197−-- | The width of an Image. This is the number display columns the image will occupy.
198−image_width :: Image -> Word
199−image_width HorizText { output_width = w } = w
200−image_width HorizJoin { output_width = w } = w
201−image_width VertJoin { output_width = w } = w
202−image_width BGFill { output_width = w } = w
203−image_width EmptyImage = 0
204−image_width ( Translation _v i ) = image_width i
205−
206−-- | The height of an Image. This is the number of display rows the image will occupy.
207−image_height :: Image -> Word
208−image_height HorizText {} = 1
209−image_height HorizJoin { output_height = r } = r
210−image_height VertJoin { output_height = r } = r
211−image_height BGFill { output_height = r } = r
212−image_height EmptyImage = 0
213−image_height ( Translation _v i ) = image_width i
214−
215−-- | Combines two images side by side.
97+-- | Combines two images horizontally. This is an alias for 'horizJoin'.
21698--
217−-- The result image will have a width equal to the sum of the two images width. And the height will
218−-- equal the largest height of the two images. The area not defined in one image due to a height
219−-- missmatch will be filled with the background pattern.
99+-- infixr 5
220100(<|>) :: Image -> Image -> Image
101+(<|>) = horizJoin
221102
222−-- Two horizontal text spans with the same attributes can be merged.
223−h0@(HorizText attr_0 text_0 ow_0 _) <|> h1@(HorizText attr_1 text_1 ow_1 _)
224− | attr_0 == attr_1 = horiz_text attr_0 (text_0 Seq.>< text_1) (ow_0 + ow_1)
225− | otherwise = horiz_join h0 h1 (ow_0 + ow_1) 1
226−
227−-- Anything placed to the right of a join wil be joined to the right sub image.
228−-- The total columns for the join is the sum of the two arguments columns
229−h0@( HorizJoin {} ) <|> h1
230− = horiz_join ( part_left h0 )
231− ( part_right h0 <|> h1 )
232− ( image_width h0 + image_width h1 )
233− ( max (image_height h0) (image_height h1) )
234−
235−-- Anything but a join placed to the left of a join wil be joined to the left sub image.
236−-- The total columns for the join is the sum of the two arguments columns
237−h0 <|> h1@( HorizJoin {} )
238− = horiz_join ( h0 <|> part_left h1 )
239− ( part_right h1 )
240− ( image_width h0 + image_width h1 )
241− ( max (image_height h0) (image_height h1) )
242−
243−h0 <|> h1
244− = horiz_join h0
245− h1
246− ( image_width h0 + image_width h1 )
247− ( max (image_height h0) (image_height h1) )
248−
249−-- | Combines two images vertically.
250−-- The result image will have a height equal to the sum of the heights of both images.
251−-- The width will equal the largest width of the two images.
252−-- The area not defined in one image due to a width missmatch will be filled with the background
253−-- pattern.
103+-- | Combines two images vertically. This is an alias for 'vertJoin'.
104+--
105+-- infixr 4
254106(<->) :: Image -> Image -> Image
255−im_t <-> im_b
256− = vert_join im_t
257− im_b
258− ( max (image_width im_t) (image_width im_b) )
259− ( image_height im_t + image_height im_b )
260−
261−-- | Compose any number of images horizontally.
262−horiz_cat :: [Image] -> Image
263−horiz_cat = foldr (<|>) EmptyImage
264−
265−-- | Compose any number of images vertically.
266−vert_cat :: [Image] -> Image
267−vert_cat = foldr (<->) EmptyImage
268−
269−-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
270−-- the ISO-10646 encoding.
107+(<->) = vertJoin
108+
109+-- | Compose any number of images together horizontally, with the first
110+-- in the list being leftmost.
111+horizCat :: [Image] -> Image
112+horizCat = foldr horizJoin EmptyImage
113+
114+-- | Compose any number of images vertically, with the first in the list
115+-- being topmost.
116+vertCat :: [Image] -> Image
117+vertCat = foldr vertJoin EmptyImage
118+
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.
122+text :: Attr -> TL.Text -> Image
123+text a txt = let displayWidth = safeWctlwidth txt
124+ in HorizText a txt displayWidth (fromIntegral $! TL.length txt)
125+
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.
129+text' :: Attr -> T.Text -> Image
130+text' a txt = let displayWidth = safeWctwidth txt
131+ in HorizText a (TL.fromStrict txt) displayWidth (T.length txt)
132+
133+-- | Make an image from a single character. This is a standard Haskell
134+-- 31-bit character assumed to be in the ISO-10646 encoding.
271135char :: Attr -> Char -> Image
272−char !a !c = HorizText a (Seq.singleton c) (safe_wcwidth c) 1
273−
274−-- | A string of characters layed out on a single row with the same display attribute. The string is
275−-- assumed to be a sequence of ISO-10646 characters.
136+char a c =
137+ let displayWidth = safeWcwidth c
138+ in HorizText a (TL.singleton c) displayWidth 1
139+
140+-- | Make an image from a string of characters laid out on a single
141+-- row with the same display attribute. The string is assumed to be a
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'.
276158--
277−-- Note: depending on how the Haskell compiler represents string literals a string literal in a
278−-- UTF-8 encoded source file, for example, may be represented as a ISO-10646 string.
279−-- That is, I think, the case with GHC 6.10. This means, for the most part, you don't need to worry
280−-- about the encoding format when outputting string literals. Just provide the string literal
281−-- directly to iso_10646_string or string.
282−--
283−iso_10646_string :: Attr -> String -> Image
284−iso_10646_string !a !str = horiz_text a (Seq.fromList str) (safe_wcswidth str)
285−
286−-- | Alias for iso_10646_string. Since the usual case is that a literal string like "foo" is
287−-- represented internally as a list of ISO 10646 31 bit characters.
159+-- This is an alias for iso10646String since the usual case is that a
160+-- literal string like "foo" is represented internally as a list of ISO
161+-- 10646 31 bit characters.
288162--
289−-- Note: Keep in mind that GHC will compile source encoded as UTF-8 but the literal strings, while
290−-- UTF-8 encoded in the source, will be transcoded to a ISO 10646 31 bit characters runtime
291−-- representation.
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
165+-- transcoded to a ISO 10646 31 bit characters runtime representation.
292166string :: Attr -> String -> Image
293−string = iso_10646_string
167+string = iso10646String
294168
295−-- | A string of characters layed out on a single row. The string is assumed to be a sequence of
296−-- UTF-8 characters.
297−utf8_string :: Attr -> [Word8] -> Image
298−utf8_string !a !str = string a ( decode str )
169+-- | Make an 'Image' from a string of characters layed out on a single
170+-- row. The input is assumed to be the bytes for UTF-8 encoded text.
171+utf8String :: Attr -> [Word8] -> Image
172+utf8String a bytes = utf8Bytestring a (BL.pack bytes)
299173
300−safe_wcwidth :: Char -> Word
301−safe_wcwidth c = case wcwidth c of
302− i | i < 0 -> 0 -- error "negative wcwidth"
303− | otherwise -> toEnum i
174+-- | Make an 'Image' from a UTF-8 encoded lazy bytestring.
175+utf8Bytestring :: Attr -> BL.ByteString -> Image
176+utf8Bytestring a bs = text a (TL.decodeUtf8 bs)
304177
305−safe_wcswidth :: String -> Word
306−safe_wcswidth str = case wcswidth str of
307− i | i < 0 -> 0 -- error "negative wcswidth"
308− | otherwise -> toEnum i
178+-- | Make an 'Image' from a UTF-8 encoded strict bytestring.
179+utf8Bytestring' :: Attr -> B.ByteString -> Image
180+utf8Bytestring' a bs = text' a (T.decodeUtf8 bs)
309181
310−-- | Renders a UTF-8 encoded bytestring.
311−utf8_bytestring :: Attr -> BS.ByteString -> Image
312−utf8_bytestring !a !bs = string a (UTF8.toString $ UTF8.fromRep bs)
313−
314−-- | creates a fill of the specified character. The dimensions are in number of characters wide and
315−-- number of rows high.
182+-- | Make an image filling a region with the specified character.
316183--
317−-- Unlike the Background fill character this character can have double column display width.
318−char_fill :: Enum d => Attr -> Char -> d -> d -> Image
319−char_fill !a !c w h =
320− vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) $ char a c
321−
322−-- | The empty image. Useful for fold combinators. These occupy no space nor define any display
323−-- attributes.
324−empty_image :: Image
325−empty_image = EmptyImage
326−
327−translate :: (Int, Int) -> Image -> Image
328−translate v i = Translation v i
329−
184+-- If either the width or height are less than or equal to 0, then
185+-- the result is the empty image.
186+charFill :: Integral d
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
199+ $ replicate (fromIntegral h)
200+ $ HorizText a txt displayWidth charWidth
201+ where
202+ txt = TL.replicate charWidth (TL.singleton c)
203+ displayWidth = safeWcwidth c * charWidth
204+
205+ charWidth :: Num a => a
206+ charWidth = fromIntegral w
207+
208+-- | The empty image. Useful for fold combinators. These occupy no space
209+-- and do not affect display attributes.
210+emptyImage :: Image
211+emptyImage = EmptyImage
212+
213+-- | Pad the given image. This adds background character fills to the
214+-- left, top, right, bottom.
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
226+pad 0 0 0 0 i = i
227+pad inL inT inR inB inImage
228+ | inL < 0 || inT < 0 || inR < 0 || inB < 0 = error "cannot pad by negative amount"
229+ | otherwise = go inL inT inR inB inImage
230+ where
231+ go 0 0 0 0 i = i
232+ go 0 0 0 b i = VertJoin i (BGFill w b) w h
233+ where w = imageWidth i
234+ h = imageHeight i + b
235+ go 0 0 r b i = go 0 0 0 b $ HorizJoin i (BGFill r h) w h
236+ where w = imageWidth i + r
237+ h = imageHeight i
238+ go 0 t r b i = go 0 0 r b $ VertJoin (BGFill w t) i w h
239+ where w = imageWidth i
240+ h = imageHeight i + t
241+ go l t r b i = go 0 t r b $ HorizJoin (BGFill l h) i w h
242+ where w = imageWidth i + l
243+ h = imageHeight i
244+
245+-- | Translates an image by padding or cropping the left and top.
246+--
247+-- If translation offsets are negative then the image is cropped.
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
255+translate x y i = translateX x (translateY y i)
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+
273+-- | Ensure an image is no larger than the provided size. If the image
274+-- is larger then crop the right or bottom.
275+--
276+-- This is equivalent to a vertical crop from the bottom followed by
277+-- horizontal crop from the right.
278+crop :: Int
279+ -- ^ Cropping width
280+ -> Int
281+ -- ^ Cropping height
282+ -> Image
283+ -- ^ The image to crop
284+ -> Image
285+crop 0 _ _ = EmptyImage
286+crop _ 0 _ = EmptyImage
287+crop w h i = cropBottom h (cropRight w i)
288+
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
291+-- the image is cropped from the bottom.
292+cropBottom :: Int -> Image -> Image
293+cropBottom 0 _ = EmptyImage
294+cropBottom h inI
295+ | h < 0 = error "cannot crop height to less than zero"
296+ | otherwise = go inI
297+ where
298+ go EmptyImage = EmptyImage
299+ go i@(CropBottom {croppedImage, outputWidth, outputHeight})
300+ | outputHeight <= h = i
301+ | otherwise = CropBottom croppedImage outputWidth h
302+ go i
303+ | h >= imageHeight i = i
304+ | otherwise = CropBottom i (imageWidth i) h
305+
306+-- | Crop an image's width. If the image's width is less than or equal
307+-- to the specified width then this operation has no effect. Otherwise
308+-- the image is cropped from the right.
309+cropRight :: Int -> Image -> Image
310+cropRight 0 _ = EmptyImage
311+cropRight w inI
312+ | w < 0 = error "cannot crop width to less than zero"
313+ | otherwise = go inI
314+ where
315+ go EmptyImage = EmptyImage
316+ go i@(CropRight {croppedImage, outputWidth, outputHeight})
317+ | outputWidth <= w = i
318+ | otherwise = CropRight croppedImage w outputHeight
319+ go i
320+ | w >= imageWidth i = i
321+ | otherwise = CropRight i w (imageHeight i)
322+
323+-- | Crop an image's width. If the image's width is less than or equal
324+-- to the specified width then this operation has no effect. Otherwise
325+-- the image is cropped from the left.
326+cropLeft :: Int -> Image -> Image
327+cropLeft 0 _ = EmptyImage
328+cropLeft w inI
329+ | w < 0 = error "cannot crop the width to less than zero"
330+ | otherwise = go inI
331+ where
332+ go EmptyImage = EmptyImage
333+ go i@(CropLeft {croppedImage, leftSkip, outputWidth, outputHeight})
334+ | outputWidth <= w = i
335+ | otherwise =
336+ let leftSkip' = leftSkip + outputWidth - w
337+ in CropLeft croppedImage leftSkip' w outputHeight
338+ go i
339+ | imageWidth i <= w = i
340+ | otherwise = CropLeft i (imageWidth i - w) w (imageHeight i)
341+
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
344+-- the image is cropped from the top.
345+cropTop :: Int -> Image -> Image
346+cropTop 0 _ = EmptyImage
347+cropTop h inI
348+ | h < 0 = error "cannot crop the height to less than zero"
349+ | otherwise = go inI
350+ where
351+ go EmptyImage = EmptyImage
352+ go i@(CropTop {croppedImage, topSkip, outputWidth, outputHeight})
353+ | outputHeight <= h = i
354+ | otherwise =
355+ let topSkip' = topSkip + outputHeight - h
356+ in CropTop croppedImage topSkip' outputWidth h
357+ go i
358+ | imageHeight i <= h = i
359+ | otherwise = CropTop i (imageHeight i - h) (imageWidth i) h
360+
361+-- | Generic resize. Pads and crops are added to ensure that the
362+-- resulting image matches the specified dimensions. This is biased to
363+-- pad/crop the right and bottom.
364+resize :: Int -> Int -> Image -> Image
365+resize w h i = resizeHeight h (resizeWidth w i)
366+
367+-- | Resize the width. Pads and crops as required to assure the given
368+-- display width. This is biased to pad/crop on the right.
369+resizeWidth :: Int -> Image -> Image
370+resizeWidth w i = case w `compare` imageWidth i of
371+ LT -> cropRight w i
372+ EQ -> i
373+ GT -> i <|> BGFill (w - imageWidth i) (imageHeight i)
374+
375+-- | Resize the height. Pads and crops as required to assure the given
376+-- display height. This is biased to pad/crop on the bottom.
377+resizeHeight :: Int -> Image -> Image
378+resizeHeight h i = case h `compare` imageHeight i of
379+ LT -> cropBottom h i
380+ EQ -> i
381+ GT -> i <-> BGFill (imageWidth i) (h - imageHeight i)
src/Graphics/Vty/Image/Internal.hsnew file+285
@@ -0,0 +1,285 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+{-# LANGUAGE DeriveGeneric #-}
4+{-# OPTIONS_HADDOCK hide #-}
5+
6+module Graphics.Vty.Image.Internal where
7+
8+import Graphics.Vty.Attributes
9+import Graphics.Text.Width
10+
11+import GHC.Generics
12+
13+import Control.DeepSeq
14+
15+#if !(MIN_VERSION_base(4,11,0))
16+import Data.Semigroup (Semigroup(..))
17+#endif
18+import qualified Data.Text.Lazy as TL
19+
20+-- | A display text is a Data.Text.Lazy
21+type DisplayText = TL.Text
22+
23+clipText :: DisplayText -> Int -> Int -> DisplayText
24+clipText txt leftSkip rightClip =
25+ -- CPS would clarify this I think
26+ let (toDrop,padPrefix) = clipForCharWidth leftSkip txt 0
27+ txt' = if padPrefix then TL.cons '…' (TL.drop (toDrop+1) txt) else TL.drop toDrop txt
28+ (toTake,padSuffix) = clipForCharWidth rightClip txt' 0
29+ txt'' = TL.append (TL.take toTake txt') (if padSuffix then TL.singleton '…' else TL.empty)
30+ -- Note: some characters and zero-width and combining characters
31+ -- combine to the left, so keep taking characters even if the
32+ -- width is zero.
33+ clipForCharWidth w t n
34+ | TL.null t = (n, False)
35+ | w < cw = (n, w /= 0)
36+ | otherwise = clipForCharWidth (w - cw) (TL.tail t) (n + 1)
37+ where cw = safeWcwidth (TL.head t)
38+ in txt''
39+
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
46+--
47+-- * a horizontal or vertical join of two images
48+--
49+-- * a two dimensional fill of the 'Picture's background character
50+--
51+-- * a cropped image
52+--
53+-- * an empty image of no size or content.
54+data Image =
55+ -- | A horizontal text span has a row height of 1.
56+ HorizText
57+ { attr :: Attr
58+ -- | The text to display. The display width of the text is always
59+ -- outputWidth.
60+ , displayText :: DisplayText
61+ -- | The number of display columns for the text.
62+ , outputWidth :: Int
63+ -- | the number of characters in the text.
64+ , charWidth :: Int
65+ }
66+ -- | A horizontal join can be constructed between any two images.
67+ -- However a HorizJoin instance is required to be between two images
68+ -- of equal height. The horizJoin constructor adds background fills
69+ -- to the provided images that assure this is true for the HorizJoin
70+ -- value produced.
71+ | HorizJoin
72+ { partLeft :: Image
73+ , partRight :: Image
74+ , outputWidth :: Int
75+ -- ^ imageWidth partLeft == imageWidth partRight. Always > 0
76+ , outputHeight :: Int
77+ -- ^ imageHeight partLeft == imageHeight partRight. Always > 0
78+ }
79+ -- | A veritical join can be constructed between any two images.
80+ -- However a VertJoin instance is required to be between two images
81+ -- of equal width. The vertJoin constructor adds background fills
82+ -- to the provides images that assure this is true for the VertJoin
83+ -- value produced.
84+ | VertJoin
85+ { partTop :: Image
86+ , partBottom :: Image
87+ , outputWidth :: Int
88+ -- ^ imageWidth partTop == imageWidth partBottom. always > 0
89+ , outputHeight :: Int
90+ -- ^ imageHeight partTop == imageHeight partBottom. always > 1
91+ }
92+ -- | A background fill will be filled with the background char. The
93+ -- background char is defined as a property of the Picture this
94+ -- Image is used to form.
95+ | BGFill
96+ { outputWidth :: Int -- ^ always > 0
97+ , outputHeight :: Int -- ^ always > 0
98+ }
99+ -- | Crop an image horizontally to a size by reducing the size from
100+ -- the right.
101+ | CropRight
102+ { croppedImage :: Image
103+ -- | Always < imageWidth croppedImage > 0
104+ , outputWidth :: Int
105+ , outputHeight :: Int -- ^ imageHeight croppedImage
106+ }
107+ -- | Crop an image horizontally to a size by reducing the size from
108+ -- the left.
109+ | CropLeft
110+ { croppedImage :: Image
111+ -- | Always < imageWidth croppedImage > 0
112+ , leftSkip :: Int
113+ -- | Always < imageWidth croppedImage > 0
114+ , outputWidth :: Int
115+ , outputHeight :: Int
116+ }
117+ -- | Crop an image vertically to a size by reducing the size from
118+ -- the bottom
119+ | CropBottom
120+ { croppedImage :: Image
121+ -- | imageWidth croppedImage
122+ , outputWidth :: Int
123+ -- | height image is cropped to. Always < imageHeight croppedImage > 0
124+ , outputHeight :: Int
125+ }
126+ -- | Crop an image vertically to a size by reducing the size from
127+ -- the top
128+ | CropTop
129+ { croppedImage :: Image
130+ -- | Always < imageHeight croppedImage > 0
131+ , topSkip :: Int
132+ -- | imageWidth croppedImage
133+ , outputWidth :: Int
134+ -- | Always < imageHeight croppedImage > 0
135+ , outputHeight :: Int
136+ }
137+ -- | The empty image
138+ --
139+ -- The combining operators identity constant.
140+ -- EmptyImage <|> a = a
141+ -- EmptyImage <-> a = a
142+ --
143+ -- Any image of zero size equals the empty image.
144+ | EmptyImage
145+ deriving (Eq, Generic, Show, Read)
146+
147+-- | pretty print just the structure of an image.
148+ppImageStructure :: Image -> String
149+ppImageStructure = go 0
150+ where
151+ go indent img = tab indent ++ pp indent img
152+ tab indent = concat $ replicate indent " "
153+ pp _ (HorizText {outputWidth}) = "HorizText(" ++ show outputWidth ++ ")"
154+ pp _ (BGFill {outputWidth, outputHeight})
155+ = "BGFill(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")"
156+ pp i (HorizJoin {partLeft = l, partRight = r, outputWidth = c})
157+ = "HorizJoin(" ++ show c ++ ")\n" ++ go (i+1) l ++ "\n" ++ go (i+1) r
158+ pp i (VertJoin {partTop = t, partBottom = b, outputWidth = c, outputHeight = r})
159+ = "VertJoin(" ++ show c ++ ", " ++ show r ++ ")\n"
160+ ++ go (i+1) t ++ "\n"
161+ ++ go (i+1) b
162+ pp i (CropRight {croppedImage, outputWidth, outputHeight})
163+ = "CropRight(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"
164+ ++ go (i+1) croppedImage
165+ pp i (CropLeft {croppedImage, leftSkip, outputWidth, outputHeight})
166+ = "CropLeft(" ++ show leftSkip ++ "->" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"
167+ ++ go (i+1) croppedImage
168+ pp i (CropBottom {croppedImage, outputWidth, outputHeight})
169+ = "CropBottom(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"
170+ ++ go (i+1) croppedImage
171+ pp i (CropTop {croppedImage, topSkip, outputWidth, outputHeight})
172+ = "CropTop("++ show outputWidth ++ "," ++ show topSkip ++ "->" ++ show outputHeight ++ ")\n"
173+ ++ go (i+1) croppedImage
174+ pp _ EmptyImage = "EmptyImage"
175+
176+instance NFData Image where
177+ rnf EmptyImage = ()
178+ rnf (CropRight i w h) = i `deepseq` w `seq` h `seq` ()
179+ rnf (CropLeft i s w h) = i `deepseq` s `seq` w `seq` h `seq` ()
180+ rnf (CropBottom i w h) = i `deepseq` w `seq` h `seq` ()
181+ rnf (CropTop i s w h) = i `deepseq` s `seq` w `seq` h `seq` ()
182+ rnf (BGFill w h) = w `seq` h `seq` ()
183+ rnf (VertJoin t b w h) = t `deepseq` b `deepseq` w `seq` h `seq` ()
184+ rnf (HorizJoin l r w h) = l `deepseq` r `deepseq` w `seq` h `seq` ()
185+ rnf (HorizText a s w cw) = a `seq` s `deepseq` w `seq` cw `seq` ()
186+
187+-- | The width of an Image. This is the number display columns the image
188+-- will occupy.
189+imageWidth :: Image -> Int
190+imageWidth HorizText { outputWidth = w } = w
191+imageWidth HorizJoin { outputWidth = w } = w
192+imageWidth VertJoin { outputWidth = w } = w
193+imageWidth BGFill { outputWidth = w } = w
194+imageWidth CropRight { outputWidth = w } = w
195+imageWidth CropLeft { outputWidth = w } = w
196+imageWidth CropBottom { outputWidth = w } = w
197+imageWidth CropTop { outputWidth = w } = w
198+imageWidth EmptyImage = 0
199+
200+-- | The height of an Image. This is the number of display rows the
201+-- image will occupy.
202+imageHeight :: Image -> Int
203+imageHeight HorizText {} = 1
204+imageHeight HorizJoin { outputHeight = h } = h
205+imageHeight VertJoin { outputHeight = h } = h
206+imageHeight BGFill { outputHeight = h } = h
207+imageHeight CropRight { outputHeight = h } = h
208+imageHeight CropLeft { outputHeight = h } = h
209+imageHeight CropBottom { outputHeight = h } = h
210+imageHeight CropTop { outputHeight = h } = h
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."
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+108−40
@@ -1,58 +1,126 @@
1−{-# LANGUAGE BangPatterns #-}
2−module Graphics.Vty.Inline ( module Graphics.Vty.Inline
3− )
4− where
1+-- | The inline module provides a limited interface to changing the
2+-- style of terminal output. The intention is for this interface to be
3+-- used inline with other output systems.
4+--
5+-- The changes specified by the InlineM monad are applied to the
6+-- terminal's display attributes. These display attributes affect the
7+-- display of all following text output to the terminal file descriptor.
8+--
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
11+-- background and underlined.
12+--
13+-- @
14+-- putStr \"Not styled. \"
15+-- putAttrChange_ $ do
16+-- backColor red
17+-- applyStyle underline
18+-- putStr \" Styled! \"
19+-- putAttrChange_ $ defaultAll
20+-- putStrLn \"Not styled.\"
21+-- @
22+--
23+-- 'putAttrChange' emits the control codes to the terminal device
24+-- attached to 'Handle'. This is a duplicate of the 'stdout' handle when
25+-- the 'terminalHandle' was (first) acquired. If 'stdout' has since been
26+-- changed then 'putStr', 'putStrLn', 'print' etc. will output to a
27+-- different 'Handle' than 'putAttrChange'.
28+--
29+-- Copyright 2009-2010 Corey O'Connor
30+module Graphics.Vty.Inline
31+ ( module Graphics.Vty.Inline
32+ , withVty
33+ )
34+where
535
6−import Graphics.Vty.Attributes
36+import Graphics.Vty
737import Graphics.Vty.DisplayAttributes
8−import Graphics.Vty.Terminal.Generic
38+import Graphics.Vty.Inline.Unsafe
39+
40+import Blaze.ByteString.Builder (writeToByteString)
941
10−import Control.Applicative
1142import Control.Monad.State.Strict
12−import Control.Monad.Trans
1343
1444import Data.Bits ( (.&.), complement )
1545import Data.IORef
16−import Data.Monoid ( mappend )
1746
18−type InlineM v = State Attr v
47+import System.IO
48+
49+type InlineM v = State InlineState v
50+
51+data InlineState =
52+ InlineState { inlineAttr :: Attr
53+ , inlineUrlsEnabled :: Bool
54+ }
1955
20−back_color :: Color -> InlineM ()
21−back_color c = modify $ flip mappend ( current_attr `with_back_color` c )
56+-- | Set the background color to the provided 'Color'.
57+backColor :: Color -> InlineM ()
58+backColor c = modify $ \s ->
59+ s { inlineAttr = inlineAttr s `withBackColor` c
60+ }
2261
23−fore_color :: Color -> InlineM ()
24−fore_color c = modify $ flip mappend ( current_attr `with_fore_color` c )
62+-- | Set the foreground color to the provided 'Color'.
63+foreColor :: Color -> InlineM ()
64+foreColor c = modify $ \s ->
65+ s { inlineAttr = inlineAttr s `withForeColor` c
66+ }
2567
26−apply_style :: Style -> InlineM ()
27−apply_style s = modify $ flip mappend ( current_attr `with_style` s )
68+-- | Attempt to change the 'Style' of the following text..
69+--
70+-- If the terminal does not support the style change then no error is
71+-- produced. The style can still be removed.
72+applyStyle :: Style -> InlineM ()
73+applyStyle st = modify $ \s ->
74+ s { inlineAttr = inlineAttr s `withStyle` st
75+ }
2876
29−remove_style :: Style -> InlineM ()
30−remove_style s_mask = modify $ \attr ->
31− let style' = case attr_style attr of
32− Default -> error $ "Graphics.Vty.Inline: Cannot remove_style if apply_style never used."
33− KeepCurrent -> error $ "Graphics.Vty.Inline: Cannot remove_style if apply_style never used."
34− SetTo s -> s .&. complement s_mask
35− in attr { attr_style = SetTo style' }
77+-- | Attempt to remove the specified 'Style' from the display of the
78+-- following text.
79+--
80+-- This will fail if 'applyStyle' for the given style has not been
81+-- previously called.
82+removeStyle :: Style -> InlineM ()
83+removeStyle sMask = modify $ \s ->
84+ s { inlineAttr =
85+ let style' = case attrStyle (inlineAttr s) of
86+ Default -> error "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."
87+ KeepCurrent -> error "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."
88+ SetTo st -> st .&. complement sMask
89+ in (inlineAttr s) { attrStyle = SetTo style' }
90+ }
3691
37−default_all :: InlineM ()
38−default_all = put def_attr
92+-- | Reset the display attributes.
93+defaultAll :: InlineM ()
94+defaultAll = modify $ \s -> s { inlineAttr = defAttr }
3995
40−put_attr_change :: ( Applicative m, MonadIO m ) => TerminalHandle -> InlineM () -> m ()
41−put_attr_change t c = do
42− bounds <- display_bounds t
43− d <- display_context t bounds
44− mfattr <- liftIO $ known_fattr <$> readIORef ( state_ref t )
96+-- | Apply the provided display attribute changes to the given terminal
97+-- output device.
98+--
99+-- This does not flush the terminal.
100+putAttrChange :: ( Applicative m, MonadIO m ) => Output -> InlineM () -> m ()
101+putAttrChange out c = liftIO $ do
102+ bounds <- displayBounds out
103+ dc <- displayContext out bounds
104+ mfattr <- prevFattr <$> readIORef (assumedStateRef out)
45105 fattr <- case mfattr of
46106 Nothing -> do
47− marshall_to_terminal t (default_attr_required_bytes d) (serialize_default_attr d)
48− return $ FixedAttr default_style_mask Nothing Nothing
107+ liftIO $ outputByteBuffer out $ writeToByteString $ writeDefaultAttr dc False
108+ return $ FixedAttr defaultStyleMask Nothing Nothing Nothing
49109 Just v -> return v
50− let attr = execState c current_attr
51− attr' = limit_attr_for_display d attr
52− fattr' = fix_display_attr fattr attr'
53− diffs = display_attr_diffs fattr fattr'
54− marshall_to_terminal t ( attr_required_bytes d fattr attr' diffs )
55− ( serialize_set_attr d fattr attr' diffs )
56− liftIO $ modifyIORef ( state_ref t ) $ \s -> s { known_fattr = Just fattr' }
57− inline_hack d
110+ let InlineState attr urlsEnabled = execState c (InlineState currentAttr False)
111+ attr' = limitAttrForDisplay out attr
112+ fattr' = fixDisplayAttr fattr attr'
113+ diffs = displayAttrDiffs fattr fattr'
114+ outputByteBuffer out $ writeToByteString $ writeSetAttr dc urlsEnabled fattr attr' diffs
115+ modifyIORef (assumedStateRef out) $ \s -> s { prevFattr = Just fattr' }
116+ inlineHack dc
58117
118+-- | Apply the provided display attributes changes to the terminal
119+-- output device.
120+--
121+-- This will flush the terminal output.
122+putAttrChange_ :: ( Applicative m, MonadIO m ) => InlineM () -> m ()
123+putAttrChange_ c = liftIO $ withOutput $ \out -> do
124+ hFlush stdout
125+ putAttrChange out c
126+ hFlush stdout
src/Graphics/Vty/Inline/Unsafe.hsnew file+59
@@ -0,0 +1,59 @@
1+{-# OPTIONS_HADDOCK hide #-}
2+
3+module Graphics.Vty.Inline.Unsafe where
4+
5+import Graphics.Vty
6+
7+import Data.IORef
8+
9+import GHC.IO.Handle (hDuplicate)
10+
11+import System.IO (stdin, stdout, hSetBuffering, BufferMode(NoBuffering))
12+
13+import System.IO.Unsafe
14+
15+import System.Posix.IO (handleToFd)
16+
17+globalVty :: IORef (Maybe Vty)
18+{-# NOINLINE globalVty #-}
19+globalVty = unsafePerformIO $ newIORef Nothing
20+
21+globalOutput :: IORef (Maybe Output)
22+{-# NOINLINE globalOutput #-}
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.hsnew file+176
@@ -0,0 +1,176 @@
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.
14+--
15+-- = VTY's Implementation
16+--
17+-- There are two input modes:
18+--
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.
65+--
66+-- 2. Control keys that contain @ESC@ in their encoding: The @ESC byte
67+-- is followed by more bytes read within 'VMIN' milliseconds. All bytes
68+-- up until the next valid input block are passed to the classifier.
69+--
70+-- If the current runtime is the threaded runtime then the terminal's
71+-- @VMIN@ and @VTIME@ behavior reliably implement the above rules. If
72+-- the current runtime does not support 'forkOS' then there is currently
73+-- no implementation.
74+--
75+-- == Unicode Input and Escaped Control Key Sequences
76+--
77+-- The input encoding determines how UTF-8 encoded characters are
78+-- recognized.
79+--
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(..)
116+ , Modifier(..)
117+ , Button(..)
118+ , Event(..)
119+ , Input(..)
120+ , inputForConfig
121+ )
122+where
123+
124+import Graphics.Vty.Config
125+import Graphics.Vty.Input.Events
126+import Graphics.Vty.Input.Loop
127+import Graphics.Vty.Input.Terminfo
128+
129+import Control.Concurrent.STM
130+import Lens.Micro
131+
132+import qualified System.Console.Terminfo as Terminfo
133+import System.Posix.Signals.Exts
134+
135+#if !MIN_VERSION_base(4,11,0)
136+import Data.Monoid ((<>))
137+#endif
138+
139+-- | Set up the terminal with file descriptor `inputFd` for input.
140+-- Returns an 'Input'.
141+--
142+-- The table used to determine the 'Events' to produce for the input
143+-- bytes comes from 'classifyMapForTerm' which is then overridden by
144+-- the the applicable entries from the configuration's 'inputMap'.
145+--
146+-- The terminal device's mode flags are configured by the
147+-- 'attributeControl' function.
148+inputForConfig :: Config -> IO Input
149+inputForConfig config@Config{ termName = Just termName
150+ , inputFd = Just termFd
151+ , vmin = Just _
152+ , vtime = Just _
153+ , .. } = do
154+ terminal <- Terminfo.setupTerm termName
155+ let inputOverrides = [(s,e) | (t,s,e) <- inputMap, t == Nothing || t == Just termName]
156+ activeInputMap = classifyMapForTerm termName terminal `mappend` inputOverrides
157+ (setAttrs, unsetAttrs) <- attributeControl termFd
158+ setAttrs
159+ input <- initInput config activeInputMap
160+ let pokeIO = Catch $ do
161+ setAttrs
162+ atomically $ writeTChan (input^.eventChannel) ResumeAfterSignal
163+ _ <- installHandler windowChange pokeIO Nothing
164+ _ <- installHandler continueProcess pokeIO Nothing
165+
166+ let restore = unsetAttrs
167+
168+ return $ input
169+ { shutdownInput = do
170+ shutdownInput input
171+ _ <- installHandler windowChange Ignore Nothing
172+ _ <- installHandler continueProcess Ignore Nothing
173+ restore
174+ , restoreInputState = restoreInputState input >> restore
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/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/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.hsnew file+186
@@ -0,0 +1,186 @@
1+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
2+module Graphics.Vty.Input.Terminfo where
3+
4+import Graphics.Vty.Input.Events
5+import qualified Graphics.Vty.Input.Terminfo.ANSIVT as ANSIVT
6+
7+import Control.Arrow
8+import System.Console.Terminfo
9+
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
50+capsClassifyMap terminal table = [(x,y) | (Just x,y) <- map extractCap table]
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/LLInput.hsdeleted−226
@@ -1,226 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# OPTIONS_GHC -Wall #-}
3−{-# LANGUAGE ForeignFunctionInterface #-}
4−module Graphics.Vty.LLInput ( Key(..)
5− , Modifier(..)
6− , Button(..)
7− , Event(..)
8− , initTermInput
9− )
10− where
11−
12−import Data.Char
13−import Data.Maybe ( mapMaybe
14− )
15−import Data.List( inits )
16−import Data.Word
17−import qualified Data.Map as M( fromList, lookup )
18−import qualified Data.Set as S( fromList, member )
19−
20−import Codec.Binary.UTF8.Generic (decode)
21−
22−import Control.Monad (when)
23−import Control.Concurrent
24−import Control.Exception
25−
26−import System.Console.Terminfo
27−
28−import System.Posix.Signals.Exts
29−import System.Posix.Signals
30−import System.Posix.Terminal
31−import System.Posix.IO ( stdInput
32− ,fdRead
33− ,setFdOption
34− ,FdOption(..)
35− )
36−
37−-- |Representations of non-modifier keys.
38−data Key = KEsc | KFun Int | KBackTab | KPrtScr | KPause | KASCII Char | KBS | KIns
39− | KHome | KPageUp | KDel | KEnd | KPageDown | KNP5 | KUp | KMenu
40− | KLeft | KDown | KRight | KEnter
41− deriving (Eq,Show,Ord)
42−
43−-- |Modifier keys. Key codes are interpreted such that users are more likely to
44−-- have Meta than Alt; for instance on the PC Linux console, 'MMeta' will
45−-- generally correspond to the physical Alt key.
46−data Modifier = MShift | MCtrl | MMeta | MAlt
47− deriving (Eq,Show,Ord)
48−
49−-- |Mouse buttons. Not yet used.
50−data Button = BLeft | BMiddle | BRight
51− deriving (Eq,Show,Ord)
52−
53−-- |Generic events.
54−data Event = EvKey Key [Modifier] | EvMouse Int Int Button [Modifier]
55− | EvResize Int Int
56− deriving (Eq,Show,Ord)
57−
58−data KClass = Valid Key [Modifier] | Invalid | Prefix | MisPfx Key [Modifier] [Char]
59− deriving(Show)
60−
61−-- | Set up the terminal for input. Returns a function which reads key
62−-- events, and a function for shutting down the terminal access.
63−initTermInput :: Int -> Terminal -> IO (IO Event, IO ())
64−initTermInput escDelay terminal = do
65− eventChannel <- newChan
66− inputChannel <- newChan
67− hadInput <- newEmptyMVar
68− oattr <- getTerminalAttributes stdInput
69− let nattr = foldl withoutMode oattr [StartStopOutput, KeyboardInterrupts,
70− EnableEcho, ProcessInput, ExtendedFunctions]
71− setTerminalAttributes stdInput nattr Immediately
72− set_term_timing
73− let inputToEventThread :: IO ()
74− inputToEventThread = loop []
75− where loop kb = case (classify kb) of
76− Prefix -> do c <- readChan inputChannel
77− loop (kb ++ [c])
78− Invalid -> loop ""
79− MisPfx k m s -> writeChan eventChannel (EvKey k m) >> loop s
80− Valid k m -> writeChan eventChannel (EvKey k m) >> loop ""
81−
82− finishAtomicInput = writeChan inputChannel '\xFFFE'
83−
84− inputThread :: IO ()
85− inputThread = loop
86− where
87− loop = do
88− setFdOption stdInput NonBlockingRead False
89− threadWaitRead stdInput
90− setFdOption stdInput NonBlockingRead True
91− try readAll :: IO (Either IOException ())
92− when (escDelay == 0) finishAtomicInput
93− loop
94− readAll = do
95− (bytes, bytes_read) <- fdRead stdInput 1
96− when (bytes_read > 0) $ do
97− tryPutMVar hadInput () -- signal input
98− writeChan inputChannel (head bytes)
99− readAll
100− -- | If there is no input for some time, this thread puts '\xFFFE' in the
101− -- inputChannel.
102− noInputThread :: IO ()
103− noInputThread = when (escDelay > 0) loop
104− where loop = do
105− takeMVar hadInput -- wait for some input
106− threadDelay escDelay -- microseconds
107− hadNoInput <- isEmptyMVar hadInput -- no input yet?
108− when hadNoInput $ do
109− finishAtomicInput
110− loop
111−
112−
113− compile :: [[([Char],(Key,[Modifier]))]] -> [Char] -> KClass
114− compile lst = cl' where
115− lst' = concat lst
116− pfx = S.fromList $ concatMap (init . inits . fst) $ lst'
117− mlst = M.fromList lst'
118− cl' str = case S.member str pfx of
119− True -> Prefix
120− False -> case M.lookup str mlst of
121− Just (k,m) -> Valid k m
122− Nothing -> case head $ mapMaybe (\s -> (,) s `fmap` M.lookup s mlst) $ init $ inits str of
123− (s,(k,m)) -> MisPfx k m (drop (length s) str)
124−
125− -- ANSI specific bits
126−
127−
128− classify, classifyTab :: [Char] -> KClass
129−
130− -- As soon as
131− classify "\xFFFE" = Invalid
132− classify s@(c:_) | ord c >= 0xC2 =
133− if utf8Length (ord c) > length s then Prefix else classifyUtf8 s -- beginning of an utf8 sequence
134− classify other = classifyTab other
135−
136− classifyUtf8 s = case decode ((map (fromIntegral . ord) s) :: [Word8]) of
137− Just (unicodeChar, _) -> Valid (KASCII unicodeChar) []
138− _ -> Invalid -- something bad happened; just ignore and continue.
139−
140− classifyTab = compile (caps_classify_table : ansi_classify_table)
141−
142− caps_tabls = [("khome", (KHome, [])),
143− ("kend", (KEnd, [])),
144− ("cbt", (KBackTab, [])),
145− ("kcud1", (KDown, [])),
146− ("kcuu1", (KUp, [])),
147− ("kcuf1", (KRight, [])),
148− ("kcub1", (KLeft, [])),
149−
150− ("kLFT", (KLeft, [MShift])),
151− ("kRIT", (KRight, [MShift]))
152− ]
153−
154− caps_classify_table = [(x,y) | (Just x,y) <- map (first (getCapability terminal . tiGetStr)) $ caps_tabls]
155−
156− ansi_classify_table :: [[([Char], (Key, [Modifier]))]]
157− ansi_classify_table =
158− [ let k c s = ("\ESC["++c,(s,[])) in [ k "G" KNP5, k "P" KPause, k "A" KUp, k "B" KDown, k "C" KRight, k "D" KLeft ],
159−
160− -- Support for arrows
161− [("\ESC[" ++ charCnt ++ show mc++c,(s,m))
162− | charCnt <- ["1;", ""], -- we can have a count or not
163− (m,mc) <- [([MShift],2::Int), ([MCtrl],5), ([MMeta],3),
164− ([MShift, MCtrl],6), ([MShift, MMeta],4)], -- modifiers and their codes
165− (c,s) <- [("A", KUp), ("B", KDown), ("C", KRight), ("D", KLeft)] -- directions and their codes
166− ],
167−
168− let k n s = ("\ESC["++show n++"~",(s,[])) in zipWith k [2::Int,3,5,6] [KIns,KDel,KPageUp,KPageDown],
169−
170− -- Support for simple characters.
171− [ (x:[],(KASCII x,[])) | x <- map toEnum [0..255] ],
172−
173− -- Support for function keys (should use terminfo)
174− [ ("\ESC[["++[toEnum(64+i)],(KFun i,[])) | i <- [1..5] ],
175− let f ff nrs m = [ ("\ESC["++show n++"~",(KFun (n-(nrs!!0)+ff), m)) | n <- nrs ] in
176− concat [ f 6 [17..21] [], f 11 [23,24] [], f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ],
177− [ ('\ESC':[x],(KASCII x,[MMeta])) | x <- '\ESC':'\t':[' ' .. '\DEL'] ],
178−
179− -- Ctrl+Char
180− [ ([toEnum x],(KASCII y,[MCtrl]))
181− | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_']),
182− y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
183− ],
184−
185− -- Ctrl+Meta+Char
186− [ ('\ESC':[toEnum x],(KASCII y,[MMeta,MCtrl])) | (x,y) <- zip [0..31] ('@':['a'..'z']++['['..'_']) ],
187−
188− -- Special support
189− [ -- special support for ESC
190− ("\ESC",(KEsc,[])) , ("\ESC\ESC",(KEsc,[MMeta])),
191−
192− -- Special support for backspace
193− ("\DEL",(KBS,[])), ("\ESC\DEL",(KBS,[MMeta])),
194−
195− -- Special support for Enter
196− ("\ESC\^J",(KEnter,[MMeta])), ("\^J",(KEnter,[])) ]
197− ]
198−
199− eventThreadId <- forkIO $ inputToEventThread
200− inputThreadId <- forkIO $ inputThread
201− noInputThreadId <- forkIO $ noInputThread
202− let pokeIO = (Catch $ do let e = error "(getsize in input layer)"
203− setTerminalAttributes stdInput nattr Immediately
204− writeChan eventChannel (EvResize e e))
205− installHandler windowChange pokeIO Nothing
206− installHandler continueProcess pokeIO Nothing
207− let uninit = do killThread eventThreadId
208− killThread inputThreadId
209− killThread noInputThreadId
210− installHandler windowChange Ignore Nothing
211− installHandler continueProcess Ignore Nothing
212− setTerminalAttributes stdInput oattr Immediately
213− return (readChan eventChannel, uninit)
214−
215−first :: (a -> b) -> (a,c) -> (b,c)
216−first f (x,y) = (f x, y)
217−
218−utf8Length :: (Num t, Ord a, Num a) => a -> t
219−utf8Length c
220− | c < 0x80 = 1
221− | c < 0xE0 = 2
222− | c < 0xF0 = 3
223− | otherwise = 4
224−
225−foreign import ccall "set_term_timing" set_term_timing :: IO ()
226−
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+116−57
@@ -1,72 +1,131 @@
1−-- Copyright 2009 Corey O'Connor
2−module Graphics.Vty.Picture ( module Graphics.Vty.Picture
3− , Image
4− , image_width
5− , image_height
6− , (<|>)
7− , (<->)
8− , horiz_cat
9− , vert_cat
10− , background_fill
11− , char
12− , string
13− , iso_10646_string
14− , utf8_string
15− , utf8_bytestring
16− , char_fill
17− , empty_image
18− , translate
19− -- | The possible display attributes used in constructing an `Image`.
20− , module Graphics.Vty.Attributes
21− )
22− where
1+-- A 'Picture' is a background paired with a set of 'Image' layers. The
2+-- 'Picture' data structure is representative of the final terminal
3+-- view.
4+module Graphics.Vty.Picture
5+ ( Picture(..)
6+ , Cursor(..)
7+ , Background(..)
8+ , emptyPicture
9+ , addToTop
10+ , addToBottom
11+ , picForImage
12+ , picForLayers
13+ , picImage
14+ )
15+where
2316
17+import Graphics.Vty.Image
2418import Graphics.Vty.Attributes
25−import Graphics.Vty.Image hiding ( attr )
2619
27−import Data.Word
20+import Control.DeepSeq
2821
29−-- |The type of images to be displayed using 'update'.
30−-- Can be constructed directly or using `pic_for_image`. Which provides an initial instance with
31−-- reasonable defaults for pic_cursor and pic_background.
22+-- | A Vty picture.
23+--
24+-- These can be constructed directly or using `picForImage`.
3225data Picture = Picture
33− { pic_cursor :: Cursor
34− , pic_image :: Image
35− , pic_background :: Background
36− }
26+ { picCursor :: Cursor
27+ -- ^ The picture's cursor.
28+ , picLayers :: [Image]
29+ -- ^ The picture's image layers (top-most first).
30+ , picBackground :: Background
31+ -- ^ The picture's background to be displayed in locations with no
32+ -- Image data.
33+ } deriving Eq
34+
35+instance Show Picture where
36+ show (Picture _ layers _ ) = "Picture ?? " ++ show layers ++ " ??"
37+
38+instance NFData Picture where
39+ rnf (Picture c l b) = c `deepseq` l `deepseq` b `deepseq` ()
40+
41+-- | A picture with no cursor, background or image layers.
42+emptyPicture :: Picture
43+emptyPicture = Picture NoCursor [] ClearBackground
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}
3748
38−-- | Create a picture for display for the given image. The picture will not have a displayed cursor
39−-- and the background display attribute will be `current_attr`.
40−pic_for_image :: Image -> Picture
41−pic_for_image i = Picture
42− { pic_cursor = NoCursor
43− , pic_image = i
44− , pic_background = Background ' ' current_attr
49+-- | Add an 'Image' as the bottom-most layer of a 'Picture'.
50+addToBottom :: Picture -> Image -> Picture
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.
56+picForImage :: Image -> Picture
57+picForImage i = Picture
58+ { picCursor = NoCursor
59+ , picLayers = [i]
60+ , picBackground = ClearBackground
4561 }
4662
47−-- | A picture can be configured either to not show the cursor or show the cursor at the specified
48−-- character position.
63+-- | Create a picture with the given layers, top-most first.
4964--
50−-- There is not a 1 to 1 map from character positions to a row and column on the screen due to
51−-- characters that take more than 1 column.
65+-- The picture will not have a displayed cursor and no background
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.
5276--
53−-- todo: The Cursor can be given a (character,row) offset outside of the visible bounds of the
54−-- output region. In this case the cursor will not be shown.
55−data Cursor =
56− NoCursor
57− | Cursor Word Word
77+-- There is not a 1:1 map from character positions to a row and column
78+-- on the screen due to characters that take more than 1 column.
79+data Cursor =
80+ -- | Hide the cursor
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.
92+ | Cursor !Int !Int
93+ -- | Show the cursor at the given absolute terminal column and row
94+ | AbsoluteCursor !Int !Int
95+ deriving Eq
5896
59−-- | Unspecified regions are filled with the picture's background pattern. The background pattern
60−-- can specify a character and a display attribute. If the display attribute used previously should
61−-- be used for a background fill then use `current_attr` for the background attribute. This is the
62−-- default background display attribute.
97+instance NFData Cursor where
98+ rnf c = c `seq` ()
99+
100+-- | A 'Picture' has a background pattern. The background is either:
63101--
64−-- (tofix) The current attribute is always set to the default attributes at the start of updating the
65−-- screen to a picture.
102+-- * ClearBackground, which shows the layer below or is blank if the
103+-- bottom layer
104+-- * A character and a display attribute
66105--
67−-- (tofix) The background character *must* occupy a single column and no more.
68−data Background = Background
69− { background_char :: Char
70− , background_attr :: Attr
106+-- If the display attribute used previously should be used for a
107+-- background fill then use `currentAttr` for the background attribute.
108+data Background
109+ = Background
110+ { backgroundChar :: Char
111+ , backgroundAttr :: Attr
71112 }
113+ -- | A ClearBackground is:
114+ --
115+ -- * the space character if there are remaining non-skip ops
116+ --
117+ -- * End of line if there are no remaining non-skip ops.
118+ | ClearBackground
119+ deriving Eq
120+
121+instance NFData Background where
122+ rnf (Background c a) = c `seq` a `seq` ()
123+ rnf ClearBackground = ()
72124
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
129+-- more than a single layer.
130+picImage :: Picture -> Image
131+picImage = head . picLayers
src/Graphics/Vty/PictureToSpans.hs+344−3
@@ -1,7 +1,348 @@
1−-- Copyright 2009 Corey O'Connor
2−module Graphics.Vty.PictureToSpans
3− where
1+-- Copyright Corey O'Connor<coreyoconnor@gmail.com>
2+{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE ExistentialQuantification #-}
4+{-# LANGUAGE NamedFieldPuns #-}
5+{-# LANGUAGE RankNTypes #-}
6+{-# LANGUAGE TemplateHaskell #-}
7+
8+-- | Transforms an image into rows of operations.
9+module Graphics.Vty.PictureToSpans where
410
11+import Graphics.Vty.Attributes (Attr, currentAttr)
12+import Graphics.Vty.Image
13+import Graphics.Vty.Image.Internal
514import Graphics.Vty.Picture
615import Graphics.Vty.Span
716
17+import Lens.Micro
18+import Lens.Micro.Mtl
19+import Lens.Micro.TH
20+import Control.Monad.Reader
21+import Control.Monad.State.Strict hiding ( state )
22+import Control.Monad.ST.Strict
23+
24+import qualified Data.Vector as Vector hiding ( take, replicate )
25+import Data.Vector.Mutable ( MVector(..))
26+import qualified Data.Vector.Mutable as MVector
27+
28+import qualified Data.Text.Lazy as TL
29+
30+type MRowOps s = MVector s SpanOps
31+
32+type MSpanOps s = MVector s SpanOp
33+
34+-- transform plus clip. More or less.
35+data BlitState = BlitState
36+ -- we always snoc to the operation vectors. Thus the columnOffset =
37+ -- length of row at rowOffset although, one possibility is to merge
38+ -- layers right in snocOp (naming it something else, of course). In
39+ -- which case columnnOffset would be applicable. Right now we need
40+ -- it to exist.
41+ { _columnOffset :: Int
42+ , _rowOffset :: Int
43+ -- clip coordinate space is in image space. Which means it's >= 0
44+ -- and < imageWidth.
45+ , _skipColumns :: Int
46+ -- >= 0 and < imageHeight
47+ , _skipRows :: Int
48+ -- includes consideration of skipColumns. In display space. The
49+ -- number of columns from the next column to be defined to the end
50+ -- of the display for the row.
51+ , _remainingColumns :: Int
52+ -- includes consideration of skipRows. In display space.
53+ , _remainingRows :: Int
54+ }
55+
56+makeLenses ''BlitState
57+
58+data BlitEnv s = BlitEnv
59+ { _region :: DisplayRegion
60+ , _mrowOps :: MRowOps s
61+ }
62+
63+makeLenses ''BlitEnv
64+
65+type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
66+
67+-- | Produces the span ops that will render the given picture, possibly
68+-- cropped or padded, into the specified region.
69+displayOpsForPic :: Picture -> DisplayRegion -> DisplayOps
70+displayOpsForPic pic r = Vector.create (combinedOpsForLayers pic r)
71+
72+-- | Returns the DisplayOps for an image rendered to a window the size
73+-- of the image.
74+--
75+-- largely used only for debugging.
76+displayOpsForImage :: Image -> DisplayOps
77+displayOpsForImage i = displayOpsForPic (picForImage i) (imageWidth i, imageHeight i)
78+
79+-- | Produces the span ops for each layer then combines them.
80+combinedOpsForLayers :: Picture -> DisplayRegion -> ST s (MRowOps s)
81+combinedOpsForLayers pic r
82+ | regionWidth r == 0 || regionHeight r == 0 = MVector.new 0
83+ | otherwise = do
84+ layerOps <- mapM (`buildSpans` r) (picLayers pic)
85+ case layerOps of
86+ [] -> fail "empty picture"
87+ [ops] -> substituteSkips (picBackground pic) ops
88+ -- instead of merging ops after generation the merging can
89+ -- be performed as part of snocOp.
90+ topOps : lowerOps -> do
91+ ops <- foldM mergeUnder topOps lowerOps
92+ substituteSkips (picBackground pic) ops
93+
94+substituteSkips :: Background -> MRowOps s -> ST s (MRowOps s)
95+substituteSkips ClearBackground ops = do
96+ forM_ [0 .. MVector.length ops - 1] $ \row -> do
97+ rowOps <- MVector.read ops row
98+ -- the image operations assure that background fills are
99+ -- combined. clipping a background fill does not split the
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+ -- mean we can check the last operation and remove it if it's a
103+ -- skip
104+ let rowOps' = case Vector.last rowOps of
105+ Skip w -> Vector.init rowOps `Vector.snoc` RowEnd w
106+ _ -> rowOps
107+ -- now all the skips can be replaced by replications of ' ' of
108+ -- the required width.
109+ let rowOps'' = swapSkipsForSingleColumnCharSpan ' ' currentAttr rowOps'
110+ MVector.write ops row rowOps''
111+ return ops
112+substituteSkips (Background {backgroundChar, backgroundAttr}) ops = do
113+ -- At this point we decide if the background character is single
114+ -- column or not. obviously, single column is easier.
115+ case safeWcwidth backgroundChar of
116+ w | w == 0 -> fail $ "invalid background character " ++ show backgroundChar
117+ | w == 1 -> do
118+ forM_ [0 .. MVector.length ops - 1] $ \row -> do
119+ rowOps <- MVector.read ops row
120+ let rowOps' = swapSkipsForSingleColumnCharSpan backgroundChar backgroundAttr rowOps
121+ MVector.write ops row rowOps'
122+ | otherwise -> do
123+ forM_ [0 .. MVector.length ops - 1] $ \row -> do
124+ rowOps <- MVector.read ops row
125+ let rowOps' = swapSkipsForCharSpan w backgroundChar backgroundAttr rowOps
126+ MVector.write ops row rowOps'
127+ return ops
128+
129+mergeUnder :: MRowOps s -> MRowOps s -> ST s (MRowOps s)
130+mergeUnder upper lower = do
131+ forM_ [0 .. MVector.length upper - 1] $ \row -> do
132+ upperRowOps <- MVector.read upper row
133+ lowerRowOps <- MVector.read lower row
134+ let rowOps = mergeRowUnder upperRowOps lowerRowOps
135+ MVector.write upper row rowOps
136+ return upper
137+
138+mergeRowUnder :: SpanOps -> SpanOps -> SpanOps
139+mergeRowUnder upperRowOps =
140+ onUpperOp Vector.empty (Vector.head upperRowOps) (Vector.tail upperRowOps)
141+ where
142+ -- H: it will never be the case that we are out of upper ops
143+ -- before lower ops.
144+ onUpperOp :: SpanOps -> SpanOp -> SpanOps -> SpanOps -> SpanOps
145+ onUpperOp outOps op@(TextSpan _ w _ _) upperOps lowerOps =
146+ let lowerOps' = dropOps w lowerOps
147+ outOps' = Vector.snoc outOps op
148+ in if Vector.null lowerOps'
149+ then outOps'
150+ else onUpperOp outOps' (Vector.head upperOps) (Vector.tail upperOps) lowerOps'
151+ onUpperOp outOps (Skip w) upperOps lowerOps =
152+ let (ops', lowerOps') = splitOpsAt w lowerOps
153+ outOps' = outOps `mappend` ops'
154+ in if Vector.null lowerOps'
155+ then outOps'
156+ else onUpperOp outOps' (Vector.head upperOps) (Vector.tail upperOps) lowerOps'
157+ onUpperOp _ (RowEnd _) _ _ = error "cannot merge rows containing RowEnd ops"
158+
159+
160+swapSkipsForSingleColumnCharSpan :: Char -> Attr -> SpanOps -> SpanOps
161+swapSkipsForSingleColumnCharSpan c a = Vector.map f
162+ where f (Skip ow) = let txt = TL.pack $ replicate ow c
163+ in TextSpan a ow ow txt
164+ f v = v
165+
166+swapSkipsForCharSpan :: Int -> Char -> Attr -> SpanOps -> SpanOps
167+swapSkipsForCharSpan w c a = Vector.map f
168+ where
169+ f (Skip ow) = let txt0Cw = ow `div` w
170+ txt0 = TL.pack $ replicate txt0Cw c
171+ txt1Cw = ow `mod` w
172+ txt1 = TL.pack $ replicate txt1Cw '…'
173+ cw = txt0Cw + txt1Cw
174+ txt = txt0 `TL.append` txt1
175+ in TextSpan a ow cw txt
176+ f v = v
177+
178+-- | Builds a vector of row operations that will output the given
179+-- picture to the terminal.
180+--
181+-- Crops to the given display region.
182+buildSpans :: Image -> DisplayRegion -> ST s (MRowOps s)
183+buildSpans image outRegion = do
184+ -- First we create a mutable vector for each rows output operations.
185+ outOps <- MVector.replicate (regionHeight outRegion) Vector.empty
186+ -- It's possible that building the span operations in display order
187+ -- would provide better performance.
188+ --
189+ -- A depth first traversal of the image is performed. ordered
190+ -- according to the column range defined by the image from least
191+ -- to greatest. The output row ops will at least have the region
192+ -- of the image specified. Iterate over all output rows and output
193+ -- background fills for all unspecified columns.
194+ --
195+ -- The images are made into span operations from left to right. It's
196+ -- possible that this could easily be made to assure top to bottom
197+ -- output as well.
198+ when (regionHeight outRegion > 0 && regionWidth outRegion > 0) $ do
199+ -- The ops builder recursively descends the image and outputs
200+ -- span ops that would display that image. The number of columns
201+ -- remaining in this row before exceeding the bounds is also
202+ -- provided. This is used to clip the span ops produced to the
203+ -- display.
204+ let fullBuild = do
205+ startImageBuild image
206+ -- Fill in any unspecified columns with a skip.
207+ forM_ [0 .. (regionHeight outRegion - 1)] (addRowCompletion outRegion)
208+ initEnv = BlitEnv outRegion outOps
209+ initState = BlitState 0 0 0 0 (regionWidth outRegion) (regionHeight outRegion)
210+ _ <- runStateT (runReaderT fullBuild initEnv) initState
211+ return ()
212+ return outOps
213+
214+-- | Add the operations required to build a given image to the current
215+-- set of row operations.
216+startImageBuild :: Image -> BlitM s ()
217+startImageBuild image = do
218+ outOfBounds <- isOutOfBounds image <$> get
219+ when (not outOfBounds) $ addMaybeClipped image
220+
221+isOutOfBounds :: Image -> BlitState -> Bool
222+isOutOfBounds i s
223+ | s ^. remainingColumns <= 0 = True
224+ | s ^. remainingRows <= 0 = True
225+ | s ^. skipColumns >= imageWidth i = True
226+ | s ^. skipRows >= imageHeight i = True
227+ | otherwise = False
228+
229+-- | This adds an image that might be partially clipped to the output
230+-- ops.
231+--
232+-- This is a very touchy algorithm. Too touchy. For instance, the
233+-- CropRight and CropBottom implementations are odd. They pass the
234+-- current tests but something seems terribly wrong about all this.
235+addMaybeClipped :: forall s . Image -> BlitM s ()
236+addMaybeClipped EmptyImage = return ()
237+addMaybeClipped (HorizText a textStr ow _cw) = do
238+ -- This assumes that text spans are only 1 row high.
239+ s <- use skipRows
240+ when (s < 1) $ do
241+ leftClip <- use skipColumns
242+ rightClip <- use remainingColumns
243+ let leftClipped = leftClip > 0
244+ rightClipped = (ow - leftClip) > rightClip
245+ if leftClipped || rightClipped
246+ then let textStr' = clipText textStr leftClip rightClip
247+ in addUnclippedText a textStr'
248+ else addUnclippedText a textStr
249+addMaybeClipped (VertJoin topImage bottomImage _ow oh) = do
250+ when (imageHeight topImage + imageHeight bottomImage > 0) $
251+ addMaybeClippedJoin "vert_join" skipRows remainingRows rowOffset
252+ (imageHeight topImage)
253+ topImage
254+ bottomImage
255+ oh
256+addMaybeClipped (HorizJoin leftImage rightImage ow _oh) = do
257+ when (imageWidth leftImage + imageWidth rightImage > 0) $
258+ addMaybeClippedJoin "horiz_join" skipColumns remainingColumns columnOffset
259+ (imageWidth leftImage)
260+ leftImage
261+ rightImage
262+ ow
263+addMaybeClipped BGFill {outputWidth, outputHeight} = do
264+ s <- get
265+ let outputWidth' = min (outputWidth - s^.skipColumns) (s^.remainingColumns)
266+ outputHeight' = min (outputHeight - s^.skipRows ) (s^.remainingRows)
267+ y <- use rowOffset
268+ forM_ [y..y+outputHeight'-1] $ snocOp (Skip outputWidth')
269+addMaybeClipped CropRight {croppedImage, outputWidth} = do
270+ s <- use skipColumns
271+ r <- use remainingColumns
272+ let x = outputWidth - s
273+ when (x < r) $ remainingColumns .= x
274+ addMaybeClipped croppedImage
275+addMaybeClipped CropLeft {croppedImage, leftSkip} = do
276+ skipColumns += leftSkip
277+ addMaybeClipped croppedImage
278+addMaybeClipped CropBottom {croppedImage, outputHeight} = do
279+ s <- use skipRows
280+ r <- use remainingRows
281+ let x = outputHeight - s
282+ when (x < r) $ remainingRows .= x
283+ addMaybeClipped croppedImage
284+addMaybeClipped CropTop {croppedImage, topSkip} = do
285+ skipRows += topSkip
286+ addMaybeClipped croppedImage
287+
288+addMaybeClippedJoin :: forall s . String
289+ -> Lens BlitState BlitState Int Int
290+ -> Lens BlitState BlitState Int Int
291+ -> Lens BlitState BlitState Int Int
292+ -> Int
293+ -> Image
294+ -> Image
295+ -> Int
296+ -> BlitM s ()
297+addMaybeClippedJoin name skip remaining offset i0Dim i0 i1 size = do
298+ state <- get
299+ when (state^.remaining <= 0) $ fail $ name ++ " with remaining <= 0"
300+ case state^.skip of
301+ s | s > size -> put $ state & skip %~ subtract size
302+ | s == 0 -> if state^.remaining > i0Dim
303+ then do
304+ addMaybeClipped i0
305+ put $ state & offset %~ (+ i0Dim) & remaining %~ subtract i0Dim
306+ addMaybeClipped i1
307+ else addMaybeClipped i0
308+ | s < i0Dim ->
309+ let i0Dim' = i0Dim - s
310+ in if state^.remaining <= i0Dim'
311+ then addMaybeClipped i0
312+ else do
313+ addMaybeClipped i0
314+ put $ state & offset %~ (+ i0Dim') & remaining %~ subtract i0Dim' & skip .~ 0
315+ addMaybeClipped i1
316+ | s >= i0Dim -> do
317+ put $ state & skip %~ subtract i0Dim
318+ addMaybeClipped i1
319+ _ -> fail $ name ++ " has unhandled skip class"
320+
321+addUnclippedText :: Attr -> DisplayText -> BlitM s ()
322+addUnclippedText a txt = do
323+ let op = TextSpan a usedDisplayColumns
324+ (fromIntegral $ TL.length txt)
325+ txt
326+ usedDisplayColumns = wctlwidth txt
327+ use rowOffset >>= snocOp op
328+
329+addRowCompletion :: DisplayRegion -> Int -> BlitM s ()
330+addRowCompletion displayRegion row = do
331+ allRowOps <- view mrowOps
332+ rowOps <- lift $ lift $ MVector.read allRowOps row
333+ let endX = spanOpsAffectedColumns rowOps
334+ when (endX < regionWidth displayRegion) $ do
335+ let ow = regionWidth displayRegion - endX
336+ snocOp (Skip ow) row
337+
338+-- | snocs the operation to the operations for the given row.
339+snocOp :: SpanOp -> Int -> BlitM s ()
340+snocOp !op !row = do
341+ theMrowOps <- view mrowOps
342+ theRegion <- view region
343+ lift $ lift $ do
344+ ops <- MVector.read theMrowOps row
345+ let ops' = Vector.snoc ops op
346+ when (spanOpsAffectedColumns ops' > regionWidth theRegion)
347+ $ fail $ "row " ++ show row ++ " now exceeds region width"
348+ MVector.write theMrowOps row ops'
src/Graphics/Vty/Span.hs+119−213
@@ -1,223 +1,129 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE ExistentialQuantification #-}
4−{-# LANGUAGE NamedFieldPuns #-}
1+-- Copyright Corey O'Connor
52{-# LANGUAGE GADTs #-}
6−{-# LANGUAGE ExistentialQuantification #-}
7−{-# LANGUAGE ScopedTypeVariables #-}
8−-- The ops to define the content for an output region.
9−module Graphics.Vty.Span
10− where
11−
12−import Codec.Binary.UTF8.Width ( wcwidth )
13−
3+-- | A picture is translated into a sequences of state changes and
4+-- character spans. The attribute is applied to all following spans,
5+-- including spans of the next row. The nth element of the sequence
6+-- represents the nth row (from top to bottom) of the picture to render.
7+--
8+-- A span op sequence will be defined for all rows and columns (and no
9+-- more) of the region provided with the picture to 'spansForPic'.
10+module Graphics.Vty.Span where
11+
12+import Graphics.Vty.Attributes (Attr)
1413import Graphics.Vty.Image
15−import Graphics.Vty.Picture
16−import Graphics.Vty.DisplayRegion
17−
18−import Codec.Binary.UTF8.String ( encode )
19−
20−import Control.Monad ( forM_ )
21−import Control.Monad.ST.Strict
22−
23−import Data.Array.ST
24−import qualified Data.ByteString as B
25−import qualified Data.ByteString.Internal as BInt
26−import qualified Data.Foldable as Foldable
27−import Data.Function
28−import Data.List
29−import Data.Word
30−import qualified Data.ByteString.UTF8 as BSUTF8
31−import qualified Data.String.UTF8 as UTF8
32−
33−import Foreign.Storable ( pokeByteOff )
34−
35−import GHC.Arr
36−
37−{- | A picture is translated into a sequences of state changes and character spans.
38− - State changes are currently limited to new attribute values. The attribute is applied to all
39− - following spans. Including spans of the next row. The nth element of the sequence represents the
40− - nth row (from top to bottom) of the picture to render.
41− -
42− - A span op sequence will be defined for all rows and columns (and no more) of the region provided
43− - with the picture to spans_for_pic.
44− -
45− -
46− - todo: Partition attribute changes into multiple categories according to the serialized
47− - representation of the various attributes.
48− -}
49−data SpanOpSequence = SpanOpSequence
50− { effected_region :: DisplayRegion
51− , row_ops :: RowOps
52− }
53−
54−type RowOps = Array Word SpanOps
55−type SpanOps = [SpanOp]
14+import Graphics.Vty.Image.Internal ( clipText )
5615
57−span_ops_columns :: SpanOpSequence -> Word
58−span_ops_columns ops = region_width $ effected_region ops
16+import qualified Data.Text.Lazy as TL
17+import Data.Vector (Vector)
18+import qualified Data.Vector as Vector
5919
60−span_ops_rows :: SpanOpSequence -> Word
61−span_ops_rows ops = region_height $ effected_region ops
62−
63−span_ops_effected_columns :: [SpanOp] -> Word
64−span_ops_effected_columns in_ops = span_ops_effected_columns' 0 in_ops
65− where
66− span_ops_effected_columns' t [] = t
67− span_ops_effected_columns' t (TextSpan w _ _ : r) = span_ops_effected_columns' (t + w) r
68− span_ops_effected_columns' t (_ : r) = span_ops_effected_columns' t r
69−
70−-- |
71−--
72−-- todo: This type may need to be restructured to increase sharing in the bytestring
73−--
74−-- todo: Make foldable
20+-- | This represents an operation on the terminal: either an attribute
21+-- change or the output of a text string.
7522data SpanOp =
76− AttributeChange !Attr
77− -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
78− -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
79− -- TextSpan [output width in columns] [number of characters] [data]
80− | TextSpan !Word !Word (UTF8.UTF8 B.ByteString)
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
25+ -- colunm. See Codec.Binary.UTF8.Width TextSpan [Attr] [output width
26+ -- in columns] [number of characters] [data]
27+ TextSpan
28+ { textSpanAttr :: !Attr
29+ , textSpanOutputWidth :: !Int
30+ , textSpanCharWidth :: !Int
31+ , textSpanText :: DisplayText
32+ }
33+ -- | Skips the given number of columns.
34+ | Skip !Int
35+ -- | Marks the end of a row. Specifies how many columns are
36+ -- remaining. These columns will not be explicitly overwritten with
37+ -- the span ops. The terminal is require to assure the remaining
38+ -- columns are clear.
39+ | RowEnd !Int
8140 deriving Eq
8241
83−span_op_has_width :: SpanOp -> Maybe (Word, Word)
84−span_op_has_width (TextSpan ow cw _) = Just (cw, ow)
85−span_op_has_width _ = Nothing
86−
87−columns_to_char_offset :: Word -> SpanOp -> Word
88−columns_to_char_offset cx (TextSpan _ _ utf8_str) =
89− let str = UTF8.toString utf8_str
90− in toEnum $ sum $ map wcwidth $ take (fromEnum cx) str
91−columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
92−
93−-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
94−-- specified region.
95−spans_for_pic :: Picture -> DisplayRegion -> SpanOpSequence
96−spans_for_pic pic r = SpanOpSequence r $ runSTArray (build_spans pic r)
97−
98−build_spans :: Picture -> DisplayRegion -> ST s (STArray s Word [SpanOp])
99−build_spans pic region = do
100− -- m for mutable
101− mrow_ops <- newSTArray (0, region_height region - 1) []
102− -- I think this can be better optimized if the build produced span operations in display order.
103− -- However, I got stuck trying to implement an algorithm that did this. This will be considered
104− -- as a possible future optimization.
105− --
106− -- A depth first traversal of the image is performed.
107− -- ordered according to the column range defined by the image from least to greatest.
108− -- The output row ops will at least have the region of the image specified. Iterate over all
109− -- output rows and output background fills for all unspecified columns.
110− if region_height region > 0
111− then do
112− -- The ops builder recursively descends the image and outputs span ops that would
113− -- display that image. The number of columns remaining in this row before exceeding the
114− -- bounds is also provided. This is used to clip the span ops produced.
115− ops_for_row mrow_ops (pic_background pic) region (pic_image pic) 0 (region_width region)
116− -- Fill in any unspecified columns with the background pattern.
117− forM_ [0 .. region_height region - 1] $ \row -> do
118− end_x <- readSTArray mrow_ops row >>= return . span_ops_effected_columns
119− if end_x < region_width region
120− then snoc_bg_fill mrow_ops (pic_background pic) (region_width region - end_x) row
121− else return ()
122− else return ()
123− return mrow_ops
42+-- | A vector of span operations executed in succession. This represents
43+-- the operations required to render a row of the terminal. The
44+-- operations in one row may affect subsequent rows. For example,
45+-- setting the foreground color in one row will affect all subsequent
46+-- rows until the foreground color is changed.
47+type SpanOps = Vector SpanOp
12448
125−type MRowOps s = STArray s Word SpanOps
49+dropOps :: Int -> SpanOps -> SpanOps
50+dropOps w = snd . splitOpsAt w
12651
127−ops_for_row :: MRowOps s -> Background -> DisplayRegion -> Image -> Word -> Word -> ST s ()
128−ops_for_row mrow_ops bg region image y remaining_columns
129− | remaining_columns == 0 = return ()
130− | y >= region_height region = return ()
131− | otherwise = case image of
132− EmptyImage -> return ()
133− -- The width provided is the number of columns this text span will occupy when displayed.
134− -- if this is greater than the number of remaining columsn the output has to be produced a
135− -- character at a time.
136− HorizText a text_str ow cw -> do
137− snoc_text_span a text_str ow cw mrow_ops y remaining_columns
138− VertJoin t b _ _ -> do
139− ops_for_row mrow_ops bg region t y remaining_columns
140− ops_for_row mrow_ops bg region b (y + image_height t) remaining_columns
141− HorizJoin l r _ _ -> do
142− ops_for_row mrow_ops bg region l y remaining_columns
143− -- Don't output the right part unless there is at least a single column left after
144− -- outputting the left part.
145− if image_width l < remaining_columns
146− then ops_for_row mrow_ops bg region r y (remaining_columns - image_width l)
147− else return ()
148− BGFill width height -> do
149− let actual_height = if y + height > region_height region
150− then region_height region - y
151− else height
152− actual_width = if width > remaining_columns
153− then remaining_columns
154− else width
155− forM_ [y .. y + actual_height - 1] $ \y' -> snoc_bg_fill mrow_ops bg actual_width y'
156− Translation v i -> ops_for_row mrow_ops bg region i y remaining_columns
157−
158−snoc_text_span :: (Foldable.Foldable t)
159− => Attr
160− -> t Char
161− -> Word -- width of text span in output display columns
162− -> Word -- width of text span in characters
163− -> MRowOps s
164− -> Word
165− -> Word
166− -> ST s ()
167−snoc_text_span a text_str ow cw mrow_ops y remaining_columns = do
168− if ow > remaining_columns
169− then do
170− snoc_op mrow_ops y $ AttributeChange a
171− let (ow', cw', txt) = Foldable.foldl'
172− build_cropped_txt
173− ( 0, 0, B.empty )
174− text_str
175− snoc_op mrow_ops y $ TextSpan ow' cw' (UTF8.fromRep txt)
176− else do
177− snoc_op mrow_ops y $ AttributeChange a
178− let utf8_bs = ({-# SCC "BSUTF8.fromString" #-} BSUTF8.fromString) $ Foldable.foldMap (\c -> [c]) text_str
179− snoc_op mrow_ops y $ TextSpan ow cw (UTF8.fromRep utf8_bs)
52+splitOpsAt :: Int -> SpanOps -> (SpanOps, SpanOps)
53+splitOpsAt = splitOpsAt'
18054 where
181− build_cropped_txt (ow', char_count', b0) c = {-# SCC "build_cropped_txt" #-}
182− let w = wcwidth c
183− -- Characters with unknown widths occupy 1 column.
184− --
185− -- todo: Not sure if this is actually correct.
186− -- I presume there is a replacement character that is output by the terminal instead of
187− -- the character. If so then this replacement process may need to be implemented
188− -- manually for consistent behavior across terminals.
189− w' = toEnum $ if w < 0 then 1 else w
190− in if (w' + ow') > remaining_columns
191− then ( ow', char_count', b0 )
192− else ( ow' + w', char_count' + 1, B.append b0 $ B.pack $ encode [c] )
193−
194−snoc_bg_fill :: MRowOps s -> Background -> Word -> Word -> ST s ()
195−snoc_bg_fill _row_ops _bg 0 _row
196− = return ()
197−snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
198− = do
199− snoc_op mrow_ops row $ AttributeChange back_attr
200− -- By all likelyhood the background character will be an ASCII character. Which is a single
201− -- byte in utf8. Optimize for this special case.
202− utf8_bs <- if c <= (toEnum 255 :: Char)
203− then
204− let !(c_byte :: Word8) = BInt.c2w c
205− in unsafeIOToST $ do
206− BInt.create ( fromEnum fill_length )
207− $ \ptr -> mapM_ (\i -> pokeByteOff ptr i c_byte)
208− [0 .. fromEnum (fill_length - 1)]
209− else
210− let !(c_bytes :: [Word8]) = encode [c]
211− in unsafeIOToST $ do
212− BInt.create (fromEnum fill_length * length c_bytes)
213− $ \ptr -> mapM_ (\(i,b) -> pokeByteOff ptr i b)
214− $ zip [0 .. fromEnum (fill_length - 1)] (cycle c_bytes)
215− snoc_op mrow_ops row $ TextSpan fill_length fill_length (UTF8.fromRep utf8_bs)
216−
217−snoc_op :: MRowOps s -> Word -> SpanOp -> ST s ()
218−snoc_op !mrow_ops !row !op = do
219− ops <- readSTArray mrow_ops row
220− let ops' = ops ++ [op]
221− writeSTArray mrow_ops row ops'
222− return ()
223−
55+ splitOpsAt' 0 ops = (Vector.empty, ops)
56+ splitOpsAt' remainingColumns ops = case Vector.head ops of
57+ t@(TextSpan {}) -> if remainingColumns >= textSpanOutputWidth t
58+ then let (pre,post) = splitOpsAt' (remainingColumns - textSpanOutputWidth t)
59+ (Vector.tail ops)
60+ in (Vector.cons t pre, post)
61+ else let preTxt = clipText (textSpanText t) 0 remainingColumns
62+ preOp = TextSpan { textSpanAttr = textSpanAttr t
63+ , textSpanOutputWidth = remainingColumns
64+ , textSpanCharWidth = fromIntegral $! TL.length preTxt
65+ , textSpanText = preTxt
66+ }
67+ postWidth = textSpanOutputWidth t - remainingColumns
68+ postTxt = clipText (textSpanText t) remainingColumns postWidth
69+ postOp = TextSpan { textSpanAttr = textSpanAttr t
70+ , textSpanOutputWidth = postWidth
71+ , textSpanCharWidth = fromIntegral $! TL.length postTxt
72+ , textSpanText = postTxt
73+ }
74+ in ( Vector.singleton preOp
75+ , Vector.cons postOp (Vector.tail ops)
76+ )
77+ Skip w -> if remainingColumns >= w
78+ then let (pre,post) = splitOpsAt' (remainingColumns - w) (Vector.tail ops)
79+ in (Vector.cons (Skip w) pre, post)
80+ else ( Vector.singleton $ Skip remainingColumns
81+ , Vector.cons (Skip (w - remainingColumns)) (Vector.tail ops)
82+ )
83+ RowEnd _ -> error "cannot split ops containing a row end"
84+
85+-- | A vector of span operation vectors for display, one per row of the
86+-- output region.
87+type DisplayOps = Vector SpanOps
88+
89+instance Show SpanOp where
90+ show (TextSpan attr ow cw _) = "TextSpan(" ++ show attr ++ ")(" ++ show ow ++ ", " ++ show cw ++ ")"
91+ show (Skip ow) = "Skip(" ++ show ow ++ ")"
92+ show (RowEnd ow) = "RowEnd(" ++ show ow ++ ")"
93+
94+-- | The number of columns the DisplayOps are defined for.
95+--
96+-- All spans are verified to define same number of columns.
97+displayOpsColumns :: DisplayOps -> Int
98+displayOpsColumns ops
99+ | Vector.length ops == 0 = 0
100+ | otherwise = Vector.length $ Vector.head ops
101+
102+-- | The number of rows the DisplayOps are defined for.
103+displayOpsRows :: DisplayOps -> Int
104+displayOpsRows = Vector.length
105+
106+affectedRegion :: DisplayOps -> DisplayRegion
107+affectedRegion ops = (displayOpsColumns ops, displayOpsRows ops)
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−129
@@ -1,129 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−-- * Graphics.Vty.Terminal: This instantiates an abtract interface to the terminal interface
3−-- based on the TERM and COLORTERM environment variables.
4−-- * Graphics.Vty.Terminal.Generic: Defines the generic interface all terminals need to implement.
5−-- * Graphics.Vty.Terminal.TerminfoBased: Defines a terminal instance that uses terminfo for all
6−-- control strings.
7−-- - No attempt is made to change the character set to UTF-8 for these terminals. I don't
8−-- know a way to reliably determine if that is required or how to do so.
9−-- * Graphics.Vty.Terminal.XTermColor: This module contains an interface suitable for
10−-- xterm-like terminals. These are the terminals where TERM == xterm. This does use terminfo
11−-- for as many control codes as possible. H: This should derive functionality from the
12−-- TerminfoBased terminal.
13−--
14−--
15−{-# LANGUAGE ScopedTypeVariables #-}
16−module Graphics.Vty.Terminal ( module Graphics.Vty.Terminal
17− , Terminal(..)
18− , TerminalHandle(..)
19− , DisplayHandle(..)
20− , output_picture
21− , display_context
22− )
23− where
24−
25−
26−import Graphics.Vty.Terminal.Generic
27−import Graphics.Vty.Terminal.MacOSX as MacOSX
28−import Graphics.Vty.Terminal.XTermColor as XTermColor
29−import Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
30−
31−import Control.Applicative
32−import Control.Exception ( SomeException, try )
33−import Control.Monad.Trans
34−
35−import Data.Either
36−import Data.List ( isPrefixOf )
37−import Data.Word
38−
39−import System.Environment
40−
41−-- | Returns a TerminalHandle (an abstract Terminal instance) for the current terminal.
42−--
43−-- The specific Terminal implementation used is hidden from the API user. All terminal
44−-- implementations are assumed to perform more, or less, the same. Currently all implementations use
45−-- terminfo for at least some terminal specific information. This is why platforms without terminfo
46−-- are not supported. However, as mentioned before, any specifics about it being based on terminfo
47−-- are hidden from the API user. If a terminal implementation is developed for a terminal for a
48−-- platform without terminfo support then Vty should work as expected on that terminal.
49−--
50−-- Selection of a terminal is done as follows:
51−--
52−-- * If TERM == xterm
53−-- then the terminal might be one of the Mac OS X .app terminals. Check if that might be
54−-- the case and use MacOSX if so.
55−-- otherwise use XTermColor.
56−--
57−-- * for any other TERM value TerminfoBased is used.
58−--
59−--
60−-- The terminal has to be determined dynamically at runtime. To satisfy this requirement all
61−-- terminals instances are lifted into an abstract terminal handle via existential qualification.
62−-- This implies that the only equations that can used are those in the terminal class.
63−--
64−-- To differentiate between Mac OS X terminals this uses the TERM_PROGRAM environment variable.
65−-- However, an xterm started by Terminal or iTerm *also* has TERM_PROGRAM defined since the
66−-- environment variable is not reset/cleared by xterm. However a Terminal.app or iTerm.app started
67−-- from an xterm under X11 on mac os x will likely be done via open. Since this does not propogate
68−-- environment variables (I think?) this assumes that XTERM_VERSION will never be set for a true
69−-- Terminal.app or iTerm.app session.
70−--
71−-- todo: add an implementation for windows that does not depend on terminfo. Should be installable
72−-- with only what is provided in the haskell platform.
73−--
74−-- todo: The Terminal interface does not provide any input support.
75−terminal_handle :: ( Applicative m, MonadIO m ) => m TerminalHandle
76−terminal_handle = do
77− term_type <- liftIO $ getEnv "TERM"
78− t <- if "xterm" `isPrefixOf` term_type
79− then do
80− maybe_terminal_app <- get_env "TERM_PROGRAM"
81− case maybe_terminal_app of
82− Nothing
83− -> XTermColor.terminal_instance term_type >>= new_terminal_handle
84− Just v | v == "Apple_Terminal" || v == "iTerm.app"
85− -> do
86− maybe_xterm <- get_env "XTERM_VERSION"
87− case maybe_xterm of
88− Nothing -> MacOSX.terminal_instance v >>= new_terminal_handle
89− Just _ -> XTermColor.terminal_instance term_type >>= new_terminal_handle
90− -- Assume any other terminal that sets TERM_PROGRAM to not be an OS X terminal.app
91− -- like terminal?
92− _ -> XTermColor.terminal_instance term_type >>= new_terminal_handle
93− -- Not an xterm-like terminal. try for generic terminfo.
94− else TerminfoBased.terminal_instance term_type >>= new_terminal_handle
95− return t
96− where
97− get_env var = do
98− mv <- liftIO $ try $ getEnv var
99− case mv of
100− Left (_e :: SomeException) -> return $ Nothing
101− Right v -> return $ Just v
102−
103−-- | Sets the cursor position to the given output column and row.
104−--
105−-- This is not necessarially the same as the character position with the same coordinates.
106−-- Characters can be a variable number of columns in width.
107−--
108−-- Currently, the only way to set the cursor position to a given character coordinate is to specify
109−-- the coordinate in the Picture instance provided to output_picture or refresh.
110−set_cursor_pos :: MonadIO m => TerminalHandle -> Word -> Word -> m ()
111−set_cursor_pos t x y = do
112− bounds <- display_bounds t
113− d <- display_context t bounds
114− marshall_to_terminal t (move_cursor_required_bytes d x y) (serialize_move_cursor d x y)
115−
116−-- | Hides the cursor
117−hide_cursor :: MonadIO m => TerminalHandle -> m ()
118−hide_cursor t = do
119− bounds <- display_bounds t
120− d <- display_context t bounds
121− marshall_to_terminal t (hide_cursor_required_bytes d) (serialize_hide_cursor d)
122−
123−-- | Shows the cursor
124−show_cursor :: MonadIO m => TerminalHandle -> m ()
125−show_cursor t = do
126− bounds <- display_bounds t
127− d <- display_context t bounds
128− marshall_to_terminal t (show_cursor_required_bytes d) (serialize_show_cursor d)
129−
src/Graphics/Vty/Terminal/Debug.hsdeleted−117
@@ -1,117 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# LANGUAGE FlexibleInstances #-}
3−{-# LANGUAGE TypeFamilies #-}
4−module Graphics.Vty.Terminal.Debug ( DebugTerminal(..)
5− , DebugDisplay(..)
6− , terminal_instance
7− , dehandle
8− )
9− where
10−
11−import Graphics.Vty.DisplayRegion
12−import Graphics.Vty.Terminal.Generic
13−
14−import Control.Applicative
15−import Control.Monad.Trans
16−import Control.Monad.State.Strict
17−
18−import qualified Data.ByteString.UTF8 as BS
19−import qualified Data.ByteString as BSCore
20−import Data.IORef
21−import qualified Data.Sequence as Seq
22−import qualified Data.String.UTF8 as UTF8
23−import Data.Word
24−
25−import Foreign.Marshal.Array ( peekArray )
26−import Foreign.Ptr ( plusPtr )
27−import Foreign.Storable ( poke )
28−
29−import Unsafe.Coerce
30−
31−-- | The debug display terminal produces a string representation of the requested picture. There is
32−-- *not* an isomorphism between the string representation and the picture. The string
33−-- representation is a simplification of the picture that is only useful in debugging VTY without
34−-- considering terminal specific issues.
35−--
36−-- The debug implementation is useful in manually determining if the sequence of terminal operations
37−-- matches the expected sequence. So requirement of the produced representation is simplicity in
38−-- parsing the text representation and determining how the picture was mapped to terminal
39−-- operations.
40−--
41−-- All terminals support the operations specified in the Terminal class defined in
42−-- Graphics.Vty.Terminal. As an instance of the Terminal class is also an instance of the Monad
43−-- class there exists a monoid that defines it's algebra. The string representation is a sequence of
44−-- identifiers where each identifier is the name of an operation in the algebra.
45−
46−data DebugTerminal = DebugTerminal
47− { debug_terminal_last_output :: IORef (UTF8.UTF8 BS.ByteString)
48− , debug_terminal_bounds :: DisplayRegion
49− }
50−
51−instance Terminal DebugTerminal where
52− terminal_ID _t = "debug_terminal"
53− release_terminal _t = return ()
54− reserve_display _t = return ()
55− release_display _t = return ()
56− display_bounds t = return $ debug_terminal_bounds t
57− display_terminal_instance t r c = return $ c (DebugDisplay r)
58− output_byte_buffer t out_buffer buffer_size
59− = liftIO $ do
60− putStrLn $ "output_byte_buffer ?? " ++ show buffer_size
61− peekArray (fromEnum buffer_size) out_buffer
62− >>= return . UTF8.fromRep . BSCore.pack
63− >>= writeIORef (debug_terminal_last_output t)
64−
65−data DebugDisplay = DebugDisplay
66− { debug_display_bounds :: DisplayRegion
67− }
68−
69−terminal_instance :: ( Applicative m, MonadIO m ) => DisplayRegion -> m TerminalHandle
70−terminal_instance r = do
71− output_ref <- liftIO $ newIORef undefined
72− new_terminal_handle $ DebugTerminal output_ref r
73−
74−dehandle :: TerminalHandle -> DebugTerminal
75−dehandle (TerminalHandle t _) = unsafeCoerce t
76−
77−instance DisplayTerminal DebugDisplay where
78− -- | Provide the current bounds of the output terminal.
79− context_region d = debug_display_bounds d
80−
81− -- | A cursor move is always visualized as the single character 'M'
82− move_cursor_required_bytes _d _x _y = 1
83−
84− -- | A cursor move is always visualized as the single character 'M'
85− serialize_move_cursor _d _x _y ptr = do
86− liftIO $ poke ptr (toEnum $ fromEnum 'M')
87− return $ ptr `plusPtr` 1
88−
89− -- | Show cursor is always visualized as the single character 'S'
90− show_cursor_required_bytes _d = 1
91−
92− -- | Show cursor is always visualized as the single character 'S'
93− serialize_show_cursor _d ptr = do
94− liftIO $ poke ptr (toEnum $ fromEnum 'S')
95− return $ ptr `plusPtr` 1
96−
97− -- | Hide cursor is always visualized as the single character 'H'
98− hide_cursor_required_bytes _d = 1
99−
100− -- | Hide cursor is always visualized as the single character 'H'
101− serialize_hide_cursor _d ptr = do
102− liftIO $ poke ptr (toEnum $ fromEnum 'H')
103− return $ ptr `plusPtr` 1
104−
105− -- | An attr change is always visualized as the single character 'A'
106− attr_required_bytes _d _fattr _diffs _attr = 1
107−
108− -- | An attr change is always visualized as the single character 'A'
109− serialize_set_attr _d _fattr _diffs _attr ptr = do
110− liftIO $ poke ptr (toEnum $ fromEnum 'A')
111− return $ ptr `plusPtr` 1
112−
113− default_attr_required_bytes _d = 1
114− serialize_default_attr _d ptr = do
115− liftIO $ poke ptr (toEnum $ fromEnum 'D')
116− return $ ptr `plusPtr` 1
117−
src/Graphics/Vty/Terminal/Generic.hsdeleted−405
@@ -1,405 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# LANGUAGE ScopedTypeVariables #-}
3−{-# LANGUAGE Rank2Types #-}
4−{-# LANGUAGE ExistentialQuantification #-}
5−{-# LANGUAGE GADTs #-}
6−{-# LANGUAGE FlexibleContexts #-}
7−module Graphics.Vty.Terminal.Generic ( module Graphics.Vty.Terminal.Generic
8− , OutputBuffer
9− )
10− where
11−
12−import Data.Marshalling
13−
14−import Graphics.Vty.Picture
15−import Graphics.Vty.Span
16−import Graphics.Vty.DisplayRegion
17−
18−import Graphics.Vty.DisplayAttributes
19−
20−import Control.Monad ( liftM )
21−import Control.Monad.Trans
22−
23−import Data.Array
24−import qualified Data.ByteString.Internal as BSCore
25−import Data.Foldable
26−import Data.IORef
27−import Data.Word
28−import Data.String.UTF8 hiding ( foldl )
29−
30−data TerminalHandle where
31− TerminalHandle :: Terminal t => t -> IORef TerminalState -> TerminalHandle
32−
33−state_ref :: TerminalHandle -> IORef TerminalState
34−state_ref (TerminalHandle _ s_ref) = s_ref
35−
36−new_terminal_handle :: forall m t. ( MonadIO m, Terminal t ) => t -> m TerminalHandle
37−new_terminal_handle t = do
38− s_ref <- liftIO $ newIORef initial_terminal_state
39− return $ TerminalHandle t s_ref
40−
41−data TerminalState = TerminalState
42− { -- | The current terminal display attributes or Nothing if they are not known.
43− known_fattr :: Maybe FixedAttr
44− }
45−
46−initial_terminal_state :: TerminalState
47−initial_terminal_state = TerminalState Nothing
48−
49−class Terminal t where
50− -- | Text identifier for the terminal. Used for debugging.
51− terminal_ID :: t -> String
52− -- |
53− release_terminal :: MonadIO m => t -> m ()
54− -- | Clear the display and initialize the terminal to some initial display state.
55− --
56− -- The expectation of a program is that the display starts in some initial state.
57− -- The initial state would consist of fixed values:
58− -- - cursor at top left
59− -- - UTF-8 character encoding
60− -- - drawing characteristics are the default
61− -- The abstract operation I think all these behaviors are instances of is reserving exclusive
62− -- access to a display such that:
63− -- - The previous state cannot be determined
64− -- - When exclusive access to a display is release the display returns to the previous state.
65− reserve_display :: MonadIO m => t -> m ()
66−
67− -- | Return the display to the state before reserve_display
68− -- If no previous state then set the display state to the initial state.
69− release_display :: MonadIO m => t -> m ()
70−
71− -- | Returns the current display bounds.
72− display_bounds :: MonadIO m => t -> m DisplayRegion
73−
74− -- Internal method used to provide the DisplayTerminal instance to the DisplayHandle
75− -- constructor.
76− display_terminal_instance :: MonadIO m
77− => t
78− -> DisplayRegion
79− -> (forall d. DisplayTerminal d => d -> DisplayHandle)
80− -> m DisplayHandle
81−
82− -- | Output the byte buffer of the specified size to the terminal device. The size is equal to
83− -- end_ptr - start_ptr
84− output_byte_buffer :: MonadIO m => t -> OutputBuffer -> Word -> m ()
85−
86−instance Terminal TerminalHandle where
87− terminal_ID (TerminalHandle t _) = terminal_ID t
88− release_terminal (TerminalHandle t _) = release_terminal t
89− reserve_display (TerminalHandle t _) = reserve_display t
90− release_display (TerminalHandle t _) = release_display t
91− display_bounds (TerminalHandle t _) = display_bounds t
92− display_terminal_instance (TerminalHandle t _) = display_terminal_instance t
93− output_byte_buffer (TerminalHandle t _) = output_byte_buffer t
94−
95−data DisplayHandle where
96− DisplayHandle :: DisplayTerminal d => d -> TerminalHandle -> DisplayState -> DisplayHandle
97−
98−-- | Acquire display access to the given region of the display.
99−-- Currently all regions have the upper left corner of (0,0) and the lower right corner at
100−-- (max display_width provided_width, max display_height provided_height)
101−display_context :: MonadIO m => TerminalHandle -> DisplayRegion -> m DisplayHandle
102−display_context t b = do
103− s <- initial_display_state
104− let c d = DisplayHandle d t s
105− display_terminal_instance t b c
106−
107−data DisplayState = DisplayState
108− { previous_output_ref :: IORef (Maybe SpanOpSequence)
109− }
110−
111−initial_display_state :: MonadIO m => m DisplayState
112−initial_display_state = liftM DisplayState $ liftIO $ newIORef Nothing
113−
114−class DisplayTerminal d where
115− -- | Provide the bounds of the display context.
116− context_region :: d -> DisplayRegion
117−
118− -- | Maximum number of colors supported by the context.
119− context_color_count :: d -> Word
120−
121− -- | sets the output position to the specified row and column. Where the number of bytes
122− -- required for the control codes can be specified seperate from the actual byte sequence.
123− move_cursor_required_bytes :: d -> Word -> Word -> Word
124− serialize_move_cursor :: MonadIO m => d -> Word -> Word -> OutputBuffer -> m OutputBuffer
125−
126− show_cursor_required_bytes :: d -> Word
127− serialize_show_cursor :: MonadIO m => d -> OutputBuffer -> m OutputBuffer
128−
129− hide_cursor_required_bytes :: d -> Word
130− serialize_hide_cursor :: MonadIO m => d -> OutputBuffer -> m OutputBuffer
131−
132− -- | Assure the specified output attributes will be applied to all the following text until the
133− -- next output attribute change. Where the number of bytes required for the control codes can
134− -- be specified seperate from the actual byte sequence. The required number of bytes must be
135− -- at least the maximum number of bytes required by any attribute changes. The serialization
136− -- equations must provide the ptr to the next byte to be specified in the output buffer.
137− --
138− -- The currently applied display attributes are provided as well. The Attr data type can
139− -- specify the style or color should not be changed from the currently applied display
140− -- attributes. In order to support this the currently applied display attributes are required.
141− -- In addition it may be possible to optimize the state changes based off the currently applied
142− -- display attributes.
143− attr_required_bytes :: d -> FixedAttr -> Attr -> DisplayAttrDiff -> Word
144− serialize_set_attr :: MonadIO m => d -> FixedAttr -> Attr -> DisplayAttrDiff -> OutputBuffer -> m OutputBuffer
145−
146− -- | Reset the display attributes to the default display attributes
147− default_attr_required_bytes :: d -> Word
148− serialize_default_attr :: MonadIO m => d -> OutputBuffer -> m OutputBuffer
149−
150− -- | See Graphics.Vty.Terminal.XTermColor.inline_hack
151− inline_hack :: MonadIO m => d -> m ()
152− inline_hack _d = return ()
153−
154−
155−instance DisplayTerminal DisplayHandle where
156− context_region (DisplayHandle d _ _) = context_region d
157− context_color_count (DisplayHandle d _ _) = context_color_count d
158− move_cursor_required_bytes (DisplayHandle d _ _) = move_cursor_required_bytes d
159− serialize_move_cursor (DisplayHandle d _ _) = serialize_move_cursor d
160− show_cursor_required_bytes (DisplayHandle d _ _) = show_cursor_required_bytes d
161− serialize_show_cursor (DisplayHandle d _ _) = serialize_show_cursor d
162− hide_cursor_required_bytes (DisplayHandle d _ _) = hide_cursor_required_bytes d
163− serialize_hide_cursor (DisplayHandle d _ _) = serialize_hide_cursor d
164− attr_required_bytes (DisplayHandle d _ _) = attr_required_bytes d
165− serialize_set_attr (DisplayHandle d _ _) = serialize_set_attr d
166− default_attr_required_bytes (DisplayHandle d _ _) = default_attr_required_bytes d
167− serialize_default_attr (DisplayHandle d _ _) = serialize_default_attr d
168− inline_hack (DisplayHandle d _ _) = inline_hack d
169−
170−-- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.
171−utf8_text_required_bytes :: UTF8 BSCore.ByteString -> Word
172−utf8_text_required_bytes str =
173− let (_, _, src_bytes_length) = BSCore.toForeignPtr (toRep str)
174− in toEnum src_bytes_length
175−
176−-- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.
177−serialize_utf8_text :: MonadIO m => UTF8 BSCore.ByteString -> OutputBuffer -> m OutputBuffer
178−serialize_utf8_text str dest_ptr =
179− let (src_fptr, src_ptr_offset, src_bytes_length) = BSCore.toForeignPtr (toRep str)
180− in liftIO $ withForeignPtr src_fptr $ \src_ptr -> do
181− let src_ptr' = src_ptr `plusPtr` src_ptr_offset
182− BSCore.memcpy dest_ptr src_ptr' (toEnum src_bytes_length)
183− return (dest_ptr `plusPtr` src_bytes_length)
184−
185−
186−-- | Displays the given `Picture`.
187−--
188−-- 0. The image is cropped to the display size.
189−--
190−-- 1. Converted into a sequence of attribute changes and text spans.
191−--
192−-- 2. The cursor is hidden.
193−--
194−-- 3. Serialized to the display.
195−--
196−-- 4. The cursor is then shown and positioned or kept hidden.
197−--
198−--
199−-- todo: specify possible IO exceptions.
200−-- abstract from IO monad to a MonadIO instance.
201−output_picture :: MonadIO m => DisplayHandle -> Picture -> m ()
202−output_picture (DisplayHandle d t s) pic = do
203− let !r = context_region d
204− let !ops = spans_for_pic pic r
205− let !initial_attr = FixedAttr default_style_mask Nothing Nothing
206− -- Diff the previous output against the requested output. Differences are currently on a per-row
207− -- basis.
208− diffs :: [Bool]
209− <- liftIO ( readIORef (previous_output_ref s) )
210− >>= \mprevious_ops -> case mprevious_ops of
211− Nothing
212− -> return $ replicate ( fromEnum $ region_height $ effected_region ops )
213− True
214− Just previous_ops
215− -> if effected_region previous_ops /= effected_region ops
216− then return $ replicate ( fromEnum $ region_height $ effected_region ops )
217− True
218− else return $ zipWith (/=) ( elems $ row_ops previous_ops )
219− ( elems $ row_ops ops )
220−
221− -- determine the number of bytes required to completely serialize the output ops.
222− let total = hide_cursor_required_bytes d
223− + default_attr_required_bytes d
224− + required_bytes d initial_attr diffs ops
225− + case pic_cursor pic of
226− NoCursor -> 0
227− Cursor x y -> show_cursor_required_bytes d
228− + move_cursor_required_bytes d x y
229−
230− -- ... then serialize
231− liftIO $ allocaBytes (fromEnum total) $ \start_ptr -> do
232− ptr <- serialize_hide_cursor d start_ptr
233− ptr' <- serialize_default_attr d ptr
234− ptr'' <- serialize_output_ops d ptr' initial_attr diffs ops
235− end_ptr <- case pic_cursor pic of
236− NoCursor -> return ptr''
237− Cursor x y -> do
238− let m = cursor_output_map ops $ pic_cursor pic
239− (ox, oy) = char_to_output_pos m (x,y)
240− serialize_show_cursor d ptr'' >>= serialize_move_cursor d ox oy
241− -- todo: How to handle exceptions?
242− case end_ptr `minusPtr` start_ptr of
243− count | count < 0 -> fail "End pointer before start of buffer."
244− | toEnum count > total -> fail $ "End pointer past end of buffer by " ++ show (toEnum count - total)
245− | otherwise -> output_byte_buffer t start_ptr (toEnum count)
246− -- Cache the output spans.
247− liftIO $ writeIORef (previous_output_ref s) (Just ops)
248− return ()
249−
250−required_bytes :: DisplayTerminal d => d -> FixedAttr -> [Bool] -> SpanOpSequence -> Word
251−required_bytes d in_fattr diffs ops =
252− let (_, n, _, _) = foldl' required_bytes' (0, 0, in_fattr, diffs) ( row_ops ops )
253− in n
254− where
255− required_bytes' (y, current_sum, fattr, True : diffs') span_ops
256− = let (s, fattr') = span_ops_required_bytes d y fattr span_ops
257− in ( y + 1, s + current_sum, fattr', diffs' )
258− required_bytes' (y, current_sum, fattr, False : diffs') _span_ops
259− = ( y + 1, current_sum, fattr, diffs' )
260− required_bytes' (_y, _current_sum, _fattr, [] ) _span_ops
261− = error "shouldn't be possible"
262−
263−span_ops_required_bytes :: DisplayTerminal d => d -> Word -> FixedAttr -> SpanOps -> (Word, FixedAttr)
264−span_ops_required_bytes d y in_fattr span_ops =
265− -- The first operation is to set the cursor to the start of the row
266− let header_required_bytes = move_cursor_required_bytes d 0 y
267− -- then the span ops are serialized in the order specified
268− in foldl' ( \(current_sum, fattr) op -> let (c, fattr') = span_op_required_bytes d fattr op
269− in (c + current_sum, fattr')
270− )
271− (header_required_bytes, in_fattr)
272− span_ops
273−
274−span_op_required_bytes :: DisplayTerminal d => d -> FixedAttr -> SpanOp -> (Word, FixedAttr)
275−span_op_required_bytes d fattr (AttributeChange attr) =
276− let attr' = limit_attr_for_display d attr
277− diffs = display_attr_diffs fattr fattr'
278− c = attr_required_bytes d fattr attr' diffs
279− fattr' = fix_display_attr fattr attr'
280− in (c, fattr')
281−span_op_required_bytes _d fattr (TextSpan _ _ str) = (utf8_text_required_bytes str, fattr)
282−
283−serialize_output_ops :: ( MonadIO m, DisplayTerminal d )
284− => d
285− -> OutputBuffer
286− -> FixedAttr
287− -> [Bool]
288− -> SpanOpSequence
289− -> m OutputBuffer
290−serialize_output_ops d start_ptr in_fattr diffs ops = do
291− (_, end_ptr, _, _) <- foldlM serialize_output_ops'
292− ( 0, start_ptr, in_fattr, diffs )
293− ( row_ops ops )
294− return end_ptr
295− where
296− serialize_output_ops' ( y, out_ptr, fattr, True : diffs' ) span_ops
297− = serialize_span_ops d y out_ptr fattr span_ops
298− >>= return . ( \(out_ptr', fattr') -> ( y + 1, out_ptr', fattr', diffs' ) )
299− serialize_output_ops' ( y, out_ptr, fattr, False : diffs' ) _span_ops
300− = return ( y + 1, out_ptr, fattr, diffs' )
301− serialize_output_ops' (_y, _out_ptr, _fattr, [] ) _span_ops
302− = error "shouldn't be possible"
303−
304−serialize_span_ops :: ( MonadIO m, DisplayTerminal d )
305− => d
306− -> Word
307− -> OutputBuffer
308− -> FixedAttr
309− -> SpanOps
310− -> m (OutputBuffer, FixedAttr)
311−serialize_span_ops d y out_ptr in_fattr span_ops = do
312− -- The first operation is to set the cursor to the start of the row
313− out_ptr' <- serialize_move_cursor d 0 y out_ptr
314− -- then the span ops are serialized in the order specified
315− foldlM ( \(out_ptr'', fattr) op -> serialize_span_op d op out_ptr'' fattr )
316− (out_ptr', in_fattr)
317− span_ops
318−
319−serialize_span_op :: ( MonadIO m, DisplayTerminal d )
320− => d
321− -> SpanOp
322− -> OutputBuffer
323− -> FixedAttr
324− -> m (OutputBuffer, FixedAttr)
325−serialize_span_op d (AttributeChange attr) out_ptr fattr = do
326− let attr' = limit_attr_for_display d attr
327− fattr' = fix_display_attr fattr attr'
328− diffs = display_attr_diffs fattr fattr'
329− out_ptr' <- serialize_set_attr d fattr attr' diffs out_ptr
330− return (out_ptr', fattr')
331−serialize_span_op _d (TextSpan _ _ str) out_ptr fattr = do
332− out_ptr' <- serialize_utf8_text str out_ptr
333− return (out_ptr', fattr)
334−
335−marshall_to_terminal :: ( MonadIO m, Terminal t )
336− => t -> Word -> (Ptr Word8 -> m (Ptr Word8)) -> m ()
337−marshall_to_terminal t c f = do
338− start_ptr <- liftIO $ mallocBytes (fromEnum c)
339− --
340− -- todo: capture exceptions?
341− end_ptr <- f start_ptr
342− case end_ptr `minusPtr` start_ptr of
343− count | count < 0 -> fail "End pointer before start pointer."
344− | toEnum count > c -> fail $ "End pointer past end of buffer by " ++ show (toEnum count - c)
345− | otherwise -> output_byte_buffer t start_ptr (toEnum count)
346− liftIO $ free start_ptr
347− return ()
348−
349−data CursorOutputMap = CursorOutputMap
350− { char_to_output_pos :: (Word, Word) -> (Word, Word)
351− }
352−
353−cursor_output_map :: SpanOpSequence -> Cursor -> CursorOutputMap
354−cursor_output_map span_ops _cursor = CursorOutputMap
355− { char_to_output_pos = \(cx, cy) -> (cursor_column_offset span_ops cx cy, cy)
356− }
357−
358−cursor_column_offset :: SpanOpSequence -> Word -> Word -> Word
359−cursor_column_offset span_ops cx cy =
360− let cursor_row_ops = row_ops span_ops ! cy
361− (out_offset, _, _)
362− = foldl' ( \(d, current_cx, done) op ->
363− if done then (d, current_cx, done) else case span_op_has_width op of
364− Nothing -> (d, current_cx, False)
365− Just (cw, ow) -> case compare cx (current_cx + cw) of
366− GT -> ( d + ow
367− , current_cx + cw
368− , False
369− )
370− EQ -> ( d + ow
371− , current_cx + cw
372− , True
373− )
374− LT -> ( d + columns_to_char_offset (cx - current_cx) op
375− , current_cx + cw
376− , True
377− )
378− )
379− (0, 0, False)
380− cursor_row_ops
381− in out_offset
382−
383−limit_attr_for_display :: DisplayTerminal d => d -> Attr -> Attr
384−limit_attr_for_display d attr
385− = attr { attr_fore_color = clamp_color $ attr_fore_color attr
386− , attr_back_color = clamp_color $ attr_back_color attr
387− }
388− where
389− clamp_color Default = Default
390− clamp_color KeepCurrent = KeepCurrent
391− clamp_color (SetTo c) = clamp_color' c
392− clamp_color' (ISOColor v)
393− | context_color_count d < 8 = Default
394− | context_color_count d < 16 && v >= 8 = SetTo $ ISOColor (v - 8)
395− | otherwise = SetTo $ ISOColor v
396− clamp_color' (Color240 v)
397− -- TODO: Choose closes ISO color?
398− | context_color_count d < 8 = Default
399− | context_color_count d < 16 = Default
400− | context_color_count d == 240 = SetTo $ Color240 v
401− | otherwise
402− = let p :: Double = fromIntegral v / 240.0
403− v' = floor $ p * (fromIntegral $ context_color_count d)
404− in SetTo $ Color240 v'
405−
src/Graphics/Vty/Terminal/MacOSX.hsdeleted−95
@@ -1,95 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−-- The standard Mac OS X terminals Terminal.app and iTerm both declare themselves to be
3−-- "xterm-color" by default. However the terminfo database for xterm-color included with OS X is
4−-- incomplete.
5−--
6−-- This terminal implementation modifies the standard terminfo terminal as required for complete OS
7−-- X support.
8−{-# LANGUAGE ForeignFunctionInterface #-}
9−{-# LANGUAGE ScopedTypeVariables #-}
10−{-# LANGUAGE MagicHash #-}
11−{-# LANGUAGE FlexibleInstances #-}
12−{-# LANGUAGE TypeFamilies #-}
13−module Graphics.Vty.Terminal.MacOSX ( terminal_instance
14− )
15− where
16−
17−import Graphics.Vty.Terminal.Generic
18−import qualified Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
19−
20−import Control.Applicative
21−import Control.Monad.Trans
22−
23−import System.IO
24−
25−data Term = Term
26− { super_term :: TerminalHandle
27− , term_app :: String
28− }
29−
30−-- for Terminal.app use "xterm". For iTerm.app use "xterm-256color"
31−terminal_instance :: ( Applicative m, MonadIO m ) => String -> m Term
32−terminal_instance v = do
33− let base_term "iTerm.app" = "xterm-256color"
34− base_term _ = "xterm"
35− t <- TerminfoBased.terminal_instance (base_term v) >>= new_terminal_handle
36− return $ Term t v
37−
38−flushed_put :: MonadIO m => String -> m ()
39−flushed_put str = do
40− liftIO $ hPutStr stdout str
41− liftIO $ hFlush stdout
42−
43−-- Terminal.app really does want the xterm-color smcup and rmcup caps. Not the generic xterm ones.
44−smcup_str, rmcup_str :: String
45−smcup_str = "\ESC7\ESC[?47h"
46−rmcup_str = "\ESC[2J\ESC[?47l\ESC8"
47−
48−-- iTerm needs a clear screen after smcup as well?
49−clear_screen_str :: String
50−clear_screen_str = "\ESC[H\ESC[2J"
51−
52−instance Terminal Term where
53− terminal_ID t = term_app t ++ " :: MacOSX"
54−
55− release_terminal t = do
56− release_terminal $ super_term t
57−
58− reserve_display _t = do
59− flushed_put smcup_str
60− flushed_put clear_screen_str
61−
62− release_display _t = do
63− flushed_put rmcup_str
64−
65− display_terminal_instance t b c = do
66− d <- display_context (super_term t) b
67− return $ c (DisplayContext d)
68−
69− display_bounds t = display_bounds (super_term t)
70−
71− output_byte_buffer t = output_byte_buffer (super_term t)
72−
73−data DisplayContext = DisplayContext
74− { super_display :: DisplayHandle
75− }
76−
77−instance DisplayTerminal DisplayContext where
78− context_region d = context_region (super_display d)
79− context_color_count d = context_color_count (super_display d)
80−
81− move_cursor_required_bytes d = move_cursor_required_bytes (super_display d)
82− serialize_move_cursor d = serialize_move_cursor (super_display d)
83−
84− show_cursor_required_bytes d = show_cursor_required_bytes (super_display d)
85− serialize_show_cursor d = serialize_show_cursor (super_display d)
86−
87− hide_cursor_required_bytes d = hide_cursor_required_bytes (super_display d)
88− serialize_hide_cursor d = serialize_hide_cursor (super_display d)
89−
90− attr_required_bytes d = attr_required_bytes (super_display d)
91− serialize_set_attr d = serialize_set_attr (super_display d)
92−
93− default_attr_required_bytes d = default_attr_required_bytes (super_display d)
94− serialize_default_attr d = serialize_default_attr (super_display d)
95−
src/Graphics/Vty/Terminal/TerminfoBased.hsdeleted−412
@@ -1,412 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# LANGUAGE ForeignFunctionInterface #-}
3−{-# LANGUAGE ScopedTypeVariables #-}
4−{-# LANGUAGE FlexibleInstances #-}
5−module Graphics.Vty.Terminal.TerminfoBased ( terminal_instance
6− )
7− where
8−
9−import Data.Terminfo.Parse
10−import Data.Terminfo.Eval
11−
12−import Graphics.Vty.Attributes
13−import Graphics.Vty.DisplayAttributes
14−import Graphics.Vty.Terminal.Generic
15−import Graphics.Vty.DisplayRegion
16−
17−import Control.Applicative
18−import Control.Monad ( foldM )
19−import Control.Monad.Trans
20−
21−import Data.Bits ( (.&.) )
22−import Data.Maybe ( isJust, isNothing, fromJust )
23−import Data.Word
24−
25−import Foreign.C.Types ( CLong )
26−
27−import qualified System.Console.Terminfo as Terminfo
28−import System.IO
29−
30−data Term = Term
31− { term_info_ID :: String
32− , term_info :: Terminfo.Terminal
33− , smcup :: Maybe CapExpression
34− , rmcup :: Maybe CapExpression
35− , cup :: CapExpression
36− , cnorm :: CapExpression
37− , civis :: CapExpression
38− , set_fore_color :: CapExpression
39− , set_back_color :: CapExpression
40− , set_default_attr :: CapExpression
41− , clear_screen :: CapExpression
42− , display_attr_caps :: DisplayAttrCaps
43− }
44−
45−data DisplayAttrCaps = DisplayAttrCaps
46− { set_attr_states :: Maybe CapExpression
47− , enter_standout :: Maybe CapExpression
48− , exit_standout :: Maybe CapExpression
49− , enter_underline :: Maybe CapExpression
50− , exit_underline :: Maybe CapExpression
51− , enter_reverse_video :: Maybe CapExpression
52− , enter_dim_mode :: Maybe CapExpression
53− , enter_bold_mode :: Maybe CapExpression
54− }
55−
56−marshall_cap_to_terminal :: MonadIO m => Term -> (Term -> CapExpression) -> [CapParam] -> m ()
57−marshall_cap_to_terminal t cap_selector cap_params = do
58− marshall_to_terminal t ( cap_expression_required_bytes (cap_selector t) cap_params )
59− ( serialize_cap_expression (cap_selector t) cap_params )
60− return ()
61−
62−{- | Uses terminfo for all control codes. While this should provide the most compatible terminal
63− - terminfo does not support some features that would increase efficiency and improve compatibility:
64− - * determine the character encoding supported by the terminal. Should this be taken from the LANG
65− - environment variable?
66− - * Provide independent string capabilities for all display attributes.
67− -
68− -
69− - todo: Some display attributes like underline and bold have independent string capabilities that
70− - should be used instead of the generic "sgr" string capability.
71− -}
72−terminal_instance :: ( Applicative m, MonadIO m ) => String -> m Term
73−terminal_instance in_ID = do
74− ti <- liftIO $ Terminfo.setupTerm in_ID
75− let require_cap str
76− = case Terminfo.getCapability ti (Terminfo.tiGetStr str) of
77− Nothing -> fail $ "Terminal does not define required capability \"" ++ str ++ "\""
78− Just cap_str -> case parse_cap_expression cap_str of
79− Left e -> fail $ show e
80− Right cap -> return cap
81− probe_cap cap_name
82− = case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
83− Nothing -> return Nothing
84− Just cap_str -> case parse_cap_expression cap_str of
85− Left e -> fail $ show e
86− Right cap -> return $ Just cap
87− pure Term
88− <*> pure in_ID
89− <*> pure ti
90− <*> probe_cap "smcup"
91− <*> probe_cap "rmcup"
92− <*> require_cap "cup"
93− <*> require_cap "cnorm"
94− <*> require_cap "civis"
95− <*> require_cap "setaf"
96− <*> require_cap "setab"
97− <*> require_cap "sgr0"
98− <*> require_cap "clear"
99− <*> current_display_attr_caps ti
100−
101−current_display_attr_caps :: ( Applicative m, MonadIO m )
102− => Terminfo.Terminal
103− -> m DisplayAttrCaps
104−current_display_attr_caps ti
105− = pure DisplayAttrCaps
106− <*> probe_cap "sgr"
107− <*> probe_cap "smso"
108− <*> probe_cap "rmso"
109− <*> probe_cap "smul"
110− <*> probe_cap "rmul"
111− <*> probe_cap "rev"
112− <*> probe_cap "dim"
113− <*> probe_cap "bold"
114− where probe_cap cap_name
115− = case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
116− Nothing -> return Nothing
117− Just cap_str -> case parse_cap_expression cap_str of
118− Left e -> fail $ show e
119− Right cap -> return $ Just cap
120−
121−instance Terminal Term where
122− terminal_ID t = term_info_ID t ++ " :: TerminfoBased"
123−
124− release_terminal t = do
125− marshall_cap_to_terminal t set_default_attr []
126− marshall_cap_to_terminal t cnorm []
127− return ()
128−
129− reserve_display t = do
130− if (isJust $ smcup t)
131− then marshall_cap_to_terminal t (fromJust . smcup) []
132− else return ()
133− -- Screen on OS X does not appear to support smcup?
134− -- To approximate the expected behavior: clear the screen and then move the mouse to the
135− -- home position.
136− marshall_cap_to_terminal t clear_screen []
137− return ()
138−
139− release_display t = do
140− if (isJust $ rmcup t)
141− then marshall_cap_to_terminal t (fromJust . rmcup) []
142− else return ()
143− marshall_cap_to_terminal t cnorm []
144− return ()
145−
146− display_terminal_instance t b c = do
147− let color_count
148− = case Terminfo.getCapability (term_info t) (Terminfo.tiGetNum "colors" ) of
149− Nothing -> 8
150− Just v -> toEnum v
151− return $ c (DisplayContext b t color_count)
152−
153− display_bounds _t = do
154− raw_size <- liftIO $ get_window_size
155− case raw_size of
156− ( w, h ) | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show raw_size
157− | otherwise -> return $ DisplayRegion (toEnum w) (toEnum h)
158−
159− -- Output the byte buffer of the specified size to the terminal device.
160− output_byte_buffer _t out_ptr out_byte_count = do
161− if out_byte_count == 0
162− then return ()
163− else liftIO $ hPutBuf stdout out_ptr (fromEnum out_byte_count)
164− liftIO $ hFlush stdout
165−
166−foreign import ccall "gwinsz.h c_get_window_size" c_get_window_size :: IO CLong
167−
168−get_window_size :: IO (Int,Int)
169−get_window_size = do
170− (a,b) <- (`divMod` 65536) `fmap` c_get_window_size
171− return (fromIntegral b, fromIntegral a)
172−
173−data DisplayContext = DisplayContext
174− { bounds :: DisplayRegion
175− , term :: Term
176− , supported_colors :: Word
177− }
178−
179−instance DisplayTerminal DisplayContext where
180− context_region d = bounds d
181−
182− context_color_count d = supported_colors d
183−
184− move_cursor_required_bytes d x y
185− = cap_expression_required_bytes (cup $ term d) [y, x]
186− serialize_move_cursor d x y out_ptr
187− = serialize_cap_expression (cup $ term d) [y, x] out_ptr
188−
189− show_cursor_required_bytes d
190− = cap_expression_required_bytes (cnorm $ term d) []
191− serialize_show_cursor d out_ptr
192− = serialize_cap_expression (cnorm $ term d) [] out_ptr
193−
194− hide_cursor_required_bytes d
195− = cap_expression_required_bytes (civis $ term d) []
196− serialize_hide_cursor d out_ptr
197− = serialize_cap_expression (civis $ term d) [] out_ptr
198−
199− -- Instead of evaluating all the rules related to setting display attributes twice (once in
200− -- required bytes and again in serialize) or some memoization scheme just return a size
201− -- requirement as big the longest possible control string.
202− --
203− -- Which is assumed to the be less than 512 for now.
204− --
205− -- \todo Not verified as safe and wastes memory.
206− attr_required_bytes _d _prev_attr _req_attr _diffs = 512
207−
208− -- Portably setting the display attributes is a giant pain in the ass.
209− -- If the terminal supports the sgr capability (which sets the on/off state of each style
210− -- directly ; and, for no good reason, resets the colors to the default) this always works:
211− -- 0. set the style attributes. This resets the fore and back color.
212− -- 1, If a foreground color is to be set then set the foreground color
213− -- 2. likewise with the background color
214− --
215− -- If the terminal does not support the sgr then
216− -- if there is a change from an applied color to the default (in either the fore or back color)
217− -- then:
218− -- 0. reset all display attributes (sgr0)
219− -- 1. enter required style modes
220− -- 2. set the fore color if required
221− -- 3. set the back color if required
222− --
223− -- Entering the required style modes could require a reset of the display attributes. If this is
224− -- the case then the back and fore colors always need to be set if not default.
225− --
226− -- All this (I think) is satisfied by the following logic:
227− serialize_set_attr d prev_attr req_attr diffs out_ptr = do
228− case (fore_color_diff diffs == ColorToDefault) || (back_color_diff diffs == ColorToDefault) of
229− -- The only way to reset either color, portably, to the default is to use either the set
230− -- state capability or the set default capability.
231− True -> do
232− case req_display_cap_seq_for ( display_attr_caps $ term d )
233− ( fixed_style attr )
234− ( style_to_apply_seq $ fixed_style attr )
235− of
236− EnterExitSeq caps
237− -- only way to reset a color to the defaults
238− -> serialize_default_attr d out_ptr
239− >>= (\out_ptr' -> foldM (\ptr cap -> serialize_cap_expression cap [] ptr) out_ptr' caps)
240− >>= set_colors
241− SetState state
242− -- implicitly resets the colors to the defaults
243− -> serialize_cap_expression ( fromJust $ set_attr_states
244− $ display_attr_caps
245− $ term d
246− )
247− ( sgr_args_for_state state )
248− out_ptr
249− >>= set_colors
250− -- Otherwise the display colors are not changing or changing between two non-default
251− -- points.
252− False -> do
253− -- Still, it could be the case that the change in display attributes requires the
254− -- colors to be reset because the required capability was not available.
255− case req_display_cap_seq_for ( display_attr_caps $ term d )
256− ( fixed_style attr )
257− ( style_diffs diffs )
258− of
259− -- Really, if terminals were re-implemented with modern concepts instead of
260− -- bowing down to 40 yr old dumb terminal requirements this would be the
261− -- only case ever reached!
262− -- Changes the style and color states according to the differences with the
263− -- currently applied states.
264− EnterExitSeq caps
265− -> foldM (\ptr cap -> serialize_cap_expression cap [] ptr) out_ptr caps
266− >>= apply_color_diff set_fore_color ( fore_color_diff diffs )
267− >>= apply_color_diff set_back_color ( back_color_diff diffs )
268− SetState state
269− -- implicitly resets the colors to the defaults
270− -> serialize_cap_expression ( fromJust $ set_attr_states
271− $ display_attr_caps
272− $ term d
273− )
274− ( sgr_args_for_state state )
275− out_ptr
276− >>= set_colors
277− where
278− attr = fix_display_attr prev_attr req_attr
279− set_colors ptr = do
280− ptr' <- case fixed_fore_color attr of
281− Just c -> serialize_cap_expression ( set_fore_color $ term d )
282− [ ansi_color_index c ]
283− ptr
284− Nothing -> return ptr
285− ptr'' <- case fixed_back_color attr of
286− Just c -> serialize_cap_expression ( set_back_color $ term d )
287− [ ansi_color_index c ]
288− ptr'
289− Nothing -> return ptr'
290− return ptr''
291− apply_color_diff _f NoColorChange ptr
292− = return ptr
293− apply_color_diff _f ColorToDefault _ptr
294− = fail "ColorToDefault is not a possible case for apply_color_diffs"
295− apply_color_diff f ( SetColor c ) ptr
296− = serialize_cap_expression ( f $ term d )
297− [ ansi_color_index c ]
298− ptr
299−
300− default_attr_required_bytes d
301− = cap_expression_required_bytes (set_default_attr $ term d) []
302− serialize_default_attr d out_ptr = do
303− serialize_cap_expression ( set_default_attr $ term d ) [] out_ptr
304−
305−ansi_color_index :: Color -> Word
306−ansi_color_index (ISOColor v) = toEnum $ fromEnum v
307−ansi_color_index (Color240 v) = 16 + ( toEnum $ fromEnum v )
308−
309−{- The sequence of terminfo caps to apply a given style are determined according to these rules.
310− -
311− - 1. The assumption is that it's preferable to use the simpler enter/exit mode capabilities than
312− - the full set display attribute state capability.
313− -
314− - 2. If a mode is supposed to be removed but there is not an exit capability defined then the
315− - display attributes are reset to defaults then the display attribute state is set.
316− -
317− - 3. If a mode is supposed to be applied but there is not an enter capability defined then then
318− - display attribute state is set if possible. Otherwise the mode is not applied.
319− -
320− - 4. If the display attribute state is being set then just update the arguments to that for any
321− - apply/remove.
322− -
323− -}
324−
325−data DisplayAttrSeq
326− = EnterExitSeq [CapExpression]
327− | SetState DisplayAttrState
328−
329−data DisplayAttrState = DisplayAttrState
330− { apply_standout :: Bool
331− , apply_underline :: Bool
332− , apply_reverse_video :: Bool
333− , apply_blink :: Bool
334− , apply_dim :: Bool
335− , apply_bold :: Bool
336− }
337−
338−sgr_args_for_state :: DisplayAttrState -> [CapParam]
339−sgr_args_for_state attr_state = map (\b -> if b then 1 else 0)
340− [ apply_standout attr_state
341− , apply_underline attr_state
342− , apply_reverse_video attr_state
343− , apply_blink attr_state
344− , apply_dim attr_state
345− , apply_bold attr_state
346− , False -- invis
347− , False -- protect
348− , False -- alt char set
349− ]
350−
351−req_display_cap_seq_for :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq
352−req_display_cap_seq_for caps s diffs
353− -- if the state transition implied by any diff cannot be supported with an enter/exit mode cap
354− -- then either the state needs to be set or the attribute change ignored.
355− = case (any no_enter_exit_cap diffs, isJust $ set_attr_states caps) of
356− -- If all the diffs have an enter-exit cap then just use those
357− ( False, _ ) -> EnterExitSeq $ map enter_exit_cap diffs
358− -- If not all the diffs have an enter-exit cap and there is no set state cap then filter out
359− -- all unsupported diffs and just apply the rest
360− ( True, False ) -> EnterExitSeq $ map enter_exit_cap
361− $ filter (not . no_enter_exit_cap) diffs
362− -- if not all the diffs have an enter-exit can and there is a set state cap then just use
363− -- the set state cap.
364− ( True, True ) -> SetState $ state_for_style s
365− where
366− no_enter_exit_cap ApplyStandout = isNothing $ enter_standout caps
367− no_enter_exit_cap RemoveStandout = isNothing $ exit_standout caps
368− no_enter_exit_cap ApplyUnderline = isNothing $ enter_underline caps
369− no_enter_exit_cap RemoveUnderline = isNothing $ exit_underline caps
370− no_enter_exit_cap ApplyReverseVideo = isNothing $ enter_reverse_video caps
371− no_enter_exit_cap RemoveReverseVideo = True
372− no_enter_exit_cap ApplyBlink = True
373− no_enter_exit_cap RemoveBlink = True
374− no_enter_exit_cap ApplyDim = isNothing $ enter_dim_mode caps
375− no_enter_exit_cap RemoveDim = True
376− no_enter_exit_cap ApplyBold = isNothing $ enter_bold_mode caps
377− no_enter_exit_cap RemoveBold = True
378− enter_exit_cap ApplyStandout = fromJust $ enter_standout caps
379− enter_exit_cap RemoveStandout = fromJust $ exit_standout caps
380− enter_exit_cap ApplyUnderline = fromJust $ enter_underline caps
381− enter_exit_cap RemoveUnderline = fromJust $ exit_underline caps
382− enter_exit_cap ApplyReverseVideo = fromJust $ enter_reverse_video caps
383− enter_exit_cap ApplyDim = fromJust $ enter_dim_mode caps
384− enter_exit_cap ApplyBold = fromJust $ enter_bold_mode caps
385− enter_exit_cap _ = error "enter_exit_cap applied to diff that was known not to have one."
386−
387−state_for_style :: Style -> DisplayAttrState
388−state_for_style s = DisplayAttrState
389− { apply_standout = is_style_set standout
390− , apply_underline = is_style_set underline
391− , apply_reverse_video = is_style_set reverse_video
392− , apply_blink = is_style_set blink
393− , apply_dim = is_style_set dim
394− , apply_bold = is_style_set bold
395− }
396− where is_style_set = has_style s
397−
398−style_to_apply_seq :: Style -> [StyleStateChange]
399−style_to_apply_seq s = concat
400− [ apply_if_required ApplyStandout standout
401− , apply_if_required ApplyUnderline underline
402− , apply_if_required ApplyReverseVideo reverse_video
403− , apply_if_required ApplyBlink blink
404− , apply_if_required ApplyDim dim
405− , apply_if_required ApplyBlink bold
406− ]
407− where
408− apply_if_required ap flag
409− = if 0 == ( flag .&. s )
410− then []
411− else [ ap ]
412−
src/Graphics/Vty/Terminal/XTermColor.hsdeleted−94
@@ -1,94 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# LANGUAGE ForeignFunctionInterface #-}
3−{-# LANGUAGE ScopedTypeVariables #-}
4−{-# LANGUAGE MagicHash #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE TypeFamilies #-}
7−module Graphics.Vty.Terminal.XTermColor ( terminal_instance
8− )
9− where
10−
11−import Graphics.Vty.Terminal.Generic
12−import qualified Graphics.Vty.Terminal.TerminfoBased as TerminfoBased
13−
14−import Control.Applicative
15−import Control.Monad.Trans
16−
17−import System.IO
18−
19−data XTermColor = XTermColor
20− { xterm_variant :: String
21− , super_term :: TerminalHandle
22− }
23−
24−-- Initialize the display to UTF-8
25−-- Regardless of what is output the text encoding is assumed to be UTF-8
26−terminal_instance :: ( Applicative m, MonadIO m ) => String -> m XTermColor
27−terminal_instance variant = do
28− -- If the terminal variant is xterm-color use xterm instead since, more often than not,
29− -- xterm-color is broken.
30− let variant' = if variant == "xterm-color" then "xterm" else variant
31− flushed_put set_utf8_char_set
32− t <- TerminfoBased.terminal_instance variant' >>= new_terminal_handle
33− return $ XTermColor variant' t
34−
35−flushed_put :: MonadIO m => String -> m ()
36−flushed_put str = do
37− liftIO $ hPutStr stdout str
38− liftIO $ hFlush stdout
39−
40−-- Since I don't know of a terminfo string cap that produces these strings these are hardcoded.
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−instance Terminal XTermColor where
46− terminal_ID t = (show $ xterm_variant t) ++ " :: XTermColor"
47−
48− release_terminal t = do
49− flushed_put set_default_char_set
50− release_terminal $ super_term t
51−
52− reserve_display t = reserve_display (super_term t)
53−
54− release_display t = release_display (super_term t)
55−
56− display_terminal_instance t b c = do
57− d <- display_context (super_term t) b
58− return $ c (DisplayContext d)
59−
60− display_bounds t = display_bounds (super_term t)
61−
62− output_byte_buffer t = output_byte_buffer (super_term t)
63−
64−data DisplayContext = DisplayContext
65− { super_display :: DisplayHandle
66− }
67−
68−instance DisplayTerminal DisplayContext where
69− context_region d = context_region (super_display d)
70−
71− context_color_count d = context_color_count (super_display d)
72−
73− move_cursor_required_bytes d = move_cursor_required_bytes (super_display d)
74− serialize_move_cursor d = serialize_move_cursor (super_display d)
75−
76− show_cursor_required_bytes d = show_cursor_required_bytes (super_display d)
77− serialize_show_cursor d = serialize_show_cursor (super_display d)
78−
79− hide_cursor_required_bytes d = hide_cursor_required_bytes (super_display d)
80− serialize_hide_cursor d = serialize_hide_cursor (super_display d)
81−
82− attr_required_bytes d = attr_required_bytes (super_display d)
83− serialize_set_attr d = serialize_set_attr (super_display d)
84−
85− default_attr_required_bytes d = default_attr_required_bytes (super_display d)
86− serialize_default_attr d = serialize_default_attr (super_display d)
87−
88− -- I think xterm is broken: Reseting the background color as the first bytes serialized on a new
89− -- line does not effect the background color xterm uses to clear the line. Which is used *after*
90− -- the next newline.
91− inline_hack _d = do
92− liftIO $ hPutStr stdout "\ESC[K"
93− liftIO $ hFlush stdout
94−
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/Bench.hsdeleted−62
@@ -1,62 +0,0 @@
1−module Main where
2−
3−import Graphics.Vty
4−
5−import Control.Concurrent( threadDelay )
6−import Control.Monad( liftM2 )
7−
8−import Data.List
9−import Data.Word
10−
11−import System.Environment( getArgs )
12−import System.IO
13−import System.Random
14−
15−
16−main = do
17− let fixed_gen = mkStdGen 0
18− setStdGen fixed_gen
19− args <- getArgs
20− case args of
21− [] -> mkVty >>= liftM2 (>>) (run $ Just 0) shutdown
22− ["--delay"] -> mkVty >>= liftM2 (>>) (run $ Just 1000000) shutdown
23− ["--slow"] -> mkVty >>= liftM2 (>>) (run Nothing ) shutdown
24− _ -> fail "usage: ./Bench [--slow|--delay]"
25−
26−run (Just delay) vt = mapM_ (\p -> update vt p >> threadDelay delay) . benchgen =<< display_bounds (terminal vt)
27−run Nothing vt = mapM_ (update vt) (benchgen $ DisplayRegion 200 100)
28−
29−-- Currently, we just do scrolling.
30−takem :: (a -> Word) -> Word -> [a] -> ([a],[a])
31−takem len n [] = ([],[])
32−takem len n (x:xs) | lx > n = ([], x:xs)
33− | True = let (tk,dp) = takem len (n - lx) xs in (x:tk,dp)
34− where lx = len x
35−
36−fold :: (a -> Word) -> [Word] -> [a] -> [[a]]
37−fold len [] xs = []
38−fold len (ll:lls) xs = let (tk,dp) = takem len ll xs in tk : fold len lls dp
39−
40−lengths :: Word -> StdGen -> [Word]
41−lengths ml g =
42− let (x,g2) = randomR (0,fromEnum ml) g
43− (y,g3) = randomR (0,x) g2
44− in (toEnum y) : lengths ml g3
45−
46−nums :: StdGen -> [(Attr, String)]
47−nums g = let (x,g2) = (random g :: (Int, StdGen))
48− (c,g3) = random g2
49− in ( if c then def_attr `with_fore_color` red else def_attr
50− , shows x " "
51− ) : nums g3
52−
53−pad :: Word -> Image -> Image
54−pad ml img = img <|> char_fill def_attr ' ' (ml - image_width img) 1
55−
56−clines :: StdGen -> Word -> [Image]
57−clines g maxll = map (pad maxll . horiz_cat . map (uncurry string)) $ fold (toEnum . length . snd) (lengths maxll g1) (nums g2)
58− where (g1,g2) = split g
59−
60−benchgen :: DisplayRegion -> [Picture]
61−benchgen (DisplayRegion w h) = take 500 $ map ((\i -> pic_for_image i) . vert_cat . take (fromEnum h)) $ tails $ clines (mkStdGen 42) w
62−
test/Bench2.hsdeleted−30
@@ -1,30 +0,0 @@
1−{-# LANGUAGE BangPatterns #-}
2−import Graphics.Vty
3−import System.IO
4−import System.Environment( getArgs )
5−import Control.Concurrent( threadDelay )
6−import System.Random
7−import Data.List
8−import Control.Monad( liftM2 )
9−
10−import qualified Data.ByteString.Char8 as B
11−
12−main = do
13− let fixed_gen = mkStdGen 0
14− setStdGen fixed_gen
15− args <- getArgs
16− vt <- mkVty
17− (w, h) <- getSize vt
18− let !image_0 = renderFill attr 'X' w h
19− let !image_1 = renderFill attr '0' w h
20− run vt image_0 image_1
21− shutdown vt
22−
23−run vt image_0 image_1 = run' vt 300 image_0 image_1
24−
25−run' vt 0 image_0 image_1 = return ()
26−run' vt n image_0 image_1 = do
27− let p = pic { pImage = image_0 }
28− update vt p
29− run' vt (n - 1) image_1 image_0
30−
test/BenchImageFuzz.hsnew file+29
@@ -0,0 +1,29 @@
1+module BenchImageFuzz where
2+import Graphics.Vty
3+import Graphics.Vty.Debug
4+
5+import Verify.Graphics.Vty.Image
6+import Verify
7+
8+import Control.Applicative
9+import Control.Monad
10+
11+import System.Random
12+
13+rand :: Arbitrary a => IO a
14+rand = head <$> sample' arbitrary
15+
16+randomImage :: IO Image
17+randomImage = rand
18+
19+randomPicture = picForImage <$> randomImage
20+
21+bench0 = do
22+ vty <- mkVty defaultConfig
23+ (w,h) <- displayBounds $ outputIface vty
24+ let pictures = replicateM 3000 randomPicture
25+ bench ps = do
26+ forM ps (update vty)
27+ shutdown vty
28+ return $ Bench pictures bench
29+
test/BenchNoDiffOpt.hsnew file+38
@@ -0,0 +1,38 @@
1+{-# LANGUAGE BangPatterns #-}
2+module BenchNoDiffOpt where
3+
4+{-# LANGUAGE BangPatterns #-}
5+import Graphics.Vty
6+import Verify
7+
8+import Control.Concurrent( threadDelay )
9+import Control.Monad( liftM2 )
10+
11+import qualified Data.ByteString.Char8 as B
12+import Data.List
13+
14+import System.Environment( getArgs )
15+import System.IO
16+import System.Random
17+
18+bench0 = do
19+ let fixedGen = mkStdGen 0
20+ setStdGen fixedGen
21+ vty <- mkVty defaultConfig
22+ (w,h) <- displayBounds $ outputIface vty
23+ let images = return $ (image0, image1)
24+ image0 = charFill defAttr 'X' w h
25+ image1 = charFill defAttr '0' w h
26+ bench d = do
27+ flipOut vty 300 image0 image1
28+ shutdown vty
29+ return $ Bench images bench
30+
31+flipOut vty n image0 image1 =
32+ let !pLeft = picForImage image0
33+ !pRight = picForImage image1
34+ wLeft 0 = return ()
35+ wLeft n = update vty pLeft >> wRight (n-1)
36+ wRight 0 = return ()
37+ wRight n = update vty pRight >> wLeft (n-1)
38+ in wLeft n
test/BenchRenderChar.hs+32−26
@@ -1,32 +1,38 @@
1−{- benchmarks composing images using the renderChar operation.
2− - This is what Yi uses in Yi.UI.Vty.drawText. Ideally a sequence of renderChar images horizontally
3− - composed should provide no worse performance than a fill render op.
4− -}
1+-- benchmarks composing images using the renderChar operation. This is
2+-- what Yi uses in Yi.UI.Vty.drawText. Ideally a sequence of renderChar
3+-- images horizontally composed should provide no worse performance than
4+-- a fill render op.
5+module BenchRenderChar where
6+
57import Graphics.Vty
8+import Verify
69
710import Control.Monad ( forM_ )
811
9−import System.Time
10−
11−main = do
12− vty <- mkVty
13− (w, h) <- getSize vty
14− let test_chars = take 500 $ cycle $ [ c | c <- ['a'..'z']]
15− start_time_0 <- getClockTime
16− forM_ test_chars $ \test_char -> do
17− let test_image = test_image_using_renderChar test_char w h
18− out_pic = pic { pImage = test_image }
19− update vty out_pic
20− end_time_0 <- getClockTime
21− let start_time_1 = end_time_0
22− forM_ test_chars $ \test_char -> do
23− let test_image = renderFill attr test_char w h
24− out_pic = pic { pImage = test_image }
25− update vty out_pic
26− end_time_1 <- getClockTime
27− shutdown vty
28− putStrLn $ timeDiffToString $ diffClockTimes end_time_0 start_time_0
29− putStrLn $ timeDiffToString $ diffClockTimes end_time_1 start_time_1
12+bench0 = do
13+ vty <- mkVty defaultConfig
14+ (w,h) <- displayBounds $ outputIface vty
15+ let testChars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
16+ bench d = do
17+ forM_ d $ \testChar -> do
18+ let testImage = testImageUsingChar testChar w h
19+ outPic = picForImage testImage
20+ update vty outPic
21+ shutdown vty
22+ return $ Bench testChars bench
3023
31−test_image_using_renderChar c w h = vertcat $ replicate h $ horzcat $ map (renderChar attr) (replicate w c)
24+testImageUsingChar c w h
25+ = vertCat $ replicate (fromIntegral h)
26+ $ horizCat $ map (char defAttr) (replicate (fromIntegral w) c)
3227
28+bench1 = do
29+ vty <- mkVty defaultConfig
30+ (w,h) <- displayBounds $ outputIface vty
31+ let testChars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
32+ bench d = do
33+ forM_ d $ \testChar -> do
34+ let testImage = charFill defAttr testChar w h
35+ outPic = picForImage testImage
36+ update vty outPic
37+ shutdown vty
38+ return $ Bench testChars bench
test/BenchVerticalScroll.hsnew file+59
@@ -0,0 +1,59 @@
1+module BenchVerticalScroll where
2+
3+import Graphics.Vty hiding ( pad )
4+import Verify
5+
6+import Control.Concurrent( threadDelay )
7+import Control.Monad( liftM2 )
8+
9+import Data.List
10+import Data.Word
11+
12+import System.Environment( getArgs )
13+import System.IO
14+import System.Random
15+
16+bench0 = do
17+ let fixedGen = mkStdGen 0
18+ setStdGen fixedGen
19+ return $ Bench (return ()) (\() -> mkVty defaultConfig >>= liftM2 (>>) run shutdown)
20+
21+run vt = mapM_ (\p -> update vt p) . benchgen =<< displayBounds (outputIface vt)
22+
23+-- Currently, we just do scrolling.
24+takem :: (a -> Int) -> Int -> [a] -> ([a],[a])
25+takem len n [] = ([],[])
26+takem len n (x:xs) | lx > n = ([], x:xs)
27+ | True = let (tk,dp) = takem len (n - lx) xs in (x:tk,dp)
28+ where lx = len x
29+
30+fold :: (a -> Int) -> [Int] -> [a] -> [[a]]
31+fold len [] xs = []
32+fold len (ll:lls) xs = let (tk,dp) = takem len ll xs in tk : fold len lls dp
33+
34+lengths :: Int -> StdGen -> [Int]
35+lengths ml g =
36+ let (x,g2) = randomR (0,ml) g
37+ (y,g3) = randomR (0,x) g2
38+ in y : lengths ml g3
39+
40+nums :: StdGen -> [(Attr, String)]
41+nums g = let (x,g2) = (random g :: (Int, StdGen))
42+ (c,g3) = random g2
43+ in ( if c then defAttr `withForeColor` red else defAttr
44+ , shows x " "
45+ ) : nums g3
46+
47+pad :: Int -> Image -> Image
48+pad ml img = img <|> charFill defAttr ' ' (ml - imageWidth img) 1
49+
50+clines :: StdGen -> Int -> [Image]
51+clines g maxll = map (pad maxll . horizCat . map (uncurry string))
52+ $ fold (length . snd) (lengths maxll g1) (nums g2)
53+ where (g1,g2) = split g
54+
55+benchgen :: DisplayRegion -> [Picture]
56+benchgen (w,h)
57+ = take 2000 $ map ((\i -> picForImage i) . vertCat . take (fromEnum h))
58+ $ tails
59+ $ clines (mkStdGen 80) w
test/ControlTable.hs+4−4
@@ -5,10 +5,10 @@ import Graphics.Vty.ControlStrings
55import System.Console.Terminfo
66
77main = do
8− terminal <- setupTermFromEnv
9− control_table <- init_control_table terminal
8+ terminal <- setupTermFromEnv
9+ controlTable <- init_controlTable terminal
1010 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)
1212 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)
1414
test/EventEcho.hsnew file+40
@@ -0,0 +1,40 @@
1+module Main where
2+
3+import Graphics.Vty
4+
5+import Control.Applicative
6+import Control.Arrow
7+import Control.Monad.RWS
8+
9+import Data.Sequence (Seq, (<|) )
10+import qualified Data.Sequence as Seq
11+import Data.Foldable
12+
13+eventBufferSize = 1000
14+
15+type App = RWST Vty () (Seq String) IO
16+
17+main = do
18+ vty <- mkVty defaultConfig
19+ _ <- execRWST (vtyInteract False) vty Seq.empty
20+ shutdown vty
21+
22+vtyInteract :: Bool -> App ()
23+vtyInteract shouldExit = do
24+ updateDisplay
25+ unless shouldExit $ handleNextEvent >>= vtyInteract
26+
27+updateDisplay :: App ()
28+updateDisplay = do
29+ let info = string defAttr "Press ESC to exit."
30+ eventLog <- foldMap (string defAttr) <$> get
31+ let pic = picForImage $ info <-> eventLog
32+ vty <- ask
33+ liftIO $ update vty pic
34+
35+handleNextEvent = ask >>= liftIO . nextEvent >>= handleEvent
36+ where
37+ handleEvent e = do
38+ modify $ (<|) (show e) >>> Seq.take eventBufferSize
39+ return $ e == EvKey KEsc []
40+
test/HereDoc.hs+3−5
@@ -1,8 +1,7 @@
11{-# LANGUAGE QuasiQuotes #-}
22{-# LANGUAGE TemplateHaskell #-}
3−{- From http://www.reddit.com/r/haskell/comments/8ereh/a_here_document_syntax/
4− - copyright unknown?
5− -}
3+-- From http://www.reddit.com/r/haskell/comments/8ereh/a_here_document_syntax/
4+-- copyright unknown?
65module HereDoc (heredoc) where
76
87import Language.Haskell.TH.Quote
@@ -10,5 +9,4 @@ import Language.Haskell.TH.Syntax
109import Language.Haskell.TH.Lib
1110
1211heredoc :: QuasiQuoter
13−heredoc = QuasiQuoter (litE . stringL) (litP . stringL)
14−
12+heredoc = QuasiQuoter (litE . stringL) (litP . stringL) undefined undefined
test/vty_issue_18.hs → test/Issue18.hsrenamed+4−6
@@ -1,5 +1,4 @@
1−module Main
2− where
1+module Main where
32
43import Graphics.Vty
54import Graphics.Vty.Debug
@@ -13,8 +12,8 @@ main = do
1312 play vty sx sy
1413
1514play :: Vty -> Int -> Int -> IO ()
16−play vty sx sy =
17− let
15+play vty sx sy =
16+ let
1817 testScreen = pic {
1918 pCursor = NoCursor
2019 , pImage = box 10 10 }
@@ -31,6 +30,5 @@ box w h =
3130 vertLine = renderFill attr '|' 1 (h - 2)
3231 horizLine = corner <|> renderHFill attr '-' (w - 2) <|> corner
3332 centerArea = vertLine <|> renderFill attr 'X' (w - 2) (h - 2) <|> vertLine
34− in
33+ in
3534 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+20−8
@@ -4,19 +4,17 @@ verify_display_attributes \
44verify_parse_terminfo_caps \
55verify_eval_terminfo_caps \
66verify_utf8_width \
7−verify_image_ops \
7+VerifyImageOps \
88verify_image_trans \
99verify_empty_image_props \
1010verify_picture_ops \
11−verify_span_ops \
11+VerifySpanOps \
1212verify_debug_terminal \
1313verify_inline \
1414
1515
1616TESTS :=\
17−Bench \
18−Bench2 \
19−BenchRenderChar \
17+benchmark \
2018Test \
2119Test2 \
2220yi_issue_264 \
@@ -26,7 +24,17 @@ $(VERIF_TESTS)
2624$(shell mkdir -p objects )
2725
2826# TODO: Tests should also be buildable referencing the currently installed vty
29−GHC_ARGS=--make -i../src -package parallel-1.1.0.1 -hide-package transformers -hide-package monads-fd -hide-package monads-tf -package QuickCheck-2.1.0.2 -ignore-package vty ../cbits/gwinsz.c ../cbits/set_term_timing.c ../cbits/mk_wcwidth.c -O -funbox-strict-fields -Wall -threaded -fno-full-laziness -fspec-constr -odir objects -hidir objects
27+GHC_ARGS=--make -i../src \
28+ -package parallel \
29+ -package deepseq-1.3.0.1 \
30+ -hide-package transformers \
31+ -hide-package monads-tf \
32+ -package QuickCheck \
33+ -ignore-package vty \
34+ ../cbits/gwinsz.c ../cbits/set_term_timing.c ../cbits/mk_wcwidth.c \
35+ -O2 -funbox-strict-fields -Wall -threaded -fno-full-laziness -fspec-constr -fspec-constr-count=10 \
36+ -rtsopts \
37+ -odir objects -hidir objects
3038
3139GHC_PROF_ARGS=-prof -auto-all $(GHC_ARGS)
3240
@@ -37,7 +45,7 @@ all : $(VERIF_TESTS)
3745
3846.PHONY: $(TESTS)
3947.SECONDEXPANSION:
40−$(TESTS) :
48+$(TESTS) :
4149 @echo running test $@
4250 @mkdir -p results/$@
4351 ( ghc $(GHC_PROF_ARGS) $@ \
@@ -46,6 +54,10 @@ $(TESTS) :
4654 )
4755
4856.PHONY: interactive_terminal_test
49−interactive_terminal_test :
57+interactive_terminal_test :
5058 ghc $(GHC_ARGS) $@ && ./$@
5159
60+.PHONY: core
61+core :
62+ ghc-core --no-asm --no-cast -- $(GHC_ARGS) benchmark
63+
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/Setup.lhsnew file+3
@@ -0,0 +1,3 @@
1+#!/usr/bin/env runhaskell
2+> import Distribution.Simple
3+> main = defaultMain
test/Test.hsdeleted−44
@@ -1,44 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−module Main where
3−
4−import Graphics.Vty
5−
6−import qualified Data.ByteString as B
7−import Data.Word
8−
9−import System.IO
10−
11−
12−main = do
13− vt <- mkVty
14− DisplayRegion w h <- display_bounds $ terminal vt
15− putStrLn $ show $ DisplayRegion w h
16− play vt 0 1 w h ""
17−
18−pieceA = def_attr `with_fore_color` red
19−dumpA = def_attr `with_style` reverse_video
20−
21−play :: Vty -> Word -> Word -> Word -> Word -> String -> IO ()
22−play vt x y sx sy btl = do update vt (current_pic x y sx sy btl)
23− k <- next_event vt
24− case k of EvKey (KASCII 'r') [MCtrl] -> refresh vt >> play vt x y sx sy btl
25− EvKey KLeft [] | x /= 0 -> play vt (x-1) y sx sy btl
26− EvKey KRight [] | x /= (sx-1) -> play vt (x+1) y sx sy btl
27− EvKey KUp [] | y /= 1 -> play vt x (y-1) sx sy btl
28− EvKey KDown [] | y /= (sy-2) -> play vt x (y+1) sx sy btl
29− EvKey KEsc [] -> shutdown vt >> return ()
30− EvResize nx ny -> play vt (min x (toEnum nx - 1))
31− (min y (toEnum ny - 2))
32− (toEnum nx)
33− (toEnum ny)
34− btl
35− _ -> play vt x y sx sy (take (fromEnum sx) (show k ++ btl))
36−
37−
38−current_pic :: Word -> Word -> Word -> Word -> String -> Picture
39−current_pic x y sx sy btl = pic_for_image i
40− where i = string def_attr "Move the @ character around with the arrow keys. Escape exits."
41− <-> char_fill pieceA ' ' sx (y - 1)
42− <-> char_fill pieceA ' ' x 1 <|> char pieceA '@' <|> char_fill pieceA ' ' (sx - x - 1) 1
43− <-> char_fill pieceA ' ' sx (sy - y - 2)
44− <-> iso_10646_string dumpA btl
test/Test2.hs+1
@@ -1,4 +1,5 @@
11module Main where
2+
23import Graphics.Vty
34
45main = do
test/Verify.hs+59−55
@@ -1,68 +1,69 @@
1+{-# LANGUAGE RankNTypes #-}
2+{-# LANGUAGE GADTs #-}
3+{-# LANGUAGE RecordWildCards #-}
14{-# LANGUAGE TypeSynonymInstances #-}
25{-# LANGUAGE FlexibleInstances #-}
36{-# LANGUAGE StandaloneDeriving #-}
47{-# LANGUAGE DisambiguateRecordFields #-}
58{-# LANGUAGE NamedFieldPuns #-}
6−module Verify ( module Verify
7− , module Test.QuickCheck
8− , succeeded
9− , failed
10− , Result(..)
11− , monadicIO
12− , liftIO
13− , liftIOResult
14− , liftResult
15− , liftBool
16− )
17− where
9+module Verify
10+ ( module Verify
11+ , module Control.Applicative
12+ , module Control.DeepSeq
13+ , module Control.Exception
14+ , module Control.Monad
15+ , module Test.QuickCheck
16+ , module Test.QuickCheck.Modifiers
17+ , module Text.Printf
18+ , succeeded
19+ , failed
20+ , monadicIO
21+ , liftIO
22+ , liftBool
23+ , Test(..)
24+ , Prop.Result(..)
25+ )
26+where
27+
28+import Control.Exception ( bracket, try, SomeException(..) )
29+
30+import Distribution.TestSuite hiding ( Result(..) )
31+import qualified Distribution.TestSuite as TS
1832
1933import Test.QuickCheck hiding ( Result(..) )
2034import qualified Test.QuickCheck as QC
21−import Test.QuickCheck.Property
22−import Test.QuickCheck.Monadic ( monadicIO )
35+import Test.QuickCheck.Modifiers
36+import Test.QuickCheck.Property hiding ( Result(..) )
37+import qualified Test.QuickCheck.Property as Prop
38+import Test.QuickCheck.Monadic ( monadicIO )
39+
40+import Text.Printf
2341
2442import qualified Codec.Binary.UTF8.String as UTF8
2543
26−import Control.Applicative
44+import Control.Applicative hiding ( (<|>) )
45+import Control.DeepSeq
46+import Control.Monad ( forM, mapM, mapM_, forM_ )
2747import Control.Monad.State.Strict
28−import Control.Monad.Trans ( liftIO )
29−
30−import Data.IORef
31−import Data.Word
3248
3349import Numeric ( showHex )
3450
35−import System.IO
36−
37−type Test = StateT TestState IO
38−
39−instance Applicative Test where
40− pure = return
41− ( <*> ) = ap
42−
43−data TestState = TestState
44− { results_ref :: IORef [QC.Result]
45− }
46−
47−run_test :: Test () -> IO ()
48−run_test t = do
49− s <- newIORef [] >>= return . TestState
50− s' <- runStateT t s >>= return . snd
51− results <- readIORef $ results_ref s'
52− let fail_results = [ fail_result | fail_result@(QC.Failure {}) <- results ]
53− case fail_results of
54− [] -> putStrLn "state: PASS"
55− rs -> do
56− putStrLn "state: FAIL"
57− putStrLn $ "fail_count: " ++ show (length rs)
58−
59−verify :: Testable prop => String -> prop -> Test QC.Result
60−verify prop_name prop = do
61− liftIO $ putStrLn $ "verify " ++ prop_name
62− get >>= \s -> do
63− r <- liftIO $ quickCheckResult prop
64− liftIO $ modifyIORef (results_ref s) (\rs -> r : rs)
65− return r
51+verify :: Testable t => String -> t -> Test
52+verify testName p = Test $ TestInstance
53+ { name = testName
54+ , run = do
55+ qcResult <- quickCheckWithResult (stdArgs {chatty = False}) p
56+ case qcResult of
57+ QC.Success {..} -> return $ Finished TS.Pass
58+ QC.Failure {numShrinks,reason} -> return $ Finished
59+ $ TS.Fail $ "After "
60+ ++ show numShrinks ++ " shrinks determined failure to be: "
61+ ++ show reason
62+ _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"
63+ , tags = []
64+ , options = []
65+ , setOption = \_ _ -> Left "no options supported"
66+ }
6667
6768data SingleColumnChar = SingleColumnChar Char
6869 deriving (Show, Eq)
@@ -77,9 +78,12 @@ instance Show DoubleColumnChar where
7778 show (DoubleColumnChar c) = "(0x" ++ showHex (fromEnum c) "" ++ ") ->" ++ UTF8.encodeString [c]
7879
7980instance Arbitrary DoubleColumnChar where
80− arbitrary = elements $ map DoubleColumnChar $
81− [ toEnum 0x3040 .. toEnum 0x3098 ]
82− ++ [ toEnum 0x309B .. toEnum 0xA4CF]
81+ arbitrary = elements $ map DoubleColumnChar $
82+ [ toEnum 0x3040 .. toEnum 0x3098 ]
83+ ++ [ toEnum 0x309B .. toEnum 0xA4CF ]
84+
85+liftIOResult :: Testable prop => IO prop -> Property
86+liftIOResult = ioProperty
8387
84−instance Arbitrary Word where
85− arbitrary = choose (0, 1024) >>= return . toEnum
88+data Bench where
89+ Bench :: forall v . NFData v => IO v -> (v -> IO ()) -> Bench
test/Verify/Data/Terminfo/Parse.hs+61−63
@@ -1,47 +1,41 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−module Verify.Data.Terminfo.Parse ( module Verify.Data.Terminfo.Parse
3− , module Data.Terminfo.Parse
4− )
5− where
1+module Verify.Data.Terminfo.Parse
2+ ( module Verify.Data.Terminfo.Parse
3+ , module Data.Terminfo.Parse
4+ )
5+where
66
77import Data.Terminfo.Parse
88import Data.Terminfo.Eval
99import Verify
1010
11−import Data.Array.Unboxed
1211import Data.Word
12+import qualified Data.Vector.Unboxed as Vector
1313
1414import Numeric
1515
16−instance Show CapExpression where
17− show c
18− = "CapExpression { " ++ show (cap_ops c) ++ " }"
19− ++ " <- [" ++ hex_dump (cap_bytes c) ++ "]"
20− ++ " <= " ++ show (source_string c)
21−
22−hex_dump :: CapBytes -> String
23−hex_dump bytes = foldr (\b s -> showHex b s) "" $ elems bytes
16+hexDump :: [Word8] -> String
17+hexDump bytes = foldr (\b s -> showHex b s) "" bytes
2418
2519data NonParamCapString = NonParamCapString String
2620 deriving Show
2721
2822instance Arbitrary NonParamCapString where
29− arbitrary
23+ arbitrary
3024 = ( do
3125 s <- listOf1 $ (choose (0, 255) >>= return . toEnum) `suchThat` (/= '%')
3226 return $ NonParamCapString s
33− ) `suchThat` ( \(NonParamCapString str) -> length str < 255 )
27+ ) `suchThat` ( \(NonParamCapString str) -> length str < 255 )
3428
3529data LiteralPercentCap = LiteralPercentCap String [Word8]
3630 deriving ( Show )
3731
3832instance Arbitrary LiteralPercentCap where
39− arbitrary
33+ arbitrary
4034 = ( do
4135 NonParamCapString s <- arbitrary
42− (s', bytes) <- insert_escape_op "%" [toEnum $ fromEnum '%'] s
36+ (s', bytes) <- insertEscapeOp "%" [toEnum $ fromEnum '%'] s
4337 return $ LiteralPercentCap s' bytes
44− ) `suchThat` ( \(LiteralPercentCap str _) -> length str < 255 )
38+ ) `suchThat` ( \(LiteralPercentCap str _) -> length str < 255 )
4539
4640data IncFirstTwoCap = IncFirstTwoCap String [Word8]
4741 deriving ( Show )
@@ -50,65 +44,69 @@ instance Arbitrary IncFirstTwoCap where
5044 arbitrary
5145 = ( do
5246 NonParamCapString s <- arbitrary
53− (s', bytes) <- insert_escape_op "i" [] s
47+ (s', bytes) <- insertEscapeOp "i" [] s
5448 return $ IncFirstTwoCap s' bytes
55− ) `suchThat` ( \(IncFirstTwoCap str _) -> length str < 255 )
49+ ) `suchThat` ( \(IncFirstTwoCap str _) -> length str < 255 )
5650
57−data PushParamCap = PushParamCap String Word [Word8]
51+data PushParamCap = PushParamCap String Int [Word8]
5852 deriving ( Show )
5953
6054instance Arbitrary PushParamCap where
6155 arbitrary
6256 = ( do
6357 NonParamCapString s <- arbitrary
64− n :: Word <- choose (1,9) >>= return . toEnum
65− (s', bytes) <- insert_escape_op ("p" ++ show n) [] s
58+ n <- choose (1,9)
59+ (s', bytes) <- insertEscapeOp ("p" ++ show n) [] s
6660 return $ PushParamCap s' n bytes
67− ) `suchThat` ( \(PushParamCap str _ _) -> length str < 255 )
61+ ) `suchThat` ( \(PushParamCap str _ _) -> length str < 255 )
6862
69−data DecPrintCap = DecPrintCap String Word [Word8]
63+data DecPrintCap = DecPrintCap String Int [Word8]
7064 deriving ( Show )
7165
7266instance Arbitrary DecPrintCap where
7367 arbitrary
7468 = ( do
7569 NonParamCapString s <- arbitrary
76− n :: Word <- choose (1,9) >>= return . toEnum
77− (s', bytes) <- insert_escape_op ("p" ++ show n ++ "%d") [] s
70+ n <- choose (1,9)
71+ (s', bytes) <- insertEscapeOp ("p" ++ show n ++ "%d") [] s
7872 return $ DecPrintCap s' n bytes
79− ) `suchThat` ( \(DecPrintCap str _ _) -> length str < 255 )
80−
81−insert_escape_op op_str repl_bytes s = do
82− insert_points <- listOf1 $ elements [0 .. length s - 1]
83− let s' = f s ('%' : op_str)
84− remaining_bytes = f (map (toEnum . fromEnum) s) repl_bytes
85− f in_vs out_v = concat [ vs
86− | vi <- zip in_vs [0 .. length s - 1]
87− , let vs = fst vi : ( if snd vi `elem` insert_points
88− then out_v
89− else []
90− )
91− ]
92− return (s', remaining_bytes)
93−
94−is_bytes_op :: CapOp -> Bool
95−is_bytes_op (Bytes {}) = True
96−-- is_bytes_op _ = False
97−
98−collect_bytes :: CapExpression -> [Word8]
99−collect_bytes e = concat [ bytes
100− | Bytes offset c <- cap_ops e
101− , let bytes = bytes_for_range e offset c
102− ]
103−
104−verify_bytes_equal out_bytes expected_bytes
105− = if out_bytes == expected_bytes
106− then liftResult succeeded
107− else liftResult $ failed
108− { reason = "out_bytes ["
109− ++ hex_dump ( listArray (0, toEnum $ length out_bytes - 1) out_bytes )
110− ++ "] /= expected_bytes ["
111− ++ hex_dump ( listArray (0, toEnum $ length expected_bytes - 1) expected_bytes )
112− ++ "]"
73+ ) `suchThat` ( \(DecPrintCap str _ _) -> length str < 255 )
74+
75+insertEscapeOp opStr replBytes s = do
76+ insertPoints <- listOf1 $ elements [0 .. length s - 1]
77+ let s' = f s ('%' : opStr)
78+ remainingBytes = f (map (toEnum . fromEnum) s) replBytes
79+ f inVs out_v = concat [ vs
80+ | vi <- zip inVs [0 .. length s - 1]
81+ , let vs = fst vi : ( if snd vi `elem` insertPoints
82+ then out_v
83+ else []
84+ )
85+ ]
86+ return (s', remainingBytes)
87+
88+isBytesOp :: CapOp -> Bool
89+isBytesOp (Bytes {}) = True
90+-- isBytesOp _ = False
91+
92+bytesForRange cap offset count
93+ = Vector.toList $ Vector.take count $ Vector.drop offset $ capBytes cap
94+
95+collectBytes :: CapExpression -> [Word8]
96+collectBytes e = concat [ bytes
97+ | Bytes offset count <- capOps e
98+ , let bytes = bytesForRange e offset count
99+ ]
100+
101+
102+verifyBytesEqual :: [Word8] -> [Word8] -> Result
103+verifyBytesEqual outBytes expectedBytes
104+ = if outBytes == expectedBytes
105+ then succeeded
106+ else failed
107+ { reason = "outBytes ["
108+ ++ hexDump outBytes
109+ ++ "] /= expectedBytes ["
110+ ++ hexDump expectedBytes
111+ ++ "]"
113112 }
114−
test/Verify/Graphics/Vty/Attributes.hs+52−19
@@ -1,40 +1,73 @@
1−module Verify.Graphics.Vty.Attributes ( module Verify.Graphics.Vty.Attributes
2− , module Graphics.Vty.Attributes
3− )
4− where
1+module Verify.Graphics.Vty.Attributes
2+ ( module Verify.Graphics.Vty.Attributes
3+ , module Graphics.Vty.Attributes
4+ )
5+where
56
67import Graphics.Vty.Attributes
78import Verify
89
910import Data.List ( delete )
1011
11−-- Limit the possible attributes to just a few for now.
12−possible_attr_mods :: [ AttrOp ]
13−possible_attr_mods =
14− [ set_bold_op
15− , id_op
12+allColors :: [Color]
13+allColors =
14+ [ black
15+ , red
16+ , green
17+ , yellow
18+ , blue
19+ , magenta
20+ , cyan
21+ , white
22+ , brightBlack
23+ , brightRed
24+ , brightGreen
25+ , brightYellow
26+ , brightBlue
27+ , brightMagenta
28+ , brightCyan
29+ , brightWhite
30+ ] ++ map Color240 [0..239]
31+
32+allStyles :: [Style]
33+allStyles =
34+ [ standout
35+ , underline
36+ , reverseVideo
37+ , blink
38+ , dim
39+ , bold
1640 ]
1741
42+-- Limit the possible attributes to just a few for now.
43+possibleAttrMods :: [ AttrOp ]
44+possibleAttrMods =
45+ [ idOp
46+ ] ++ map setForeColorOp allColors
47+ ++ map setBackColorOp allColors
48+ ++ map setStyleOp allStyles
49+
1850instance Arbitrary Attr where
19− arbitrary = elements possible_attr_mods >>= return . flip apply_op def_attr
51+ arbitrary = elements possibleAttrMods >>= return . flip applyOp defAttr
2052
2153data DiffAttr = DiffAttr Attr Attr
2254
2355instance Arbitrary DiffAttr where
2456 arbitrary = do
25− op0 <- elements possible_attr_mods
26− let possible_attr_mods' = delete op0 possible_attr_mods
27− op1 <- elements possible_attr_mods'
28− return $ DiffAttr (apply_op op0 def_attr) (apply_op op1 def_attr)
57+ op0 <- elements possibleAttrMods
58+ let possibleAttrMods' = delete op0 possibleAttrMods
59+ op1 <- elements possibleAttrMods'
60+ return $ DiffAttr (applyOp op0 defAttr) (applyOp op1 defAttr)
2961
3062data AttrOp = AttrOp String (Attr -> Attr)
3163
3264instance Eq AttrOp where
3365 AttrOp n0 _ == AttrOp n1 _ = n0 == n1
3466
35−set_bold_op = AttrOp "set_bold" (flip with_style bold)
36−id_op = AttrOp "id" id
37−
38−apply_op :: AttrOp -> Attr -> Attr
39−apply_op (AttrOp _ f) a = f a
67+setStyleOp s = AttrOp "set_style" (flip withStyle s)
68+setForeColorOp c = AttrOp "set_fore_color" (flip withForeColor c)
69+setBackColorOp c = AttrOp "set_back_color" (flip withBackColor c)
70+idOp = AttrOp "id" id
4071
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
2− , module Graphics.Vty.DisplayAttributes
3− )
4− where
1+module Verify.Graphics.Vty.DisplayAttributes
2+ ( module Verify.Graphics.Vty.DisplayAttributes
3+ , module Graphics.Vty.DisplayAttributes
4+ )
5+where
56
67import Graphics.Vty.DisplayAttributes
78import Verify
8−
test/Verify/Graphics/Vty/DisplayRegion.hsdeleted−27
@@ -1,27 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−module Verify.Graphics.Vty.DisplayRegion ( module Verify.Graphics.Vty.DisplayRegion
3− , module Graphics.Vty.DisplayRegion
4− )
5− where
6−
7−import Graphics.Vty.Debug
8−import Graphics.Vty.DisplayRegion
9−
10−import Verify
11−
12−import Data.Word
13−
14−data EmptyWindow = EmptyWindow DebugWindow
15−
16−instance Arbitrary EmptyWindow where
17− arbitrary = return $ EmptyWindow (DebugWindow (0 :: Word) (0 :: Word))
18−
19−instance Show EmptyWindow where
20− show (EmptyWindow _) = "EmptyWindow"
21−
22−instance Arbitrary DebugWindow where
23− arbitrary = do
24− w <- suchThat arbitrary (/= 0)
25− h <- suchThat arbitrary (/= 0)
26− return $ DebugWindow w h
27−
test/Verify/Graphics/Vty/Image.hs+178−51
@@ -1,91 +1,218 @@
1+{-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE NamedFieldPuns #-}
23{-# LANGUAGE DisambiguateRecordFields #-}
3−module Verify.Graphics.Vty.Image ( module Verify.Graphics.Vty.Image
4− , module Graphics.Vty.Image
5− )
6− where
4+module Verify.Graphics.Vty.Image
5+ ( module Verify.Graphics.Vty.Image
6+ , module Graphics.Vty.Image
7+ )
8+where
79
8−import Graphics.Vty.Debug
10+import Verify.Graphics.Vty.Attributes
911import Graphics.Vty.Image
10−import Graphics.Vty.DisplayRegion
12+import Graphics.Vty.Image.Internal
1113
1214import Verify
13−import Verify.Graphics.Vty.Attributes
14−
15−import Data.Word
1615
1716data UnitImage = UnitImage Char Image
1817
1918instance Arbitrary UnitImage where
2019 arbitrary = do
2120 SingleColumnChar c <- arbitrary
22− return $ UnitImage c (char def_attr c)
21+ a <- arbitrary
22+ return $ UnitImage c (char a c)
2323
2424instance Show UnitImage where
2525 show (UnitImage c _) = "UnitImage " ++ show c
2626
27−data DefaultImage = DefaultImage Image ImageConstructLog
27+data DefaultImage = DefaultImage Image
2828
2929instance Show DefaultImage where
30− show (DefaultImage i image_log)
31− = "DefaultImage (" ++ show i ++ ") " ++ show (image_width i, image_height i) ++ " " ++ show image_log
30+ show (DefaultImage i)
31+ = "DefaultImage (" ++ show i ++ ") " ++ show (imageWidth i, imageHeight i)
3232
3333instance Arbitrary DefaultImage where
3434 arbitrary = do
35− i <- return $ char def_attr 'X' -- elements forward_image_ops >>= return . (\op -> op empty_image)
36− return $ DefaultImage i []
37−
38−data SingleRowSingleAttrImage
39− = SingleRowSingleAttrImage
40− { expected_attr :: Attr
41− , expected_columns :: Word
42− , row_image :: Image
35+ i <- return $ char defAttr 'X'
36+ return $ DefaultImage i
37+
38+data SingleRowSingleAttrImage
39+ = SingleRowSingleAttrImage
40+ { expectedAttr :: Attr
41+ , expectedColumns :: Int
42+ , rowImage :: Image
4343 }
4444
4545instance Show SingleRowSingleAttrImage where
46− show (SingleRowSingleAttrImage attr columns image)
46+ show (SingleRowSingleAttrImage attr columns image)
4747 = "SingleRowSingleAttrImage (" ++ show attr ++ ") " ++ show columns ++ " ( " ++ show image ++ " )"
4848
49+newtype WidthResize = WidthResize (Image -> (Image, Int))
50+
51+instance Arbitrary WidthResize where
52+ arbitrary = do
53+ WidthResize f <- arbitrary
54+ w <- choose (1,64)
55+ oneof $ map (return . WidthResize)
56+ [ \i -> (i, imageWidth i)
57+ , \i -> (resizeWidth w $ fst $ f i, w)
58+ , \i -> let i' = fst $ f i in (cropLeft w i', min (imageWidth i') w)
59+ , \i -> let i' = fst $ f i in (cropRight w i', min (imageWidth i') w)
60+ ]
61+
62+newtype HeightResize = HeightResize (Image -> (Image, Int))
63+
64+instance Arbitrary HeightResize where
65+ arbitrary = do
66+ HeightResize f <- arbitrary
67+ h <- choose (1,64)
68+ oneof $ map (return . HeightResize)
69+ [ \i -> (i, imageHeight i)
70+ , \i -> (resizeHeight h $ fst $ f i, h)
71+ , \i -> let i' = fst $ f i in (cropTop h i', min (imageHeight i') h)
72+ , \i -> let i' = fst $ f i in (cropBottom h i', min (imageHeight i') h)
73+ ]
74+
75+newtype ImageResize = ImageResize (Image -> (Image, (Int, Int)))
76+
77+instance Arbitrary ImageResize where
78+ arbitrary = oneof
79+ [ return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
80+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
81+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
82+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
83+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
84+ , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))
85+ , do
86+ ImageResize f <- arbitrary
87+ WidthResize g <- arbitrary
88+ return $! ImageResize $! \i ->
89+ let (i0, (_, outHeight)) = f i
90+ gI = g i0
91+ in (fst gI, (snd gI, outHeight))
92+ , do
93+ ImageResize f <- arbitrary
94+ HeightResize g <- arbitrary
95+ return $! ImageResize $! \i ->
96+ let (i0, (outWidth, _)) = f i
97+ gI = g i0
98+ in (fst gI, (outWidth, snd gI))
99+ ]
100+
101+
49102instance Arbitrary SingleRowSingleAttrImage where
50103 arbitrary = do
51− -- The text must contain at least one character. Otherwise the image simplifies to the
52− -- IdImage which has a height of 0. If this is to represent a single row then the height
53− -- must be 1
54− single_column_row_text <- listOf1 arbitrary
55− attr <- arbitrary
56− return $ SingleRowSingleAttrImage
57− attr
58− ( fromIntegral $ length single_column_row_text )
59− ( horiz_cat $ [ char attr c | SingleColumnChar c <- single_column_row_text ] )
60−
61−data SingleRowTwoAttrImage
62− = SingleRowTwoAttrImage
63− { part_0 :: SingleRowSingleAttrImage
64− , part_1 :: SingleRowSingleAttrImage
65− , join_image :: Image
104+ -- The text must contain at least one character. Otherwise the
105+ -- image simplifies to the IdImage which has a height of 0. If
106+ -- this is to represent a single row then the height must be 1
107+ singleColumnRowText <- Verify.resize 16 (listOf1 arbitrary)
108+ a <- arbitrary
109+ let outImage = horizCat $ [char a c | SingleColumnChar c <- singleColumnRowText]
110+ outWidth = length singleColumnRowText
111+ return $ SingleRowSingleAttrImage a outWidth outImage
112+
113+data SingleRowTwoAttrImage
114+ = SingleRowTwoAttrImage
115+ { part0 :: SingleRowSingleAttrImage
116+ , part1 :: SingleRowSingleAttrImage
117+ , joinImage :: Image
66118 } deriving Show
67119
68120instance Arbitrary SingleRowTwoAttrImage where
69121 arbitrary = do
70122 p0 <- arbitrary
71123 p1 <- arbitrary
72− return $ SingleRowTwoAttrImage p0 p1 (row_image p0 <|> row_image p1)
124+ return $ SingleRowTwoAttrImage p0 p1 (rowImage p0 <|> rowImage p1)
73125
74−data SingleAttrSingleSpanStack = SingleAttrSingleSpanStack
75− { stack_image :: Image
76− , stack_source_images :: [SingleRowSingleAttrImage]
77− , stack_width :: Word
78− , stack_height :: Word
126+data SingleAttrSingleSpanStack = SingleAttrSingleSpanStack
127+ { stackImage :: Image
128+ , stackSourceImages :: [SingleRowSingleAttrImage]
129+ , stackWidth :: Int
130+ , stackHeight :: Int
79131 }
80132 deriving Show
81133
82134instance Arbitrary SingleAttrSingleSpanStack where
83135 arbitrary = do
84− NonEmpty image_list <- arbitrary
85− let image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- image_list ]
86− return $ SingleAttrSingleSpanStack
87− image
88− image_list
89− ( maximum $ map expected_columns image_list )
90− ( toEnum $ length image_list )
91−
136+ imageList <- Verify.resize 16 (listOf1 arbitrary)
137+ return $ mkSingleAttrSingleSpanStack imageList
138+ shrink s = do
139+ imageList <- shrink $ stackSourceImages s
140+ if null imageList
141+ then []
142+ else return $ mkSingleAttrSingleSpanStack imageList
143+
144+mkSingleAttrSingleSpanStack imageList =
145+ let image = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- imageList ]
146+ in SingleAttrSingleSpanStack image imageList (maximum $ map expectedColumns imageList)
147+ (toEnum $ length imageList)
148+
149+instance Arbitrary Image where
150+ arbitrary = oneof
151+ [ return EmptyImage
152+ , do
153+ SingleAttrSingleSpanStack {stackImage} <- Verify.resize 8 arbitrary
154+ ImageResize f <- Verify.resize 2 arbitrary
155+ return $! fst $! f stackImage
156+ , do
157+ SingleAttrSingleSpanStack {stackImage} <- Verify.resize 8 arbitrary
158+ ImageResize f <- Verify.resize 2 arbitrary
159+ return $! fst $! f stackImage
160+ , do
161+ i0 <- arbitrary
162+ i1 <- arbitrary
163+ let i = i0 <|> i1
164+ ImageResize f <- Verify.resize 2 arbitrary
165+ return $! fst $! f i
166+ , do
167+ i0 <- arbitrary
168+ i1 <- arbitrary
169+ let i = i0 <-> i1
170+ ImageResize f <- Verify.resize 2 arbitrary
171+ return $! fst $! f i
172+ ]
173+ {-
174+ shrink i@(HorizJoin {partLeft, partRight}) = do
175+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
176+ !partLeft' <- shrink partLeft
177+ !partRight' <- shrink partRight
178+ [i_alt, partLeft' <|> partRight']
179+ shrink i@(VertJoin {partTop, partBottom}) = do
180+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
181+ !partTop' <- shrink partTop
182+ !partBottom' <- shrink partBottom
183+ [i_alt, partTop' <-> partBottom']
184+ shrink i@(CropRight {croppedImage, outputWidth}) = do
185+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
186+ [i_alt, croppedImage]
187+ shrink i@(CropLeft {croppedImage, leftSkip, outputWidth}) = do
188+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
189+ [i_alt, croppedImage]
190+ shrink i@(CropBottom {croppedImage, outputHeight}) = do
191+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
192+ [i_alt, croppedImage]
193+ shrink i@(CropTop {croppedImage, topSkip, outputHeight}) = do
194+ let !i_alt = backgroundFill (imageWidth i) (imageHeight i)
195+ [i_alt, croppedImage]
196+ shrink i = [emptyImage, backgroundFill (imageWidth i) (imageHeight i)]
197+ -}
198+
199+data CropOperation
200+ = CropFromLeft
201+ | CropFromRight
202+ | CropFromTop
203+ | CropFromBottom
204+ deriving (Eq, Show)
205+
206+instance Arbitrary CropOperation where
207+ arbitrary = oneof $ map return [CropFromLeft, CropFromRight, CropFromTop, CropFromBottom]
208+
209+data Translation = Translation Image (Int, Int) Image
210+ deriving (Eq, Show)
211+
212+instance Arbitrary Translation where
213+ arbitrary = do
214+ i <- arbitrary
215+ x <- arbitrary `suchThat` (> 0)
216+ y <- arbitrary `suchThat` (> 0)
217+ let i' = translate x y i
218+ return $ Translation i (x,y) i'
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+30−28
@@ -1,50 +1,52 @@
11{-# LANGUAGE DisambiguateRecordFields #-}
2−module Verify.Graphics.Vty.Picture ( module Verify.Graphics.Vty.Picture
3− , module Graphics.Vty.Picture
4− )
5− where
2+module Verify.Graphics.Vty.Picture
3+ ( module Verify.Graphics.Vty.Picture
4+ , module Graphics.Vty.Picture
5+ )
6+where
7+
8+import Verify.Graphics.Vty.Prelude
69
710import Graphics.Vty.Picture
811import Graphics.Vty.Debug
912
1013import Verify.Graphics.Vty.Attributes
1114import Verify.Graphics.Vty.Image
12−import Verify.Graphics.Vty.DisplayRegion
1315
1416import Verify
1517
16−data DefaultPic = DefaultPic
17− { default_pic :: Picture
18− , default_win :: DebugWindow
19− , default_construct_log :: ImageConstructLog
18+data DefaultPic = DefaultPic
19+ { defaultPic :: Picture
20+ , defaultWin :: MockWindow
2021 }
2122
2223instance Show DefaultPic where
23− show (DefaultPic pic win image_log)
24− = "DefaultPic\n\t( " ++ show pic ++ ")\n\t" ++ show win ++ "\n\t" ++ show image_log ++ "\n"
24+ show (DefaultPic pic win)
25+ = "DefaultPic\n\t( " ++ show pic ++ ")\n\t" ++ show win ++ "\n"
2526
2627instance Arbitrary DefaultPic where
2728 arbitrary = do
28− DefaultImage image image_construct_events <- arbitrary
29− let win = DebugWindow (image_width image) (image_height image)
30− return $ DefaultPic (pic_for_image image)
31− win
32− image_construct_events
33−
34−data PicWithBGAttr = PicWithBGAttr
35− { with_attr_pic :: Picture
36− , with_attr_win :: DebugWindow
37− , with_attr_construct_log :: ImageConstructLog
38− , with_attr_specified_attr :: Attr
29+ DefaultImage image <- arbitrary
30+ let win = MockWindow (imageWidth image) (imageHeight image)
31+ return $ DefaultPic (picForImage image)
32+ win
33+
34+data PicWithBGAttr = PicWithBGAttr
35+ { withAttrPic :: Picture
36+ , withAttrWin :: MockWindow
37+ , withAttrSpecifiedAttr :: Attr
3938 } deriving ( Show )
4039
4140instance Arbitrary PicWithBGAttr where
4241 arbitrary = do
43− DefaultImage image image_construct_events <- arbitrary
44− let win = DebugWindow (image_width image) (image_height image)
42+ DefaultImage image <- arbitrary
43+ let win = MockWindow (imageWidth image) (imageHeight image)
4544 attr <- arbitrary
46− return $ PicWithBGAttr (pic_for_image image)
47− win
48− image_construct_events
45+ return $ PicWithBGAttr (picForImage image)
46+ win
4947 attr
50−
48+
49+instance Arbitrary Picture where
50+ arbitrary = do
51+ layers <- Verify.resize 20 (listOf1 arbitrary)
52+ return $ picForLayers layers
test/Verify/Graphics/Vty/Prelude.hsnew file+24
@@ -0,0 +1,24 @@
1+module Verify.Graphics.Vty.Prelude
2+ ( module Verify.Graphics.Vty.Prelude
3+ , MockWindow(..)
4+ )
5+where
6+
7+import Graphics.Vty.Debug
8+
9+import Verify
10+
11+data EmptyWindow = EmptyWindow MockWindow
12+
13+instance Arbitrary EmptyWindow where
14+ arbitrary = return $ EmptyWindow (MockWindow (0 :: Int) (0 :: Int))
15+
16+instance Show EmptyWindow where
17+ show (EmptyWindow _) = "EmptyWindow"
18+
19+instance Arbitrary MockWindow where
20+ arbitrary = do
21+ w <- choose (1,1024)
22+ h <- choose (1,1024)
23+ return $ MockWindow w h
24+
test/Verify/Graphics/Vty/Span.hs+25−17
@@ -1,29 +1,37 @@
11{-# LANGUAGE FlexibleInstances #-}
2−module Verify.Graphics.Vty.Span ( module Verify.Graphics.Vty.Span
3− , module Graphics.Vty.Span
4− )
5− where
2+module Verify.Graphics.Vty.Span
3+ ( module Verify.Graphics.Vty.Span
4+ , module Graphics.Vty.Span
5+ )
6+where
67
78import Graphics.Vty.Debug
89import Graphics.Vty.Span
910
1011import Verify.Graphics.Vty.Picture
1112
13+import qualified Data.Vector as Vector
1214import Data.Word
1315
1416import Verify
1517
16−is_attr_span_op :: SpanOp -> Bool
17−is_attr_span_op AttributeChange {} = True
18−is_attr_span_op _ = False
18+isAttrSpanOp :: SpanOp -> Bool
19+isAttrSpanOp TextSpan {} = True
20+isAttrSpanOp _ = False
1921
20−verify_all_spans_have_width i spans w
21− = case all_spans_have_width spans w of
22− True -> succeeded
23− False -> failed { reason = "Not all spans contained operations defining exactly "
24− ++ show w
25− ++ " columns of output -\n"
26− ++ show i
27− ++ "\n->\n"
28− ++ show spans
29− }
22+verifyAllSpansHaveWidth i spans w
23+ = let v = map (\s -> (spanOpsAffectedColumns s /= w, s)) (Vector.toList spans)
24+ in case any ((== True) . fst) v of
25+ False -> succeeded
26+ True -> failed { reason = "Not all spans contained operations defining exactly "
27+ ++ show w
28+ ++ " columns of output - \n"
29+ ++ (concatMap ((++ "\n") . show)) v
30+ }
31+
32+verifyOpsEquality i_ops i_alt_ops =
33+ if i_ops == i_alt_ops
34+ then succeeded
35+ else failed { reason = "ops for alternate image " ++ show i_alt_ops
36+ ++ " are not the same as " ++ show i_ops
37+ }
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.hsnew file+92
@@ -0,0 +1,92 @@
1+{-# LANGUAGE DisambiguateRecordFields #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+module VerifyCropSpanGeneration where
4+
5+import Verify.Graphics.Vty.Prelude
6+
7+import Verify.Graphics.Vty.Image
8+import Verify.Graphics.Vty.Picture
9+import Verify.Graphics.Vty.Span
10+
11+import Graphics.Vty.Debug
12+import Graphics.Vty.PictureToSpans
13+
14+import Verify
15+
16+import qualified Data.Vector as Vector
17+
18+cropOpDisplayOps :: (Int -> Image -> Image) ->
19+ Int -> Image -> (DisplayOps, Image)
20+cropOpDisplayOps cropOp v i =
21+ let iOut = cropOp v i
22+ p = picForImage iOut
23+ w = MockWindow (imageWidth iOut) (imageHeight iOut)
24+ in (displayOpsForPic p (regionForWindow w), iOut)
25+
26+widthCropOutputColumns :: (Int -> Image -> Image) ->
27+ SingleAttrSingleSpanStack ->
28+ NonNegative Int ->
29+ Property
30+widthCropOutputColumns cropOp s (NonNegative w) = stackWidth s > w ==>
31+ let (ops, iOut) = cropOpDisplayOps cropOp w (stackImage s)
32+ in verifyAllSpansHaveWidth iOut ops w
33+
34+heightCropOutputColumns :: (Int -> Image -> Image) ->
35+ SingleAttrSingleSpanStack ->
36+ NonNegative Int ->
37+ Property
38+heightCropOutputColumns cropOp s (NonNegative h) = stackHeight s > h ==>
39+ let (ops, _) = cropOpDisplayOps cropOp h (stackImage s)
40+ in displayOpsRows ops == h
41+
42+cropRightOutputColumns :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
43+cropRightOutputColumns = widthCropOutputColumns cropRight
44+
45+cropLeftOutputColumns :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
46+cropLeftOutputColumns = widthCropOutputColumns cropLeft
47+
48+cropTopOutputRows :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
49+cropTopOutputRows = heightCropOutputColumns cropTop
50+
51+cropBottomOutputRows :: SingleAttrSingleSpanStack -> NonNegative Int -> Property
52+cropBottomOutputRows = heightCropOutputColumns cropBottom
53+
54+-- TODO: known benign failure.
55+cropRightAndLeftRejoinedEquivalence :: SingleAttrSingleSpanStack -> Property
56+cropRightAndLeftRejoinedEquivalence stack = imageWidth (stackImage stack) `mod` 2 == 0 ==>
57+ let i = stackImage stack
58+ -- the right part is made by cropping the image from the left.
59+ iR = cropLeft (imageWidth i `div` 2) i
60+ -- the left part is made by cropping the image from the right
61+ iL = cropRight (imageWidth i `div` 2) i
62+ iAlt = iL <|> iR
63+ iOps = displayOpsForImage i
64+ iAltOps = displayOpsForImage iAlt
65+ in verifyOpsEquality iOps iAltOps
66+
67+cropTopAndBottomRejoinedEquivalence :: SingleAttrSingleSpanStack -> Property
68+cropTopAndBottomRejoinedEquivalence stack = imageHeight (stackImage stack) `mod` 2 == 0 ==>
69+ let i = stackImage stack
70+ -- the top part is made by cropping the image from the bottom.
71+ iT = cropBottom (imageHeight i `div` 2) i
72+ -- the bottom part is made by cropping the image from the top.
73+ iB = cropTop (imageHeight i `div` 2) i
74+ iAlt = iT <-> iB
75+ in displayOpsForImage i == displayOpsForImage iAlt
76+
77+tests :: IO [Test]
78+tests = return
79+ [ verify "cropping from the bottom produces display operations covering the expected rows"
80+ cropBottomOutputRows
81+ , verify "cropping from the top produces display operations covering the expected rows"
82+ cropTopOutputRows
83+ , verify "cropping from the left produces display operations covering the expected columns"
84+ cropLeftOutputColumns
85+ , verify "cropping from the right produces display operations covering the expected columns"
86+ cropRightOutputColumns
87+ -- TODO: known benign failure.
88+ -- , verify "the output of a stack is the same as that stack cropped left & right and joined together"
89+ -- cropRightAndLeftRejoinedEquivalence
90+ , verify "the output of a stack is the same as that stack cropped top & bottom and joined together"
91+ cropTopAndBottomRejoinedEquivalence
92+ ]
test/verify_display_attributes.hs → test/VerifyDisplayAttributes.hsrenamed+3−3
@@ -1,9 +1,9 @@
1−module Main where
1+module VerifyDisplayAttributes where
22
33import Verify.Graphics.Vty.DisplayAttributes
44import Verify.Graphics.Vty.Attributes
55
66import Verify
77
8−main = run_test $ do
9− return ()
8+tests :: IO [Test]
9+tests = return []
test/VerifyEmptyImageProps.hsnew file+14
@@ -0,0 +1,14 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+module VerifyEmptyImageProps where
3+
4+import Verify
5+
6+-- should be exported by Graphics.Vty.Picture
7+import Graphics.Vty.Image ( Image, emptyImage )
8+
9+tests :: IO [Test]
10+tests = do
11+ -- should provide an image type.
12+ let _ :: Image = emptyImage
13+ return []
14+
test/VerifyEvalTerminfoCaps.hsnew file+92
@@ -0,0 +1,92 @@
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+module VerifyEvalTerminfoCaps where
5+
6+import Blaze.ByteString.Builder.Internal.Write (runWrite, getBound)
7+import Data.Terminfo.Eval
8+import Data.Terminfo.Parse
9+import Control.DeepSeq
10+
11+import qualified System.Console.Terminfo as Terminfo
12+
13+import Verify
14+import Verify.Graphics.Vty.Output
15+
16+import Control.Applicative ( (<$>) )
17+import Control.Exception ( try, SomeException(..) )
18+
19+import Control.Monad ( mapM_, forM, forM_ )
20+
21+import Data.Maybe ( fromJust )
22+import Data.Word
23+
24+import Foreign.Marshal.Alloc (mallocBytes)
25+import Foreign.Ptr (Ptr, minusPtr)
26+import Numeric
27+
28+-- If a terminal defines one of the caps then it's expected to be
29+-- parsable.
30+capsOfInterest =
31+ [ "cup"
32+ , "sc"
33+ , "rc"
34+ , "setf"
35+ , "setb"
36+ , "setaf"
37+ , "setab"
38+ , "op"
39+ , "cnorm"
40+ , "civis"
41+ , "smcup"
42+ , "rmcup"
43+ , "clear"
44+ , "hpa"
45+ , "vpa"
46+ , "sgr"
47+ , "sgr0"
48+ ]
49+
50+fromCapname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
51+
52+tests :: IO [Test]
53+tests = do
54+ -- 1 MB should be big enough for any termcaps ;-)
55+ evalBuffer :: Ptr Word8 <- mallocBytes (1024 * 1024)
56+ fmap concat $ forM terminalsOfInterest $ \termName -> do
57+ putStrLn $ "adding tests for terminal: " ++ termName
58+ mti <- try $ Terminfo.setupTerm termName
59+ case mti of
60+ Left (_e :: SomeException)
61+ -> return []
62+ Right ti -> do
63+ fmap concat $ forM capsOfInterest $ \capName -> do
64+ case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
65+ Just capDef -> do
66+ putStrLn $ "\tadding test for cap: " ++ capName
67+ let testName = termName ++ "(" ++ capName ++ ")"
68+ case parseCapExpression capDef of
69+ Left error -> return [verify testName (failed {reason = "parse error " ++ show error})]
70+ Right !cap_expr -> return [verify testName (verifyEvalCap evalBuffer cap_expr)]
71+ Nothing -> do
72+ return []
73+
74+{-# NOINLINE verifyEvalCap #-}
75+verifyEvalCap :: Ptr Word8 -> CapExpression -> Int -> Property
76+verifyEvalCap evalBuffer expr !junkInt = do
77+ forAll (vector 9) $ \inputValues ->
78+ let write = writeCapExpr expr inputValues
79+ !byteCount = getBound write
80+ in liftIOResult $ do
81+ let startPtr :: Ptr Word8 = evalBuffer
82+ forM_ [0..100] $ \i -> runWrite write startPtr
83+ endPtr <- runWrite write startPtr
84+ case endPtr `minusPtr` startPtr of
85+ count | count < 0 ->
86+ return $ failed { reason = "End pointer before start pointer." }
87+ | toEnum count > byteCount ->
88+ return $ failed { reason = "End pointer past end of buffer by "
89+ ++ show (toEnum count - byteCount)
90+ }
91+ | otherwise ->
92+ return succeeded
test/VerifyImageOps.hsnew file+174
@@ -0,0 +1,174 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+module VerifyImageOps where
3+
4+import Graphics.Vty.Attributes
5+import Graphics.Vty.Image.Internal
6+import Verify.Graphics.Vty.Image
7+
8+import Verify
9+
10+import Control.DeepSeq
11+
12+twoSwHorizConcat :: SingleColumnChar -> SingleColumnChar -> Bool
13+twoSwHorizConcat (SingleColumnChar c1) (SingleColumnChar c2) =
14+ imageWidth (char defAttr c1 <|> char defAttr c2) == 2
15+
16+manySwHorizConcat :: [SingleColumnChar] -> Bool
17+manySwHorizConcat cs =
18+ let chars = [ char | SingleColumnChar char <- cs ]
19+ l = fromIntegral $ length cs
20+ in imageWidth ( horizCat $ map (char defAttr) chars ) == l
21+
22+twoSwVertConcat :: SingleColumnChar -> SingleColumnChar -> Bool
23+twoSwVertConcat (SingleColumnChar c1) (SingleColumnChar c2) =
24+ imageHeight (char defAttr c1 <-> char defAttr c2) == 2
25+
26+horizConcatSwAssoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool
27+horizConcatSwAssoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) =
28+ (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2
29+ ==
30+ char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2)
31+
32+twoDwHorizConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool
33+twoDwHorizConcat (DoubleColumnChar c1) (DoubleColumnChar c2) =
34+ imageWidth (char defAttr c1 <|> char defAttr c2) == 4
35+
36+manyDwHorizConcat :: [DoubleColumnChar] -> Bool
37+manyDwHorizConcat cs =
38+ let chars = [ char | DoubleColumnChar char <- cs ]
39+ l = fromIntegral $ length cs
40+ in imageWidth ( horizCat $ map (char defAttr) chars ) == l * 2
41+
42+twoDwVertConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool
43+twoDwVertConcat (DoubleColumnChar c1) (DoubleColumnChar c2) =
44+ imageHeight (char defAttr c1 <-> char defAttr c2) == 2
45+
46+horizConcatDwAssoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool
47+horizConcatDwAssoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) =
48+ (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2
49+ ==
50+ char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2)
51+
52+vertContatSingleRow :: NonEmptyList SingleRowSingleAttrImage -> Bool
53+vertContatSingleRow (NonEmpty stack) =
54+ let expectedHeight :: Int = length stack
55+ stackImage = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack ]
56+ in imageHeight stackImage == expectedHeight
57+
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
69+ -> NonEmptyList SingleRowSingleAttrImage
70+ -> Bool
71+disjointHeightHorizJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
72+ let stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
73+ stackImage1 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
74+ image = stackImage0 <|> stackImage1
75+ expectedHeight = imageHeight image
76+ in case image of
77+ HorizJoin {} -> ( expectedHeight == (imageHeight $ partLeft image) )
78+ &&
79+ ( expectedHeight == (imageHeight $ partRight image) )
80+ _ -> True
81+
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
95+ -> NonEmptyList SingleRowSingleAttrImage
96+ -> Bool
97+disjointWidthVertJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
98+ let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)
99+ stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
100+ stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
101+ stack0Image = vertCat stack0Images
102+ stack1Image = vertCat stack1Images
103+ image = stack0Image <-> stack1Image
104+ in case image of
105+ VertJoin {} -> ( expectedWidth == (imageWidth $ partTop image) )
106+ &&
107+ ( expectedWidth == (imageWidth $ partBottom image) )
108+ _ -> True
109+
110+translationIsLinearOnOutSize :: Translation -> Bool
111+translationIsLinearOnOutSize (Translation i (x,y) i') =
112+ imageWidth i' == imageWidth i + x && imageHeight i' == imageHeight i + y
113+
114+paddingIsLinearOnOutSize :: Image -> Gen Bool
115+paddingIsLinearOnOutSize i = do
116+ l <- offset
117+ t <- offset
118+ r <- offset
119+ b <- offset
120+ let i' = pad l t r b i
121+ return $ imageWidth i' == imageWidth i + l + r && imageHeight i' == imageHeight i + t + b
122+ where offset = choose (1,1024)
123+
124+cropLeftLimitsWidth :: Image -> Int -> Property
125+cropLeftLimitsWidth i v = v >= 0 ==>
126+ v >= imageWidth (cropLeft v i)
127+
128+cropRightLimitsWidth :: Image -> Int -> Property
129+cropRightLimitsWidth i v = v >= 0 ==>
130+ v >= imageWidth (cropRight v i)
131+
132+cropTopLimitsHeight :: Image -> Int -> Property
133+cropTopLimitsHeight i v = v >= 0 ==>
134+ v >= imageHeight (cropTop v i)
135+
136+cropBottomLimitsHeight :: Image -> Int -> Property
137+cropBottomLimitsHeight i v = v >= 0 ==>
138+ v >= imageHeight (cropBottom v i)
139+
140+-- ridiculous tests just to satisfy my desire for nice code coverage :-P
141+canShowImage :: Image -> Bool
142+canShowImage i = length (show i) > 0
143+
144+canRnfImage :: Image -> Bool
145+canRnfImage i = rnf i == ()
146+
147+canPpImage :: Image -> Bool
148+canPpImage i = length (ppImageStructure i) > 0
149+
150+tests :: IO [Test]
151+tests = return
152+ [ verify "twoSwHorizConcat" twoSwHorizConcat
153+ , verify "manySwHorizConcat" manySwHorizConcat
154+ , verify "twoSwVertConcat" twoSwVertConcat
155+ , verify "horizConcatSwAssoc" horizConcatSwAssoc
156+ , verify "manyDwHorizConcat" manyDwHorizConcat
157+ , verify "twoDwHorizConcat" twoDwHorizConcat
158+ , verify "twoDwVertConcat" twoDwVertConcat
159+ , verify "horizConcatDwAssoc" horizConcatDwAssoc
160+ , verify "single row vert concats to correct height" vertContatSingleRow
161+ , verify "disjointHeightHorizJoin" disjointHeightHorizJoin
162+ , verify "disjointHeightHorizJoin BG fill" disjointHeightHorizJoinBgFill
163+ , verify "disjointWidthVertJoin" disjointWidthVertJoin
164+ , verify "disjointWidthVertJoin BG fill" disjointWidthVertJoinBgFill
165+ , verify "translation effects output dimensions linearly" translationIsLinearOnOutSize
166+ , verify "padding effects output dimensions linearly" paddingIsLinearOnOutSize
167+ , verify "crop left limits width" cropLeftLimitsWidth
168+ , verify "crop right limits width" cropRightLimitsWidth
169+ , verify "crop top limits height" cropTopLimitsHeight
170+ , verify "crop bottom limits height" cropBottomLimitsHeight
171+ , verify "can show image" canShowImage
172+ , verify "can rnf image" canRnfImage
173+ , verify "can pp image" canPpImage
174+ ]
test/VerifyImageTrans.hsnew file+34
@@ -0,0 +1,34 @@
1+{-# LANGUAGE NamedFieldPuns #-}
2+module VerifyImageTrans where
3+
4+import Verify.Graphics.Vty.Image
5+
6+import Graphics.Vty.Image.Internal
7+
8+import Verify
9+
10+import Data.Word
11+
12+isHorizTextOfColumns :: Image -> Int -> Bool
13+isHorizTextOfColumns (HorizText { outputWidth = inW }) expectedW = inW == expectedW
14+isHorizTextOfColumns (BGFill { outputWidth = inW }) expectedW = inW == expectedW
15+isHorizTextOfColumns _image _expectedW = False
16+
17+verifyHorizContatWoAttrChangeSimplifies :: SingleRowSingleAttrImage -> Bool
18+verifyHorizContatWoAttrChangeSimplifies (SingleRowSingleAttrImage _attr charCount image) =
19+ isHorizTextOfColumns image charCount
20+
21+verifyHorizContatWAttrChangeSimplifies :: SingleRowTwoAttrImage -> Bool
22+verifyHorizContatWAttrChangeSimplifies ( SingleRowTwoAttrImage (SingleRowSingleAttrImage attr0 charCount0 _image0)
23+ (SingleRowSingleAttrImage attr1 charCount1 _image1)
24+ i
25+ )
26+ | charCount0 == 0 || charCount1 == 0 || attr0 == attr1 = isHorizTextOfColumns i (charCount0 + charCount1)
27+ | otherwise = False == isHorizTextOfColumns i (charCount0 + charCount1)
28+
29+tests :: IO [Test]
30+tests = return
31+ [ verify "verifyHorizContatWoAttrChangeSimplifies" verifyHorizContatWoAttrChangeSimplifies
32+ , verify "verifyHorizContatWAttrChangeSimplifies" verifyHorizContatWAttrChangeSimplifies
33+ ]
34+
test/VerifyInline.hsnew file+31
@@ -0,0 +1,31 @@
1+module VerifyInline where
2+
3+import Graphics.Vty.Inline
4+import Graphics.Vty.Output
5+import Graphics.Vty.Output.TerminfoBased as TerminfoBased
6+
7+import Verify.Graphics.Vty.Output
8+
9+import Verify
10+
11+import Distribution.TestSuite
12+
13+import System.IO
14+
15+tests :: IO [Test]
16+tests = concat <$> forM terminalsOfInterest (\termName -> return $
17+ [ Test $ TestInstance
18+ { name = "verify vty inline"
19+ , run = do
20+ {- disabled because I cannot get useful output out of cabal why this fails.
21+ nullOut <- openFile "/dev/null" WriteMode
22+ t <- TerminfoBased.reserveTerminal termName nullOut
23+ putAttrChange t $ default_all
24+ releaseTerminal t
25+ -}
26+ return $ Finished Pass
27+ , tags = []
28+ , options = []
29+ , setOption = \_ _ -> Left "no options supported"
30+ }
31+ ])
test/VerifyLayersSpanGeneration.hsnew file+130
@@ -0,0 +1,130 @@
1+{-# LANGUAGE DisambiguateRecordFields #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+module VerifyLayersSpanGeneration where
4+
5+import Verify.Graphics.Vty.Prelude
6+
7+import Verify.Graphics.Vty.Attributes
8+import Verify.Graphics.Vty.Image
9+import Verify.Graphics.Vty.Picture
10+import Verify.Graphics.Vty.Span
11+
12+import Graphics.Vty.Debug
13+import Graphics.Vty.PictureToSpans
14+
15+import Verify
16+
17+import qualified Data.Vector as Vector
18+
19+largerHorizSpanOcclusion :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
20+largerHorizSpanOcclusion row0 row1 =
21+ let i0 = rowImage row0
22+ i1 = rowImage row1
23+ (iLarger, iSmaller) = if imageWidth i0 > imageWidth i1 then (i0, i1) else (i1, i0)
24+ expectedOps = displayOpsForImage iLarger
25+ p = picForLayers [iLarger, iSmaller]
26+ ops = displayOpsForPic p (imageWidth iLarger,imageHeight iLarger)
27+ in verifyOpsEquality expectedOps ops
28+
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
31+-- background fill where the first row would be.
32+vertStackLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
33+vertStackLayerEquivalence0 row0 row1 =
34+ let i0 = rowImage row0
35+ i1 = rowImage row1
36+ i = i0 <-> i1
37+ p = picForImage i
38+ iLower = backgroundFill (imageWidth i0) 1 <-> i1
39+ pLayered = picForLayers [i0, iLower]
40+ expectedOps = displayOpsForImage i
41+ opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
42+ in verifyOpsEquality expectedOps opsLayered
43+
44+vertStackLayerEquivalence1 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
45+vertStackLayerEquivalence1 row0 row1 =
46+ let i0 = rowImage row0
47+ i1 = rowImage row1
48+ i = i0 <-> i1
49+ p = picForImage i
50+ iLower = i0 <-> backgroundFill (imageWidth i1) 1
51+ iUpper = backgroundFill (imageWidth i0) 1 <-> i1
52+ pLayered = picForLayers [iUpper, iLower]
53+ expectedOps = displayOpsForImage i
54+ opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
55+ in verifyOpsEquality expectedOps opsLayered
56+
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
59+-- background fill where the first row would be.
60+horizJoinLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
61+horizJoinLayerEquivalence0 row0 row1 =
62+ let i0 = rowImage row0
63+ i1 = rowImage row1
64+ i = i0 <|> i1
65+ p = picForImage i
66+ iLower = backgroundFill (imageWidth i0) 1 <|> i1
67+ pLayered = picForLayers [i0, iLower]
68+ expectedOps = displayOpsForImage i
69+ opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
70+ in verifyOpsEquality expectedOps opsLayered
71+
72+horizJoinLayerEquivalence1 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
73+horizJoinLayerEquivalence1 row0 row1 =
74+ let i0 = rowImage row0
75+ i1 = rowImage row1
76+ i = i0 <|> i1
77+ p = picForImage i
78+ iLower = i0 <|> backgroundFill (imageWidth i1) 1
79+ iUpper = backgroundFill (imageWidth i0) 1 <|> i1
80+ pLayered = picForLayers [iUpper, iLower]
81+ expectedOps = displayOpsForImage i
82+ opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
83+ in verifyOpsEquality expectedOps opsLayered
84+
85+horizJoinAlternate0 :: Result
86+horizJoinAlternate0 =
87+ let size = 4
88+ str0 = replicate size 'a'
89+ str1 = replicate size 'b'
90+ i0 = string defAttr str0
91+ i1 = string defAttr str1
92+ i = horizCat $ zipWith horizJoin (replicate size i0) (replicate size i1)
93+ layer0 = horizCat $ replicate size $ i0 <|> backgroundFill size 1
94+ layer1 = horizCat $ replicate size $ backgroundFill size 1 <|> i1
95+ expectedOps = displayOpsForImage i
96+ opsLayered = displayOpsForPic (picForLayers [layer0, layer1])
97+ (imageWidth i,imageHeight i)
98+ in verifyOpsEquality expectedOps opsLayered
99+
100+horizJoinAlternate1 :: Result
101+horizJoinAlternate1 =
102+ let size = 4
103+ str0 = replicate size 'a'
104+ str1 = replicate size 'b'
105+ i0 = string defAttr str0
106+ i1 = string defAttr str1
107+ i = horizCat $ zipWith horizJoin (replicate size i0) (replicate size i1)
108+ layers = [l | b <- take 4 [0,size*2..], let l = backgroundFill b 1 <|> i0 <|> i1]
109+ expectedOps = displayOpsForImage i
110+ opsLayered = displayOpsForPic (picForLayers layers)
111+ (imageWidth i,imageHeight i)
112+ in verifyOpsEquality expectedOps opsLayered
113+
114+tests :: IO [Test]
115+tests = return
116+ [ verify "a larger horiz span occludes a smaller span on a lower layer"
117+ largerHorizSpanOcclusion
118+ , verify "two rows stack vertical equiv to first image layered on top of second with padding (0)"
119+ vertStackLayerEquivalence0
120+ , verify "two rows stack vertical equiv to first image layered on top of second with padding (1)"
121+ vertStackLayerEquivalence1
122+ -- , verify "two rows horiz joined equiv to first image layered on top of second with padding (0)"
123+ -- horizJoinLayerEquivalence0
124+ -- , verify "two rows horiz joined equiv to first image layered on top of second with padding (1)"
125+ -- horizJoinLayerEquivalence1
126+ -- , verify "alternating images using joins is the same as alternating images using layers (0)"
127+ -- horizJoinAlternate0
128+ -- , verify "alternating images using joins is the same as alternating images using layers (1)"
129+ -- horizJoinAlternate1
130+ ]
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.hsnew file+101
@@ -0,0 +1,101 @@
1+{-# LANGUAGE NamedFieldPuns #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+module VerifyParseTerminfoCaps where
4+
5+import Prelude hiding ( catch )
6+
7+import qualified System.Console.Terminfo as Terminfo
8+
9+import Verify.Data.Terminfo.Parse
10+import Verify.Graphics.Vty.Output
11+import Verify
12+
13+import Data.Maybe ( catMaybes, fromJust )
14+import Data.Word
15+
16+import Numeric
17+
18+-- If a terminal defines one of the caps then it's expected to be parsable.
19+-- TODO: reduce duplication with terminfo terminal implementation.
20+capsOfInterest =
21+ [ "cup"
22+ , "sc"
23+ , "rc"
24+ , "setf"
25+ , "setb"
26+ , "setaf"
27+ , "setab"
28+ , "op"
29+ , "cnorm"
30+ , "civis"
31+ , "smcup"
32+ , "rmcup"
33+ , "clear"
34+ , "hpa"
35+ , "vpa"
36+ , "sgr"
37+ , "sgr0"
38+ ]
39+
40+fromCapname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
41+
42+tests :: IO [Test]
43+tests = do
44+ parseTests <- concat <$> forM terminalsOfInterest (\termName ->
45+ liftIO (try $ Terminfo.setupTerm termName)
46+ >>= either (\(_e :: SomeException) -> return [])
47+ (\ti -> concat <$> forM capsOfInterest (\capName -> do
48+ let caseName = "\tparsing cap: " ++ capName
49+ liftIO $ putStrLn caseName
50+ return $ case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
51+ Just capDef -> [verify (caseName ++ " -> " ++ show capDef)
52+ (verifyParseCap capDef $ const succeeded)]
53+ Nothing -> []
54+ )
55+ )
56+ )
57+ return $ [ verify "parse_nonParamaterizedCaps" nonParamaterizedCaps
58+ , verify "parse cap string with literal %" literalPercentCaps
59+ , verify "parse cap string with %i op" incFirstTwoCaps
60+ , verify "parse cap string with %pN op" pushParamCaps
61+ ] ++ parseTests
62+
63+verifyParseCap capString onParse =
64+ case parseCapExpression capString of
65+ Left error -> failed { reason = "parse error " ++ show error }
66+ Right e -> onParse e
67+
68+nonParamaterizedCaps (NonParamCapString cap) = do
69+ verifyParseCap cap $ \e ->
70+ let expectedBytes = map (toEnum . fromEnum) cap
71+ outBytes = bytesForRange e 0 (length cap)
72+ in verifyBytesEqual outBytes expectedBytes
73+
74+literalPercentCaps (LiteralPercentCap capString expectedBytes) = do
75+ verifyParseCap capString $ \e -> verifyBytesEqual (collectBytes e) expectedBytes
76+
77+incFirstTwoCaps (IncFirstTwoCap capString expectedBytes) = do
78+ verifyParseCap capString $ \e -> verifyBytesEqual (collectBytes e) expectedBytes
79+
80+pushParamCaps (PushParamCap capString expectedParamCount expectedBytes) = do
81+ verifyParseCap capString $ \e ->
82+ let outBytes = collectBytes e
83+ outParamCount = paramCount e
84+ in if outParamCount == expectedParamCount
85+ then verifyBytesEqual outBytes expectedBytes
86+ else failed { reason = "out param count /= expected param count" }
87+
88+decPrintParamCaps (DecPrintCap capString expectedParamCount expectedBytes) = do
89+ verifyParseCap capString $ \e ->
90+ let outBytes = collectBytes e
91+ outParamCount = paramCount e
92+ in if outParamCount == expectedParamCount
93+ then verifyBytesEqual outBytes expectedBytes
94+ else failed { reason = "out param count /= expected param count" }
95+
96+printCap ti capName = do
97+ putStrLn $ capName ++ ": " ++ show (fromCapname ti capName)
98+
99+printExpression ti capName = do
100+ let parseResult = parseCapExpression $ fromCapname ti capName
101+ putStrLn $ capName ++ ": " ++ show parseResult
test/VerifySimpleSpanGeneration.hsnew file+188
@@ -0,0 +1,188 @@
1+{-# LANGUAGE DisambiguateRecordFields #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+module VerifySimpleSpanGeneration where
4+
5+import Verify.Graphics.Vty.Prelude
6+
7+import Verify.Graphics.Vty.Image
8+import Verify.Graphics.Vty.Picture
9+import Verify.Graphics.Vty.Span
10+
11+import Graphics.Vty.Debug
12+import Graphics.Vty.PictureToSpans
13+
14+import Verify
15+
16+import qualified Data.Vector as Vector
17+
18+unitImageAndZeroWindow0 :: UnitImage -> EmptyWindow -> Bool
19+unitImageAndZeroWindow0 (UnitImage _ i) (EmptyWindow w) =
20+ let p = picForImage i
21+ ops = displayOpsForPic p (regionForWindow w)
22+ in displayOpsColumns ops == 0 && displayOpsRows ops == 0
23+
24+unitImageAndZeroWindow1 :: UnitImage -> EmptyWindow -> Bool
25+unitImageAndZeroWindow1 (UnitImage _ i) (EmptyWindow w) =
26+ let p = picForImage i
27+ ops = displayOpsForPic p (regionForWindow w)
28+ in ( spanOpsAffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )
29+
30+horizSpanImageAndZeroWindow0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
31+horizSpanImageAndZeroWindow0 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) =
32+ let p = picForImage i
33+ ops = displayOpsForPic p (regionForWindow w)
34+ in displayOpsColumns ops == 0 && displayOpsRows ops == 0
35+
36+horizSpanImageAndZeroWindow1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
37+horizSpanImageAndZeroWindow1 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) =
38+ let p = picForImage i
39+ ops = displayOpsForPic p (regionForWindow w)
40+ in ( spanOpsAffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )
41+
42+horizSpanImageAndEqualWindow0 :: SingleRowSingleAttrImage -> Result
43+horizSpanImageAndEqualWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
44+ let p = picForImage i
45+ w = MockWindow c 1
46+ ops = displayOpsForPic p (regionForWindow w)
47+ in verifyAllSpansHaveWidth i ops c
48+
49+horizSpanImageAndEqualWindow1 :: SingleRowSingleAttrImage -> Bool
50+horizSpanImageAndEqualWindow1 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
51+ let p = picForImage i
52+ w = MockWindow c 1
53+ ops = displayOpsForPic p (regionForWindow w)
54+ in spanOpsAffectedRows ops == 1
55+
56+horizSpanImageAndLesserWindow0 :: SingleRowSingleAttrImage -> Result
57+horizSpanImageAndLesserWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
58+ let p = picForImage i
59+ lesserWidth = c `div` 2
60+ w = MockWindow lesserWidth 1
61+ ops = displayOpsForPic p (regionForWindow w)
62+ in verifyAllSpansHaveWidth i ops lesserWidth
63+
64+singleAttrSingleSpanStackCropped0 :: SingleAttrSingleSpanStack -> Result
65+singleAttrSingleSpanStackCropped0 stack =
66+ let p = picForImage (stackImage stack)
67+ w = MockWindow (stackWidth stack `div` 2) (stackHeight stack)
68+ ops = displayOpsForPic p (regionForWindow w)
69+ in verifyAllSpansHaveWidth (stackImage stack) ops (stackWidth stack `div` 2)
70+
71+singleAttrSingleSpanStackCropped1 :: SingleAttrSingleSpanStack -> Bool
72+singleAttrSingleSpanStackCropped1 stack =
73+ let p = picForImage (stackImage stack)
74+ expectedRowCount = stackHeight stack `div` 2
75+ w = MockWindow (stackWidth stack) expectedRowCount
76+ ops = displayOpsForPic p (regionForWindow w)
77+ actualRowCount = spanOpsAffectedRows ops
78+ in expectedRowCount == actualRowCount
79+
80+singleAttrSingleSpanStackCropped2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
81+singleAttrSingleSpanStackCropped2 stack0 stack1 =
82+ let p = picForImage (stackImage stack0 <|> stackImage stack1)
83+ w = MockWindow (stackWidth stack0) (imageHeight (picImage p))
84+ ops = displayOpsForPic p (regionForWindow w)
85+ in verifyAllSpansHaveWidth (picImage p) ops (stackWidth stack0)
86+
87+singleAttrSingleSpanStackCropped3 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
88+singleAttrSingleSpanStackCropped3 stack0 stack1 =
89+ let p = picForImage (stackImage stack0 <|> stackImage stack1)
90+ w = MockWindow (imageWidth (picImage p)) expectedRowCount
91+ ops = displayOpsForPic p (regionForWindow w)
92+ expectedRowCount = imageHeight (picImage p) `div` 2
93+ actualRowCount = spanOpsAffectedRows ops
94+ in expectedRowCount == actualRowCount
95+
96+singleAttrSingleSpanStackCropped4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
97+singleAttrSingleSpanStackCropped4 stack0 stack1 =
98+ let p = picForImage (stackImage stack0 <-> stackImage stack1)
99+ w = MockWindow expectedWidth (imageHeight (picImage p))
100+ ops = displayOpsForPic p (regionForWindow w)
101+ expectedWidth = imageWidth (picImage p) `div` 2
102+ in verifyAllSpansHaveWidth (picImage p) ops expectedWidth
103+
104+singleAttrSingleSpanStackCropped5 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
105+singleAttrSingleSpanStackCropped5 stack0 stack1 =
106+ let p = picForImage (stackImage stack0 <-> stackImage stack1)
107+ w = MockWindow (imageWidth (picImage p)) (stackHeight stack0)
108+ ops = displayOpsForPic p (regionForWindow w)
109+ expectedRowCount = stackHeight stack0
110+ actualRowCount = spanOpsAffectedRows ops
111+ in expectedRowCount == actualRowCount
112+
113+horizSpanImageAndGreaterWindow0 :: SingleRowSingleAttrImage -> Result
114+horizSpanImageAndGreaterWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
115+ let p = picForImage i
116+ -- SingleRowSingleAttrImage always has width >= 1
117+ greaterWidth = c * 2
118+ w = MockWindow greaterWidth 1
119+ ops = displayOpsForPic p (regionForWindow w)
120+ in verifyAllSpansHaveWidth i ops greaterWidth
121+
122+arbImageIsCropped :: DefaultImage -> MockWindow -> Bool
123+arbImageIsCropped (DefaultImage image) win@(MockWindow w h) =
124+ let pic = picForImage image
125+ ops = displayOpsForPic pic (regionForWindow win)
126+ in ( spanOpsAffectedRows ops == h ) && ( allSpansHaveWidth ops w )
127+
128+spanOpsActuallyFillRows :: DefaultPic -> Bool
129+spanOpsActuallyFillRows (DefaultPic pic win) =
130+ let ops = displayOpsForPic pic (regionForWindow win)
131+ expectedRowCount = regionHeight (regionForWindow win)
132+ actualRowCount = spanOpsAffectedRows ops
133+ in expectedRowCount == actualRowCount
134+
135+spanOpsActuallyFillColumns :: DefaultPic -> Bool
136+spanOpsActuallyFillColumns (DefaultPic pic win) =
137+ let ops = displayOpsForPic pic (regionForWindow win)
138+ expectedColumnCount = regionWidth (regionForWindow win)
139+ in allSpansHaveWidth ops expectedColumnCount
140+
141+firstSpanOpSetsAttr :: DefaultPic -> Bool
142+firstSpanOpSetsAttr DefaultPic { defaultPic = pic, defaultWin = win } =
143+ let ops = displayOpsForPic pic (regionForWindow win)
144+ in all ( isAttrSpanOp . Vector.head ) ( Vector.toList ops )
145+
146+singleAttrSingleSpanStackOpCoverage :: SingleAttrSingleSpanStack -> Result
147+singleAttrSingleSpanStackOpCoverage stack =
148+ let p = picForImage (stackImage stack)
149+ w = MockWindow (stackWidth stack) (stackHeight stack)
150+ ops = displayOpsForPic p (regionForWindow w)
151+ in verifyAllSpansHaveWidth (stackImage stack) ops (stackWidth stack)
152+
153+imageCoverageMatchesBounds :: Image -> Result
154+imageCoverageMatchesBounds i =
155+ let p = picForImage i
156+ r = (imageWidth i,imageHeight i)
157+ ops = displayOpsForPic p r
158+ in verifyAllSpansHaveWidth i ops (imageWidth i)
159+
160+tests :: IO [Test]
161+tests = return
162+ [ verify "unit image is cropped when window size == (0,0) [0]" unitImageAndZeroWindow0
163+ , verify "unit image is cropped when window size == (0,0) [1]" unitImageAndZeroWindow1
164+ , verify "horiz span image is cropped when window size == (0,0) [0]" horizSpanImageAndZeroWindow0
165+ , verify "horiz span image is cropped when window size == (0,0) [1]" horizSpanImageAndZeroWindow1
166+ , verify "horiz span image is not cropped when window size == size of image [width]" horizSpanImageAndEqualWindow0
167+ , verify "horiz span image is not cropped when window size == size of image [height]" horizSpanImageAndEqualWindow1
168+ , verify "horiz span image is not cropped when window size < size of image [width]" horizSpanImageAndLesserWindow0
169+ , verify "horiz span image is not cropped when window size > size of image [width]" horizSpanImageAndGreaterWindow0
170+ , verify "first span op is always to set the text attribute" firstSpanOpSetsAttr
171+ , verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"
172+ singleAttrSingleSpanStackOpCoverage
173+ , verify "a single attr text span is cropped when window size < size of stack image [width]"
174+ singleAttrSingleSpanStackCropped0
175+ , verify "a single attr text span is cropped when window size < size of stack image [height]"
176+ singleAttrSingleSpanStackCropped1
177+ , verify "single attr text span <|> single attr text span display cropped. [width]"
178+ singleAttrSingleSpanStackCropped2
179+ , verify "single attr text span <|> single attr text span display cropped. [height]"
180+ singleAttrSingleSpanStackCropped3
181+ , verify "single attr text span <-> single attr text span display cropped. [width]"
182+ singleAttrSingleSpanStackCropped4
183+ , verify "single attr text span <-> single attr text span display cropped. [height]"
184+ singleAttrSingleSpanStackCropped5
185+ , verify "an arbitrary image when rendered to a window of the same size will cover the entire window"
186+ imageCoverageMatchesBounds
187+ ]
188+
test/VerifyUsingMockInput.hsnew file+223
@@ -0,0 +1,223 @@
1+{-# LANGUAGE MultiParamTypeClasses #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+-- Generate some input bytes and delays between blocks of input bytes.
5+-- Verify the events produced are as expected.
6+module Main where
7+
8+import Verify.Graphics.Vty.Output
9+
10+import Graphics.Vty hiding (resize)
11+import Graphics.Vty.Input.Events
12+import Graphics.Vty.Input.Loop
13+import Graphics.Vty.Input.Terminfo
14+
15+import Control.Concurrent
16+import Control.Concurrent.STM
17+import Control.Exception
18+import Lens.Micro ((^.))
19+import Control.Monad
20+
21+import Data.IORef
22+import Data.List (intersperse, reverse, nubBy)
23+
24+import System.Console.Terminfo
25+import System.Posix.IO
26+import System.Posix.Terminal (openPseudoTerminal)
27+import System.Posix.Types
28+import System.Timeout
29+
30+import Test.Framework.Providers.SmallCheck
31+import Test.Framework
32+import Test.SmallCheck
33+import Test.SmallCheck.Series
34+
35+import Text.Printf
36+
37+-- processing a block of 16 chars is the largest I can do without taking
38+-- too long to run the test.
39+maxBlockSize :: Int
40+maxBlockSize = 16
41+
42+maxTableSize :: Int
43+maxTableSize = 28
44+
45+forEachOf :: (Show a, Testable m b) => [a] -> (a -> b) -> Property m
46+forEachOf l = over (generate (\n -> take n l))
47+
48+data InputEvent
49+ = Bytes String
50+ -- ^ Input sequence encoded as a string. Regardless, the input is
51+ -- read a byte at a time.
52+ | Delay Int
53+ -- ^ Microsecond delay
54+ deriving Show
55+
56+type InputSpec = [InputEvent]
57+
58+type ExpectedSpec = [Event]
59+
60+synthesizeInput :: InputSpec -> Fd -> IO ()
61+synthesizeInput input outHandle = forM_ input f >> (void $ fdWrite outHandle "\xFFFD")
62+ where
63+ f (Bytes str) = void $ fdWrite outHandle str
64+ f (Delay t) = threadDelay t
65+
66+minDetectableDelay :: Int
67+minDetectableDelay = 4000
68+
69+minTimout :: Int
70+minTimout = 4000000
71+
72+testKeyDelay :: Int
73+testKeyDelay = minDetectableDelay * 4
74+
75+testEscSampleDelay :: Int
76+testEscSampleDelay = minDetectableDelay * 2
77+
78+genEventsUsingIoActions :: Int -> IO () -> IO () -> IO ()
79+genEventsUsingIoActions maxDuration inputAction outputAction = do
80+ let maxDuration' = max minTimout maxDuration
81+ readComplete <- newEmptyMVar
82+ writeComplete <- newEmptyMVar
83+ _ <- forkOS $ inputAction `finally` putMVar writeComplete ()
84+ _ <- forkOS $ outputAction `finally` putMVar readComplete ()
85+ Just () <- timeout maxDuration' $ takeMVar writeComplete
86+ Just () <- timeout maxDuration' $ takeMVar readComplete
87+ return ()
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+
109+assertEventsFromSynInput :: ClassifyMap -> InputSpec -> ExpectedSpec -> IO Bool
110+assertEventsFromSynInput table inputSpec expectedEvents = do
111+ let maxDuration = sum [t | Delay t <- inputSpec] + minDetectableDelay
112+ eventCount = length expectedEvents
113+ (writeFd, readFd) <- openPseudoTerminal
114+ (setTermAttr,_) <- attributeControl readFd
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
122+ eventsRef <- newIORef []
123+ let writeWaitClose = do
124+ synthesizeInput inputSpec writeFd
125+ threadDelay minDetectableDelay
126+ shutdownInput input
127+ threadDelay minDetectableDelay
128+ closeFd writeFd
129+ closeFd readFd
130+ -- drain output pipe
131+ let readEvents = readLoop eventCount
132+ readLoop 0 = return ()
133+ readLoop n = do
134+ e <- atomically $ readTChan $ input^.eventChannel
135+ case e of
136+ InputEvent ev -> modifyIORef eventsRef ((:) ev)
137+ ResumeAfterSignal -> return ()
138+ readLoop (n - 1)
139+ genEventsUsingIoActions maxDuration writeWaitClose readEvents
140+ outEvents <- reverse <$> readIORef eventsRef
141+ compareEvents inputSpec expectedEvents outEvents
142+
143+newtype InputBlocksUsingTable event
144+ = InputBlocksUsingTable ([(String,event)] -> [(String, event)])
145+
146+instance Show (InputBlocksUsingTable event) where
147+ show (InputBlocksUsingTable _g) = "InputBlocksUsingTable"
148+
149+instance Monad m => Serial m (InputBlocksUsingTable event) where
150+ series = do
151+ n :: Int <- localDepth (const maxTableSize) series
152+ return $ InputBlocksUsingTable $ \raw_table ->
153+ let table = reverse $ nubBy (\(s0,_) (s1,_) -> s0 == s1) $ reverse raw_table
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
160+verifyVisibleSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) -> monadic $ do
161+ let table = visibleChars
162+ inputSeq = gen table
163+ events = map snd inputSeq
164+ keydowns = map (Bytes . fst) inputSeq
165+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
166+ assertEventsFromSynInput universalTable input events
167+
168+verifyCapsSynInputToEvent :: Property IO
169+verifyCapsSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
170+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
171+ term <- setupTerm terminalName
172+ let table = capsClassifyMap term keysFromCapsTable
173+ inputSeq = gen table
174+ events = map snd inputSeq
175+ keydowns = map (Bytes . fst) inputSeq
176+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
177+ assertEventsFromSynInput table input events
178+
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
189+verifyFullSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
190+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
191+ term <- setupTerm terminalName
192+ let table = classifyMapForTerm terminalName term
193+ inputSeq = gen table
194+ events = map snd inputSeq
195+ keydowns = map (Bytes . fst) inputSeq
196+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
197+ assertEventsFromSynInput table input events
198+
199+verifyFullSynInputToEvent_2x :: Property IO
200+verifyFullSynInputToEvent_2x = forAll $ \(InputBlocksUsingTable gen) ->
201+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
202+ term <- setupTerm terminalName
203+ let table = classifyMapForTerm terminalName term
204+ inputSeq = gen table
205+ events = concatMap ((\s -> [s,s]) . snd) inputSeq
206+ keydowns = map (Bytes . (\s -> s ++ s) . fst) inputSeq
207+ input = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]
208+ assertEventsFromSynInput table input events
209+
210+main :: IO ()
211+main = defaultMain
212+ [ testProperty "synthesized typing of single visible chars translates to expected events"
213+ verifyVisibleSynInputToEvent
214+ , testProperty "synthesized typing of keys from capabilities tables translates to expected events"
215+ verifyCapsSynInputToEvent
216+ , testProperty "synthesized typing of hard coded special keys translates to expected events"
217+ verifySpecialSynInputToEvent
218+ , testProperty "synthesized typing of any key in the table translates to its paired event"
219+ verifyFullSynInputToEvent
220+ , testProperty "synthesized typing of 2x any key in the table translates to 2x paired event"
221+ verifyFullSynInputToEvent_2x
222+ ]
223+
test/VerifyUsingMockTerminal.hsnew file+108
@@ -0,0 +1,108 @@
1+module VerifyUsingMockTerminal where
2+
3+import Verify.Graphics.Vty.Prelude
4+
5+import Verify.Graphics.Vty.Picture
6+import Verify.Graphics.Vty.Image
7+import Verify.Graphics.Vty.Attributes
8+import Verify.Graphics.Vty.Span
9+import Verify.Graphics.Vty.Output
10+import Graphics.Vty.Output
11+import Graphics.Vty.Output.Interface
12+import Graphics.Vty.Output.Mock
13+
14+import Graphics.Vty.Debug
15+
16+import Verify
17+
18+import qualified Data.ByteString as BS
19+import Data.IORef
20+import qualified Data.String.UTF8 as UTF8
21+
22+import System.IO
23+
24+unitImageUnitBounds :: UnitImage -> Property
25+unitImageUnitBounds (UnitImage _ i) = liftIOResult $ do
26+ (_,t) <- mockTerminal (1,1)
27+ dc <- displayBounds t >>= displayContext t
28+ let pic = picForImage i
29+ outputPicture dc pic
30+ return succeeded
31+
32+unitImageArbBounds :: UnitImage -> MockWindow -> Property
33+unitImageArbBounds (UnitImage _ i) (MockWindow w h) = liftIOResult $ do
34+ (_,t) <- mockTerminal (w,h)
35+ dc <- displayBounds t >>= displayContext t
36+ let pic = picForImage i
37+ outputPicture dc pic
38+ return succeeded
39+
40+singleTRow :: MockWindow -> Property
41+singleTRow (MockWindow w h) = liftIOResult $ do
42+ (mockData,t) <- mockTerminal (w,h)
43+ dc <- displayBounds t >>= displayContext t
44+ -- create an image that contains just the character T repeated for a
45+ -- single row
46+ let i = horizCat $ replicate (fromEnum w) (char defAttr 'T')
47+ pic = (picForImage i) { picBackground = Background 'B' defAttr }
48+ outputPicture dc pic
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
51+ -- to the background attribute and then the background character
52+ let expected = "H" ++ "MDA" ++ replicate (fromEnum w) 'T'
53+ ++ concat (replicate (fromEnum h - 1) $ "MDA" ++ replicate (fromEnum w) 'B')
54+ compareMockOutput mockData expected
55+
56+manyTRows :: MockWindow -> Property
57+manyTRows (MockWindow w h) = liftIOResult $ do
58+ (mockData, t) <- mockTerminal (w,h)
59+ dc <- displayBounds t >>= displayContext t
60+ -- create an image that contains the character 'T' repeated for all
61+ -- the rows
62+ let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')
63+ pic = (picForImage i) { picBackground = Background 'B' defAttr }
64+ outputPicture dc pic
65+ -- The UTF8 string that represents the output bytes is h repeats of
66+ -- a move, 'M', followed by an attribute change. 'A', followed by w
67+ -- 'T's
68+ let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
69+ compareMockOutput mockData expected
70+
71+manyTRowsCroppedWidth :: MockWindow -> Property
72+manyTRowsCroppedWidth (MockWindow w h) = liftIOResult $ do
73+ (mockData,t) <- mockTerminal (w,h)
74+ dc <- displayBounds t >>= displayContext t
75+ -- create an image that contains the character 'T' repeated for all
76+ -- the rows
77+ let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w * 2) (char defAttr 'T')
78+ pic = (picForImage i) { picBackground = Background 'B' defAttr }
79+ outputPicture dc pic
80+ -- The UTF8 string that represents the output bytes is h repeats of
81+ -- a move, 'M', followed by an attribute change. 'A', followed by w
82+ -- 'T's
83+ let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
84+ compareMockOutput mockData expected
85+
86+manyTRowsCroppedHeight :: MockWindow -> Property
87+manyTRowsCroppedHeight (MockWindow w h) = liftIOResult $ do
88+ (mockData,t) <- mockTerminal (w,h)
89+ dc <- displayBounds t >>= displayContext t
90+ -- create an image that contains the character 'T' repeated for all
91+ -- the rows
92+ let i = vertCat $ replicate (fromEnum h * 2) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')
93+ pic = (picForImage i) { picBackground = Background 'B' defAttr }
94+ outputPicture dc pic
95+ -- The UTF8 string that represents the output bytes is h repeats of
96+ -- a move, 'M', followed by an attribute change. 'A', followed by w
97+ -- count 'T's
98+ let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
99+ compareMockOutput mockData expected
100+
101+tests :: IO [Test]
102+tests = return [ verify "unitImageUnitBounds" unitImageUnitBounds
103+ , verify "unitImageArbBounds" unitImageArbBounds
104+ , verify "singleTRow" singleTRow
105+ , verify "manyTRows" manyTRows
106+ , verify "manyTRowsCroppedWidth" manyTRowsCroppedWidth
107+ , verify "manyTRowsCroppedHeight" manyTRowsCroppedHeight
108+ ]
test/VerifyUtf8Width.hsnew file+30
@@ -0,0 +1,30 @@
1+module VerifyUtf8Width where
2+
3+import Verify
4+
5+import Graphics.Text.Width
6+import Graphics.Vty.Attributes
7+import Graphics.Vty.Picture
8+import Graphics.Vty.Image
9+
10+swIs1Column :: SingleColumnChar -> Bool
11+swIs1Column (SingleColumnChar c) = imageWidth (char defAttr c) == 1
12+
13+dwIs2Column :: DoubleColumnChar -> Bool
14+dwIs2Column (DoubleColumnChar c) = imageWidth (char defAttr c) == 2
15+
16+dcStringIsEven :: NonEmptyList DoubleColumnChar -> Bool
17+dcStringIsEven (NonEmpty dw_list) =
18+ even $ safeWcswidth [ c | DoubleColumnChar c <- dw_list ]
19+
20+safeWcwidthForControlChars :: Bool
21+safeWcwidthForControlChars = 0 == safeWcwidth '\NUL'
22+
23+tests :: IO [Test]
24+tests = return
25+ [ verify "swIs1Column" swIs1Column
26+ , verify "dwIs2Column" dwIs2Column
27+ , verify "a string of double characters is an even width" dcStringIsEven
28+ , verify "safeWcwidth provides a width of 0 for chars without widths" safeWcwidthForControlChars
29+ ]
30+
test/yi_issue_264.hs → test/YiIssue264.hsrenamed+1
@@ -1,4 +1,5 @@
11module Main where
2+
23import Graphics.Vty
34import Control.Exception
45
test/benchmark.hsnew file+56
@@ -0,0 +1,56 @@
1+{-# LANGUAGE QuasiQuotes #-}
2+module Main where
3+
4+import Graphics.Vty
5+
6+import qualified BenchImageFuzz
7+import qualified BenchNoDiffOpt
8+import qualified BenchRenderChar
9+import qualified BenchVerticalScroll
10+
11+import Control.Monad
12+
13+import Data.Maybe
14+import Data.List
15+
16+import System.Environment
17+import System.Posix.Process
18+
19+import Verify
20+
21+main = do
22+ args <- getArgs
23+ let benches = [ ("no-diff-opt-0", BenchNoDiffOpt.bench0)
24+ , ("render-char-0", BenchRenderChar.bench0)
25+ , ("render-char-1", BenchRenderChar.bench1)
26+ , ("vertical-scroll-0", BenchVerticalScroll.bench0)
27+ , ("image-fuzz-0", BenchImageFuzz.bench0) ]
28+ help = forM_ benches $ \(b,_) -> putStrLn $ "--" ++ b
29+ case args of
30+ ["--help"] -> help
31+ ["-h" ] -> help
32+ _ -> do
33+ let args' = if args /= []
34+ then map (drop 2) args
35+ else map fst benches
36+ -- drop the dash-dash "--"
37+ results <- forM args' $ \bName -> do
38+ case lookup bName benches of
39+ Just b -> bench bName b
40+ Nothing -> fail $ "No benchmark named " ++ bName
41+ print results
42+ return ()
43+
44+bench bName b = do
45+ putStrLn $ "starting " ++ bName
46+ Bench bDataGen bProc <- b
47+ bData <- bDataGen
48+ startTimes <- bData `deepseq` getProcessTimes
49+ bProc bData
50+ endTimes <- getProcessTimes
51+ let ut = userTime endTimes - userTime startTimes
52+ st = systemTime endTimes - systemTime startTimes
53+ putStrLn $ "user time: " ++ show ut
54+ putStrLn $ "system time: " ++ show st
55+ return (bName, ut, st)
56+
test/current_benchmark_results.txtnew file+40
@@ -0,0 +1,40 @@
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+
24+no unclipped opt 5.0
25+on toast
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)]
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)]
28+
29+on ooyala, no screen, iTerm:
30+
31+ * 5.0 initial:
32+ * [("no-diff-opt-0",24,3),("render-char-0",102,8),("render-char-1",41,4),("vertical-scroll-0",226,24),("image-fuzz-0",17,1)]
33+
34+ * post background span replacement:
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)]
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)]
37+
38+release 4.7
39+on toast
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+739−450
@@ -2,41 +2,42 @@
22{-# LANGUAGE ScopedTypeVariables #-}
33module Main where
44
5+import Graphics.Vty
56import Graphics.Vty.Attributes
6−import Graphics.Vty.Image
77import Graphics.Vty.Inline
88import Graphics.Vty.Picture
9−import Graphics.Vty.Terminal
10−import Graphics.Vty.DisplayRegion
9+import Graphics.Vty.Output
1110
11+import Control.Concurrent (threadDelay)
12+import Control.Exception
1213import Control.Monad
1314
1415import Data.List ( lookup )
1516import Data.Maybe ( isJust, fromJust )
1617import Data.Monoid
18+import Data.String.QQ
1719import Data.Word
1820
19−import Foreign.Marshal.Array
20−import HereDoc
21+import Foreign.Marshal.Array
2122
2223import qualified System.Environment as Env
2324
2425import System.IO ( hFlush, hPutStr, hPutBuf, stdout )
2526
2627main = do
27− print_intro
28+ printIntro
2829
29−output_file_path = "test_results.list"
30+outputFilePath = "test_results.list"
3031
31−print_intro = do
32− putStr $ [$heredoc|
32+printIntro = do
33+ putStr $ [s|
3334This is an interactive verification program for the terminal input and output
3435support of the VTY library. This will ask a series of questions about what you
3536see on screen. The goal is to verify that VTY's output and input support
3637performs as expected with your terminal.
3738
3839This program produces a file named
39− |] ++ output_file_path ++ [$heredoc|
40+ |] ++ outputFilePath ++ [s|
4041in the current directory that contains the results for each test assertion. This
4142can be emailed to coreyoconnor@gmail.com and used by the VTY authors to improve
4243support for your terminal. No personal information is contained in the report.
@@ -62,42 +63,43 @@ All the tests assume the following about the terminal display:
6263 a good range of the unicode characters. Each test involving unicode display
6364 describes the expected appearance of each glyph.
6465
65−Thanks for the help! :-D
66−To exit the test early enter "q" anytime at the following menu screen. Even if
67−you exit the test early please email the test_results.list file to
68−coreyoconnor@gmail.com. The results file will still contain information useful
69−to debug terminal support.
70−
71−|]
72− wait_for_return
73− results <- do_test_menu 1
74− env_attributes <- mapM ( \env_name -> catch ( Env.getEnv env_name >>= return . (,) env_name )
75− ( const $ return (env_name, "") )
76− )
77− [ "TERM", "COLORTERM", "LANG", "TERM_PROGRAM", "XTERM_VERSION" ]
78− t <- terminal_handle
79− let results_txt = show env_attributes ++ "\n"
80− ++ terminal_ID t ++ "\n"
81− ++ show results ++ "\n"
82− release_terminal t
83− writeFile output_file_path results_txt
84−
85−wait_for_return = do
66+Thanks for the help! :-D
67+To exit the test early enter "q" anytime at the following menu screen.
68+
69+If any test fails then please post an issue to
70+ https://github.com/coreyoconnor/vty/issues
71+with the test_results.list file pasted into the issue. A suitable summary is:
72+"interactive terminal test failure".
73+|]
74+ waitForReturn
75+ results <- doTestMenu 1
76+ envAttributes <- mapM ( \envName -> Control.Exception.catch ( Env.getEnv envName >>= return . (,) envName )
77+ ( \ (_ :: SomeException) -> return (envName, "") )
78+ )
79+ [ "TERM", "COLORTERM", "LANG", "TERM_PROGRAM", "XTERM_VERSION" ]
80+ t <- standardIOConfig >>= outputForConfig
81+ let resultsTxt = show envAttributes ++ "\n"
82+ ++ terminalID t ++ "\n"
83+ ++ show results ++ "\n"
84+ releaseTerminal t
85+ writeFile outputFilePath resultsTxt
86+
87+waitForReturn = do
8688 putStr "\n(press return to continue)"
8789 hFlush stdout
8890 getLine
8991
90−test_menu :: [(String, Test)]
91−test_menu = zip (map show [1..]) all_tests
92+testMenu :: [(String, Test)]
93+testMenu = zip (map show [1..]) allTests
9294
93−do_test_menu :: Int -> IO [(String, Bool)]
94−do_test_menu next_ID
95− | next_ID > length all_tests = do
96− putStrLn $ "Done! Please email the " ++ output_file_path ++ " file to coreyoconnor@gmail.com"
95+doTestMenu :: Int -> IO [(String, Bool)]
96+doTestMenu nextID
97+ | nextID > length allTests = do
98+ putStrLn $ "Done! Please email the " ++ outputFilePath ++ " file to coreyoconnor@gmail.com"
9799 return []
98100 | otherwise = do
99− display_test_menu
100− putStrLn $ "Press return to start with #" ++ show next_ID ++ "."
101+ displayTestMenu
102+ putStrLn $ "Press return to start with #" ++ show nextID ++ "."
101103 putStrLn "Enter a test number to perform only that test."
102104 putStrLn "q (or control-C) to quit."
103105 putStr "> "
@@ -105,90 +107,102 @@ do_test_menu next_ID
105107 s <- getLine >>= return . filter (/= '\n')
106108 case s of
107109 "q" -> return mempty
108− "" -> do
109− r <- run_test $ show next_ID
110− rs <- do_test_menu ( next_ID + 1 )
110+ "" -> do
111+ r <- runTest $ show nextID
112+ rs <- doTestMenu ( nextID + 1 )
111113 return $ r : rs
112− i | isJust ( lookup i test_menu ) -> do
113− r <- run_test i
114− rs <- do_test_menu ( read i + 1 )
114+ i | isJust ( lookup i testMenu ) -> do
115+ r <- runTest i
116+ rs <- doTestMenu ( read i + 1 )
115117 return $ r : rs
116118 where
117− display_test_menu
118− = mapM_ display_test_menu' test_menu
119− display_test_menu' ( i, t )
120− = putStrLn $ ( if i == show next_ID
121− then "> "
119+ displayTestMenu
120+ = mapM_ displayTestMenu' testMenu
121+ displayTestMenu' ( i, t )
122+ = putStrLn $ ( if i == show nextID
123+ then "> "
122124 else " "
123− ) ++ i ++ ". " ++ test_name t
124−
125−run_test :: String -> IO (String, Bool)
126−run_test i = do
127− let t = fromJust $ lookup i test_menu
128− print_summary t
129− wait_for_return
130− test_action t
131− r <- confirm_results t
132− return (test_ID t, r)
133−
134−default_success_confirm_results = do
125+ ) ++ i ++ ". " ++ testName t
126+
127+runTest :: String -> IO (String, Bool)
128+runTest i = do
129+ let t = fromJust $ lookup i testMenu
130+ printSummary t
131+ waitForReturn
132+ testAction t
133+ r <- confirmResults t
134+ return (testID t, r)
135+
136+defaultSuccessConfirmResults = do
135137 putStr "\n"
136138 putStr "[Y/n] "
137139 hFlush stdout
138140 r <- getLine
139141 case r of
140− "" -> return True
142+ "" -> return True
141143 "y" -> return True
142144 "Y" -> return True
143145 "n" -> return False
144146
145147data Test = Test
146− { test_name :: String
147− , test_ID :: String
148− , test_action :: IO ()
149− , print_summary :: IO ()
150− , confirm_results :: IO Bool
148+ { testName :: String
149+ , testID :: String
150+ , testAction :: IO ()
151+ , printSummary :: IO ()
152+ , confirmResults :: IO Bool
151153 }
152154
153−all_tests
154− = [ reserve_output_test
155− , display_bounds_test_0
156− , display_bounds_test_1
157− , display_bounds_test_2
158− , display_bounds_test_3
159− , unicode_single_width_0
160− , unicode_single_width_1
161− , unicode_double_width_0
162− , unicode_double_width_1
163− , attributes_test_0
164− , attributes_test_1
165− , attributes_test_2
166− , attributes_test_3
167− , attributes_test_4
168− , attributes_test_5
169− , inline_test_0
170− , inline_test_1
155+allTests
156+ = [ reserveOutputTest
157+ , displayBoundsTest0
158+ , displayBoundsTest1
159+ , displayBoundsTest2
160+ , displayBoundsTest3
161+ , unicodeSingleWidth0
162+ , unicodeSingleWidth1
163+ , unicodeDoubleWidth0
164+ , unicodeDoubleWidth1
165+ , attributesTest0
166+ , attributesTest1
167+ , attributesTest2
168+ , attributesTest3
169+ , attributesTest4
170+ , attributesTest5
171+ , inlineTest0
172+ , inlineTest1
173+ , inlineTest2
174+ , cursorHideTest0
175+ , vertCropTest0
176+ , vertCropTest1
177+ , vertCropTest2
178+ , vertCropTest3
179+ , horizCropTest0
180+ , horizCropTest1
181+ , horizCropTest2
182+ , horizCropTest3
183+ , layer0
184+ , layer1
171185 ]
172186
173−reserve_output_test = Test
174− { test_name = "Initialize and reserve terminal output then restore previous state."
175− , test_ID = "reserve_output_test"
176− , test_action = do
177− t <- terminal_handle
178− reserve_display t
187+reserveOutputTest = Test
188+ { testName = "Initialize and reserve terminal output then restore previous state."
189+ , testID = "reserveOutputTest"
190+ , testAction = do
191+ t <- standardIOConfig >>= outputForConfig
192+ reserveDisplay t
179193 putStrLn "Line 1"
180194 putStrLn "Line 2"
181195 putStrLn "Line 3"
182196 putStrLn "Line 4 (press return)"
183197 hFlush stdout
184198 getLine
185− release_display t
186− release_terminal t
199+ releaseDisplay t
200+ releaseTerminal t
187201 return ()
188− , print_summary = do
189− putStr $ [$heredoc|
202+ , printSummary = do
203+ putStr $ [s|
190204Once return is pressed:
191− 0. The screen will be cleared.
205+ 0. The screen will be cleared.
192206 1. Four lines of text should be visible.
193207 1. The cursor should be visible and at the start of the fifth line.
194208
@@ -196,135 +210,143 @@ After return is pressed for the second time this test then:
196210 * The screen containing the test summary should be restored;
197211 * The cursor is visible.
198212|]
199− , confirm_results = do
200− putStr $ [$heredoc|
213+ , confirmResults = do
214+ putStr $ [s|
201215Did the test output match the description?
202216|]
203− default_success_confirm_results
217+ defaultSuccessConfirmResults
204218 }
205219
206−display_bounds_test_0 = Test
207− { test_name = "Verify display bounds are correct test 0: Using spaces."
208− , test_ID = "display_bounds_test_0"
209− , test_action = do
210− t <- terminal_handle
211− reserve_display t
212− DisplayRegion w h <- display_bounds t
213− let row_0 = replicate (fromEnum w) 'X' ++ "\n"
214− row_h = replicate (fromEnum w - 1) 'X'
215− row_n = "X" ++ replicate (fromEnum w - 2) ' ' ++ "X\n"
216− image = row_0 ++ (concat $ replicate (fromEnum h - 2) row_n) ++ row_h
220+displayBoundsTest0 = Test
221+ { testName = "Verify display bounds are correct test 0: Using spaces."
222+ , testID = "displayBoundsTest0"
223+ , testAction = do
224+ t <- standardIOConfig >>= outputForConfig
225+ reserveDisplay t
226+ (w,h) <- displayBounds t
227+ let row0 = replicate (fromEnum w) 'X' ++ "\n"
228+ rowH = replicate (fromEnum w - 1) 'X'
229+ rowN = "X" ++ replicate (fromEnum w - 2) ' ' ++ "X\n"
230+ image = row0 ++ (concat $ replicate (fromEnum h - 2) rowN) ++ rowH
217231 putStr image
218232 hFlush stdout
219233 getLine
220− release_display t
221− release_terminal t
234+ releaseDisplay t
235+ releaseTerminal t
222236 return ()
223− , print_summary = display_bounds_test_summary True
224− , confirm_results = generic_output_match_confirm
237+ , printSummary = displayBoundsTestSummary True
238+ , confirmResults = genericOutputMatchConfirm
225239 }
226240
227−display_bounds_test_1 = Test
228− { test_name = "Verify display bounds are correct test 0: Using cursor movement."
229− , test_ID = "display_bounds_test_1"
230− , test_action = do
231− t <- terminal_handle
232− reserve_display t
233− DisplayRegion w h <- display_bounds t
234− set_cursor_pos t 0 0
235− let row_0 = replicate (fromEnum w) 'X' ++ "\n"
236− putStr row_0
241+displayBoundsTest1 = Test
242+ { testName = "Verify display bounds are correct test 0: Using cursor movement."
243+ , testID = "displayBoundsTest1"
244+ , testAction = do
245+ t <- standardIOConfig >>= outputForConfig
246+ reserveDisplay t
247+ (w,h) <- displayBounds t
248+ setCursorPos t 0 0
249+ let row0 = replicate (fromEnum w) 'X' ++ "\n"
250+ putStr row0
237251 forM_ [1 .. h - 2] $ \y -> do
238− set_cursor_pos t 0 y
252+ setCursorPos t 0 y
239253 putStr "X"
240− set_cursor_pos t (w - 1) y
254+ hFlush stdout
255+ setCursorPos t (w - 1) y
241256 putStr "X"
242− set_cursor_pos t 0 (h - 1)
243− let row_h = replicate (fromEnum w - 1) 'X'
244− putStr row_h
257+ hFlush stdout
258+ setCursorPos t 0 (h - 1)
259+ let rowH = replicate (fromEnum w - 1) 'X'
260+ putStr rowH
245261 hFlush stdout
246262 getLine
247− release_display t
248− release_terminal t
263+ releaseDisplay t
264+ releaseTerminal t
249265 return ()
250− , print_summary = display_bounds_test_summary True
251− , confirm_results = generic_output_match_confirm
266+ , printSummary = displayBoundsTestSummary True
267+ , confirmResults = genericOutputMatchConfirm
252268 }
253269
254−display_bounds_test_2 = Test
255− { test_name = "Verify display bounds are correct test 0: Using Image ops."
256− , test_ID = "display_bounds_test_2"
257− , test_action = do
258− t <- terminal_handle
259− reserve_display t
260− bounds@(DisplayRegion w h) <- display_bounds t
261− let first_row = horiz_cat $ replicate (fromEnum w) (char def_attr 'X')
262− middle_rows = vert_cat $ replicate (fromEnum h - 2) middle_row
263− middle_row = (char def_attr 'X') <|> background_fill (w - 2) 1 <|> (char def_attr 'X')
264− end_row = first_row
265− image = first_row <-> middle_rows <-> end_row
266− pic = (pic_for_image image) { pic_cursor = Cursor (w - 1) (h - 1) }
267− d <- display_context t bounds
268− output_picture d pic
270+displayBoundsTest2 = Test
271+ { testName = "Verify display bounds are correct test 0: Using Image ops."
272+ , testID = "displayBoundsTest2"
273+ , testAction = do
274+ t <- standardIOConfig >>= outputForConfig
275+ reserveDisplay t
276+ bounds@(w,h) <- displayBounds t
277+ let firstRow = horizCat $ replicate (fromEnum w) (char defAttr 'X')
278+ middleRows = vertCat $ replicate (fromEnum h - 2) middleRow
279+ middleRow = (char defAttr 'X') <|> backgroundFill (w - 2) 1 <|> (char defAttr 'X')
280+ endRow = firstRow
281+ image = firstRow <-> middleRows <-> endRow
282+ pic = (picForImage image) { picCursor = Cursor (w - 1) (h - 1) }
283+ d <- displayContext t bounds
284+ outputPicture d pic
269285 getLine
270− release_display t
271− release_terminal t
286+ releaseDisplay t
287+ releaseTerminal t
272288 return ()
273− , print_summary = display_bounds_test_summary True
274− , confirm_results = generic_output_match_confirm
289+ , printSummary = displayBoundsTestSummary True
290+ , confirmResults = genericOutputMatchConfirm
275291 }
276292
277−display_bounds_test_3 = Test
278− { test_name = "Verify display bounds are correct test 0: Hide cursor; Set cursor pos."
279− , test_ID = "display_bounds_test_3"
280− , test_action = do
281− t <- terminal_handle
282− reserve_display t
283− DisplayRegion w h <- display_bounds t
284− hide_cursor t
285− set_cursor_pos t 0 0
286− let row_0 = replicate (fromEnum w) 'X'
287− putStrLn row_0
293+displayBoundsTest3 = Test
294+ { testName = "Verify display bounds are correct test 0: Hide cursor; Set cursor pos."
295+ , testID = "displayBoundsTest3"
296+ , testAction = do
297+ t <- standardIOConfig >>= outputForConfig
298+ reserveDisplay t
299+ (w,h) <- displayBounds t
300+ hideCursor t
301+ setCursorPos t 0 0
302+ let row0 = replicate (fromEnum w) 'X'
303+ putStrLn row0
288304 forM_ [1 .. h - 2] $ \y -> do
289− set_cursor_pos t 0 y
305+ setCursorPos t 0 y
290306 putStr "X"
291− set_cursor_pos t (w - 1) y
307+ hFlush stdout
308+ setCursorPos t (w - 1) y
292309 putStr "X"
293− set_cursor_pos t 0 (h - 1)
294− let row_h = row_0
295− putStr row_h
310+ hFlush stdout
311+ setCursorPos t 0 (h - 1)
312+ let rowH = row0
313+ putStr rowH
296314 hFlush stdout
297315 getLine
298− show_cursor t
299− release_display t
300− release_terminal t
316+ showCursor t
317+ releaseDisplay t
318+ releaseTerminal t
301319 return ()
302− , print_summary = display_bounds_test_summary False
303− , confirm_results = generic_output_match_confirm
320+ , printSummary = displayBoundsTestSummary False
321+ , confirmResults = genericOutputMatchConfirm
304322 }
305323
306−display_bounds_test_summary has_cursor = do
307− putStr $ [$heredoc|
324+displayBoundsTestSummary hasCursor = do
325+ putStr $ [s|
308326Once return is pressed:
309327 0. The screen will be cleared.
310328|]
311− if has_cursor
329+ if hasCursor
312330 then putStr " 1. The cursor will be visible."
313331 else putStr " 1. The cursor will NOT be visible."
314− putStr [$heredoc|
332+ putStr [s|
333+
315334 2. The border of the display will be outlined in Xs.
316335 So if - and | represented the edge of the terminal window:
317336 |-------------|
318337 |XXXXXXXXXXXXX|
319338 |X X||]
320339
321− if has_cursor
322− then putStr $ [$heredoc|
340+ if hasCursor
341+ then putStr $ [s|
342+
323343 |XXXXXXXXXXXXC| |]
324− else putStr $ [$heredoc|
344+ else putStr $ [s|
345+
325346 |XXXXXXXXXXXXX| |]
326347
327− putStr $ [$heredoc|
348+ putStr $ [s|
349+
328350 |-------------|
329351
330352 ( Where C is the final position of the cursor. There may be an X drawn
@@ -336,13 +358,13 @@ After return is pressed for the second time:
336358 1. The cursor should be visible.
337359|]
338360
339−generic_output_match_confirm = do
340− putStr $ [$heredoc|
361+genericOutputMatchConfirm = do
362+ putStr $ [s|
341363Did the test output match the description?
342364|]
343− default_success_confirm_results
365+ defaultSuccessConfirmResults
344366
345−-- Explicitely definethe bytes that encode each example text.
367+-- Explicitly define the bytes that encode each example text.
346368-- This avoids any issues with how the compiler represents string literals.
347369--
348370-- This document is UTF-8 encoded so the UTF-8 string is still included for
@@ -351,24 +373,24 @@ Did the test output match the description?
351373-- It's assumed the compiler will at least not barf on UTF-8 encoded text in
352374-- comments ;-)
353375--
354−-- txt_0 = ↑↑↓↓←→←→BA
355−
356−utf8_txt_0 :: [[Word8]]
357−utf8_txt_0 = [ [ 0xe2 , 0x86 , 0x91 ]
358− , [ 0xe2 , 0x86 , 0x91 ]
359− , [ 0xe2 , 0x86 , 0x93 ]
360− , [ 0xe2 , 0x86 , 0x93 ]
361− , [ 0xe2 , 0x86 , 0x90 ]
362− , [ 0xe2 , 0x86 , 0x92 ]
363− , [ 0xe2 , 0x86 , 0x90 ]
364− , [ 0xe2 , 0x86 , 0x92 ]
365− , [ 0x42 ]
366− , [ 0x41 ]
367− ]
368−
369−iso_10646_txt_0 :: String
370−iso_10646_txt_0 = map toEnum
371− [ 8593
376+-- txt0 = ↑↑↓↓←→←→BA
377+
378+utf8Txt0 :: [[Word8]]
379+utf8Txt0 = [ [ 0xe2 , 0x86 , 0x91 ]
380+ , [ 0xe2 , 0x86 , 0x91 ]
381+ , [ 0xe2 , 0x86 , 0x93 ]
382+ , [ 0xe2 , 0x86 , 0x93 ]
383+ , [ 0xe2 , 0x86 , 0x90 ]
384+ , [ 0xe2 , 0x86 , 0x92 ]
385+ , [ 0xe2 , 0x86 , 0x90 ]
386+ , [ 0xe2 , 0x86 , 0x92 ]
387+ , [ 0x42 ]
388+ , [ 0x41 ]
389+ ]
390+
391+iso10646Txt0 :: String
392+iso10646Txt0 = map toEnum
393+ [ 8593
372394 , 8593
373395 , 8595
374396 , 8595
@@ -380,46 +402,46 @@ iso_10646_txt_0 = map toEnum
380402 , 65
381403 ]
382404
383−unicode_single_width_0 = Test
384− { test_name = "Verify terminal can display unicode single-width characters. (Direct UTF-8)"
385− , test_ID = "unicode_single_width_0"
386− , test_action = do
387− t <- terminal_handle
388− reserve_display t
389− hide_cursor t
390− withArrayLen (concat utf8_txt_0) (flip $ hPutBuf stdout)
405+unicodeSingleWidth0 = Test
406+ { testName = "Verify terminal can display unicode single-width characters. (Direct UTF-8)"
407+ , testID = "unicodeSingleWidth0"
408+ , testAction = do
409+ t <- standardIOConfig >>= outputForConfig
410+ reserveDisplay t
411+ hideCursor t
412+ withArrayLen (concat utf8Txt0) (flip $ hPutBuf stdout)
391413 hPutStr stdout "\n"
392414 hPutStr stdout "0123456789\n"
393415 hFlush stdout
394416 getLine
395− release_display t
396− release_terminal t
417+ releaseDisplay t
418+ releaseTerminal t
397419 return ()
398− , print_summary = unicode_single_width_summary
399− , confirm_results = generic_output_match_confirm
420+ , printSummary = unicodeSingleWidthSummary
421+ , confirmResults = genericOutputMatchConfirm
400422 }
401423
402−unicode_single_width_1 = Test
403− { test_name = "Verify terminal can display unicode single-width characters. (Image ops)"
404− , test_ID = "unicode_single_width_1"
405− , test_action = do
406− t <- terminal_handle
407− reserve_display t
408− let pic = pic_for_image image
409− image = line_0 <-> line_1
410− line_0 = iso_10646_string def_attr iso_10646_txt_0
411− line_1 = string def_attr "0123456789"
412− d <- display_bounds t >>= display_context t
413− output_picture d pic
424+unicodeSingleWidth1 = Test
425+ { testName = "Verify terminal can display unicode single-width characters. (Image ops)"
426+ , testID = "unicodeSingleWidth1"
427+ , testAction = do
428+ t <- standardIOConfig >>= outputForConfig
429+ reserveDisplay t
430+ let pic = picForImage image
431+ image = line0 <-> line1
432+ line0 = iso10646String defAttr iso10646Txt0
433+ line1 = string defAttr "0123456789"
434+ d <- displayBounds t >>= displayContext t
435+ outputPicture d pic
414436 getLine
415− release_display t
416− release_terminal t
437+ releaseDisplay t
438+ releaseTerminal t
417439 return ()
418− , print_summary = unicode_single_width_summary
419− , confirm_results = generic_output_match_confirm
440+ , printSummary = unicodeSingleWidthSummary
441+ , confirmResults = genericOutputMatchConfirm
420442 }
421443
422−unicode_single_width_summary = putStr [$heredoc|
444+unicodeSingleWidthSummary = putStr [s|
423445Once return is pressed:
424446 0. The screen will be cleared.
425447 1. The cursor will be hidden.
@@ -453,55 +475,55 @@ After return is pressed for the second time:
453475
454476-- The second example is a unicode string containing double-width glyphs
455477-- 你好吗
456−utf8_txt_1 :: [[Word8]]
457−utf8_txt_1 = [ [0xe4,0xbd,0xa0]
458− , [0xe5,0xa5,0xbd]
459− , [0xe5,0x90,0x97]
460− ]
461−
462−iso_10646_txt_1 :: String
463−iso_10646_txt_1 = map toEnum [20320,22909,21527]
464−
465−unicode_double_width_0 = Test
466− { test_name = "Verify terminal can display unicode double-width characters. (Direct UTF-8)"
467− , test_ID = "unicode_double_width_0"
468− , test_action = do
469− t <- terminal_handle
470− reserve_display t
471− hide_cursor t
472− withArrayLen (concat utf8_txt_1) (flip $ hPutBuf stdout)
478+utf8Txt1 :: [[Word8]]
479+utf8Txt1 = [ [0xe4,0xbd,0xa0]
480+ , [0xe5,0xa5,0xbd]
481+ , [0xe5,0x90,0x97]
482+ ]
483+
484+iso10646Txt1 :: String
485+iso10646Txt1 = map toEnum [20320,22909,21527]
486+
487+unicodeDoubleWidth0 = Test
488+ { testName = "Verify terminal can display unicode double-width characters. (Direct UTF-8)"
489+ , testID = "unicodeDoubleWidth0"
490+ , testAction = do
491+ t <- standardIOConfig >>= outputForConfig
492+ reserveDisplay t
493+ hideCursor t
494+ withArrayLen (concat utf8Txt1) (flip $ hPutBuf stdout)
473495 hPutStr stdout "\n"
474496 hPutStr stdout "012345\n"
475497 hFlush stdout
476498 getLine
477− release_display t
478− release_terminal t
499+ releaseDisplay t
500+ releaseTerminal t
479501 return ()
480− , print_summary = unicode_double_width_summary
481− , confirm_results = generic_output_match_confirm
502+ , printSummary = unicodeDoubleWidthSummary
503+ , confirmResults = genericOutputMatchConfirm
482504 }
483505
484−unicode_double_width_1 = Test
485− { test_name = "Verify terminal can display unicode double-width characters. (Image ops)"
486− , test_ID = "unicode_double_width_1"
487− , test_action = do
488− t <- terminal_handle
489− reserve_display t
490− let pic = pic_for_image image
491− image = line_0 <-> line_1
492− line_0 = iso_10646_string def_attr iso_10646_txt_1
493− line_1 = string def_attr "012345"
494− d <- display_bounds t >>= display_context t
495− output_picture d pic
506+unicodeDoubleWidth1 = Test
507+ { testName = "Verify terminal can display unicode double-width characters. (Image ops)"
508+ , testID = "unicodeDoubleWidth1"
509+ , testAction = do
510+ t <- standardIOConfig >>= outputForConfig
511+ reserveDisplay t
512+ let pic = picForImage image
513+ image = line0 <-> line1
514+ line0 = iso10646String defAttr iso10646Txt1
515+ line1 = string defAttr "012345"
516+ d <- displayBounds t >>= displayContext t
517+ outputPicture d pic
496518 getLine
497− release_display t
498− release_terminal t
519+ releaseDisplay t
520+ releaseTerminal t
499521 return ()
500− , print_summary = unicode_double_width_summary
501− , confirm_results = generic_output_match_confirm
522+ , printSummary = unicodeDoubleWidthSummary
523+ , confirmResults = genericOutputMatchConfirm
502524 }
503525
504−unicode_double_width_summary = putStr [$heredoc|
526+unicodeDoubleWidthSummary = putStr [s|
505527Once return is pressed:
506528 0. The screen will be cleared.
507529 1. The cursor will be hidden.
@@ -528,33 +550,33 @@ After return is pressed for the second time:
528550 1. The cursor should be visible.
529551|]
530552
531−all_colors = zip [ black, red, green, yellow, blue, magenta, cyan, white ]
532− [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]
553+allColors = zip [ black, red, green, yellow, blue, magenta, cyan, white ]
554+ [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]
533555
534−all_bright_colors
535− = zip [ bright_black, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white ]
556+allBrightColors
557+ = zip [ brightBlack, brightRed, brightGreen, brightYellow, brightBlue, brightMagenta, brightCyan, brightWhite ]
536558 [ "bright black", "bright red", "bright green", "bright yellow", "bright blue", "bright magenta", "bright cyan", "bright white" ]
537559
538−attributes_test_0 = Test
539− { test_name = "Character attributes: foreground colors."
540− , test_ID = "attributes_test_0"
541− , test_action = do
542− t <- terminal_handle
543− reserve_display t
544− let pic = pic_for_image image
545− image = border <|> column_0 <|> border <|> column_1 <|> border
546− column_0 = vert_cat $ map line_with_color all_colors
547− border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
548− column_1 = vert_cat $ map (string def_attr . snd) all_colors
549− line_with_color (c, c_name) = string (def_attr `with_fore_color` c) c_name
550− d <- display_bounds t >>= display_context t
551− output_picture d pic
560+attributesTest0 = Test
561+ { testName = "Character attributes: foreground colors."
562+ , testID = "attributesTest0"
563+ , testAction = do
564+ t <- standardIOConfig >>= outputForConfig
565+ reserveDisplay t
566+ let pic = picForImage image
567+ image = border <|> column0 <|> border <|> column1 <|> border
568+ column0 = vertCat $ map lineWithColor allColors
569+ border = vertCat $ replicate (length allColors) $ string defAttr " | "
570+ column1 = vertCat $ map (string defAttr . snd) allColors
571+ lineWithColor (c, cName) = string (defAttr `withForeColor` c) cName
572+ d <- displayBounds t >>= displayContext t
573+ outputPicture d pic
552574 getLine
553− release_display t
554− release_terminal t
575+ releaseDisplay t
576+ releaseTerminal t
555577 return ()
556− , print_summary = do
557− putStr $ [$heredoc|
578+ , printSummary = do
579+ putStr $ [s|
558580Once return is pressed:
559581 0. The screen will be cleared.
560582 1. The cursor will be hidden.
@@ -574,33 +596,33 @@ After return is pressed for the second time:
574596 0. The screen containing the test summary should be restored.
575597 1. The cursor should be visible.
576598|]
577− , confirm_results = do
578− putStr $ [$heredoc|
599+ , confirmResults = do
600+ putStr $ [s|
579601Did the test output match the description?
580602|]
581− default_success_confirm_results
603+ defaultSuccessConfirmResults
582604 }
583605
584−attributes_test_1 = Test
585− { test_name = "Character attributes: background colors."
586− , test_ID = "attributes_test_1"
587− , test_action = do
588− t <- terminal_handle
589− reserve_display t
590− let pic = pic_for_image image
591− image = border <|> column_0 <|> border <|> column_1 <|> border
592− column_0 = vert_cat $ map line_with_color all_colors
593− border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
594− column_1 = vert_cat $ map (string def_attr . snd) all_colors
595− line_with_color (c, c_name) = string (def_attr `with_back_color` c) c_name
596− d <- display_bounds t >>= display_context t
597− output_picture d pic
606+attributesTest1 = Test
607+ { testName = "Character attributes: background colors."
608+ , testID = "attributesTest1"
609+ , testAction = do
610+ t <- standardIOConfig >>= outputForConfig
611+ reserveDisplay t
612+ let pic = picForImage image
613+ image = border <|> column0 <|> border <|> column1 <|> border
614+ column0 = vertCat $ map lineWithColor allColors
615+ border = vertCat $ replicate (length allColors) $ string defAttr " | "
616+ column1 = vertCat $ map (string defAttr . snd) allColors
617+ lineWithColor (c, cName) = string (defAttr `withBackColor` c) cName
618+ d <- displayBounds t >>= displayContext t
619+ outputPicture d pic
598620 getLine
599− release_display t
600− release_terminal t
621+ releaseDisplay t
622+ releaseTerminal t
601623 return ()
602− , print_summary = do
603− putStr $ [$heredoc|
624+ , printSummary = do
625+ putStr $ [s|
604626Once return is pressed:
605627 0. The screen will be cleared.
606628 1. The cursor will be hidden.
@@ -629,57 +651,57 @@ After return is pressed for the second time:
629651 0. The screen containing the test summary should be restored.
630652 1. The cursor should be visible.
631653|]
632− , confirm_results = do
633− putStr $ [$heredoc|
654+ , confirmResults = do
655+ putStr $ [s|
634656Did the test output match the description?
635657|]
636− default_success_confirm_results
658+ defaultSuccessConfirmResults
637659 }
638660
639−attributes_test_2 = Test
640− { test_name = "Character attributes: Vivid foreground colors."
641− , test_ID = "attributes_test_2"
642− , test_action = do
643− t <- terminal_handle
644− reserve_display t
645− let pic = pic_for_image image
646− image = horiz_cat [border, column_0, border, column_1, border, column_2, border]
647− border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
648− column_0 = vert_cat $ map line_with_color_0 all_colors
649− column_1 = vert_cat $ map line_with_color_1 all_bright_colors
650− column_2 = vert_cat $ map (string def_attr . snd) all_colors
651− line_with_color_0 (c, c_name) = string (def_attr `with_fore_color` c) c_name
652− line_with_color_1 (c, c_name) = string (def_attr `with_fore_color` c) c_name
653− d <- display_bounds t >>= display_context t
654− output_picture d pic
661+attributesTest2 = Test
662+ { testName = "Character attributes: Vivid foreground colors."
663+ , testID = "attributesTest2"
664+ , testAction = do
665+ t <- standardIOConfig >>= outputForConfig
666+ reserveDisplay t
667+ let pic = picForImage image
668+ image = horizCat [border, column0, border, column1, border, column2, border]
669+ border = vertCat $ replicate (length allColors) $ string defAttr " | "
670+ column0 = vertCat $ map lineWithColor0 allColors
671+ column1 = vertCat $ map lineWithColor1 allBrightColors
672+ column2 = vertCat $ map (string defAttr . snd) allColors
673+ lineWithColor0 (c, cName) = string (defAttr `withForeColor` c) cName
674+ lineWithColor1 (c, cName) = string (defAttr `withForeColor` c) cName
675+ d <- displayBounds t >>= displayContext t
676+ outputPicture d pic
655677 getLine
656− release_display t
657− release_terminal t
678+ releaseDisplay t
679+ releaseTerminal t
658680 return ()
659− , print_summary = do
660− putStr $ [$heredoc|
681+ , printSummary = do
682+ putStr $ [s|
661683Once return is pressed:
662684 0. The screen will be cleared.
663685 1. The cursor will be hidden.
664686 2. 9 lines of text in three columns will be drawn:
665687 a. The first column will be a name of a standard color (for an 8 color
666− terminal) rendered with that color as the foreground color.
688+ terminal) rendered with that color as the foreground color.
667689 b. The next column will be also be the name of a standard color rendered
668690 with that color as the foreground color but the shade used should be
669− more vivid than the shade used in the first column.
691+ more vivid than the shade used in the first column.
670692 c. The final column will be the name of a color rendered with the
671693 default attributes.
672694
673695For instance, one line will be the word "magenta" and that word should be
674−rendered in the magenta color.
696+rendered in the magenta color.
675697
676698I'm not actually sure exactly what "vivid" means in this context. For xterm the
677−vivid colors are brighter.
699+vivid colors are brighter.
678700
679−Verify:
701+Verify:
680702 * The first column: The foreground color matches the named color.
681703 * The second column: The foreground color matches the named color but is
682− more vivid than the color used in the first column.
704+ more vivid than the color used in the first column.
683705 * The third column: All text is rendered with the default attributes.
684706 * The vertical bars used in each line to mark the border of a column are
685707 lined up.
@@ -688,58 +710,58 @@ After return is pressed for the second time:
688710 0. The screen containing the test summary should be restored.
689711 1. The cursor should be visible.
690712|]
691− , confirm_results = do
692− putStr $ [$heredoc|
713+ , confirmResults = do
714+ putStr $ [s|
693715Did the test output match the description?
694716|]
695− default_success_confirm_results
717+ defaultSuccessConfirmResults
696718 }
697719
698−attributes_test_3 = Test
699− { test_name = "Character attributes: Vivid background colors."
700− , test_ID = "attributes_test_3"
701− , test_action = do
702− t <- terminal_handle
703− reserve_display t
704− let pic = pic_for_image image
705− image = horiz_cat [border, column_0, border, column_1, border, column_2, border]
706− border = vert_cat $ replicate (length all_colors) $ string def_attr " | "
707− column_0 = vert_cat $ map line_with_color_0 all_colors
708− column_1 = vert_cat $ map line_with_color_1 all_bright_colors
709− column_2 = vert_cat $ map (string def_attr . snd) all_colors
710− line_with_color_0 (c, c_name) = string (def_attr `with_back_color` c) c_name
711− line_with_color_1 (c, c_name) = string (def_attr `with_back_color` c) c_name
712− d <- display_bounds t >>= display_context t
713− output_picture d pic
720+attributesTest3 = Test
721+ { testName = "Character attributes: Vivid background colors."
722+ , testID = "attributesTest3"
723+ , testAction = do
724+ t <- standardIOConfig >>= outputForConfig
725+ reserveDisplay t
726+ let pic = picForImage image
727+ image = horizCat [border, column0, border, column1, border, column2, border]
728+ border = vertCat $ replicate (length allColors) $ string defAttr " | "
729+ column0 = vertCat $ map lineWithColor0 allColors
730+ column1 = vertCat $ map lineWithColor1 allBrightColors
731+ column2 = vertCat $ map (string defAttr . snd) allColors
732+ lineWithColor0 (c, cName) = string (defAttr `withBackColor` c) cName
733+ lineWithColor1 (c, cName) = string (defAttr `withBackColor` c) cName
734+ d <- displayBounds t >>= displayContext t
735+ outputPicture d pic
714736 getLine
715− release_display t
716− release_terminal t
737+ releaseDisplay t
738+ releaseTerminal t
717739 return ()
718− , print_summary = do
719− putStr $ [$heredoc|
740+ , printSummary = do
741+ putStr $ [s|
720742Once return is pressed:
721743 0. The screen will be cleared.
722744 1. The cursor will be hidden.
723745 2. 9 lines of text in three columns will be drawn:
724746 a. The first column will contain be a name of a standard color for an 8
725747 color terminal rendered with the default foreground color with a
726− background the named color.
748+ background the named color.
727749 b. The first column will contain be a name of a standard color for an 8
728750 color terminal rendered with the default foreground color with the
729− background a vivid version of the named color.
751+ background a vivid version of the named color.
730752 c. The third column will be the name of a standard color rendered with
731753 the default attributes.
732−
754+
733755For instance, one line will contain be the word "magenta" and the word should
734−be rendered in the default foreground color over a magenta background.
756+be rendered in the default foreground color over a magenta background.
735757
736758I'm not actually sure exactly what "vivid" means in this context. For xterm the
737759vivid colors are brighter.
738760
739−Verify:
761+Verify:
740762 * The first column: The background color matches the named color.
741763 * The second column: The background color matches the named color and is
742− more vivid than the color used in the first column.
764+ more vivid than the color used in the first column.
743765 * The third column column: All text is rendered with the default attributes.
744766 * The vertical bars used in each line to mark the border of a column are
745767 lined up.
@@ -755,51 +777,51 @@ After return is pressed for the second time:
755777 0. The screen containing the test summary should be restored.
756778 1. The cursor should be visible.
757779|]
758− , confirm_results = do
759− putStr $ [$heredoc|
780+ , confirmResults = do
781+ putStr $ [s|
760782Did the test output match the description?
761783|]
762− default_success_confirm_results
784+ defaultSuccessConfirmResults
763785 }
764786
765−attr_combos =
787+attrCombos =
766788 [ ( "default", id )
767− , ( "bold", flip with_style bold )
768− , ( "blink", flip with_style blink )
769− , ( "underline", flip with_style underline )
770− , ( "bold + blink", flip with_style (bold + blink) )
771− , ( "bold + underline", flip with_style (bold + underline) )
772− , ( "underline + blink", flip with_style (underline + blink) )
773− , ( "bold + blink + underline", flip with_style (bold + blink + underline) )
789+ , ( "bold", flip withStyle bold )
790+ , ( "blink", flip withStyle blink )
791+ , ( "underline", flip withStyle underline )
792+ , ( "bold + blink", flip withStyle (bold + blink) )
793+ , ( "bold + underline", flip withStyle (bold + underline) )
794+ , ( "underline + blink", flip withStyle (underline + blink) )
795+ , ( "bold + blink + underline", flip withStyle (bold + blink + underline) )
774796 ]
775797
776−attributes_test_4 = Test
777− { test_name = "Character attributes: Bold; Blink; Underline."
778− , test_ID = "attributes_test_4"
779− , test_action = do
780− t <- terminal_handle
781− reserve_display t
782− let pic = pic_for_image image
783− image = horiz_cat [border, column_0, border, column_1, border]
784− border = vert_cat $ replicate (length attr_combos) $ string def_attr " | "
785− column_0 = vert_cat $ map line_with_attrs attr_combos
786− column_1 = vert_cat $ map (string def_attr . fst) attr_combos
787− line_with_attrs (desc, attr_f) = string (attr_f def_attr) desc
788− d <- display_bounds t >>= display_context t
789− output_picture d pic
798+attributesTest4 = Test
799+ { testName = "Character attributes: Bold; Blink; Underline."
800+ , testID = "attributesTest4"
801+ , testAction = do
802+ t <- standardIOConfig >>= outputForConfig
803+ reserveDisplay t
804+ let pic = picForImage image
805+ image = horizCat [border, column0, border, column1, border]
806+ border = vertCat $ replicate (length attrCombos) $ string defAttr " | "
807+ column0 = vertCat $ map lineWithAttrs attrCombos
808+ column1 = vertCat $ map (string defAttr . fst) attrCombos
809+ lineWithAttrs (desc, attrF) = string (attrF defAttr) desc
810+ d <- displayBounds t >>= displayContext t
811+ outputPicture d pic
790812 getLine
791− release_display t
792− release_terminal t
813+ releaseDisplay t
814+ releaseTerminal t
793815 return ()
794− , print_summary = do
795− putStr $ [$heredoc|
816+ , printSummary = do
817+ putStr $ [s|
796818Once return is pressed:
797819 0. The screen will be cleared.
798820 1. The cursor will be hidden.
799− 2. 8 rows of text in two columns.
821+ 2. 8 rows of text in two columns.
800822 The rows will contain the following text:
801823 default
802− bold
824+ bold
803825 blink
804826 underline
805827 bold + blink
@@ -808,8 +830,8 @@ Once return is pressed:
808830 bold + blink + underline
809831 The first column will be rendered with the described attributes. The second
810832 column will be rendered with the default attributes.
811−
812−Verify:
833+
834+Verify:
813835 * The vertical bars used in each line to mark the border of a column are
814836 lined up.
815837 * The text in the first column is rendered as described.
@@ -818,86 +840,353 @@ After return is pressed for the second time:
818840 0. The screen containing the test summary should be restored.
819841 1. The cursor should be visible.
820842|]
821− , confirm_results = do
822− putStr $ [$heredoc|
843+ , confirmResults = do
844+ putStr $ [s|
823845Did the test output match the description?
824846|]
825− default_success_confirm_results
847+ defaultSuccessConfirmResults
826848 }
827849
828−attributes_test_5 = Test
829− { test_name = "Character attributes: 240 color palette"
830− , test_ID = "attributes_test_5"
831− , test_action = do
832− t <- terminal_handle
833− reserve_display t
834− let pic = pic_for_image image
835− image = vert_cat $ map horiz_cat $ split_color_images color_images
836− color_images = map (\i -> string (current_attr `with_back_color` Color240 i) " ") [0..239]
837− split_color_images [] = []
838− split_color_images is = (take 20 is ++ [string def_attr " "]) : (split_color_images (drop 20 is))
839− d <- display_bounds t >>= display_context t
840− output_picture d pic
850+attributesTest5 = Test
851+ { testName = "Character attributes: 240 color palette"
852+ , testID = "attributesTest5"
853+ , testAction = do
854+ t <- standardIOConfig >>= outputForConfig
855+ reserveDisplay t
856+ let pic = picForImage image
857+ image = vertCat $ map horizCat $ splitColorImages colorImages
858+ colorImages = map (\i -> string (currentAttr `withBackColor` Color240 i) " ") [0..239]
859+ splitColorImages [] = []
860+ splitColorImages is = (take 20 is ++ [string defAttr " "]) : (splitColorImages (drop 20 is))
861+ d <- displayBounds t >>= displayContext t
862+ outputPicture d pic
841863 getLine
842− release_display t
843− release_terminal t
864+ releaseDisplay t
865+ releaseTerminal t
844866 return ()
845− , print_summary = do
846− putStr $ [$heredoc|
867+ , printSummary = do
868+ putStr $ [s|
847869Once return is pressed:
848870 0. The screen will be cleared.
849871 1. The cursor will be hidden.
850872 2. A 20 character wide and 12 row high block of color squares. This should look like a palette
851873 of some sort. I'm not exactly sure if all color terminals use the same palette. I doubt it...
852874
853−Verify:
875+Verify:
854876
855877After return is pressed for the second time:
856878 0. The screen containing the test summary should be restored.
857879 1. The cursor should be visible.
858880|]
859− , confirm_results = do
860− putStr $ [$heredoc|
881+ , confirmResults = do
882+ putStr $ [s|
861883Did the test output match the description?
862884|]
863− default_success_confirm_results
885+ defaultSuccessConfirmResults
864886 }
865887
866−inline_test_0 = Test
867− { test_name = "Verify styled output can be performed without clearing the screen."
868− , test_ID = "inline_test_0"
869− , test_action = do
870− t <- terminal_handle
888+inlineTest0 = Test
889+ { testName = "Verify styled output can be performed without clearing the screen."
890+ , testID = "inlineTest0"
891+ , testAction = do
871892 putStrLn "line 1."
872− put_attr_change t $ back_color red >> apply_style underline
893+ putAttrChange_ $ backColor red >> applyStyle underline
873894 putStrLn "line 2."
874− put_attr_change t $ default_all
895+ putAttrChange_ $ defaultAll
875896 putStrLn "line 3."
876− release_terminal t
877− return ()
878− , print_summary = putStr $ [$heredoc|
897+ , printSummary = putStr $ [s|
879898lines are in order.
880899The second line "line 2" should have a red background and the text underline.
881900The third line "line 3" should be drawn in the same style as the first line.
882901|]
883902
884− , confirm_results = generic_output_match_confirm
903+ , confirmResults = genericOutputMatchConfirm
885904 }
886905
887−inline_test_1 = Test
888− { test_name = "Verify styled output can be performed without clearing the screen."
889− , test_ID = "inline_test_1"
890− , test_action = do
891− t <- terminal_handle
906+inlineTest1 = Test
907+ { testName = "Verify styled output can be performed without clearing the screen."
908+ , testID = "inlineTest1"
909+ , testAction = do
892910 putStr "Not styled. "
893− put_attr_change t $ back_color red >> apply_style underline
911+ putAttrChange_ $ backColor red >> applyStyle underline
894912 putStr " Styled! "
895− put_attr_change t $ default_all
913+ putAttrChange_ $ defaultAll
896914 putStrLn "Not styled."
897− release_terminal t
915+ , printSummary = putStr $ [s|
916+|]
917+
918+ , confirmResults = genericOutputMatchConfirm
919+ }
920+
921+inlineTest2 = Test
922+ { testName = "Verify styled output can be performed without clearing the screen."
923+ , testID = "inlineTest1"
924+ , testAction = do
925+ putStr "Not styled. "
926+ putAttrChange_ $ backColor red >> applyStyle underline
927+ putStr " Styled! "
928+ putAttrChange_ $ defaultAll
929+ putStr "Not styled.\n"
930+ , printSummary = putStr $ [s|
931+|]
932+ , confirmResults = genericOutputMatchConfirm
933+ }
934+
935+cursorHideTest0 :: Test
936+cursorHideTest0 = Test
937+ { testName = "Verify the cursor is hid and re-shown. issue #7"
938+ , testID = "cursorHideTest0"
939+ , testAction = do
940+ vty <- mkVty defaultConfig
941+ showCursor $ outputIface vty
942+ setCursorPos (outputIface vty) 5 5
943+ nextEvent vty
944+ hideCursor $ outputIface vty
945+ nextEvent vty
946+ shutdown vty
898947 return ()
899− , print_summary = putStr $ [$heredoc|
948+ , printSummary = putStr $ [s|
949+ 1. verify the cursor is displayed.
950+ 2. press enter
951+ 3. verify the cursor is hid.
952+ 4. press enter.
953+ 5. the display should return to the state before the test.
900954|]
955+ , confirmResults = genericOutputMatchConfirm
956+ }
901957
902− , confirm_results = generic_output_match_confirm
958+outputImageAndWait :: Image -> IO ()
959+outputImageAndWait image = do
960+ let pic = picForImage image
961+ outputPicAndWait pic
962+
963+outputPicAndWait :: Picture -> IO ()
964+outputPicAndWait pic = do
965+ t <- standardIOConfig >>= outputForConfig
966+ reserveDisplay t
967+ d <- displayBounds t >>= displayContext t
968+ outputPicture d pic
969+ getLine
970+ releaseDisplay t
971+ releaseTerminal t
972+ return ()
973+
974+vertCropTest0 :: Test
975+vertCropTest0 = Test
976+ { testName = "Verify bottom cropping works as expected with single column chars"
977+ , testID = "cropTest0"
978+ , testAction = do
979+ let block0 = cropBottom 2 $ vertCat $ map (string defAttr) lorumIpsum
980+ block1 = vertCat $ map (string defAttr) $ take 2 lorumIpsum
981+ image = block0 <-> backgroundFill 10 2 <-> block1
982+ outputImageAndWait image
983+ , printSummary = putStr $ [s|
984+ 1. Verify the two text blocks are identical.
985+ 2. press enter.
986+ 3. the display should return to the state before the test.
987+|]
988+ , confirmResults = genericOutputMatchConfirm
989+ }
990+
991+vertCropTest1 :: Test
992+vertCropTest1 = Test
993+ { testName = "Verify bottom cropping works as expected with double column chars"
994+ , testID = "cropTest0"
995+ , testAction = do
996+ let block0 = cropBottom 2 $ vertCat $ map (string defAttr) lorumIpsumChinese
997+ block1 = vertCat $ map (string defAttr) $ take 2 lorumIpsumChinese
998+ image = block0 <-> backgroundFill 10 2 <-> block1
999+ outputImageAndWait image
1000+ , printSummary = putStr $ [s|
1001+ 1. Verify the two text blocks are identical.
1002+ 2. press enter.
1003+ 3. the display should return to the state before the test.
1004+|]
1005+ , confirmResults = genericOutputMatchConfirm
9031006 }
1007+
1008+vertCropTest2 :: Test
1009+vertCropTest2 = Test
1010+ { testName = "Verify top cropping works as expected with single column chars"
1011+ , testID = "cropTest2"
1012+ , testAction = do
1013+ let block0 = cropTop 2 $ vertCat $ map (string defAttr) lorumIpsum
1014+ block1 = vertCat $ map (string defAttr) $ drop (length lorumIpsum - 2) lorumIpsum
1015+ image = block0 <-> backgroundFill 10 2 <-> block1
1016+ outputImageAndWait image
1017+ , printSummary = putStr $ [s|
1018+ 1. Verify the two text blocks are identical.
1019+ 2. press enter.
1020+ 3. the display should return to the state before the test.
1021+|]
1022+ , confirmResults = genericOutputMatchConfirm
1023+ }
1024+
1025+vertCropTest3 :: Test
1026+vertCropTest3 = Test
1027+ { testName = "Verify top cropping works as expected with double column chars"
1028+ , testID = "cropTest0"
1029+ , testAction = do
1030+ let block0 = cropTop 2 $ vertCat $ map (string defAttr) lorumIpsumChinese
1031+ block1 = vertCat $ map (string defAttr) $ drop (length lorumIpsumChinese - 2 ) lorumIpsumChinese
1032+ image = block0 <-> backgroundFill 10 2 <-> block1
1033+ outputImageAndWait image
1034+ , printSummary = putStr $ [s|
1035+ 1. Verify the two text blocks are identical.
1036+ 2. press enter.
1037+ 3. the display should return to the state before the test.
1038+|]
1039+ , confirmResults = genericOutputMatchConfirm
1040+ }
1041+
1042+horizCropTest0 :: Test
1043+horizCropTest0 = Test
1044+ { testName = "Verify right cropping works as expected with single column chars"
1045+ , testID = "cropTest0"
1046+ , testAction = do
1047+ let baseImage = vertCat $ map (string defAttr) lorumIpsum
1048+ croppedImage = cropRight (imageWidth baseImage `div` 2) baseImage
1049+ image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1050+ outputImageAndWait image
1051+ , printSummary = putStr $ [s|
1052+ 1. Verify the bottom text block is about half the width of the top text block.
1053+ 2. press enter.
1054+ 3. the display should return to the state before the test.
1055+|]
1056+ , confirmResults = genericOutputMatchConfirm
1057+ }
1058+
1059+horizCropTest1 :: Test
1060+horizCropTest1 = Test
1061+ { testName = "Verify right cropping works as expected with double column chars"
1062+ , testID = "cropTest0"
1063+ , testAction = do
1064+ let baseImage = vertCat $ map (string defAttr) lorumIpsumChinese
1065+ croppedImage = cropRight (imageWidth baseImage `div` 2) baseImage
1066+ image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1067+ outputImageAndWait image
1068+ , printSummary = putStr $ [s|
1069+ 1. Verify the bottom text block is the left half of the top block. Ellipses on the right edge are OK.
1070+ 2. press enter.
1071+ 3. the display should return to the state before the test.
1072+|]
1073+ , confirmResults = genericOutputMatchConfirm
1074+ }
1075+
1076+horizCropTest2 :: Test
1077+horizCropTest2 = Test
1078+ { testName = "Verify left cropping works as expected with single column chars"
1079+ , testID = "cropTest0"
1080+ , testAction = do
1081+ let baseImage = vertCat $ map (string defAttr) lorumIpsum
1082+ croppedImage = cropLeft (imageWidth baseImage `div` 2) baseImage
1083+ image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1084+ outputImageAndWait image
1085+ , printSummary = putStr $ [s|
1086+ 1. Verify the bottom text block is the right half of the top text block.
1087+ 2. press enter.
1088+ 3. the display should return to the state before the test.
1089+|]
1090+ , confirmResults = genericOutputMatchConfirm
1091+ }
1092+
1093+horizCropTest3 :: Test
1094+horizCropTest3 = Test
1095+ { testName = "Verify right cropping works as expected with double column chars"
1096+ , testID = "cropTest0"
1097+ , testAction = do
1098+ let baseImage = vertCat $ map (string defAttr) lorumIpsumChinese
1099+ croppedImage = cropLeft (imageWidth baseImage `div` 2) baseImage
1100+ image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1101+ outputImageAndWait image
1102+ , printSummary = putStr $ [s|
1103+ 1. Verify the bottom text block is the right half of the top block. Ellipses on the left edge are OK.
1104+ 2. press enter.
1105+ 3. the display should return to the state before the test.
1106+|]
1107+ , confirmResults = genericOutputMatchConfirm
1108+ }
1109+
1110+layer0 :: Test
1111+layer0 = Test
1112+ { testName = "verify layer 0"
1113+ , testID = "layer0"
1114+ , testAction = do
1115+ let upperImage = vertCat $ map (string defAttr) lorumIpsumChinese
1116+ lowerImage = vertCat $ map (string defAttr) lorumIpsum
1117+ p = picForLayers [upperImage, lowerImage]
1118+ outputPicAndWait p
1119+ , printSummary = putStr $ [s|
1120+ 1. Verify the text block appears to be Chinese text placed on top Latin text.
1121+ 2. press enter.
1122+ 3. the display should return to the state before the test.
1123+|]
1124+ , confirmResults = genericOutputMatchConfirm
1125+ }
1126+
1127+layer1 :: Test
1128+layer1 = Test
1129+ { testName = "verify layer 1"
1130+ , testID = "layer1"
1131+ , testAction = do
1132+ let upperImage = vertCat $ map (string defAttr) lorumIpsumChinese
1133+ block = resize 10 10 upperImage
1134+ layer0 = vertCat $ map (string defAttr) lorumIpsum
1135+ layer1 = charFill (defAttr `withBackColor` blue) '#' 1000 1000
1136+ cheesyAnim0 block [layer0, layer1]
1137+ , printSummary = putStr $ [s|
1138+ 1. Verify the text block appears to be Chinese text moving on top a Latin text.
1139+ Which is all on a background of '#' characters over blue.
1140+ 2. press enter.
1141+ 3. the display should return to the state before the test.
1142+|]
1143+ , confirmResults = genericOutputMatchConfirm
1144+ }
1145+
1146+cheesyAnim0 :: Image -> [Image] -> IO ()
1147+cheesyAnim0 i background = do
1148+ t <- standardIOConfig >>= outputForConfig
1149+ reserveDisplay t
1150+ bounds <- displayBounds t
1151+ d <- displayContext t bounds
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
1161+ let i_offset = translate (t * (-1) `mod` fst bounds)
1162+ (t * (-1) `div` 2 `mod` snd bounds)
1163+ i
1164+ let pic = picForLayers $ i_offset : background
1165+ outputPicture d pic
1166+ threadDelay 50000
1167+ releaseDisplay t
1168+ releaseTerminal t
1169+ return ()
1170+
1171+lorumIpsum :: [String]
1172+lorumIpsum = lines [s|
1173+Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
1174+totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
1175+dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
1176+sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
1177+est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius
1178+modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
1179+veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea
1180+commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil
1181+molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
1182+|]
1183+
1184+lorumIpsumChinese :: [String]
1185+lorumIpsumChinese = lines [s|
1186+輐銛 螷蟞覮 裌覅詵 暕 鴅噮 槶 惝掭掝 婸媥媕 耏胠臿, 汫汭沎 忕汌卣 蚡袀 僣 蒮 瀁瀎瀊 渮湸湤 緌翢,
1187+腠腶舝 糲蘥蠩 樏殣氀 蒮 蹢鎒 滍 鸄齴 櫧櫋瀩 鬄鵊鵙 莃荶衒, 毸溠 橀 簎艜薤 莃荶衒 翣聜蒢
1188+斔櫅檷 晛桼桾 拻敁柧 犿玒 膣, 墐 笓粊紒 bacon 鼀齕, 蔝蓶蓨 顊顃餭 姴怤 骱 暕 蹢鎒鎛 藒襓謥 鄻鎟霣
1189+鬎鯪, 鐩闤 硻禂稢 谾踘遳 撱 赲 迡 箷 蛃袚觙 萇雊蜩 壿嫷 鋡 縢羱聬 跐鉠鉣 蔝蓶蓨 匢奾灱 溮煡煟 雥齆犪
1190+蔰 虈觿, 腷腯葹 鍹餳駷 蛚袲褁蜸 皯竻 瀁瀎 蜭蜸覟 梪涫湴 揗斝湁 毼
1191+|]
1192+
test/verify_attribute_ops.hsdeleted−8
@@ -1,8 +0,0 @@
1−module Main where
2−
3−import Verify.Graphics.Vty.Attributes
4−
5−import Verify
6−
7−main = run_test $ do
8− return ()
test/verify_debug_terminal.hsdeleted−115
@@ -1,115 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−module Main where
3−
4−import Verify.Graphics.Vty.DisplayRegion
5−import Verify.Graphics.Vty.Picture
6−import Verify.Graphics.Vty.Image
7−import Verify.Graphics.Vty.Span
8−import Graphics.Vty.Terminal
9−import Graphics.Vty.Terminal.Debug
10−
11−import Graphics.Vty.Debug
12−
13−import Verify
14−
15−import qualified Data.ByteString as BS
16−import Data.IORef
17−import qualified Data.String.UTF8 as UTF8
18−
19−import System.IO
20−
21−unit_image_unit_bounds :: UnitImage -> Property
22−unit_image_unit_bounds (UnitImage _ i) = liftIOResult $ do
23− t <- terminal_instance (DisplayRegion 1 1)
24− d <- display_bounds t >>= display_context t
25− let pic = pic_for_image i
26− output_picture d pic
27− return succeeded
28−
29−unit_image_arb_bounds :: UnitImage -> DebugWindow -> Property
30−unit_image_arb_bounds (UnitImage _ i) (DebugWindow w h) = liftIOResult $ do
31− t <- terminal_instance (DisplayRegion w h)
32− d <- display_bounds t >>= display_context t
33− let pic = pic_for_image i
34− output_picture d pic
35− return succeeded
36−
37−single_T_row :: DebugWindow -> Property
38−single_T_row (DebugWindow w h) = liftIOResult $ do
39− t <- terminal_instance (DisplayRegion w h)
40− d <- display_bounds t >>= display_context t
41− -- create an image that contains just the character T repeated for a single row
42− let i = horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
43− pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
44− output_picture d pic
45− out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep
46− -- The UTF8 string that represents the output bytes a single line containing the T string:
47− let expected = "HD" ++ "MA" ++ replicate (fromEnum w) 'T'
48− -- Followed by h - 1 lines of a change to the background attribute and then the background
49− -- character
50− ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')
51− expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
52− if out_bytes /= expected_bytes
53− then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
54− else return succeeded
55−
56−many_T_rows :: DebugWindow -> Property
57−many_T_rows (DebugWindow w h) = liftIOResult $ do
58− t <- terminal_instance (DisplayRegion w h)
59− d <- display_bounds t >>= display_context t
60− -- create an image that contains the character 'T' repeated for all the rows
61− let i = vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
62− pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
63− output_picture d pic
64− out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep
65− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
66− -- attribute change. 'A', followed by w 'T's
67− let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
68− expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
69− if out_bytes /= expected_bytes
70− then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
71− else return succeeded
72−
73−many_T_rows_cropped_width :: DebugWindow -> Property
74−many_T_rows_cropped_width (DebugWindow w h) = liftIOResult $ do
75− t <- terminal_instance (DisplayRegion w h)
76− d <- display_bounds t >>= display_context t
77− -- create an image that contains the character 'T' repeated for all the rows
78− let i = vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w * 2) (char def_attr 'T')
79− pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
80− output_picture d pic
81− out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep
82− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
83− -- attribute change. 'A', followed by w 'T's
84− let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
85− expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
86− if out_bytes /= expected_bytes
87− then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
88− else return succeeded
89−
90−many_T_rows_cropped_height :: DebugWindow -> Property
91−many_T_rows_cropped_height (DebugWindow w h) = liftIOResult $ do
92− t <- terminal_instance (DisplayRegion w h)
93− d <- display_bounds t >>= display_context t
94− -- create an image that contains the character 'T' repeated for all the rows
95− let i = vert_cat $ replicate (fromEnum h * 2) $ horiz_cat $ replicate (fromEnum w) (char def_attr 'T')
96− pic = (pic_for_image i) { pic_background = Background 'B' def_attr }
97− output_picture d pic
98− out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep
99− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
100− -- attribute change. 'A', followed by w count 'T's
101− let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
102− expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
103− if out_bytes /= expected_bytes
104− then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
105− else return succeeded
106−
107−main = run_test $ do
108− verify "unit_image_unit_bounds" unit_image_unit_bounds
109− verify "unit_image_arb_bounds" unit_image_arb_bounds
110− verify "single_T_row" single_T_row
111− verify "many_T_rows" many_T_rows
112− verify "many_T_rows_cropped_width" many_T_rows_cropped_width
113− verify "many_T_rows_cropped_height" many_T_rows_cropped_height
114− return ()
115−
test/verify_empty_image_props.hsdeleted−13
@@ -1,13 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−module Main where
3−
4−import Verify
5−
6−-- should be exported by Graphics.Vty.Picture
7−import Graphics.Vty.Picture ( Image, empty_image )
8−
9−main = run_test $ do
10− -- should provide an image type.
11− let _ :: Image = empty_image
12− return ()
13−
test/verify_eval_terminfo_caps.hsdeleted−129
@@ -1,129 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−{-# LANGUAGE NamedFieldPuns #-}
3−module Main where
4−
5−import Data.Marshalling
6−
7−import Data.Terminfo.Eval
8−import Data.Terminfo.Parse
9−
10−import qualified System.Console.Terminfo as Terminfo
11−
12−import Verify
13−
14−import Control.Exception ( try, SomeException(..) )
15−
16−import Control.Monad ( mapM_, forM_ )
17−
18−import Data.Array.Unboxed
19−import Data.Maybe ( fromJust )
20−import Data.Word
21−
22−import Numeric
23−
24−-- A list of terminals that ubuntu includes a terminfo cap file for.
25−-- Assuming that is a good place to start.
26−terminals_of_interest =
27− [ "wsvt25"
28− , "wsvt25m"
29− , "vt52"
30− , "vt100"
31− , "vt220"
32− , "vt102"
33− , "xterm-r5"
34− , "xterm-xfree86"
35− , "xterm-r6"
36− , "xterm-256color"
37− , "xterm-vt220"
38− , "xterm-debian"
39− , "xterm-mono"
40− , "xterm-color"
41− , "xterm"
42− , "mach"
43− , "mach-bold"
44− , "mach-color"
45− , "linux"
46− , "ansi"
47− , "hurd"
48− , "Eterm"
49− , "pcansi"
50− , "screen-256color"
51− , "screen-bce"
52− , "screen-s"
53− , "screen-w"
54− , "screen"
55− , "screen-256color-bce"
56− , "sun"
57− , "rxvt"
58− , "rxvt-unicode"
59− , "rxvt-basic"
60− , "cygwin"
61− , "cons25"
62− , "dumb"
63− ]
64−
65−-- If a terminal defines one of the caps then it's expected to be parsable.
66−caps_of_interest =
67− [ "cup"
68− , "sc"
69− , "rc"
70− , "setf"
71− , "setb"
72− , "setaf"
73− , "setab"
74− , "op"
75− , "cnorm"
76− , "civis"
77− , "smcup"
78− , "rmcup"
79− , "clear"
80− , "hpa"
81− , "vpa"
82− , "sgr"
83− , "sgr0"
84− ]
85−
86−from_capname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
87−
88−main = do
89− run_test $ do
90− forM_ terminals_of_interest $ \term_name -> do
91− liftIO $ putStrLn $ "testing parsing of caps for terminal: " ++ term_name
92− mti <- liftIO $ try $ Terminfo.setupTerm term_name
93− case mti of
94− Left (_e :: SomeException)
95− -> return ()
96− Right ti -> do
97− forM_ caps_of_interest $ \cap_name -> do
98− liftIO $ putStrLn $ "\tevaluating cap: " ++ cap_name
99− case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
100− Just cap_def -> do
101− verify ( "\teval cap " ++ cap_name ++ " -> " ++ show cap_def )
102− ( verify_eval_cap cap_def )
103− return ()
104− Nothing -> do
105− return ()
106− return ()
107− return ()
108−
109−verify_eval_cap cap_string
110− = case parse_cap_expression cap_string of
111− Left error ->
112− liftResult $ failed { reason = "parse error " ++ show error }
113− Right expr ->
114− forAll (vector 9) $ \input_values ->
115− let byte_count = cap_expression_required_bytes expr input_values
116− in liftIOResult $ do
117− start_ptr :: Ptr Word8 <- mallocBytes (fromEnum byte_count)
118− end_ptr <- serialize_cap_expression expr input_values start_ptr
119− free start_ptr
120− case end_ptr `minusPtr` start_ptr of
121− count | count < 0 ->
122− return $ failed { reason = "End pointer before start pointer." }
123− | toEnum count > byte_count ->
124− return $ failed { reason = "End pointer past end of buffer by "
125− ++ show (toEnum count - byte_count)
126− }
127− | otherwise ->
128− return succeeded
129−
test/verify_image_ops.hsdeleted−124
@@ -1,124 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−module Main where
3−import Graphics.Vty.Attributes
4−import Verify.Graphics.Vty.Image
5−
6−import Verify
7−
8−import Data.Word
9−
10−two_sw_horiz_concat :: SingleColumnChar -> SingleColumnChar -> Bool
11−two_sw_horiz_concat (SingleColumnChar c1) (SingleColumnChar c2) =
12− image_width (char def_attr c1 <|> char def_attr c2) == 2
13−
14−many_sw_horiz_concat :: [SingleColumnChar] -> Bool
15−many_sw_horiz_concat cs =
16− let chars = [ char | SingleColumnChar char <- cs ]
17− l = fromIntegral $ length cs
18− in image_width ( horiz_cat $ map (char def_attr) chars ) == l
19−
20−two_sw_vert_concat :: SingleColumnChar -> SingleColumnChar -> Bool
21−two_sw_vert_concat (SingleColumnChar c1) (SingleColumnChar c2) =
22− image_height (char def_attr c1 <-> char def_attr c2) == 2
23−
24−horiz_concat_sw_assoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool
25−horiz_concat_sw_assoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) =
26− (char def_attr c0 <|> char def_attr c1) <|> char def_attr c2
27− ==
28− char def_attr c0 <|> (char def_attr c1 <|> char def_attr c2)
29−
30−two_dw_horiz_concat :: DoubleColumnChar -> DoubleColumnChar -> Bool
31−two_dw_horiz_concat (DoubleColumnChar c1) (DoubleColumnChar c2) =
32− image_width (char def_attr c1 <|> char def_attr c2) == 4
33−
34−many_dw_horiz_concat :: [DoubleColumnChar] -> Bool
35−many_dw_horiz_concat cs =
36− let chars = [ char | DoubleColumnChar char <- cs ]
37− l = fromIntegral $ length cs
38− in image_width ( horiz_cat $ map (char def_attr) chars ) == l * 2
39−
40−two_dw_vert_concat :: DoubleColumnChar -> DoubleColumnChar -> Bool
41−two_dw_vert_concat (DoubleColumnChar c1) (DoubleColumnChar c2) =
42− image_height (char def_attr c1 <-> char def_attr c2) == 2
43−
44−horiz_concat_dw_assoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool
45−horiz_concat_dw_assoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) =
46− (char def_attr c0 <|> char def_attr c1) <|> char def_attr c2
47− ==
48− char def_attr c0 <|> (char def_attr c1 <|> char def_attr c2)
49−
50−vert_contat_single_row :: NonEmptyList SingleRowSingleAttrImage -> Bool
51−vert_contat_single_row (NonEmpty stack) =
52− let expected_height :: Word = fromIntegral $ length stack
53− stack_image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack ]
54− in image_height stack_image == expected_height
55−
56−disjoint_height_horiz_join :: NonEmptyList SingleRowSingleAttrImage
57− -> NonEmptyList SingleRowSingleAttrImage
58− -> Bool
59−disjoint_height_horiz_join (NonEmpty stack_0) (NonEmpty stack_1) =
60− let expected_height :: Word = fromIntegral $ max (length stack_0) (length stack_1)
61− stack_image_0 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]
62− stack_image_1 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]
63− in image_height (stack_image_0 <|> stack_image_1) == expected_height
64−
65−
66−disjoint_height_horiz_join_bg_fill :: NonEmptyList SingleRowSingleAttrImage
67− -> NonEmptyList SingleRowSingleAttrImage
68− -> Bool
69−disjoint_height_horiz_join_bg_fill (NonEmpty stack_0) (NonEmpty stack_1) =
70− let stack_image_0 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]
71− stack_image_1 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]
72− image = stack_image_0 <|> stack_image_1
73− expected_height = image_height image
74− in case image of
75− HorizJoin {} -> ( expected_height == (image_height $ part_left image) )
76− &&
77− ( expected_height == (image_height $ part_right image) )
78− _ -> True
79−
80−disjoint_width_vert_join :: NonEmptyList SingleRowSingleAttrImage
81− -> NonEmptyList SingleRowSingleAttrImage
82− -> Bool
83−disjoint_width_vert_join (NonEmpty stack_0) (NonEmpty stack_1) =
84− let expected_width = maximum $ map image_width (stack_0_images ++ stack_1_images)
85− stack_0_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]
86− stack_1_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]
87− stack_0_image = vert_cat stack_0_images
88− stack_1_image = vert_cat stack_1_images
89− image = stack_0_image <-> stack_1_image
90− in image_width image == expected_width
91−
92−disjoint_width_vert_join_bg_fill :: NonEmptyList SingleRowSingleAttrImage
93− -> NonEmptyList SingleRowSingleAttrImage
94− -> Bool
95−disjoint_width_vert_join_bg_fill (NonEmpty stack_0) (NonEmpty stack_1) =
96− let expected_width = maximum $ map image_width (stack_0_images ++ stack_1_images)
97− stack_0_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]
98− stack_1_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]
99− stack_0_image = vert_cat stack_0_images
100− stack_1_image = vert_cat stack_1_images
101− image = stack_0_image <-> stack_1_image
102− in case image of
103− VertJoin {} -> ( expected_width == (image_width $ part_top image) )
104− &&
105− ( expected_width == (image_width $ part_bottom image) )
106− _ -> True
107−
108−main = run_test $ do
109− verify "two_sw_horiz_concat" two_sw_horiz_concat
110− verify "many_sw_horiz_concat" many_sw_horiz_concat
111− verify "two_sw_vert_concat" two_sw_vert_concat
112− verify "horiz_concat_sw_assoc" horiz_concat_sw_assoc
113− verify "many_dw_horiz_concat" many_dw_horiz_concat
114− verify "two_dw_horiz_concat" two_dw_horiz_concat
115− verify "two_dw_vert_concat" two_dw_vert_concat
116− verify "horiz_concat_dw_assoc" horiz_concat_dw_assoc
117− liftIO $ putStrLn $ replicate 80 '-'
118− verify "single row vert concats to correct height" vert_contat_single_row
119− verify "disjoint_height_horiz_join" disjoint_height_horiz_join
120− verify "disjoint_height_horiz_join BG fill" disjoint_height_horiz_join_bg_fill
121− verify "disjoint_width_vert_join" disjoint_width_vert_join
122− verify "disjoint_width_vert_join BG fill" disjoint_width_vert_join_bg_fill
123− return ()
124−
test/verify_image_trans.hsdeleted−31
@@ -1,31 +0,0 @@
1−{-# LANGUAGE NamedFieldPuns #-}
2−module Main where
3−
4−import Verify.Graphics.Vty.Image
5−import Graphics.Vty.Debug
6−
7−import Verify
8−
9−import Data.Word
10−
11−is_horiz_text_of_columns :: Image -> Word -> Bool
12−is_horiz_text_of_columns (HorizText { output_width = in_w }) expected_w = in_w == expected_w
13−is_horiz_text_of_columns (BGFill { output_width = in_w }) expected_w = in_w == expected_w
14−is_horiz_text_of_columns _image _expected_w = False
15−
16−verify_horiz_contat_wo_attr_change_simplifies :: SingleRowSingleAttrImage -> Bool
17−verify_horiz_contat_wo_attr_change_simplifies (SingleRowSingleAttrImage _attr char_count image) =
18− is_horiz_text_of_columns image char_count
19−
20−verify_horiz_contat_w_attr_change_simplifies :: SingleRowTwoAttrImage -> Bool
21−verify_horiz_contat_w_attr_change_simplifies ( SingleRowTwoAttrImage (SingleRowSingleAttrImage attr0 char_count0 image0)
22− (SingleRowSingleAttrImage attr1 char_count1 image1)
23− i
24− )
25− | char_count0 == 0 || char_count1 == 0 || attr0 == attr1 = is_horiz_text_of_columns i (char_count0 + char_count1)
26− | otherwise = False == is_horiz_text_of_columns i (char_count0 + char_count1)
27−
28−main = run_test $ do
29− verify "verify_horiz_contat_wo_attr_change_simplifies" verify_horiz_contat_wo_attr_change_simplifies
30− verify "verify_horiz_contat_w_attr_change_simplifies" verify_horiz_contat_w_attr_change_simplifies
31− return ()
test/verify_inline.hsdeleted−11
@@ -1,11 +0,0 @@
1−module Main where
2−
3−import Graphics.Vty.Inline
4−import Graphics.Vty.Terminal
5−
6−import Verify
7−
8−main = run_test $ do
9− t <- terminal_handle
10− put_attr_change t $ default_all
11− return ()
test/verify_parse_terminfo_caps.hsdeleted−158
@@ -1,158 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−{-# LANGUAGE NamedFieldPuns #-}
3−module Main where
4−
5−import Prelude hiding ( catch )
6−
7−import Data.Terminfo.Eval ( bytes_for_range )
8−
9−import qualified System.Console.Terminfo as Terminfo
10−
11−import Verify.Data.Terminfo.Parse
12−import Verify
13−
14−import Control.Exception ( try, SomeException(..) )
15−import Control.Monad ( mapM_, forM_ )
16−
17−import Data.Array.Unboxed
18−import Data.Maybe ( fromJust )
19−import Data.Word
20−
21−import Numeric
22−
23−-- A list of terminals that ubuntu includes a terminfo cap file for.
24−-- Assuming that is a good place to start.
25−terminals_of_interest =
26− [ "wsvt25"
27− , "wsvt25m"
28− , "vt52"
29− , "vt100"
30− , "vt220"
31− , "vt102"
32− , "xterm-r5"
33− , "xterm-xfree86"
34− , "xterm-r6"
35− , "xterm-256color"
36− , "xterm-vt220"
37− , "xterm-debian"
38− , "xterm-mono"
39− , "xterm-color"
40− , "xterm"
41− , "mach"
42− , "mach-bold"
43− , "mach-color"
44− , "linux"
45− , "ansi"
46− , "hurd"
47− , "Eterm"
48− , "pcansi"
49− , "screen-256color"
50− , "screen-bce"
51− , "screen-s"
52− , "screen-w"
53− , "screen"
54− , "screen-256color-bce"
55− , "sun"
56− , "rxvt"
57− , "rxvt-unicode"
58− , "rxvt-basic"
59− , "cygwin"
60− , "cons25"
61− , "dumb"
62− ]
63−
64−-- If a terminal defines one of the caps then it's expected to be parsable.
65−caps_of_interest =
66− [ "cup"
67− , "sc"
68− , "rc"
69− , "setf"
70− , "setb"
71− , "setaf"
72− , "setab"
73− , "op"
74− , "cnorm"
75− , "civis"
76− , "smcup"
77− , "rmcup"
78− , "clear"
79− , "hpa"
80− , "vpa"
81− , "sgr"
82− , "sgr0"
83− ]
84−
85−from_capname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)
86−
87−main = do
88− run_test $ do
89− forM_ terminals_of_interest $ \term_name -> do
90− liftIO $ putStrLn $ "testing parsing of caps for terminal: " ++ term_name
91− mti <- liftIO $ try $ Terminfo.setupTerm term_name
92− case mti of
93− Left (_e :: SomeException)
94− -> return ()
95− Right ti -> do
96− forM_ caps_of_interest $ \cap_name -> do
97− liftIO $ putStrLn $ "\tparsing cap: " ++ cap_name
98− case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
99− Just cap_def -> do
100− verify ( "\tparse cap " ++ cap_name ++ " -> " ++ show cap_def )
101− ( verify_parse_cap cap_def $ const (liftResult succeeded) )
102− return ()
103− Nothing -> do
104− return ()
105− -- The quickcheck tests
106− verify "parse_non_paramaterized_caps" non_paramaterized_caps
107− verify "parse cap string with literal %" literal_percent_caps
108− verify "parse cap string with %i op" inc_first_two_caps
109− verify "parse cap string with %pN op" push_param_caps
110− return ()
111− return ()
112−
113−verify_parse_cap cap_string on_parse = do
114− case parse_cap_expression cap_string of
115− Left error -> liftResult $ failed { reason = "parse error " ++ show error }
116− Right e -> on_parse e
117−
118−non_paramaterized_caps (NonParamCapString cap) = do
119− verify_parse_cap cap $ \e ->
120− let expected_count :: Word8 = toEnum $ length cap
121− expected_bytes = map (toEnum . fromEnum) cap
122− out_bytes = bytes_for_range e 0 expected_count
123− in verify_bytes_equal out_bytes expected_bytes
124−
125−literal_percent_caps (LiteralPercentCap cap_string expected_bytes) = do
126− verify_parse_cap cap_string $ \e ->
127− let expected_count :: Word8 = toEnum $ length expected_bytes
128− out_bytes = collect_bytes e
129− in verify_bytes_equal out_bytes expected_bytes
130−
131−inc_first_two_caps (IncFirstTwoCap cap_string expected_bytes) = do
132− verify_parse_cap cap_string $ \e ->
133− let expected_count :: Word8 = toEnum $ length expected_bytes
134− out_bytes = collect_bytes e
135− in verify_bytes_equal out_bytes expected_bytes
136−
137−push_param_caps (PushParamCap cap_string expected_param_count expected_bytes) = do
138− verify_parse_cap cap_string $ \e ->
139− let expected_count :: Word8 = toEnum $ length expected_bytes
140− out_bytes = collect_bytes e
141− out_param_count = param_count e
142− in verify_bytes_equal out_bytes expected_bytes
143− .&. out_param_count == expected_param_count
144−
145−dec_print_param_caps (DecPrintCap cap_string expected_param_count expected_bytes) = do
146− verify_parse_cap cap_string $ \e ->
147− let expected_count :: Word8 = toEnum $ length expected_bytes
148− out_bytes = collect_bytes e
149− out_param_count = param_count e
150− in verify_bytes_equal out_bytes expected_bytes
151− .&. out_param_count == expected_param_count
152−
153−print_cap ti cap_name = do
154− putStrLn $ cap_name ++ ": " ++ show (from_capname ti cap_name)
155−
156−print_expression ti cap_name = do
157− putStrLn $ cap_name ++ ": " ++ show (parse_cap_expression $ from_capname ti cap_name)
158−
test/verify_picture_ops.hsdeleted−9
@@ -1,9 +0,0 @@
1−module Main where
2−
3−import Graphics.Vty.Picture ( translate )
4−
5−import Verify
6−
7−main = run_test $ do
8− return ()
9−
test/verify_picture_to_span.hsdeleted−10
@@ -1,10 +0,0 @@
1−module Main where
2−
3−import Graphics.Vty.Picture
4−import Graphics.Vty.Span
5−import Graphics.Vty.PictureToSpans
6−
7−import Verify
8−
9−main = run_test $ do
10− return ()
test/verify_span_ops.hsdeleted−179
@@ -1,179 +0,0 @@
1−{-# LANGUAGE DisambiguateRecordFields #-}
2−{-# LANGUAGE NamedFieldPuns #-}
3−module Main where
4−
5−import Verify.Graphics.Vty.Picture
6−import Verify.Graphics.Vty.Image
7−import Verify.Graphics.Vty.Span
8−import Verify.Graphics.Vty.DisplayRegion
9−
10−import Graphics.Vty.Debug
11−
12−import Verify
13−
14−import Data.Array
15−
16−unit_image_and_zero_window_0 :: UnitImage -> EmptyWindow -> Bool
17−unit_image_and_zero_window_0 (UnitImage _ i) (EmptyWindow w) =
18− let p = pic_for_image i
19− spans = spans_for_pic p (region_for_window w)
20− in span_ops_columns spans == 0 && span_ops_rows spans == 0
21−
22−unit_image_and_zero_window_1 :: UnitImage -> EmptyWindow -> Bool
23−unit_image_and_zero_window_1 (UnitImage _ i) (EmptyWindow w) =
24− let p = pic_for_image i
25− spans = spans_for_pic p (region_for_window w)
26− in ( span_ops_effected_rows spans == 0 ) && ( all_spans_have_width spans 0 )
27−
28−horiz_span_image_and_zero_window_0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
29−horiz_span_image_and_zero_window_0 (SingleRowSingleAttrImage { row_image = i }) (EmptyWindow w) =
30− let p = pic_for_image i
31− spans = spans_for_pic p (region_for_window w)
32− in span_ops_columns spans == 0 && span_ops_rows spans == 0
33−
34−horiz_span_image_and_zero_window_1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
35−horiz_span_image_and_zero_window_1 (SingleRowSingleAttrImage { row_image = i }) (EmptyWindow w) =
36− let p = pic_for_image i
37− spans = spans_for_pic p (region_for_window w)
38− in ( span_ops_effected_rows spans == 0 ) && ( all_spans_have_width spans 0 )
39−
40−horiz_span_image_and_equal_window_0 :: SingleRowSingleAttrImage -> Result
41−horiz_span_image_and_equal_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
42− let p = pic_for_image i
43− w = DebugWindow c 1
44− spans = spans_for_pic p (region_for_window w)
45− in verify_all_spans_have_width i spans c
46−
47−horiz_span_image_and_equal_window_1 :: SingleRowSingleAttrImage -> Bool
48−horiz_span_image_and_equal_window_1 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
49− let p = pic_for_image i
50− w = DebugWindow c 1
51− spans = spans_for_pic p (region_for_window w)
52− in span_ops_effected_rows spans == 1
53−
54−horiz_span_image_and_lesser_window_0 :: SingleRowSingleAttrImage -> Result
55−horiz_span_image_and_lesser_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
56− let p = pic_for_image i
57− lesser_width = c `div` 2
58− w = DebugWindow lesser_width 1
59− spans = spans_for_pic p (region_for_window w)
60− in verify_all_spans_have_width i spans lesser_width
61−
62−single_attr_single_span_stack_cropped_0 :: SingleAttrSingleSpanStack -> Result
63−single_attr_single_span_stack_cropped_0 stack =
64− let p = pic_for_image (stack_image stack)
65− w = DebugWindow (stack_width stack `div` 2) (stack_height stack)
66− spans = spans_for_pic p (region_for_window w)
67− in verify_all_spans_have_width (stack_image stack) spans (stack_width stack `div` 2)
68−
69−single_attr_single_span_stack_cropped_1 :: SingleAttrSingleSpanStack -> Bool
70−single_attr_single_span_stack_cropped_1 stack =
71− let p = pic_for_image (stack_image stack)
72− expected_row_count = stack_height stack `div` 2
73− w = DebugWindow (stack_width stack) expected_row_count
74− spans = spans_for_pic p (region_for_window w)
75− actual_row_count = span_ops_effected_rows spans
76− in expected_row_count == actual_row_count
77−
78−single_attr_single_span_stack_cropped_2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
79−single_attr_single_span_stack_cropped_2 stack_0 stack_1 =
80− let p = pic_for_image (stack_image stack_0 <|> stack_image stack_1)
81− w = DebugWindow (stack_width stack_0) (image_height (pic_image p))
82− spans = spans_for_pic p (region_for_window w)
83− in verify_all_spans_have_width (pic_image p) spans (stack_width stack_0)
84−
85−single_attr_single_span_stack_cropped_3 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
86−single_attr_single_span_stack_cropped_3 stack_0 stack_1 =
87− let p = pic_for_image (stack_image stack_0 <|> stack_image stack_1)
88− w = DebugWindow (image_width (pic_image p)) expected_row_count
89− spans = spans_for_pic p (region_for_window w)
90− expected_row_count = image_height (pic_image p) `div` 2
91− actual_row_count = span_ops_effected_rows spans
92− in expected_row_count == actual_row_count
93−
94−single_attr_single_span_stack_cropped_4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
95−single_attr_single_span_stack_cropped_4 stack_0 stack_1 =
96− let p = pic_for_image (stack_image stack_0 <-> stack_image stack_1)
97− w = DebugWindow expected_width (image_height (pic_image p))
98− spans = spans_for_pic p (region_for_window w)
99− expected_width = image_width (pic_image p) `div` 2
100− in verify_all_spans_have_width (pic_image p) spans expected_width
101−
102−single_attr_single_span_stack_cropped_5 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool
103−single_attr_single_span_stack_cropped_5 stack_0 stack_1 =
104− let p = pic_for_image (stack_image stack_0 <-> stack_image stack_1)
105− w = DebugWindow (image_width (pic_image p)) (stack_height stack_0)
106− spans = spans_for_pic p (region_for_window w)
107− expected_row_count = stack_height stack_0
108− actual_row_count = span_ops_effected_rows spans
109− in expected_row_count == actual_row_count
110−
111−horiz_span_image_and_greater_window_0 :: SingleRowSingleAttrImage -> Result
112−horiz_span_image_and_greater_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =
113− let p = pic_for_image i
114− -- SingleRowSingleAttrImage always has width >= 1
115− greater_width = c * 2
116− w = DebugWindow greater_width 1
117− spans = spans_for_pic p (region_for_window w)
118− in verify_all_spans_have_width i spans greater_width
119−
120−arb_image_is_cropped :: DefaultImage -> DebugWindow -> Bool
121−arb_image_is_cropped (DefaultImage image _) win@(DebugWindow w h) =
122− let pic = pic_for_image image
123− spans = spans_for_pic pic (region_for_window win)
124− in ( span_ops_effected_rows spans == h ) && ( all_spans_have_width spans w )
125−
126−span_ops_actually_fill_rows :: DefaultPic -> Bool
127−span_ops_actually_fill_rows (DefaultPic pic win _) =
128− let spans = spans_for_pic pic (region_for_window win)
129− expected_row_count = region_height (region_for_window win)
130− actual_row_count = span_ops_effected_rows spans
131− in expected_row_count == actual_row_count
132−
133−span_ops_actually_fill_columns :: DefaultPic -> Bool
134−span_ops_actually_fill_columns (DefaultPic pic win _) =
135− let spans = spans_for_pic pic (region_for_window win)
136− expected_column_count = region_width (region_for_window win)
137− in all_spans_have_width spans expected_column_count
138−
139−first_span_op_sets_attr :: DefaultPic -> Bool
140−first_span_op_sets_attr DefaultPic { default_pic = pic, default_win = win } =
141− let spans = spans_for_pic pic (region_for_window win)
142− in all ( is_attr_span_op . head ) ( elems $ row_ops spans )
143−
144−single_attr_single_span_stack_op_coverage :: SingleAttrSingleSpanStack -> Result
145−single_attr_single_span_stack_op_coverage stack =
146− let p = pic_for_image (stack_image stack)
147− w = DebugWindow (stack_width stack) (stack_height stack)
148− spans = spans_for_pic p (region_for_window w)
149− in verify_all_spans_have_width (stack_image stack) spans (stack_width stack)
150−
151−main :: IO ()
152−main = run_test $ do
153− verify "unit image is cropped when window size == (0,0) [0]" unit_image_and_zero_window_0
154− verify "unit image is cropped when window size == (0,0) [1]" unit_image_and_zero_window_1
155− verify "horiz span image is cropped when window size == (0,0) [0]" horiz_span_image_and_zero_window_0
156− verify "horiz span image is cropped when window size == (0,0) [1]" horiz_span_image_and_zero_window_1
157− verify "horiz span image is not cropped when window size == size of image [width]" horiz_span_image_and_equal_window_0
158− verify "horiz span image is not cropped when window size == size of image [height]" horiz_span_image_and_equal_window_1
159− verify "horiz span image is not cropped when window size < size of image [width]" horiz_span_image_and_lesser_window_0
160− verify "horiz span image is not cropped when window size > size of image [width]" horiz_span_image_and_greater_window_0
161− verify "arbitrary image is padded or cropped" arb_image_is_cropped
162− verify "The span ops actually define content for all the rows in the output region" span_ops_actually_fill_rows
163− verify "The span ops actually define content for all the columns in the output region" span_ops_actually_fill_columns
164− verify "first span op is always to set the text attribute" first_span_op_sets_attr
165− verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"
166− single_attr_single_span_stack_op_coverage
167− verify "a single attr text span is cropped when window size < size of stack image [width]"
168− single_attr_single_span_stack_cropped_0
169− verify "a single attr text span is cropped when window size < size of stack image [height]"
170− single_attr_single_span_stack_cropped_1
171− verify "single attr text span <|> single attr text span cropped. [width]"
172− single_attr_single_span_stack_cropped_2
173− verify "single attr text span <|> single attr text span cropped. [height]"
174− single_attr_single_span_stack_cropped_3
175− verify "single attr text span <-> single attr text span cropped. [width]"
176− single_attr_single_span_stack_cropped_4
177− verify "single attr text span <-> single attr text span cropped. [height]"
178− single_attr_single_span_stack_cropped_5
179− return ()
test/verify_utf8_width.hsdeleted−17
@@ -1,17 +0,0 @@
1−module Main where
2−import Verify
3−
4−import Graphics.Vty.Attributes
5−import Graphics.Vty.Picture
6−
7−sw_is_1_column :: SingleColumnChar -> Bool
8−sw_is_1_column (SingleColumnChar c) = image_width (char def_attr c) == 1
9−
10−dw_is_2_column :: DoubleColumnChar -> Bool
11−dw_is_2_column (DoubleColumnChar c) = image_width (char def_attr c) == 2
12−
13−main = run_test $ do
14− verify "sw_is_1_column" sw_is_1_column
15− verify "dw_is_2_column" dw_is_2_column
16− return ()
17−
test/vty-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−5
@@ -2,11 +2,8 @@ import Graphics.Vty
22import Graphics.Vty.Inline
33
44main = do
5− t <- terminal_handle
65 putStr "Not styled. "
7− put_attr_change t $ back_color red >> apply_style underline
6+ putAttrChange_ t $ backColor red >> applyStyle underline
87 putStr " Styled! "
9− put_attr_change t $ default_all
8+ putAttrChange_ t $ defaultAll
109 putStrLn "Not styled."
11− release_terminal t
12− return ()
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+627−96
@@ -1,102 +1,633 @@
1−Name: vty
2−Version: 4.2.1.0
3−License: BSD3
4−License-file: LICENSE
5−Author: Stefan O'Rear, Corey O'Connor
6−Maintainer: Corey O'Connor (coreyoconnor@gmail.com)
7−Homepage: http://trac.haskell.org/vty/
8−Category: User Interfaces
9−Synopsis: A simple terminal access library
10−Description:
11− vty is terminal GUI library in the niche of ncurses. It is intended to be easy to use, have no
12− confusing corner cases, and good support for common terminal types.
1+name: vty
2+version: 5.35.1
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: A simple terminal UI 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.
1314 .
14− Included in the source distribution is a program test/interactive_terminal_test.hs that
15− demonstrates the various features.
15+ See the @vty-examples@ package as well as the program
16+ @test/interactive_terminal_test.hs@ included in the @vty@ package for
17+ examples on how to use the library.
1618 .
17− If your terminal is not behaving as expected the results of the test/interactive_terminal_test.hs
18− program should be sent to the Vty maintainter to aid in debugging the issue.
19− .
20− Notable infelicities: Sometimes poor efficiency; Assumes UTF-8 character encoding support by the
21− terminal;
22− .
23− You can 'darcs get' it from <http://code.haskell.org/vty/>
19+ Import the "Graphics.Vty" convenience module to get access to the core
20+ parts of the library.
2421 .
2522 &#169; 2006-2007 Stefan O'Rear; BSD3 license.
2623 .
27− &#169; 2008-2009 Corey O'Connor; BSD3 license.
28−
29−Build-Depends: base >= 4 && < 5, bytestring, containers, unix
30−Build-Depends: terminfo >= 0.3 && < 0.4
31−Build-Depends: utf8-string >= 0.3 && < 0.4
32−Build-Depends: mtl >= 1.1.0.0 && < 1.2
33−Build-Depends: ghc-prim, parallel < 2
34−Build-Depends: array
35−Build-Depends: parsec >= 2 && < 4
36−Build-Depends: vector-space >= 0.5 && < 0.6
37−Build-Type: Simple
38−Data-Files: README, TODO
39−Exposed-Modules: Graphics.Vty
40− Graphics.Vty.Terminal
41− Graphics.Vty.LLInput
42− Graphics.Vty.Attributes
43− Graphics.Vty.Inline
44− Graphics.Vty.Picture
45− Graphics.Vty.DisplayRegion
46−
47−other-modules: Codec.Binary.UTF8.Width
48− Data.Marshalling
49− Data.Terminfo.Parse
50− Data.Terminfo.Eval
51− Graphics.Vty.DisplayAttributes
52− Graphics.Vty.Image
53− Graphics.Vty.Span
54− Graphics.Vty.Terminal.Generic
55− Graphics.Vty.Terminal.MacOSX
56− Graphics.Vty.Terminal.XTermColor
57− Graphics.Vty.Terminal.TerminfoBased
58−
59−C-Sources: cbits/gwinsz.c
60− cbits/set_term_timing.c
61− cbits/mk_wcwidth.c
62−Include-Dirs: cbits
63−hs-source-dirs: src
64−Extra-Source-Files: test/Makefile
65− test/Bench2.hs
66− test/Bench.hs
67− test/BenchRenderChar.hs
68− test/ControlTable.hs
69− test/HereDoc.hs
70− test/interactive_terminal_test.hs
71− test/Test2.hs
72− test/Test.hs
73− test/verify_attribute_ops.hs
74− test/verify_debug_terminal.hs
75− test/verify_eval_terminfo_caps.hs
76− test/Verify.hs
77− test/verify_image_ops.hs
78− test/verify_image_trans.hs
79− test/verify_parse_terminfo_caps.hs
80− test/verify_picture_ops.hs
81− test/verify_picture_to_span.hs
82− test/verify_span_ops.hs
83− test/verify_utf8_width.hs
84− test/vty_issue_18.hs
85− test/yi_issue_264.hs
86− test/Verify/Graphics/Vty/Attributes.hs
87− test/Verify/Graphics/Vty/Picture.hs
88− test/Verify/Graphics/Vty/Span.hs
89− test/Verify/Graphics/Vty/DisplayRegion.hs
90− test/Verify/Graphics/Vty/Image.hs
91− test/Verify/Data/Terminfo/Parse.hs
92− src/Codec/Binary/UTF8/Debug.hs
93− src/Graphics/Vty/Terminal/Debug.hs
94− src/Graphics/Vty/Debug.hs
95− cbits/gwinsz.c
96− cbits/mk_wcwidth.c
97− cbits/set_term_timing.c
98− cbits/gwinsz.h
99−
100−ghc-options: -funbox-strict-fields -Wall -fno-full-laziness -fspec-constr
101−ghc-prof-options: -funbox-strict-fields -caf-all -Wall -fno-full-laziness -fspec-constr
24+ &#169; Corey O'Connor; BSD3 license.
25+ .
26+ &#169; Jonathan Daugherty; BSD3 license.
27+cabal-version: 1.18
28+build-type: Simple
29+extra-doc-files: README.md,
30+ AUTHORS,
31+ CHANGELOG.md,
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
38+
39+library
40+ default-language: Haskell2010
41+ build-depends: base >= 4.8 && < 5,
42+ blaze-builder >= 0.3.3.2 && < 0.5,
43+ bytestring,
44+ containers,
45+ deepseq >= 1.1 && < 1.5,
46+ directory,
47+ filepath >= 1.0 && < 2.0,
48+ microlens < 0.4.13,
49+ microlens-mtl,
50+ microlens-th,
51+ hashable >= 1.2,
52+ mtl >= 1.1.1.0 && < 2.3,
53+ parallel >= 2.2 && < 3.3,
54+ parsec >= 2 && < 4,
55+ stm,
56+ terminfo >= 0.3 && < 0.5,
57+ transformers >= 0.3.0.0,
58+ text >= 0.11.3,
59+ unix,
60+ utf8-string >= 0.3 && < 1.1,
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
68+
69+ exposed-modules: Graphics.Vty
70+ Graphics.Vty.Attributes
71+ Graphics.Vty.Attributes.Color
72+ Graphics.Vty.Attributes.Color240
73+ Graphics.Vty.Config
74+ Graphics.Vty.Error
75+ Graphics.Vty.Image
76+ Graphics.Vty.Inline
77+ Graphics.Vty.Inline.Unsafe
78+ Graphics.Vty.Input
79+ Graphics.Vty.Input.Events
80+ Graphics.Vty.Picture
81+ Graphics.Vty.Output
82+ Graphics.Text.Width
83+ Codec.Binary.UTF8.Debug
84+ Data.Terminfo.Parse
85+ Data.Terminfo.Eval
86+ Graphics.Vty.Debug
87+ Graphics.Vty.DisplayAttributes
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
97+ Graphics.Vty.PictureToSpans
98+ Graphics.Vty.Span
99+ Graphics.Vty.Output.Mock
100+ Graphics.Vty.Output.Interface
101+ Graphics.Vty.Output.XTermColor
102+ Graphics.Vty.Output.TerminfoBased
103+ Graphics.Vty.UnicodeWidthTable.Types
104+ Graphics.Vty.UnicodeWidthTable.IO
105+ Graphics.Vty.UnicodeWidthTable.Query
106+ Graphics.Vty.UnicodeWidthTable.Install
107+
108+ other-modules: Graphics.Vty.Debug.Image
109+ Graphics.Vty.Input.Terminfo.ANSIVT
110+
111+ c-sources: cbits/gwinsz.c
112+ cbits/set_term_timing.c
113+ cbits/get_tty_erase.c
114+ cbits/mk_wcwidth.c
115+
116+ include-dirs: cbits
117+
118+ hs-source-dirs: src
119+
120+ ghc-options: -O2 -funbox-strict-fields -Wall -fspec-constr -fspec-constr-count=10
121+
122+ ghc-prof-options: -O2 -funbox-strict-fields -caf-all -Wall -fspec-constr -fspec-constr-count=10
123+
124+executable vty-build-width-table
125+ main-is: BuildWidthTable.hs
126+ hs-source-dirs: tools
127+
128+ default-language: Haskell2010
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
142+
143+ default-language: Haskell2010
144+ ghc-options: -threaded
145+
146+ build-depends: vty,
147+ base >= 4.8 && < 5,
148+ containers,
149+ microlens,
150+ microlens-mtl,
151+ mtl >= 1.1.1.0 && < 2.3
152+
153+executable vty-demo
154+ main-is: Demo.hs
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
166+
167+test-suite verify-using-mock-terminal
168+ default-language: Haskell2010
169+
170+ type: detailed-0.9
171+
172+ hs-source-dirs: test
173+
174+ test-module: VerifyUsingMockTerminal
175+
176+ other-modules: Verify
177+ Verify.Graphics.Vty.Attributes
178+ Verify.Graphics.Vty.Prelude
179+ Verify.Graphics.Vty.Picture
180+ Verify.Graphics.Vty.Image
181+ Verify.Graphics.Vty.Span
182+ Verify.Graphics.Vty.Output
183+
184+ build-depends: vty,
185+ Cabal >= 1.20,
186+ QuickCheck >= 2.7,
187+ random >= 1.0 && < 1.3,
188+ base >= 4.8 && < 5,
189+ bytestring,
190+ containers,
191+ deepseq >= 1.1 && < 1.5,
192+ mtl >= 1.1.1.0 && < 2.3,
193+ text >= 0.11.3,
194+ terminfo >= 0.3 && < 0.5,
195+ unix,
196+ utf8-string >= 0.3 && < 1.1,
197+ vector >= 0.7
198+
199+test-suite verify-terminal
200+ default-language: Haskell2010
201+
202+ type: detailed-0.9
203+
204+ hs-source-dirs: test
205+
206+ test-module: VerifyOutput
207+
208+ other-modules: Verify
209+ Verify.Graphics.Vty.Attributes
210+ Verify.Graphics.Vty.Prelude
211+ Verify.Graphics.Vty.Picture
212+ Verify.Graphics.Vty.Image
213+ Verify.Graphics.Vty.Span
214+ Verify.Graphics.Vty.Output
215+
216+ build-depends: vty,
217+ Cabal >= 1.20,
218+ QuickCheck >= 2.7,
219+ random >= 1.0 && < 1.3,
220+ base >= 4.8 && < 5,
221+ bytestring,
222+ containers,
223+ deepseq >= 1.1 && < 1.5,
224+ mtl >= 1.1.1.0 && < 2.3,
225+ terminfo >= 0.3 && < 0.5,
226+ text >= 0.11.3,
227+ unix,
228+ utf8-string >= 0.3 && < 1.1,
229+ vector >= 0.7
230+
231+test-suite verify-display-attributes
232+ default-language: Haskell2010
233+
234+ type: detailed-0.9
235+
236+ hs-source-dirs: test
237+
238+ test-module: VerifyDisplayAttributes
239+
240+ other-modules: Verify
241+ Verify.Graphics.Vty.Attributes
242+ Verify.Graphics.Vty.DisplayAttributes
243+ Verify.Graphics.Vty.Prelude
244+ Verify.Graphics.Vty.Picture
245+ Verify.Graphics.Vty.Image
246+ Verify.Graphics.Vty.Span
247+
248+ build-depends: vty,
249+ Cabal >= 1.20,
250+ QuickCheck >= 2.7,
251+ random >= 1.0 && < 1.3,
252+ base >= 4.8 && < 5,
253+ bytestring,
254+ containers,
255+ deepseq >= 1.1 && < 1.5,
256+ mtl >= 1.1.1.0 && < 2.3,
257+ text >= 0.11.3,
258+ unix,
259+ utf8-string >= 0.3 && < 1.1,
260+ vector >= 0.7
261+
262+test-suite verify-empty-image-props
263+ default-language: Haskell2010
264+
265+ type: detailed-0.9
266+
267+ hs-source-dirs: test
268+
269+ test-module: VerifyEmptyImageProps
270+
271+ other-modules: Verify
272+
273+ build-depends: vty,
274+ Cabal >= 1.20,
275+ QuickCheck >= 2.7,
276+ random >= 1.0 && < 1.3,
277+ base >= 4.8 && < 5,
278+ bytestring,
279+ containers,
280+ deepseq >= 1.1 && < 1.5,
281+ mtl >= 1.1.1.0 && < 2.3,
282+ text >= 0.11.3,
283+ unix,
284+ utf8-string >= 0.3 && < 1.1,
285+ vector >= 0.7
286+
287+test-suite verify-eval-terminfo-caps
288+ default-language: Haskell2010
289+
290+ type: detailed-0.9
291+
292+ hs-source-dirs: test
293+
294+ test-module: VerifyEvalTerminfoCaps
295+
296+ other-modules: Verify
297+ Verify.Graphics.Vty.Output
298+
299+ build-depends: vty,
300+ Cabal >= 1.20,
301+ QuickCheck >= 2.7,
302+ random >= 1.0 && < 1.3,
303+ base >= 4.8 && < 5,
304+ blaze-builder >= 0.3.3.2 && < 0.5,
305+ bytestring,
306+ containers,
307+ deepseq >= 1.1 && < 1.5,
308+ mtl >= 1.1.1.0 && < 2.3,
309+ terminfo >= 0.3 && < 0.5,
310+ text >= 0.11.3,
311+ unix,
312+ utf8-string >= 0.3 && < 1.1,
313+ vector >= 0.7
314+
315+test-suite verify-image-ops
316+ default-language: Haskell2010
317+
318+ type: detailed-0.9
319+
320+ hs-source-dirs: test
321+
322+ test-module: VerifyImageOps
323+
324+ other-modules: Verify
325+ Verify.Graphics.Vty.Attributes
326+ Verify.Graphics.Vty.Image
327+
328+ build-depends: vty,
329+ Cabal >= 1.20,
330+ QuickCheck >= 2.7,
331+ random >= 1.0 && < 1.3,
332+ base >= 4.8 && < 5,
333+ bytestring,
334+ containers,
335+ deepseq >= 1.1 && < 1.5,
336+ mtl >= 1.1.1.0 && < 2.3,
337+ text >= 0.11.3,
338+ unix,
339+ utf8-string >= 0.3 && < 1.1,
340+ vector >= 0.7
341+
342+test-suite verify-image-trans
343+ default-language: Haskell2010
344+
345+ type: detailed-0.9
346+
347+ hs-source-dirs: test
348+
349+ test-module: VerifyImageTrans
350+
351+ other-modules: Verify
352+ Verify.Graphics.Vty.Attributes
353+ Verify.Graphics.Vty.Image
354+
355+ build-depends: vty,
356+ Cabal >= 1.20,
357+ QuickCheck >= 2.7,
358+ random >= 1.0 && < 1.3,
359+ base >= 4.8 && < 5,
360+ bytestring,
361+ containers,
362+ deepseq >= 1.1 && < 1.5,
363+ mtl >= 1.1.1.0 && < 2.3,
364+ text >= 0.11.3,
365+ unix,
366+ utf8-string >= 0.3 && < 1.1,
367+ vector >= 0.7
368+
369+test-suite verify-inline
370+ default-language: Haskell2010
371+
372+ type: detailed-0.9
373+
374+ hs-source-dirs: test
375+
376+ test-module: VerifyInline
377+
378+ other-modules: Verify
379+ Verify.Graphics.Vty.Output
380+
381+ build-depends: vty,
382+ Cabal >= 1.20,
383+ QuickCheck >= 2.7,
384+ random >= 1.0 && < 1.3,
385+ base >= 4.8 && < 5,
386+ bytestring,
387+ containers,
388+ deepseq >= 1.1 && < 1.5,
389+ mtl >= 1.1.1.0 && < 2.3,
390+ text >= 0.11.3,
391+ unix,
392+ utf8-string >= 0.3 && < 1.1,
393+ vector >= 0.7
394+
395+test-suite verify-parse-terminfo-caps
396+ default-language: Haskell2010
397+
398+ type: detailed-0.9
399+
400+ hs-source-dirs: test
401+
402+ test-module: VerifyParseTerminfoCaps
403+
404+ other-modules: Verify
405+ Verify.Data.Terminfo.Parse
406+ Verify.Graphics.Vty.Output
407+
408+ build-depends: vty,
409+ Cabal >= 1.20,
410+ QuickCheck >= 2.7,
411+ random >= 1.0 && < 1.3,
412+ base >= 4.8 && < 5,
413+ bytestring,
414+ containers,
415+ deepseq >= 1.1 && < 1.5,
416+ mtl >= 1.1.1.0 && < 2.3,
417+ terminfo >= 0.3 && < 0.5,
418+ text >= 0.11.3,
419+ unix,
420+ utf8-string >= 0.3 && < 1.1,
421+ vector >= 0.7
422+
423+test-suite verify-simple-span-generation
424+ default-language: Haskell2010
425+
426+ type: detailed-0.9
427+
428+ hs-source-dirs: test
429+
430+ test-module: VerifySimpleSpanGeneration
431+
432+ other-modules: Verify
433+ Verify.Graphics.Vty.Attributes
434+ Verify.Graphics.Vty.Prelude
435+ Verify.Graphics.Vty.Picture
436+ Verify.Graphics.Vty.Image
437+ Verify.Graphics.Vty.Span
438+
439+ build-depends: vty,
440+ Cabal >= 1.20,
441+ QuickCheck >= 2.7,
442+ random >= 1.0 && < 1.3,
443+ base >= 4.8 && < 5,
444+ bytestring,
445+ containers,
446+ deepseq >= 1.1 && < 1.5,
447+ mtl >= 1.1.1.0 && < 2.3,
448+ text >= 0.11.3,
449+ unix,
450+ utf8-string >= 0.3 && < 1.1,
451+ vector >= 0.7
452+
453+
454+test-suite verify-crop-span-generation
455+ default-language: Haskell2010
456+
457+ type: detailed-0.9
458+
459+ hs-source-dirs: test
460+
461+ test-module: VerifyCropSpanGeneration
462+
463+ other-modules: Verify
464+ Verify.Graphics.Vty.Attributes
465+ Verify.Graphics.Vty.Prelude
466+ Verify.Graphics.Vty.Picture
467+ Verify.Graphics.Vty.Image
468+ Verify.Graphics.Vty.Span
469+
470+ build-depends: vty,
471+ Cabal >= 1.20,
472+ QuickCheck >= 2.7,
473+ random >= 1.0 && < 1.3,
474+ base >= 4.8 && < 5,
475+ bytestring,
476+ containers,
477+ deepseq >= 1.1 && < 1.5,
478+ mtl >= 1.1.1.0 && < 2.3,
479+ text >= 0.11.3,
480+ unix,
481+ utf8-string >= 0.3 && < 1.1,
482+ vector >= 0.7
483+
484+
485+test-suite verify-layers-span-generation
486+ default-language: Haskell2010
487+
488+ type: detailed-0.9
489+
490+ hs-source-dirs: test
491+
492+ test-module: VerifyLayersSpanGeneration
493+
494+ other-modules: Verify
495+ Verify.Graphics.Vty.Attributes
496+ Verify.Graphics.Vty.Prelude
497+ Verify.Graphics.Vty.Picture
498+ Verify.Graphics.Vty.Image
499+ Verify.Graphics.Vty.Span
500+
501+ build-depends: vty,
502+ Cabal >= 1.20,
503+ QuickCheck >= 2.7,
504+ random >= 1.0 && < 1.3,
505+ base >= 4.8 && < 5,
506+ bytestring,
507+ containers,
508+ deepseq >= 1.1 && < 1.5,
509+ mtl >= 1.1.1.0 && < 2.3,
510+ text >= 0.11.3,
511+ unix,
512+ utf8-string >= 0.3 && < 1.1,
513+ vector >= 0.7
514+
515+test-suite verify-color-mapping
516+ default-language: Haskell2010
517+
518+ type: detailed-0.9
519+
520+ hs-source-dirs: test
521+
522+ test-module: VerifyColor240
523+
524+ other-modules: Verify
525+
526+ build-depends: vty,
527+ Cabal >= 1.20,
528+ QuickCheck >= 2.7,
529+ random >= 1.0 && < 1.3,
530+ base >= 4.8 && < 5,
531+ bytestring,
532+ containers,
533+ deepseq >= 1.1 && < 1.5,
534+ mtl >= 1.1.1.0 && < 2.3,
535+ text >= 0.11.3,
536+ unix,
537+ utf8-string >= 0.3 && < 1.1,
538+ vector >= 0.7
539+
540+test-suite verify-utf8-width
541+ default-language: Haskell2010
542+
543+ type: detailed-0.9
544+
545+ hs-source-dirs: test
546+
547+ test-module: VerifyUtf8Width
548+
549+ other-modules: Verify
550+
551+ build-depends: vty,
552+ Cabal >= 1.20,
553+ QuickCheck >= 2.7,
554+ random >= 1.0 && < 1.3,
555+ base >= 4.8 && < 5,
556+ bytestring,
557+ containers,
558+ deepseq >= 1.1 && < 1.5,
559+ mtl >= 1.1.1.0 && < 2.3,
560+ text >= 0.11.3,
561+ unix,
562+ utf8-string >= 0.3 && < 1.1,
563+ vector >= 0.7
564+
565+test-suite verify-using-mock-input
566+ default-language: Haskell2010
567+
568+ type: exitcode-stdio-1.0
569+
570+ hs-source-dirs: test
571+
572+ main-is: VerifyUsingMockInput.hs
573+
574+ other-modules: Verify.Graphics.Vty.Output
575+
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,
584+ base >= 4.8 && < 5,
585+ bytestring,
586+ containers,
587+ deepseq >= 1.1 && < 1.5,
588+ microlens,
589+ microlens-mtl,
590+ mtl >= 1.1.1.0 && < 2.3,
591+ stm,
592+ terminfo >= 0.3 && < 0.5,
593+ text >= 0.11.3,
594+ unix,
595+ utf8-string >= 0.3 && < 1.1,
596+ vector >= 0.7
597+
598+ ghc-options: -threaded -Wall
599+
600+test-suite verify-config
601+ default-language: Haskell2010
602+
603+ type: exitcode-stdio-1.0
604+
605+ hs-source-dirs: test
606+
607+ main-is: VerifyConfig.hs
608+
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,
619+ base >= 4.8 && < 5,
620+ bytestring,
621+ containers,
622+ deepseq >= 1.1 && < 1.5,
623+ microlens,
624+ microlens-mtl,
625+ mtl >= 1.1.1.0 && < 2.3,
626+ string-qq,
627+ terminfo >= 0.3 && < 0.5,
628+ text >= 0.11.3,
629+ unix,
630+ utf8-string >= 0.3 && < 1.1,
631+ vector >= 0.7
102632
633+ ghc-options: -threaded -Wall