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

The content is the same.

install_examplesdeleted−4
@@ -1,4 +0,0 @@
1−#!/bin/bash
2−set -ex
3−cabal install
4−(cd test && cabal install)
src/Codec/Binary/UTF8/Debug.hs+4−6
@@ -1,7 +1,6 @@
1{-# OPTIONS_HADDOCK hide #-}
1{-# OPTIONS_HADDOCK hide #-}
2-- Copyright 2009 Corey O'Connor
2-- Copyright 2009 Corey O'Connor
3−module Codec.Binary.UTF8.Debug
3+module Codec.Binary.UTF8.Debug where
4− where
5
4
6import Codec.Binary.UTF8.String ( encode )
5import Codec.Binary.UTF8.String ( encode )
7
6
@@ -9,11 +8,10 @@ import Data.Word
9
8
10import Numeric
9import Numeric
11
10
12−-- | Converts an array of ISO-10646 characters (Char type) to an array of Word8 bytes that is the
11+-- | Converts an array of ISO-10646 characters (Char type) to an array
13−-- corresponding UTF8 byte sequence
12+-- of Word8 bytes that is the corresponding UTF8 byte sequence
14utf8FromIso :: [Int] -> [Word8]
13utf8FromIso :: [Int] -> [Word8]
15utf8FromIso = encode . map toEnum
14utf8FromIso = encode . map toEnum
16
15
17ppUtf8 :: [Int] -> IO ()
16ppUtf8 :: [Int] -> IO ()
18−ppUtf8 = print . map (\f -> f "") . map showHex . utf8FromIso
17+ppUtf8 = print . map (`showHex` "") . utf8FromIso
19−
src/Data/Sequence/Extra.hs+2−2
@@ -7,7 +7,7 @@ import Data.Sequence
7import Control.Parallel.Strategies
7import Control.Parallel.Strategies
8
8
9instance NFData a => NFData (Seq a) where
9instance NFData a => NFData (Seq a) where
10− rnf = \v -> rnf' (viewl v)
10+ rnf = rnf' . viewl
11− where
11+ where
12 rnf' EmptyL = ()
12 rnf' EmptyL = ()
13 rnf' (a :< r) = rnf a >| rnf' (viewl r)
13 rnf' (a :< r) = rnf a >| rnf' (viewl r)
src/Data/Terminfo/Eval.hs+15−23
@@ -1,18 +1,14 @@
1−{-# LANGUAGE CPP #-}
2{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE NoMonomorphismRestriction #-}
2{-# LANGUAGE NoMonomorphismRestriction #-}
4{-# OPTIONS_GHC -funbox-strict-fields #-}
3{-# OPTIONS_GHC -funbox-strict-fields #-}
5{-# OPTIONS_HADDOCK hide #-}
4{-# OPTIONS_HADDOCK hide #-}
6
5
7−#ifndef MIN_VERSION_base
8−#defined MIN_VERSION_base(x,y,z) 1
9−#endif
10−
11−{- Evaluates the paramaterized terminfo string capability with the given parameters.
6+-- | Evaluates the paramaterized terminfo string capability with the
12− -
7+-- given parameters.
13− -}
14−module Data.Terminfo.Eval (writeCapExpr)
8+module Data.Terminfo.Eval
9+ ( writeCapExpr
10+ )
15− where
11+where
16
12
17import Blaze.ByteString.Builder.Word
13import Blaze.ByteString.Builder.Word
18import Blaze.ByteString.Builder
14import Blaze.ByteString.Builder
@@ -27,10 +23,6 @@ import Data.List
27
23
28import qualified Data.Vector.Unboxed as Vector
24import qualified Data.Vector.Unboxed as Vector
29
25
30−#if !(MIN_VERSION_base(4,8,0))
31−import Data.Word
32−#endif
33−
34-- | capability evaluator state
26-- | capability evaluator state
35data EvalState = EvalState
27data EvalState = EvalState
36 { evalStack :: ![CapParam]
28 { evalStack :: ![CapParam]
@@ -50,7 +42,7 @@ pop = do
50
42
51readParam :: Word -> Eval CapParam
43readParam :: Word -> Eval CapParam
52readParam pn = do
44readParam pn = do
53− !params <- get >>= return . evalParams
45+ !params <- evalParams <$> get
54 return $! genericIndex params pn
46 return $! genericIndex params pn
55
47
56push :: CapParam -> Eval ()
48push :: CapParam -> Eval ()
@@ -72,18 +64,18 @@ writeCapExpr cap params =
72 in snd $ runWriter (runStateT (writeCapOps (capOps cap)) s0)
64 in snd $ runWriter (runStateT (writeCapOps (capOps cap)) s0)
73
65
74writeCapOps :: CapOps -> Eval ()
66writeCapOps :: CapOps -> Eval ()
75−writeCapOps ops = mapM_ writeCapOp ops
67+writeCapOps = mapM_ writeCapOp
76
68
77writeCapOp :: CapOp -> Eval ()
69writeCapOp :: CapOp -> Eval ()
78writeCapOp (Bytes !offset !count) = do
70writeCapOp (Bytes !offset !count) = do
79− !cap <- get >>= return . evalExpression
71+ !cap <- evalExpression <$> get
80 let bytes = Vector.take count $ Vector.drop offset (capBytes cap)
72 let bytes = Vector.take count $ Vector.drop offset (capBytes cap)
81 Vector.forM_ bytes $ tell.writeWord8
73 Vector.forM_ bytes $ tell.writeWord8
82writeCapOp DecOut = do
74writeCapOp DecOut = do
83 p <- pop
75 p <- pop
84 forM_ (show p) $ tell.writeWord8.toEnum.fromEnum
76 forM_ (show p) $ tell.writeWord8.toEnum.fromEnum
85writeCapOp CharOut = do
77writeCapOp CharOut = do
86− pop >>= tell.writeWord8.toEnum.fromEnum
78+ pop >>= tell.writeWord8.toEnum.fromEnum
87writeCapOp (PushParam pn) = do
79writeCapOp (PushParam pn) = do
88 readParam pn >>= push
80 readParam pn >>= push
89writeCapOp (PushValue v) = do
81writeCapOp (PushValue v) = do
@@ -91,13 +83,14 @@ writeCapOp (PushValue v) = do
91writeCapOp (Conditional expr parts) = do
83writeCapOp (Conditional expr parts) = do
92 writeCapOps expr
84 writeCapOps expr
93 writeContitionalParts parts
85 writeContitionalParts parts
94− where
86+ where
95 writeContitionalParts [] = return ()
87 writeContitionalParts [] = return ()
96 writeContitionalParts ((trueOps, falseOps) : falseParts) = do
88 writeContitionalParts ((trueOps, falseOps) : falseParts) = do
97 -- (man 5 terminfo)
89 -- (man 5 terminfo)
98− -- Usually the %? expr part pushes a value onto the stack, and %t pops it from the
90+ -- Usually the %? expr part pushes a value onto the stack,
91+ -- and %t pops it from the stack, testing if it is nonzero
99− -- stack, testing if it is nonzero (true). If it is zero (false), control
92+ -- (true). If it is zero (false), control passes to the %e
100− -- passes to the %e (else) part.
93+ -- (else) part.
101 v <- pop
94 v <- pop
102 if v /= 0
95 if v /= 0
103 then writeCapOps trueOps
96 then writeCapOps trueOps
@@ -137,4 +130,3 @@ writeCapOp CompareGt = do
137 v1 <- pop
130 v1 <- pop
138 v0 <- pop
131 v0 <- pop
139 push $ if v0 > v1 then 1 else 0
132 push $ if v0 > v1 then 1 else 0
140−
src/Data/Terminfo/Parse.hs+61−61
@@ -1,22 +1,22 @@
1−{-# LANGUAGE CPP #-}
2{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
3−{-# OPTIONS_HADDOCK hide #-}
2+{-# LANGUAGE CPP #-}
4{-# LANGUAGE NoMonomorphismRestriction #-}
3{-# LANGUAGE NoMonomorphismRestriction #-}
5−{-# LANGUAGE NamedFieldPuns #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
6{-# OPTIONS_GHC -funbox-strict-fields -O #-}
5{-# OPTIONS_GHC -funbox-strict-fields -O #-}
6+{-# OPTIONS_HADDOCK hide #-}
7
7
8−#ifndef MIN_VERSION_base
9−#defined MIN_VERSION_base(x,y,z) 1
10−#endif
11−
12−module Data.Terminfo.Parse ( module Data.Terminfo.Parse
8+module Data.Terminfo.Parse
9+ ( module Data.Terminfo.Parse
13− , Text.Parsec.ParseError
10+ , Text.Parsec.ParseError
14− )
11+ )
15− where
12+where
16
13
17import Control.Monad ( liftM )
14import Control.Monad ( liftM )
18import Control.DeepSeq
15import Control.DeepSeq
19
16
17+#if !(MIN_VERSION_base(4,11,0))
18+import Data.Semigroup (Semigroup(..))
19+#endif
20import Data.Word
20import Data.Word
21import qualified Data.Vector.Unboxed as Vector
21import qualified Data.Vector.Unboxed as Vector
22
22
@@ -24,10 +24,6 @@ import Numeric (showHex)
24
24
25import Text.Parsec
25import Text.Parsec
26
26
27−#if !(MIN_VERSION_base(4,8,0))
28−import Data.Monoid
29−#endif
30−
31data CapExpression = CapExpression
27data CapExpression = CapExpression
32 { capOps :: !CapOps
28 { capOps :: !CapOps
33 , capBytes :: !(Vector.Vector Word8)
29 , capBytes :: !(Vector.Vector Word8)
@@ -43,24 +39,24 @@ instance Show CapExpression where
43 ++ " <= " ++ show (sourceString c)
39 ++ " <= " ++ show (sourceString c)
44 where
40 where
45 hexDump :: [Word8] -> String
41 hexDump :: [Word8] -> String
46− hexDump = foldr (\b s -> showHex b s) ""
42+ hexDump = foldr showHex ""
47
43
48instance NFData CapExpression where
44instance NFData CapExpression where
49− rnf (CapExpression ops !_bytes !str !c !pOps)
45+ rnf (CapExpression ops !_bytes !str !c !pOps)
50 = rnf ops `seq` rnf str `seq` rnf c `seq` rnf pOps
46 = rnf ops `seq` rnf str `seq` rnf c `seq` rnf pOps
51
47
52type CapParam = Word
48type CapParam = Word
53
49
54type CapOps = [CapOp]
50type CapOps = [CapOp]
55−data CapOp =
51+data CapOp =
56 Bytes !Int !Int -- offset count
52 Bytes !Int !Int -- offset count
57 | DecOut | CharOut
53 | DecOut | CharOut
58− -- This stores a 0-based index to the parameter. However the operation that implies this op is
54+ -- This stores a 0-based index to the parameter. However the
59− -- 1-based
55+ -- operation that implies this op is 1-based
60 | PushParam !Word | PushValue !Word
56 | PushParam !Word | PushValue !Word
61− -- The conditional parts are the sequence of (%t expression, %e expression) pairs.
57+ -- The conditional parts are the sequence of (%t expression, %e
62− -- The %e expression may be NOP
58+ -- The expression) pairs. %e expression may be NOP
63− | Conditional
59+ | Conditional
64 { conditionalExpr :: !CapOps
60 { conditionalExpr :: !CapOps
65 , conditionalParts :: ![(CapOps, CapOps)]
61 , conditionalParts :: ![(CapOps, CapOps)]
66 }
62 }
@@ -72,8 +68,8 @@ data CapOp =
72instance NFData CapOp where
68instance NFData CapOp where
73 rnf (Bytes offset byteCount ) = rnf offset `seq` rnf byteCount
69 rnf (Bytes offset byteCount ) = rnf offset `seq` rnf byteCount
74 rnf (PushParam pn) = rnf pn
70 rnf (PushParam pn) = rnf pn
75− rnf (PushValue v) = rnf v
71+ rnf (PushValue v) = rnf v
76− rnf (Conditional cExpr cParts) = rnf cExpr `seq` rnf cParts
72+ rnf (Conditional cExpr cParts) = rnf cExpr `seq` rnf cParts
77 rnf BitwiseOr = ()
73 rnf BitwiseOr = ()
78 rnf BitwiseXOr = ()
74 rnf BitwiseXOr = ()
79 rnf BitwiseAnd = ()
75 rnf BitwiseAnd = ()
@@ -94,51 +90,51 @@ instance NFData ParamOp where
94 rnf IncFirstTwo = ()
90 rnf IncFirstTwo = ()
95
91
96parseCapExpression :: String -> Either ParseError CapExpression
92parseCapExpression :: String -> Either ParseError CapExpression
97−parseCapExpression capString =
93+parseCapExpression capString =
98 let v = runParser capExpressionParser
94 let v = runParser capExpressionParser
99 initialBuildState
95 initialBuildState
100− "terminfo cap"
96+ "terminfo cap"
101− capString
97+ capString
102 in case v of
98 in case v of
103 Left e -> Left e
99 Left e -> Left e
104 Right buildResults -> Right $ constructCapExpression capString buildResults
100 Right buildResults -> Right $ constructCapExpression capString buildResults
105
101
106−constructCapExpression :: [Char] -> BuildResults -> CapExpression
102+constructCapExpression :: String -> BuildResults -> CapExpression
107constructCapExpression capString buildResults =
103constructCapExpression capString buildResults =
108 let expr = CapExpression
104 let expr = CapExpression
109 { capOps = outCapOps buildResults
105 { capOps = outCapOps buildResults
110− -- The cap bytes are the lower 8 bits of the input string's characters.
106+ -- The cap bytes are the lower 8 bits of the input
111− -- \todo Verify the input string actually contains an 8bit byte per character.
107+ -- string's characters.
112 , capBytes = Vector.fromList $ map (toEnum.fromEnum) capString
108 , capBytes = Vector.fromList $ map (toEnum.fromEnum) capString
113 , sourceString = capString
109 , sourceString = capString
114 , paramCount = outParamCount buildResults
110 , paramCount = outParamCount buildResults
115 , paramOps = outParamOps buildResults
111 , paramOps = outParamOps buildResults
116− }
112+ }
117 in rnf expr `seq` expr
113 in rnf expr `seq` expr
118
114
119−type CapParser a = Parsec String BuildState a
115+type CapParser a = Parsec String BuildState a
120
116
121capExpressionParser :: CapParser BuildResults
117capExpressionParser :: CapParser BuildResults
122capExpressionParser = do
118capExpressionParser = do
123− rs <- many $ paramEscapeParser <|> bytesOpParser
119+ rs <- many $ paramEscapeParser <|> bytesOpParser
124 return $ mconcat rs
120 return $ mconcat rs
125
121
126paramEscapeParser :: CapParser BuildResults
122paramEscapeParser :: CapParser BuildResults
127paramEscapeParser = do
123paramEscapeParser = do
128 _ <- char '%'
124 _ <- char '%'
129 incOffset 1
125 incOffset 1
130− literalPercentParser <|> paramOpParser
126+ literalPercentParser <|> paramOpParser
131
127
132literalPercentParser :: CapParser BuildResults
128literalPercentParser :: CapParser BuildResults
133literalPercentParser = do
129literalPercentParser = do
134 _ <- char '%'
130 _ <- char '%'
135− startOffset <- getState >>= return . nextOffset
131+ startOffset <- nextOffset <$> getState
136 incOffset 1
132 incOffset 1
137 return $ BuildResults 0 [Bytes startOffset 1] []
133 return $ BuildResults 0 [Bytes startOffset 1] []
138
134
139paramOpParser :: CapParser BuildResults
135paramOpParser :: CapParser BuildResults
140paramOpParser
136paramOpParser
141− = incrementOpParser
137+ = incrementOpParser
142 <|> pushOpParser
138 <|> pushOpParser
143 <|> decOutParser
139 <|> decOutParser
144 <|> charOutParser
140 <|> charOutParser
@@ -158,7 +154,7 @@ incrementOpParser = do
158pushOpParser :: CapParser BuildResults
154pushOpParser :: CapParser BuildResults
159pushOpParser = do
155pushOpParser = do
160 _ <- char 'p'
156 _ <- char 'p'
161− paramN <- digit >>= return . (\d -> read [d])
157+ paramN <- read . pure <$> digit
162 incOffset 2
158 incOffset 2
163 return $ BuildResults (fromEnum paramN) [PushParam $ paramN - 1] []
159 return $ BuildResults (fromEnum paramN) [PushParam $ paramN - 1] []
164
160
@@ -179,16 +175,16 @@ conditionalOpParser = do
179 _ <- char '?'
175 _ <- char '?'
180 incOffset 1
176 incOffset 1
181 condPart <- manyExpr conditionalTrueParser
177 condPart <- manyExpr conditionalTrueParser
182− parts <- manyP
178+ parts <- manyP
183 ( do
179 ( do
184 truePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser
180 truePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser
185− , conditionalFalseParser
181+ , conditionalFalseParser
186 ]
182 ]
187 falsePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser
183 falsePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser
188 , conditionalTrueParser
184 , conditionalTrueParser
189 ]
185 ]
190 return ( truePart, falsePart )
186 return ( truePart, falsePart )
191− )
187+ )
192 conditionalEndParser
188 conditionalEndParser
193
189
194 let trueParts = map fst parts
190 let trueParts = map fst parts
@@ -208,10 +204,10 @@ conditionalOpParser = do
208
204
209 return $ BuildResults n'' [ Conditional cond condParts ] pOps
205 return $ BuildResults n'' [ Conditional cond condParts ] pOps
210
206
211− where
207+ where
212− manyP !p !end = choice
208+ manyP !p !end = choice
213 [ try end >> return []
209 [ try end >> return []
214− , do !v <- p
210+ , do !v <- p
215 !vs <- manyP p end
211 !vs <- manyP p end
216 return $! v : vs
212 return $! v : vs
217 ]
213 ]
@@ -233,7 +229,7 @@ conditionalEndParser = do
233 incOffset 2
229 incOffset 2
234
230
235bitwiseOpParser :: CapParser BuildResults
231bitwiseOpParser :: CapParser BuildResults
236−bitwiseOpParser
232+bitwiseOpParser
237 = bitwiseOrParser
233 = bitwiseOrParser
238 <|> bitwiseAndParser
234 <|> bitwiseAndParser
239 <|> bitwiseXorParser
235 <|> bitwiseXorParser
@@ -257,9 +253,9 @@ bitwiseXorParser = do
257 return $ BuildResults 0 [ BitwiseXOr ] [ ]
253 return $ BuildResults 0 [ BitwiseXOr ] [ ]
258
254
259arithOpParser :: CapParser BuildResults
255arithOpParser :: CapParser BuildResults
260−arithOpParser
256+arithOpParser
261− = plusOp
257+ = plusOp
262− <|> minusOp
258+ <|> minusOp
263 where
259 where
264 plusOp = do
260 plusOp = do
265 _ <- char '+'
261 _ <- char '+'
@@ -282,10 +278,10 @@ literalIntOpParser = do
282 return $ BuildResults 0 [ PushValue n ] [ ]
278 return $ BuildResults 0 [ PushValue n ] [ ]
283
279
284compareOpParser :: CapParser BuildResults
280compareOpParser :: CapParser BuildResults
285−compareOpParser
281+compareOpParser
286 = compareEqOp
282 = compareEqOp
287− <|> compareLtOp
283+ <|> compareLtOp
288− <|> compareGtOp
284+ <|> compareGtOp
289 where
285 where
290 compareEqOp = do
286 compareEqOp = do
291 _ <- char '='
287 _ <- char '='
@@ -303,7 +299,7 @@ compareOpParser
303bytesOpParser :: CapParser BuildResults
299bytesOpParser :: CapParser BuildResults
304bytesOpParser = do
300bytesOpParser = do
305 bytes <- many1 $ satisfy (/= '%')
301 bytes <- many1 $ satisfy (/= '%')
306− startOffset <- getState >>= return . nextOffset
302+ startOffset <- nextOffset <$> getState
307 let !c = length bytes
303 let !c = length bytes
308 !s <- getState
304 !s <- getState
309 let s' = s { nextOffset = startOffset + c }
305 let s' = s { nextOffset = startOffset + c }
@@ -313,14 +309,14 @@ bytesOpParser = do
313charConstParser :: CapParser BuildResults
309charConstParser :: CapParser BuildResults
314charConstParser = do
310charConstParser = do
315 _ <- char '\''
311 _ <- char '\''
316− charValue <- liftM (toEnum . fromEnum) anyChar
312+ charValue <- liftM (toEnum . fromEnum) anyChar
317 _ <- char '\''
313 _ <- char '\''
318 incOffset 3
314 incOffset 3
319 return $ BuildResults 0 [ PushValue charValue ] [ ]
315 return $ BuildResults 0 [ PushValue charValue ] [ ]
320
316
321−data BuildState = BuildState
317+data BuildState = BuildState
322 { nextOffset :: Int
318 { nextOffset :: Int
323− }
319+ }
324
320
325incOffset :: Int -> CapParser ()
321incOffset :: Int -> CapParser ()
326incOffset n = do
322incOffset n = do
@@ -337,12 +333,16 @@ data BuildResults = BuildResults
337 , outParamOps :: !ParamOps
333 , outParamOps :: !ParamOps
338 }
334 }
339
335
340−instance Monoid BuildResults where
336+instance Semigroup BuildResults where
341− mempty = BuildResults 0 [] []
342− v0 `mappend` v1
337+ v0 <> v1
343 = BuildResults
338 = BuildResults
344− { outParamCount = (outParamCount v0) `max` (outParamCount v1)
339+ { outParamCount = outParamCount v0 `max` outParamCount v1
345− , outCapOps = (outCapOps v0) `mappend` (outCapOps v1)
340+ , outCapOps = outCapOps v0 <> outCapOps v1
346− , outParamOps = (outParamOps v0) `mappend` (outParamOps v1)
341+ , outParamOps = outParamOps v0 <> outParamOps v1
347 }
342 }
348
343
344+instance Monoid BuildResults where
345+ mempty = BuildResults 0 [] []
346+#if !(MIN_VERSION_base(4,11,0))
347+ mappend = (<>)
348+#endif
src/Graphics/Text/Width.hs+51−16
@@ -1,29 +1,64 @@
1-- Copyright 2009 Corey O'Connor
1-- Copyright 2009 Corey O'Connor
2{-# OPTIONS_GHC -D_XOPEN_SOURCE #-}
2{-# OPTIONS_GHC -D_XOPEN_SOURCE #-}
3{-# LANGUAGE ForeignFunctionInterface #-}
3{-# LANGUAGE ForeignFunctionInterface #-}
4+-- | This module provides functions to measure the terminal column width
5+-- of characters and strings.
6+--
7+-- The functions provided in this module all ultimately make calls to
8+-- the C implementation in @cbits/mk_wcwidth.c@. That code manages some
9+-- global state that carries a table of Unicode character widths. For
10+-- more details, see 'Graphics.Vty.UnicodeWidthTable.Install', the C
11+-- code, and the "Multi-Column Character Support" section of the project
12+-- @README@.
4−module Graphics.Text.Width ( wcwidth
13+module Graphics.Text.Width
14+ ( wcwidth
5− , wcswidth
15+ , wcswidth
16+ , wctwidth
17+ , wctlwidth
6− , safeWcwidth
18+ , safeWcwidth
7− , safeWcswidth
19+ , safeWcswidth
20+ , safeWctwidth
21+ , safeWctlwidth
8− )
22+ )
9− where
23+where
24+
25+import Data.List (foldl')
26+import qualified Data.Text as T
27+import qualified Data.Text.Lazy as TL
10
28
11foreign import ccall unsafe "vty_mk_wcwidth" wcwidth :: Char -> Int
29foreign import ccall unsafe "vty_mk_wcwidth" wcwidth :: Char -> Int
12
30
13wcswidth :: String -> Int
31wcswidth :: String -> Int
14−wcswidth = sum . map wcwidth
32+wcswidth = foldl' (\l c -> wcwidth c + l) 0
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
15
40
16−-- XXX: Characters with unknown widths occupy 1 column?
41+{-# RULES
17−--
42+"wcswidth/unpack" forall x. wcswidth (T.unpack x) = wctwidth x
18−-- Not sure if this is actually correct. I presume there is a replacement character that is output
43+"wcswidth/lazy-unpack" forall x. wcswidth (TL.unpack x) = wctlwidth x
19−-- by the terminal instead of the character and this replacement character is 1 column wide. If this
44+ #-}
20−-- is not true for all terminals then a per-terminal replacement character width needs to be
21−-- implemented.
22
45
23−-- | Returns the display width of a character. Assumes all characters with unknown widths are 0 width
46+-- | Returns the display width of a character. Assumes all characters
47+-- with unknown widths are 0 width.
24safeWcwidth :: Char -> Int
48safeWcwidth :: Char -> Int
25safeWcwidth = max 0 . wcwidth
49safeWcwidth = max 0 . wcwidth
26
50
27−-- | Returns the display width of a string. Assumes all characters with unknown widths are 0 width
51+-- | Returns the display width of a string. Assumes all characters with
52+-- unknown widths are 0 width.
28safeWcswidth :: String -> Int
53safeWcswidth :: String -> Int
29−safeWcswidth = sum . map safeWcwidth
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+173−90
@@ -1,17 +1,20 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+
1−-- | Vty supports input and output to terminal devices.
4+-- | Vty provides interfaces for both terminal input and terminal
2−--
5+-- output.
3−-- - Input to the terminal is provided to the app as a sequence of 'Event's.
4−--
5−-- - The output is defined by a 'Picture'. Which is one or more layers of 'Image's.
6--
6--
7−-- - The module "Graphics.Vty.Image" provides a number of constructor equations that will build
7+-- - Input to the terminal is provided to the Vty application as a
8−-- correct 'Image' values. See 'string', '<|>', and '<->' for starters.
8+-- sequence of 'Event's.
9--
9--
10−-- - The constructors in "Graphics.Vty.Image.Internal" should not be used.
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.
11--
15--
12−-- - 'Image's can be styled using 'Attr'. See "Graphics.Vty.Attributes".
16+-- Vty uses threads internally, so programs made with Vty need to be
13−--
17+-- compiled with the threaded runtime using the GHC @-threaded@ option.
14−-- See the vty-examples package for a number of examples.
15--
18--
16-- @
19-- @
17-- import "Graphics.Vty"
20-- import "Graphics.Vty"
@@ -28,103 +31,153 @@
28-- 'shutdown' vty
31-- 'shutdown' vty
29-- 'print' (\"Last event was: \" '++' 'show' e)
32-- 'print' (\"Last event was: \" '++' 'show' e)
30-- @
33-- @
31−--
32−-- Good sources of documentation for terminal programming are:
33−--
34−-- - <https://github.com/b4winckler/vim/blob/master/src/term.c>
35−--
36−-- - <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
37−--
38−-- - <http://ulisse.elettra.trieste.it/services/doc/serial/config.html>
39−--
40−-- - <http://www.leonerd.org.uk/hacks/hints/xterm-8bit.html>
41−--
42−-- - <http://www.unixwiz.net/techtips/termios-vmin-vtime.html>
43−--
44−-- - <http://vt100.net/docs/vt100-ug/chapter3.html vt100 control sequences>
45−module Graphics.Vty ( Vty(..)
34+module Graphics.Vty
35+ ( Vty(..)
46− , mkVty
36+ , mkVty
37+ , setWindowTitle
38+ , Mode(..)
47− , module Graphics.Vty.Config
39+ , module Graphics.Vty.Config
48− , module Graphics.Vty.Input
40+ , module Graphics.Vty.Input
49− , module Graphics.Vty.Output
41+ , module Graphics.Vty.Output
42+ , module Graphics.Vty.Output.Interface
50− , module Graphics.Vty.Picture
43+ , module Graphics.Vty.Picture
51− , DisplayRegion
44+ , module Graphics.Vty.Image
52− , Mode(..)
45+ , module Graphics.Vty.Attributes
53− )
46+ )
54− where
47+where
55−
56−import Graphics.Vty.Prelude
57
48
58import Graphics.Vty.Config
49import Graphics.Vty.Config
59import Graphics.Vty.Input
50import Graphics.Vty.Input
51+import Graphics.Vty.Input.Events
60import Graphics.Vty.Output
52import Graphics.Vty.Output
61import Graphics.Vty.Output.Interface
53import Graphics.Vty.Output.Interface
62import Graphics.Vty.Picture
54import Graphics.Vty.Picture
55+import Graphics.Vty.Image
56+import Graphics.Vty.Attributes
57+import Graphics.Vty.UnicodeWidthTable.IO
58+import Graphics.Vty.UnicodeWidthTable.Install
63
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)
64import Control.Concurrent.STM
65import Control.Concurrent.STM
65
66
66import Data.IORef
67import Data.IORef
68+#if !(MIN_VERSION_base(4,11,0))
67−import Data.Monoid
69+import Data.Semigroup ((<>))
70+#endif
68
71
69−-- | The main object. At most one should be created.
72+-- | A Vty value represents a handle to the Vty library that the
73+-- application must create in order to use Vty.
70--
74--
71-- The use of Vty typically follows this process:
75-- The use of Vty typically follows this process:
72--
76--
73−-- 0. initialize vty
77+-- 1. Initialize vty with 'mkVty' (this takes control of the terminal).
74−--
78+--
75−-- 1. use the update equation of Vty to display a picture
79+-- 2. Use 'update' to display a picture.
76−--
77−-- 2. repeat
78−--
80+--
79−-- 3. shutdown vty.
81+-- 3. Use 'nextEvent' to get the next input event.
80−--
82+--
81−-- An alternative to tracking the Vty instance is to use 'withVty' in "Graphics.Vty.Inline.Unsafe".
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.
82−--
87+--
83−-- This does not assure any thread safety. In theory, as long as an update action is not executed
88+-- Operations on Vty handles are not thread-safe.
84−-- when another update action is already then it's safe to call this on multiple threads.
85−--
86−-- \todo Remove explicit `shutdown` requirement.
87−data Vty = Vty
89+data Vty =
90+ Vty { update :: Picture -> IO ()
88− { -- | Outputs the given Picture. Equivalent to 'outputPicture' applied to a display context
91+ -- ^ Outputs the given 'Picture'.
89− -- implicitly managed by Vty. The managed display context is reset on resize.
92+ , nextEvent :: IO Event
90− update :: Picture -> IO ()
93+ -- ^ Return the next 'Event' or block until one becomes
91− -- | Get one Event object, blocking if necessary. This will refresh the terminal if the event
94+ -- available.
92− -- is a 'EvResize'.
95+ , nextEventNonblocking :: IO (Maybe Event)
93− , nextEvent :: IO Event
96+ -- ^ Non-blocking version of 'nextEvent'.
94− -- | The input interface. See 'Input'
95− , inputIface :: Input
97+ , inputIface :: Input
96− -- | The output interface. See 'Output'
98+ -- ^ The input interface. See 'Input'.
97− , outputIface :: Output
99+ , outputIface :: Output
98− -- | Refresh the display. 'nextEvent' will refresh the display if a resize occurs.
100+ -- ^ The output interface. See 'Output'.
101+ , refresh :: IO ()
102+ -- ^ Refresh the display. If other programs output to the
99− -- If other programs output to the terminal and mess up the display then the application might
103+ -- terminal and mess up the display then the application might
100− -- want to force a refresh.
104+ -- want to force a refresh using this function.
101− , refresh :: IO ()
105+ , shutdown :: IO ()
102− -- | Clean up after vty.
106+ -- ^ Clean up after vty. A call to this function is necessary to
107+ -- cleanly restore the terminal state before application exit.
103− -- The above methods will throw an exception if executed after this is executed.
108+ -- The above methods will throw an exception if executed after
104− , shutdown :: IO ()
109+ -- this is executed. Idempotent.
110+ , isShutdown :: IO Bool
105− }
111+ }
106
112
107−-- | Set up the state object for using vty. At most one state object should be
113+-- | Create a Vty handle. At most one handle should be created at a time
108−-- created at a time for a given terminal device.
114+-- for a given terminal device.
109--
115--
116+-- The specified configuration is added to the the configuration
110−-- The specified config is added to the 'userConfig'. With the 'userConfig' taking precedence.
117+-- loaded by 'userConfig' with the 'userConfig' configuration taking
111−-- See "Graphics.Vty.Config"
118+-- precedence. See "Graphics.Vty.Config".
112--
119--
113−-- For most applications @mkVty def@ is sufficient.
120+-- For most applications @mkVty defaultConfig@ is sufficient.
114mkVty :: Config -> IO Vty
121mkVty :: Config -> IO Vty
115mkVty appConfig = do
122mkVty appConfig = do
116 config <- (<> appConfig) <$> userConfig
123 config <- (<> appConfig) <$> userConfig
117− input <- inputForConfig config
118− out <- outputForConfig config
119− intMkVty input out
120
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+
121−intMkVty :: Input -> Output -> IO Vty
167+internalMkVty :: Input -> Output -> IO Vty
122−intMkVty input out = do
168+internalMkVty input out = do
123 reserveDisplay out
169 reserveDisplay out
170+
171+ shutdownVar <- newTVarIO False
124 let shutdownIo = do
172 let shutdownIo = do
173+ alreadyShutdown <- atomically $ swapTVar shutdownVar True
174+ when (not alreadyShutdown) $ do
125− shutdownInput input
175+ shutdownInput input
126− releaseDisplay out
176+ releaseDisplay out
127− releaseTerminal out
177+ releaseTerminal out
178+
179+ let shutdownStatus = readTVarIO shutdownVar
180+
128 lastPicRef <- newIORef Nothing
181 lastPicRef <- newIORef Nothing
129 lastUpdateRef <- newIORef Nothing
182 lastUpdateRef <- newIORef Nothing
130
183
@@ -156,17 +209,47 @@ intMkVty input out = do
156 mPic <- readIORef lastPicRef
209 mPic <- readIORef lastPicRef
157 maybe (return ()) innerUpdate mPic
210 maybe (return ()) innerUpdate mPic
158
211
212+ let mkResize = uncurry EvResize <$> displayBounds out
213+
214+ translateInternalEvent ResumeAfterSignal = mkResize
215+ translateInternalEvent (InputEvent e) = return e
216+
217+ gkey = do
159− let gkey = do k <- atomically $ readTChan $ _eventChannel input
218+ e <- atomically $ readTChan $ _eventChannel input
219+ translateInternalEvent e
220+ gkey' = do
221+ mEv <- atomically $ tryReadTChan $ _eventChannel input
160− case k of
222+ case mEv of
161− (EvResize _ _) -> displayBounds out
223+ Just e -> Just <$> translateInternalEvent e
162− >>= return . (\(w,h)-> EvResize w h)
163− _ -> return k
224+ Nothing -> return Nothing
164
225
165 return $ Vty { update = innerUpdate
226 return $ Vty { update = innerUpdate
166 , nextEvent = gkey
227 , nextEvent = gkey
228+ , nextEventNonblocking = gkey'
167 , inputIface = input
229 , inputIface = input
168 , outputIface = out
230 , outputIface = out
169 , refresh = innerRefresh
231 , refresh = innerRefresh
170 , shutdown = shutdownIo
232 , shutdown = shutdownIo
233+ , isShutdown = shutdownStatus
171 }
234 }
172
235
236+-- | Set the terminal window title string.
237+--
238+-- This function emits an Xterm-compatible escape sequence that we
239+-- anticipate will work for essentially all modern terminal emulators.
240+-- Ideally we'd use a terminal capability for this, but there does not
241+-- seem to exist a termcap for setting window titles. If you find that
242+-- this function does not work for a given terminal emulator, please
243+-- report the issue.
244+--
245+-- For details, see:
246+--
247+-- https://tldp.org/HOWTO/Xterm-Title-3.html
248+setWindowTitle :: Vty -> String -> IO ()
249+setWindowTitle vty title = do
250+ let sanitize :: String -> String
251+ sanitize = concatMap sanitizeChar
252+ sanitizeChar c | not (isPrint c) = showLitChar c ""
253+ | otherwise = [c]
254+ let buf = BS8.pack $ "\ESC]2;" <> sanitize title <> "\007"
255+ outputByteBuffer (outputIface vty) buf
src/Graphics/Vty/Attributes.hs+121−120
@@ -1,68 +1,89 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2−{-# LANGUAGE StandaloneDeriving #-}
2+{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE GADTs #-}
3+{-# LANGUAGE DeriveAnyClass #-}
4−{-# LANGUAGE RankNTypes #-}
5−
6−#ifndef MIN_VERSION_base
7−#defined MIN_VERSION_base(x,y,z) 1
8−#endif
9
4
10-- | Display attributes
5-- | Display attributes
11--
6--
12−-- Typically the values 'defAttr' or 'currentAttr' are modified to form attributes:
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.,
13--
11--
14-- @
12-- @
15−-- defAttr `withForeColor` red
13+-- defAttr \`withForeColor\` red
16-- @
14-- @
17--
15--
18−-- Is the attribute that will set the foreground color to red and the background color to the
16+-- 'Image' constructors often require an 'Attr' to indicate the
19−-- default.
17+-- attributes used in the image, e.g.,
20−--
21−-- This can then be used to build an image wiht a red foreground like so:
22--
18--
23-- @
19-- @
24−-- string (defAttr `withForeColor` red) "this text will be red"
20+-- string (defAttr \`withForeColor\` red) "this text will be red"
25-- @
21-- @
26--
22--
27−-- The default attributes set by 'defAttr' have a presentation determined by the terminal. This is
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
28−-- not something VTY can control. The user is free to define the color scheme of the terminal as
25+-- to The define the color scheme of the terminal as they see fit.
29−-- they see fit. Up to the limits of the terminal anyways.
30−--
26+--
31−-- The value 'currentAttr' will keep the attributes of whatever was output previously.
27+-- The value 'currentAttr' will keep the attributes of whatever was
32−--
28+-- output previously.
33−-- \todo This API is very verbose IMO. I'd like something more succinct.
34−module Graphics.Vty.Attributes ( module Graphics.Vty.Attributes
29+module Graphics.Vty.Attributes
35− , module Graphics.Vty.Attributes.Color
30+ ( module Graphics.Vty.Attributes.Color
36− , module Graphics.Vty.Attributes.Color240
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
37− )
59+ )
38− where
60+where
39−
61+
62+import Control.DeepSeq
40import Data.Bits
63import Data.Bits
41−import Data.Default
64+import Data.Text (Text)
42import Data.Word
65import Data.Word
66+import GHC.Generics
43
67
44import Graphics.Vty.Attributes.Color
68import Graphics.Vty.Attributes.Color
45−import Graphics.Vty.Attributes.Color240
46−
47−#if !(MIN_VERSION_base(4,8,0))
48−import Data.Monoid
49−#endif
50
69
51−-- | A display attribute defines the Color and Style of all the characters rendered after the
70+-- | A display attribute defines the Color and Style of all the
52−-- attribute is applied.
71+-- characters rendered after the attribute is applied.
53--
72--
54−-- At most 256 colors, picked from a 240 and 16 color palette, are possible for the background and
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
55−-- foreground. The 240 colors and 16 colors are points in different palettes. See Color for more
75+-- 16 colors are points in different palettes. See Color for more
56−-- information.
76+-- information.
57data Attr = Attr
77data Attr = Attr
58 { attrStyle :: !(MaybeDefault Style)
78 { attrStyle :: !(MaybeDefault Style)
59 , attrForeColor :: !(MaybeDefault Color)
79 , attrForeColor :: !(MaybeDefault Color)
60 , attrBackColor :: !(MaybeDefault Color)
80 , attrBackColor :: !(MaybeDefault Color)
81+ , attrURL :: !(MaybeDefault Text)
61− } deriving ( Eq, Show, Read )
82+ } deriving ( Eq, Show, Read, Generic, NFData )
62
83
63−-- This could be encoded into a single 32 bit word. The 32 bit word is first divided
84+-- This could be encoded into a single 32 bit word. The 32 bit word is
64−-- into 4 groups of 8 bits where: The first group codes what action should be taken with regards to
85+-- first divided into 4 groups of 8 bits where: The first group codes
65−-- the other groups.
86+-- what action should be taken with regards to the other groups.
66-- XXYYZZ__
87-- XXYYZZ__
67-- XX - style action
88-- XX - style action
68-- 00 => reset to default
89-- 00 => reset to default
@@ -79,85 +100,47 @@ data Attr = Attr
79-- __ - unused
100-- __ - unused
80--
101--
81-- Next is the style flags
102-- Next is the style flags
82−-- SURBDO__
103+-- SURBDOI_
83-- S - standout
104-- S - standout
84-- U - underline
105-- U - underline
85-- R - reverse video
106-- R - reverse video
86-- B - blink
107-- B - blink
87-- D - dim
108-- D - dim
88-- O - bold
109-- O - bold
110+-- I - italic
89−-- __ - unused
111+-- _ - unused
90--
112--
91-- Then the foreground color encoded into 8 bits.
113-- Then the foreground color encoded into 8 bits.
92-- Then the background color encoded into 8 bits.
114-- Then the background color encoded into 8 bits.
93
115
94−instance Monoid Attr where
95− mempty = Attr mempty mempty mempty
96− mappend attr0 attr1 =
97− Attr ( attrStyle attr0 `mappend` attrStyle attr1 )
98− ( attrForeColor attr0 `mappend` attrForeColor attr1 )
99− ( attrBackColor attr0 `mappend` attrBackColor attr1 )
100−
101−-- | Specifies the display attributes such that the final style and color values do not depend on
116+-- | Specifies the display attributes such that the final style and
117+-- color values do not depend on the previously applied display
102−-- the previously applied display attribute. The display attributes can still depend on the
118+-- attribute. The display attributes can still depend on the terminal's
103−-- terminal's default colors (unfortunately).
119+-- default colors (unfortunately).
104data FixedAttr = FixedAttr
120data FixedAttr = FixedAttr
105 { fixedStyle :: !Style
121 { fixedStyle :: !Style
106 , fixedForeColor :: !(Maybe Color)
122 , fixedForeColor :: !(Maybe Color)
107 , fixedBackColor :: !(Maybe Color)
123 , fixedBackColor :: !(Maybe Color)
124+ , fixedURL :: !(Maybe Text)
108 } deriving ( Eq, Show )
125 } deriving ( Eq, Show )
109
126
110−-- | The style and color attributes can either be the terminal defaults. Or be equivalent to the
127+-- | The style and color attributes can either be the terminal defaults.
111−-- previously applied style. Or be a specific value.
128+-- Or be equivalent to the previously applied style. Or be a specific
129+-- value.
112−data MaybeDefault v where
130+data MaybeDefault v = Default | KeepCurrent | SetTo !v
113− Default :: MaybeDefault v
131+ deriving (Eq, Read, Show)
114− KeepCurrent :: MaybeDefault v
132+
115− SetTo :: forall v . ( Eq v, Show v, Read v ) => !v -> MaybeDefault v
116−
117−deriving instance Eq v => Eq (MaybeDefault v)
118−deriving instance Eq v => Show (MaybeDefault v)
119−deriving instance (Eq v, Show v, Read v) => Read (MaybeDefault v)
120−
121−instance Eq v => Monoid ( MaybeDefault v ) where
133+instance (NFData v) => NFData (MaybeDefault v) where
122− mempty = KeepCurrent
134+ rnf Default = ()
123− mappend Default Default = Default
124− mappend Default KeepCurrent = Default
125− mappend Default ( SetTo v ) = SetTo v
126− mappend KeepCurrent Default = Default
127− mappend KeepCurrent KeepCurrent = KeepCurrent
135+ rnf KeepCurrent = ()
128− mappend KeepCurrent ( SetTo v ) = SetTo v
136+ rnf (SetTo v) = rnf v
129− mappend ( SetTo _v ) Default = Default
137+
130− mappend ( SetTo v ) KeepCurrent = SetTo v
131− mappend ( SetTo _ ) ( SetTo v ) = SetTo v
132−
133−-- | Standard 8-color ANSI terminal color codes.
134−black, red, green, yellow, blue, magenta, cyan, white :: Color
135−black = ISOColor 0
136−red = ISOColor 1
137−green = ISOColor 2
138−yellow = ISOColor 3
139−blue = ISOColor 4
140−magenta= ISOColor 5
141−cyan = ISOColor 6
142−white = ISOColor 7
143−
144−-- | Bright/Vivid variants of the standard 8-color ANSI
145−brightBlack, brightRed, brightGreen, brightYellow :: Color
146−brightBlue, brightMagenta, brightCyan, brightWhite :: Color
147−brightBlack = ISOColor 8
148−brightRed = ISOColor 9
149−brightGreen = ISOColor 10
150−brightYellow = ISOColor 11
151−brightBlue = ISOColor 12
152−brightMagenta= ISOColor 13
153−brightCyan = ISOColor 14
154−brightWhite = ISOColor 15
155−
156−-- | Styles are represented as an 8 bit word. Each bit in the word is 1 if the style attribute
138+-- | Styles are represented as an 8 bit word. Each bit in the word is 1
139+-- if the style attribute assigned to that bit should be applied and 0
157−-- assigned to that bit should be applied and 0 if the style attribute should not be applied.
140+-- if the style attribute should not be applied.
158type Style = Word8
141type Style = Word8
159
142
160−-- | The 6 possible style attributes:
143+-- | Valid style attributes include:
161--
144--
162-- * standout
145-- * standout
163--
146--
@@ -171,21 +154,27 @@ type Style = Word8
171--
154--
172-- * bold/bright
155-- * bold/bright
173--
156--
157+-- * italic
158+--
159+-- * strikethrough (via the smxx/rmxx terminfo capabilities)
160+--
174−-- ( The invisible, protect, and altcharset display attributes some terminals support are not
161+-- (The invisible, protect, and altcharset display attributes some
175−-- supported via VTY.)
162+-- terminals support are not supported via VTY.)
176−standout, underline, reverseVideo, blink, dim, bold :: Style
163+standout, underline, reverseVideo, blink, dim, bold, italic, strikethrough :: Style
177standout = 0x01
164standout = 0x01
178underline = 0x02
165underline = 0x02
179−reverseVideo = 0x04
166+reverseVideo = 0x04
180blink = 0x08
167blink = 0x08
181dim = 0x10
168dim = 0x10
182bold = 0x20
169bold = 0x20
170+italic = 0x40
171+strikethrough = 0x80
183
172
184defaultStyleMask :: Style
173defaultStyleMask :: Style
185defaultStyleMask = 0x00
174defaultStyleMask = 0x00
186
175
187styleMask :: Attr -> Word8
176styleMask :: Attr -> Word8
188−styleMask attr
177+styleMask attr
189 = case attrStyle attr of
178 = case attrStyle attr of
190 Default -> 0
179 Default -> 0
191 KeepCurrent -> 0
180 KeepCurrent -> 0
@@ -205,23 +194,35 @@ withBackColor attr c = attr { attrBackColor = SetTo c }
205
194
206-- | Add the given style attribute
195-- | Add the given style attribute
207withStyle :: Attr -> Style -> Attr
196withStyle :: Attr -> Style -> Attr
197+withStyle attr 0 = attr
208withStyle attr styleFlag = attr { attrStyle = SetTo $ styleMask attr .|. styleFlag }
198withStyle attr styleFlag = attr { attrStyle = SetTo $ styleMask attr .|. styleFlag }
209
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+
210−-- | Sets the style, background color and foreground color to the default values for the terminal.
214+-- | Sets the style, background color and foreground color to the
215+-- default values for the terminal. There is no easy way to determine
211−-- There is no easy way to determine what the default background and foreground colors are.
216+-- what the default background and foreground colors are.
212defAttr :: Attr
217defAttr :: Attr
213−defAttr = Attr Default Default Default
218+defAttr = Attr Default Default Default Default
214−
215−instance Default Attr where
216− def = defAttr
217
219
218−-- | Keeps the style, background color and foreground color that was previously set. Used to
220+-- | Keeps the style, background color and foreground color that was
219−-- override some part of the previous style.
221+-- previously set. Used to override some part of the previous style.
220--
222--
221-- EG: current_style `withForeColor` brightMagenta
223-- EG: current_style `withForeColor` brightMagenta
222--
224--
223−-- Would be the currently applied style (be it underline, bold, etc) but with the foreground color
225+-- Would be the currently applied style (be it underline, bold, etc) but
224−-- set to brightMagenta.
226+-- with the foreground color set to brightMagenta.
225currentAttr :: Attr
227currentAttr :: Attr
226−currentAttr = Attr KeepCurrent KeepCurrent KeepCurrent
228+currentAttr = Attr KeepCurrent KeepCurrent KeepCurrent KeepCurrent
227−
src/Graphics/Vty/Attributes/Color.hs+162−22
@@ -1,16 +1,75 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE DeriveAnyClass #-}
3+{-# LANGUAGE MultiWayIf #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+
1−module Graphics.Vty.Attributes.Color where
6+module Graphics.Vty.Attributes.Color
7+ ( Color(..)
8+ , ColorMode(..)
9+
10+ -- * Detecting Terminal Color Support
11+ , detectColorMode
12+
13+ -- ** Fixed Colors
14+ -- | Standard 8-color ANSI terminal color codes.
15+ --
16+ -- Note that these map to colors in the terminal's custom palette. For
17+ -- instance, `white` maps to whatever the terminal color theme uses for
18+ -- white.
19+ --
20+ -- Use these functions if you want to make apps that fit the terminal theme.
21+ -- If you want access to more/stronger colors use `rgbColor`
22+ , black
23+ , red
24+ , green
25+ , yellow
26+ , blue
27+ , magenta
28+ , cyan
29+ , white
30+
31+ -- | Bright/Vivid variants of the standard 8-color ANSI
32+ , brightBlack
33+ , brightRed
34+ , brightGreen
35+ , brightYellow
36+ , brightBlue
37+ , brightMagenta
38+ , brightCyan
39+ , brightWhite
40+ -- ** Creating Colors From RGB
41+ , linearColor
42+ , srgbColor
43+ , rgbColor
44+ , color240
45+ , module Graphics.Vty.Attributes.Color240
46+ )
47+where
48+
2import Data.Word
49import Data.Word
50+import GHC.Generics
51+import Control.DeepSeq
52+import System.Environment (lookupEnv)
53+
54+import qualified System.Console.Terminfo as Terminfo
55+import Control.Exception (catch)
56+import Data.Maybe
57+
58+import Graphics.Vty.Attributes.Color240
3
59
4-- | Abstract data type representing a color.
60-- | Abstract data type representing a color.
5−--
6−-- Currently the foreground and background color are specified as points in either a:
7--
61--
62+-- Currently the foreground and background color are specified as points
63+-- in either a:
64+--
8−-- * 16 color palette. Where the first 8 colors are equal to the 8 colors of the ISO 6429 (ANSI) 8
65+-- * 16 color palette. Where the first 8 colors are equal to the 8
66+-- colors of the ISO 6429 (ANSI) 8 color palette and the second 8
9−-- color palette and the second 8 colors are bright/vivid versions of the first 8 colors.
67+-- colors are bright/vivid versions of the first 8 colors.
68+--
69+-- * 240 color palette. This palette is a regular sampling of the full
70+-- RGB colorspace for the first 224 colors. The remaining 16 colors is
71+-- a greyscale palette.
10--
72--
11−-- * 240 color palette. This palette is a regular sampling of the full RGB colorspace for the first
12−-- 224 colors. The remaining 16 colors is a greyscale palette.
13−--
14-- The 8 ISO 6429 (ANSI) colors are as follows:
73-- The 8 ISO 6429 (ANSI) colors are as follows:
15--
74--
16-- 0. black
75-- 0. black
@@ -29,23 +88,104 @@ import Data.Word
29--
88--
30-- 7. white
89-- 7. white
31--
90--
32−-- The mapping from points in the 240 color palette to colors actually displayable by the terminal
91+-- The mapping from points in the 240 color palette to colors actually
92+-- displayable by the terminal depends on the number of colors the
33−-- depends on the number of colors the terminal claims to support. Which is usually determined by
93+-- terminal claims to support. Which is usually determined by the
34−-- the terminfo "colors" property. If this property is not being accurately reported then the color
94+-- terminfo "colors" property. If this property is not being accurately
35−-- reproduction will be incorrect.
95+-- reported then the color reproduction will be incorrect.
36--
96--
37−-- If the terminal reports <= 16 colors then the 240 color palette points are only mapped to the 8
97+-- If the terminal reports <= 16 colors then the 240 color palette
98+-- points are only mapped to the 8 color palette. I'm not sure of
38−-- color pallete. I'm not sure of the RGB points for the "bright" colors which is why they are not
99+-- the RGB points for the "bright" colors which is why they are not
39−-- addressable via the 240 color palette.
100+-- addressable via the 240 color palette.
40--
101--
41−-- If the terminal reports > 16 colors then the 240 color palette points are mapped to the nearest
102+-- If the terminal reports > 16 colors then the 240 color palette
103+-- points are mapped to the nearest points in a ("color count" - 16)
42−-- points in a ("color count" - 16) subsampling of the 240 color palette.
104+-- subsampling of the 240 color palette.
43--
105--
44−-- All of this assumes the terminals are behaving similarly to xterm and rxvt when handling colors.
106+-- All of this assumes the terminals are behaving similarly to xterm and
107+-- rxvt when handling colors. And that the individual colors have not
45−-- And that the individual colors have not been remapped by the user. There may be a way to verify
108+-- been remapped by the user. There may be a way to verify this through
46−-- this through terminfo but I don't know it.
109+-- terminfo but I don't know it.
47--
110--
48-- Seriously, terminal color support is INSANE.
111-- Seriously, terminal color support is INSANE.
49−data Color = ISOColor !Word8 | Color240 !Word8
112+data Color = ISOColor !Word8 | Color240 !Word8 | RGBColor !Word8 !Word8 !Word8
50− deriving ( Eq, Show, Read )
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)
51
186
187+-- | Create a Vty 'Color' (in the 240 color set) from an RGB triple.
188+-- This function is lossy in the sense that we only internally support 240 colors but the
189+-- #RRGGBB format supports 16^3 colors.
190+rgbColor :: Integral i => i -> i -> i -> Color
191+rgbColor = color240
src/Graphics/Vty/Attributes/Color240.hs+291−254
@@ -1,261 +1,298 @@
1−{-
2− - This header file was generated by ./256colres.pl
1+-- This header file was generated by ./256colres.pl
3− -}
4−module Graphics.Vty.Attributes.Color240 where
2+module Graphics.Vty.Attributes.Color240
5−
3+ ( rgbColorToColor240
6−import Graphics.Vty.Attributes.Color
4+ , color240CodeToRGB
5+ )
6+where
7
7
8+import Data.Word (Word8)
8import Text.Printf
9import Text.Printf
9
10
10−-- | RGB color to 240 color palette.
11+-- Note: rgbColor's mapping from RGB to 240 colors was generated from
11−--
12−-- generated from 256colres.pl which is forked from xterm 256colres.pl
12+-- 256colres.pl which is forked from xterm 256colres.pl.
13−-- todo: all values get clamped high.
13+
14+-- | Create a value in the Color240 set from an RGB triple
14−rgbColor :: Integral i => i -> i -> i -> Color
15+rgbColorToColor240 :: Integral i => i -> i -> i -> Word8
15−rgbColor r g b
16+rgbColorToColor240 r g b
16 | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"
17 | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"
17− | r == 8 && g == 8 && b == 8 = Color240 216
18+ | r == 8 && g == 8 && b == 8 = 216
18− | r == 18 && g == 18 && b == 18 = Color240 217
19+ | r == 18 && g == 18 && b == 18 = 217
19− | r == 28 && g == 28 && b == 28 = Color240 218
20+ | r == 28 && g == 28 && b == 28 = 218
20− | r == 38 && g == 38 && b == 38 = Color240 219
21+ | r == 38 && g == 38 && b == 38 = 219
21− | r == 48 && g == 48 && b == 48 = Color240 220
22+ | r == 48 && g == 48 && b == 48 = 220
22− | r == 58 && g == 58 && b == 58 = Color240 221
23+ | r == 58 && g == 58 && b == 58 = 221
23− | r == 68 && g == 68 && b == 68 = Color240 222
24+ | r == 68 && g == 68 && b == 68 = 222
24− | r == 78 && g == 78 && b == 78 = Color240 223
25+ | r == 78 && g == 78 && b == 78 = 223
25− | r == 88 && g == 88 && b == 88 = Color240 224
26+ | r == 88 && g == 88 && b == 88 = 224
26− | r == 98 && g == 98 && b == 98 = Color240 225
27+ | r == 98 && g == 98 && b == 98 = 225
27− | r == 108 && g == 108 && b == 108 = Color240 226
28+ | r == 108 && g == 108 && b == 108 = 226
28− | r == 118 && g == 118 && b == 118 = Color240 227
29+ | r == 118 && g == 118 && b == 118 = 227
29− | r == 128 && g == 128 && b == 128 = Color240 228
30+ | r == 128 && g == 128 && b == 128 = 228
30− | r == 138 && g == 138 && b == 138 = Color240 229
31+ | r == 138 && g == 138 && b == 138 = 229
31− | r == 148 && g == 148 && b == 148 = Color240 230
32+ | r == 148 && g == 148 && b == 148 = 230
32− | r == 158 && g == 158 && b == 158 = Color240 231
33+ | r == 158 && g == 158 && b == 158 = 231
33− | r == 168 && g == 168 && b == 168 = Color240 232
34+ | r == 168 && g == 168 && b == 168 = 232
34− | r == 178 && g == 178 && b == 178 = Color240 233
35+ | r == 178 && g == 178 && b == 178 = 233
35− | r == 188 && g == 188 && b == 188 = Color240 234
36+ | r == 188 && g == 188 && b == 188 = 234
36− | r == 198 && g == 198 && b == 198 = Color240 235
37+ | r == 198 && g == 198 && b == 198 = 235
37− | r == 208 && g == 208 && b == 208 = Color240 236
38+ | r == 208 && g == 208 && b == 208 = 236
38− | r == 218 && g == 218 && b == 218 = Color240 237
39+ | r == 218 && g == 218 && b == 218 = 237
39− | r == 228 && g == 228 && b == 228 = Color240 238
40+ | r == 228 && g == 228 && b == 228 = 238
40− | r == 238 && g == 238 && b == 238 = Color240 239
41+ | r == 238 && g == 238 && b == 238 = 239
41− | r <= 0 && g <= 0 && b <= 0 = Color240 0
42+ | otherwise = 36 * go r + 6 * go g + go b
42− | r <= 0 && g <= 0 && b <= 95 = Color240 1
43+ where go = simpleColor_ (error (printf "RGB color %d %d %d does not map to 240 palette."
43− | r <= 0 && g <= 0 && b <= 135 = Color240 2
44− | r <= 0 && g <= 0 && b <= 175 = Color240 3
45− | r <= 0 && g <= 0 && b <= 215 = Color240 4
46− | r <= 0 && g <= 0 && b <= 255 = Color240 5
47− | r <= 0 && g <= 95 && b <= 0 = Color240 6
48− | r <= 0 && g <= 95 && b <= 95 = Color240 7
49− | r <= 0 && g <= 95 && b <= 135 = Color240 8
50− | r <= 0 && g <= 95 && b <= 175 = Color240 9
51− | r <= 0 && g <= 95 && b <= 215 = Color240 10
52− | r <= 0 && g <= 95 && b <= 255 = Color240 11
53− | r <= 0 && g <= 135 && b <= 0 = Color240 12
54− | r <= 0 && g <= 135 && b <= 95 = Color240 13
55− | r <= 0 && g <= 135 && b <= 135 = Color240 14
56− | r <= 0 && g <= 135 && b <= 175 = Color240 15
57− | r <= 0 && g <= 135 && b <= 215 = Color240 16
58− | r <= 0 && g <= 135 && b <= 255 = Color240 17
59− | r <= 0 && g <= 175 && b <= 0 = Color240 18
60− | r <= 0 && g <= 175 && b <= 95 = Color240 19
61− | r <= 0 && g <= 175 && b <= 135 = Color240 20
62− | r <= 0 && g <= 175 && b <= 175 = Color240 21
63− | r <= 0 && g <= 175 && b <= 215 = Color240 22
64− | r <= 0 && g <= 175 && b <= 255 = Color240 23
65− | r <= 0 && g <= 215 && b <= 0 = Color240 24
66− | r <= 0 && g <= 215 && b <= 95 = Color240 25
67− | r <= 0 && g <= 215 && b <= 135 = Color240 26
68− | r <= 0 && g <= 215 && b <= 175 = Color240 27
69− | r <= 0 && g <= 215 && b <= 215 = Color240 28
70− | r <= 0 && g <= 215 && b <= 255 = Color240 29
71− | r <= 0 && g <= 255 && b <= 0 = Color240 30
72− | r <= 0 && g <= 255 && b <= 95 = Color240 31
73− | r <= 0 && g <= 255 && b <= 135 = Color240 32
74− | r <= 0 && g <= 255 && b <= 175 = Color240 33
75− | r <= 0 && g <= 255 && b <= 215 = Color240 34
76− | r <= 0 && g <= 255 && b <= 255 = Color240 35
77− | r <= 95 && g <= 0 && b <= 0 = Color240 36
78− | r <= 95 && g <= 0 && b <= 95 = Color240 37
79− | r <= 95 && g <= 0 && b <= 135 = Color240 38
80− | r <= 95 && g <= 0 && b <= 175 = Color240 39
81− | r <= 95 && g <= 0 && b <= 215 = Color240 40
82− | r <= 95 && g <= 0 && b <= 255 = Color240 41
83− | r <= 95 && g <= 95 && b <= 0 = Color240 42
84− | r <= 95 && g <= 95 && b <= 95 = Color240 43
85− | r <= 95 && g <= 95 && b <= 135 = Color240 44
86− | r <= 95 && g <= 95 && b <= 175 = Color240 45
87− | r <= 95 && g <= 95 && b <= 215 = Color240 46
88− | r <= 95 && g <= 95 && b <= 255 = Color240 47
89− | r <= 95 && g <= 135 && b <= 0 = Color240 48
90− | r <= 95 && g <= 135 && b <= 95 = Color240 49
91− | r <= 95 && g <= 135 && b <= 135 = Color240 50
92− | r <= 95 && g <= 135 && b <= 175 = Color240 51
93− | r <= 95 && g <= 135 && b <= 215 = Color240 52
94− | r <= 95 && g <= 135 && b <= 255 = Color240 53
95− | r <= 95 && g <= 175 && b <= 0 = Color240 54
96− | r <= 95 && g <= 175 && b <= 95 = Color240 55
97− | r <= 95 && g <= 175 && b <= 135 = Color240 56
98− | r <= 95 && g <= 175 && b <= 175 = Color240 57
99− | r <= 95 && g <= 175 && b <= 215 = Color240 58
100− | r <= 95 && g <= 175 && b <= 255 = Color240 59
101− | r <= 95 && g <= 215 && b <= 0 = Color240 60
102− | r <= 95 && g <= 215 && b <= 95 = Color240 61
103− | r <= 95 && g <= 215 && b <= 135 = Color240 62
104− | r <= 95 && g <= 215 && b <= 175 = Color240 63
105− | r <= 95 && g <= 215 && b <= 215 = Color240 64
106− | r <= 95 && g <= 215 && b <= 255 = Color240 65
107− | r <= 95 && g <= 255 && b <= 0 = Color240 66
108− | r <= 95 && g <= 255 && b <= 95 = Color240 67
109− | r <= 95 && g <= 255 && b <= 135 = Color240 68
110− | r <= 95 && g <= 255 && b <= 175 = Color240 69
111− | r <= 95 && g <= 255 && b <= 215 = Color240 70
112− | r <= 95 && g <= 255 && b <= 255 = Color240 71
113− | r <= 135 && g <= 0 && b <= 0 = Color240 72
114− | r <= 135 && g <= 0 && b <= 95 = Color240 73
115− | r <= 135 && g <= 0 && b <= 135 = Color240 74
116− | r <= 135 && g <= 0 && b <= 175 = Color240 75
117− | r <= 135 && g <= 0 && b <= 215 = Color240 76
118− | r <= 135 && g <= 0 && b <= 255 = Color240 77
119− | r <= 135 && g <= 95 && b <= 0 = Color240 78
120− | r <= 135 && g <= 95 && b <= 95 = Color240 79
121− | r <= 135 && g <= 95 && b <= 135 = Color240 80
122− | r <= 135 && g <= 95 && b <= 175 = Color240 81
123− | r <= 135 && g <= 95 && b <= 215 = Color240 82
124− | r <= 135 && g <= 95 && b <= 255 = Color240 83
125− | r <= 135 && g <= 135 && b <= 0 = Color240 84
126− | r <= 135 && g <= 135 && b <= 95 = Color240 85
127− | r <= 135 && g <= 135 && b <= 135 = Color240 86
128− | r <= 135 && g <= 135 && b <= 175 = Color240 87
129− | r <= 135 && g <= 135 && b <= 215 = Color240 88
130− | r <= 135 && g <= 135 && b <= 255 = Color240 89
131− | r <= 135 && g <= 175 && b <= 0 = Color240 90
132− | r <= 135 && g <= 175 && b <= 95 = Color240 91
133− | r <= 135 && g <= 175 && b <= 135 = Color240 92
134− | r <= 135 && g <= 175 && b <= 175 = Color240 93
135− | r <= 135 && g <= 175 && b <= 215 = Color240 94
136− | r <= 135 && g <= 175 && b <= 255 = Color240 95
137− | r <= 135 && g <= 215 && b <= 0 = Color240 96
138− | r <= 135 && g <= 215 && b <= 95 = Color240 97
139− | r <= 135 && g <= 215 && b <= 135 = Color240 98
140− | r <= 135 && g <= 215 && b <= 175 = Color240 99
141− | r <= 135 && g <= 215 && b <= 215 = Color240 100
142− | r <= 135 && g <= 215 && b <= 255 = Color240 101
143− | r <= 135 && g <= 255 && b <= 0 = Color240 102
144− | r <= 135 && g <= 255 && b <= 95 = Color240 103
145− | r <= 135 && g <= 255 && b <= 135 = Color240 104
146− | r <= 135 && g <= 255 && b <= 175 = Color240 105
147− | r <= 135 && g <= 255 && b <= 215 = Color240 106
148− | r <= 135 && g <= 255 && b <= 255 = Color240 107
149− | r <= 175 && g <= 0 && b <= 0 = Color240 108
150− | r <= 175 && g <= 0 && b <= 95 = Color240 109
151− | r <= 175 && g <= 0 && b <= 135 = Color240 110
152− | r <= 175 && g <= 0 && b <= 175 = Color240 111
153− | r <= 175 && g <= 0 && b <= 215 = Color240 112
154− | r <= 175 && g <= 0 && b <= 255 = Color240 113
155− | r <= 175 && g <= 95 && b <= 0 = Color240 114
156− | r <= 175 && g <= 95 && b <= 95 = Color240 115
157− | r <= 175 && g <= 95 && b <= 135 = Color240 116
158− | r <= 175 && g <= 95 && b <= 175 = Color240 117
159− | r <= 175 && g <= 95 && b <= 215 = Color240 118
160− | r <= 175 && g <= 95 && b <= 255 = Color240 119
161− | r <= 175 && g <= 135 && b <= 0 = Color240 120
162− | r <= 175 && g <= 135 && b <= 95 = Color240 121
163− | r <= 175 && g <= 135 && b <= 135 = Color240 122
164− | r <= 175 && g <= 135 && b <= 175 = Color240 123
165− | r <= 175 && g <= 135 && b <= 215 = Color240 124
166− | r <= 175 && g <= 135 && b <= 255 = Color240 125
167− | r <= 175 && g <= 175 && b <= 0 = Color240 126
168− | r <= 175 && g <= 175 && b <= 95 = Color240 127
169− | r <= 175 && g <= 175 && b <= 135 = Color240 128
170− | r <= 175 && g <= 175 && b <= 175 = Color240 129
171− | r <= 175 && g <= 175 && b <= 215 = Color240 130
172− | r <= 175 && g <= 175 && b <= 255 = Color240 131
173− | r <= 175 && g <= 215 && b <= 0 = Color240 132
174− | r <= 175 && g <= 215 && b <= 95 = Color240 133
175− | r <= 175 && g <= 215 && b <= 135 = Color240 134
176− | r <= 175 && g <= 215 && b <= 175 = Color240 135
177− | r <= 175 && g <= 215 && b <= 215 = Color240 136
178− | r <= 175 && g <= 215 && b <= 255 = Color240 137
179− | r <= 175 && g <= 255 && b <= 0 = Color240 138
180− | r <= 175 && g <= 255 && b <= 95 = Color240 139
181− | r <= 175 && g <= 255 && b <= 135 = Color240 140
182− | r <= 175 && g <= 255 && b <= 175 = Color240 141
183− | r <= 175 && g <= 255 && b <= 215 = Color240 142
184− | r <= 175 && g <= 255 && b <= 255 = Color240 143
185− | r <= 215 && g <= 0 && b <= 0 = Color240 144
186− | r <= 215 && g <= 0 && b <= 95 = Color240 145
187− | r <= 215 && g <= 0 && b <= 135 = Color240 146
188− | r <= 215 && g <= 0 && b <= 175 = Color240 147
189− | r <= 215 && g <= 0 && b <= 215 = Color240 148
190− | r <= 215 && g <= 0 && b <= 255 = Color240 149
191− | r <= 215 && g <= 95 && b <= 0 = Color240 150
192− | r <= 215 && g <= 95 && b <= 95 = Color240 151
193− | r <= 215 && g <= 95 && b <= 135 = Color240 152
194− | r <= 215 && g <= 95 && b <= 175 = Color240 153
195− | r <= 215 && g <= 95 && b <= 215 = Color240 154
196− | r <= 215 && g <= 95 && b <= 255 = Color240 155
197− | r <= 215 && g <= 135 && b <= 0 = Color240 156
198− | r <= 215 && g <= 135 && b <= 95 = Color240 157
199− | r <= 215 && g <= 135 && b <= 135 = Color240 158
200− | r <= 215 && g <= 135 && b <= 175 = Color240 159
201− | r <= 215 && g <= 135 && b <= 215 = Color240 160
202− | r <= 215 && g <= 135 && b <= 255 = Color240 161
203− | r <= 215 && g <= 175 && b <= 0 = Color240 162
204− | r <= 215 && g <= 175 && b <= 95 = Color240 163
205− | r <= 215 && g <= 175 && b <= 135 = Color240 164
206− | r <= 215 && g <= 175 && b <= 175 = Color240 165
207− | r <= 215 && g <= 175 && b <= 215 = Color240 166
208− | r <= 215 && g <= 175 && b <= 255 = Color240 167
209− | r <= 215 && g <= 215 && b <= 0 = Color240 168
210− | r <= 215 && g <= 215 && b <= 95 = Color240 169
211− | r <= 215 && g <= 215 && b <= 135 = Color240 170
212− | r <= 215 && g <= 215 && b <= 175 = Color240 171
213− | r <= 215 && g <= 215 && b <= 215 = Color240 172
214− | r <= 215 && g <= 215 && b <= 255 = Color240 173
215− | r <= 215 && g <= 255 && b <= 0 = Color240 174
216− | r <= 215 && g <= 255 && b <= 95 = Color240 175
217− | r <= 215 && g <= 255 && b <= 135 = Color240 176
218− | r <= 215 && g <= 255 && b <= 175 = Color240 177
219− | r <= 215 && g <= 255 && b <= 215 = Color240 178
220− | r <= 215 && g <= 255 && b <= 255 = Color240 179
221− | r <= 255 && g <= 0 && b <= 0 = Color240 180
222− | r <= 255 && g <= 0 && b <= 95 = Color240 181
223− | r <= 255 && g <= 0 && b <= 135 = Color240 182
224− | r <= 255 && g <= 0 && b <= 175 = Color240 183
225− | r <= 255 && g <= 0 && b <= 215 = Color240 184
226− | r <= 255 && g <= 0 && b <= 255 = Color240 185
227− | r <= 255 && g <= 95 && b <= 0 = Color240 186
228− | r <= 255 && g <= 95 && b <= 95 = Color240 187
229− | r <= 255 && g <= 95 && b <= 135 = Color240 188
230− | r <= 255 && g <= 95 && b <= 175 = Color240 189
231− | r <= 255 && g <= 95 && b <= 215 = Color240 190
232− | r <= 255 && g <= 95 && b <= 255 = Color240 191
233− | r <= 255 && g <= 135 && b <= 0 = Color240 192
234− | r <= 255 && g <= 135 && b <= 95 = Color240 193
235− | r <= 255 && g <= 135 && b <= 135 = Color240 194
236− | r <= 255 && g <= 135 && b <= 175 = Color240 195
237− | r <= 255 && g <= 135 && b <= 215 = Color240 196
238− | r <= 255 && g <= 135 && b <= 255 = Color240 197
239− | r <= 255 && g <= 175 && b <= 0 = Color240 198
240− | r <= 255 && g <= 175 && b <= 95 = Color240 199
241− | r <= 255 && g <= 175 && b <= 135 = Color240 200
242− | r <= 255 && g <= 175 && b <= 175 = Color240 201
243− | r <= 255 && g <= 175 && b <= 215 = Color240 202
244− | r <= 255 && g <= 175 && b <= 255 = Color240 203
245− | r <= 255 && g <= 215 && b <= 0 = Color240 204
246− | r <= 255 && g <= 215 && b <= 95 = Color240 205
247− | r <= 255 && g <= 215 && b <= 135 = Color240 206
248− | r <= 255 && g <= 215 && b <= 175 = Color240 207
249− | r <= 255 && g <= 215 && b <= 215 = Color240 208
250− | r <= 255 && g <= 215 && b <= 255 = Color240 209
251− | r <= 255 && g <= 255 && b <= 0 = Color240 210
252− | r <= 255 && g <= 255 && b <= 95 = Color240 211
253− | r <= 255 && g <= 255 && b <= 135 = Color240 212
254− | r <= 255 && g <= 255 && b <= 175 = Color240 213
255− | r <= 255 && g <= 255 && b <= 215 = Color240 214
256− | r <= 255 && g <= 255 && b <= 255 = Color240 215
257− | otherwise = error (printf "RGB color %d %d %d does not map to 240 palette."
258 (fromIntegral r :: Int)
44 (fromIntegral r :: Int)
259 (fromIntegral g :: Int)
45 (fromIntegral g :: Int)
260− (fromIntegral b :: Int))
46+ (fromIntegral b :: Int)))
47+
48+simpleColor_ :: Integral i => Word8 -> i -> Word8
49+simpleColor_ e c
50+ | c <= 0 = 0
51+ | c <= 95 = 1
52+ | c <= 255 = fromIntegral ((c-16) `div` 40)
53+ | otherwise = e
261
54
55+-- | Create a RGB triple from a value in the Color240 set.
56+color240CodeToRGB :: Word8 -> Maybe (Int, Int, Int)
57+color240CodeToRGB n = case n of
58+ 0 -> Just (0, 0, 0)
59+ 1 -> Just (0, 0, 95)
60+ 2 -> Just (0, 0, 135)
61+ 3 -> Just (0, 0, 175)
62+ 4 -> Just (0, 0, 215)
63+ 5 -> Just (0, 0, 255)
64+ 6 -> Just (0, 95, 0)
65+ 7 -> Just (0, 95, 95)
66+ 8 -> Just (0, 95, 135)
67+ 9 -> Just (0, 95, 175)
68+ 10 -> Just (0, 95, 215)
69+ 11 -> Just (0, 95, 255)
70+ 12 -> Just (0, 135, 0)
71+ 13 -> Just (0, 135, 95)
72+ 14 -> Just (0, 135, 135)
73+ 15 -> Just (0, 135, 175)
74+ 16 -> Just (0, 135, 215)
75+ 17 -> Just (0, 135, 255)
76+ 18 -> Just (0, 175, 0)
77+ 19 -> Just (0, 175, 95)
78+ 20 -> Just (0, 175, 135)
79+ 21 -> Just (0, 175, 175)
80+ 22 -> Just (0, 175, 215)
81+ 23 -> Just (0, 175, 255)
82+ 24 -> Just (0, 215, 0)
83+ 25 -> Just (0, 215, 95)
84+ 26 -> Just (0, 215, 135)
85+ 27 -> Just (0, 215, 175)
86+ 28 -> Just (0, 215, 215)
87+ 29 -> Just (0, 215, 255)
88+ 30 -> Just (0, 255, 0)
89+ 31 -> Just (0, 255, 95)
90+ 32 -> Just (0, 255, 135)
91+ 33 -> Just (0, 255, 175)
92+ 34 -> Just (0, 255, 215)
93+ 35 -> Just (0, 255, 255)
94+ 36 -> Just (95, 0, 0)
95+ 37 -> Just (95, 0, 95)
96+ 38 -> Just (95, 0, 135)
97+ 39 -> Just (95, 0, 175)
98+ 40 -> Just (95, 0, 215)
99+ 41 -> Just (95, 0, 255)
100+ 42 -> Just (95, 95, 0)
101+ 43 -> Just (95, 95, 95)
102+ 44 -> Just (95, 95, 135)
103+ 45 -> Just (95, 95, 175)
104+ 46 -> Just (95, 95, 215)
105+ 47 -> Just (95, 95, 255)
106+ 48 -> Just (95, 135, 0)
107+ 49 -> Just (95, 135, 95)
108+ 50 -> Just (95, 135, 135)
109+ 51 -> Just (95, 135, 175)
110+ 52 -> Just (95, 135, 215)
111+ 53 -> Just (95, 135, 255)
112+ 54 -> Just (95, 175, 0)
113+ 55 -> Just (95, 175, 95)
114+ 56 -> Just (95, 175, 135)
115+ 57 -> Just (95, 175, 175)
116+ 58 -> Just (95, 175, 215)
117+ 59 -> Just (95, 175, 255)
118+ 60 -> Just (95, 215, 0)
119+ 61 -> Just (95, 215, 95)
120+ 62 -> Just (95, 215, 135)
121+ 63 -> Just (95, 215, 175)
122+ 64 -> Just (95, 215, 215)
123+ 65 -> Just (95, 215, 255)
124+ 66 -> Just (95, 255, 0)
125+ 67 -> Just (95, 255, 95)
126+ 68 -> Just (95, 255, 135)
127+ 69 -> Just (95, 255, 175)
128+ 70 -> Just (95, 255, 215)
129+ 71 -> Just (95, 255, 255)
130+ 72 -> Just (135, 0, 0)
131+ 73 -> Just (135, 0, 95)
132+ 74 -> Just (135, 0, 135)
133+ 75 -> Just (135, 0, 175)
134+ 76 -> Just (135, 0, 215)
135+ 77 -> Just (135, 0, 255)
136+ 78 -> Just (135, 95, 0)
137+ 79 -> Just (135, 95, 95)
138+ 80 -> Just (135, 95, 135)
139+ 81 -> Just (135, 95, 175)
140+ 82 -> Just (135, 95, 215)
141+ 83 -> Just (135, 95, 255)
142+ 84 -> Just (135, 135, 0)
143+ 85 -> Just (135, 135, 95)
144+ 86 -> Just (135, 135, 135)
145+ 87 -> Just (135, 135, 175)
146+ 88 -> Just (135, 135, 215)
147+ 89 -> Just (135, 135, 255)
148+ 90 -> Just (135, 175, 0)
149+ 91 -> Just (135, 175, 95)
150+ 92 -> Just (135, 175, 135)
151+ 93 -> Just (135, 175, 175)
152+ 94 -> Just (135, 175, 215)
153+ 95 -> Just (135, 175, 255)
154+ 96 -> Just (135, 215, 0)
155+ 97 -> Just (135, 215, 95)
156+ 98 -> Just (135, 215, 135)
157+ 99 -> Just (135, 215, 175)
158+ 100 -> Just (135, 215, 215)
159+ 101 -> Just (135, 215, 255)
160+ 102 -> Just (135, 255, 0)
161+ 103 -> Just (135, 255, 95)
162+ 104 -> Just (135, 255, 135)
163+ 105 -> Just (135, 255, 175)
164+ 106 -> Just (135, 255, 215)
165+ 107 -> Just (135, 255, 255)
166+ 108 -> Just (175, 0, 0)
167+ 109 -> Just (175, 0, 95)
168+ 110 -> Just (175, 0, 135)
169+ 111 -> Just (175, 0, 175)
170+ 112 -> Just (175, 0, 215)
171+ 113 -> Just (175, 0, 255)
172+ 114 -> Just (175, 95, 0)
173+ 115 -> Just (175, 95, 95)
174+ 116 -> Just (175, 95, 135)
175+ 117 -> Just (175, 95, 175)
176+ 118 -> Just (175, 95, 215)
177+ 119 -> Just (175, 95, 255)
178+ 120 -> Just (175, 135, 0)
179+ 121 -> Just (175, 135, 95)
180+ 122 -> Just (175, 135, 135)
181+ 123 -> Just (175, 135, 175)
182+ 124 -> Just (175, 135, 215)
183+ 125 -> Just (175, 135, 255)
184+ 126 -> Just (175, 175, 0)
185+ 127 -> Just (175, 175, 95)
186+ 128 -> Just (175, 175, 135)
187+ 129 -> Just (175, 175, 175)
188+ 130 -> Just (175, 175, 215)
189+ 131 -> Just (175, 175, 255)
190+ 132 -> Just (175, 215, 0)
191+ 133 -> Just (175, 215, 95)
192+ 134 -> Just (175, 215, 135)
193+ 135 -> Just (175, 215, 175)
194+ 136 -> Just (175, 215, 215)
195+ 137 -> Just (175, 215, 255)
196+ 138 -> Just (175, 255, 0)
197+ 139 -> Just (175, 255, 95)
198+ 140 -> Just (175, 255, 135)
199+ 141 -> Just (175, 255, 175)
200+ 142 -> Just (175, 255, 215)
201+ 143 -> Just (175, 255, 255)
202+ 144 -> Just (215, 0, 0)
203+ 145 -> Just (215, 0, 95)
204+ 146 -> Just (215, 0, 135)
205+ 147 -> Just (215, 0, 175)
206+ 148 -> Just (215, 0, 215)
207+ 149 -> Just (215, 0, 255)
208+ 150 -> Just (215, 95, 0)
209+ 151 -> Just (215, 95, 95)
210+ 152 -> Just (215, 95, 135)
211+ 153 -> Just (215, 95, 175)
212+ 154 -> Just (215, 95, 215)
213+ 155 -> Just (215, 95, 255)
214+ 156 -> Just (215, 135, 0)
215+ 157 -> Just (215, 135, 95)
216+ 158 -> Just (215, 135, 135)
217+ 159 -> Just (215, 135, 175)
218+ 160 -> Just (215, 135, 215)
219+ 161 -> Just (215, 135, 255)
220+ 162 -> Just (215, 175, 0)
221+ 163 -> Just (215, 175, 95)
222+ 164 -> Just (215, 175, 135)
223+ 165 -> Just (215, 175, 175)
224+ 166 -> Just (215, 175, 215)
225+ 167 -> Just (215, 175, 255)
226+ 168 -> Just (215, 215, 0)
227+ 169 -> Just (215, 215, 95)
228+ 170 -> Just (215, 215, 135)
229+ 171 -> Just (215, 215, 175)
230+ 172 -> Just (215, 215, 215)
231+ 173 -> Just (215, 215, 255)
232+ 174 -> Just (215, 255, 0)
233+ 175 -> Just (215, 255, 95)
234+ 176 -> Just (215, 255, 135)
235+ 177 -> Just (215, 255, 175)
236+ 178 -> Just (215, 255, 215)
237+ 179 -> Just (215, 255, 255)
238+ 180 -> Just (255, 0, 0)
239+ 181 -> Just (255, 0, 95)
240+ 182 -> Just (255, 0, 135)
241+ 183 -> Just (255, 0, 175)
242+ 184 -> Just (255, 0, 215)
243+ 185 -> Just (255, 0, 255)
244+ 186 -> Just (255, 95, 0)
245+ 187 -> Just (255, 95, 95)
246+ 188 -> Just (255, 95, 135)
247+ 189 -> Just (255, 95, 175)
248+ 190 -> Just (255, 95, 215)
249+ 191 -> Just (255, 95, 255)
250+ 192 -> Just (255, 135, 0)
251+ 193 -> Just (255, 135, 95)
252+ 194 -> Just (255, 135, 135)
253+ 195 -> Just (255, 135, 175)
254+ 196 -> Just (255, 135, 215)
255+ 197 -> Just (255, 135, 255)
256+ 198 -> Just (255, 175, 0)
257+ 199 -> Just (255, 175, 95)
258+ 200 -> Just (255, 175, 135)
259+ 201 -> Just (255, 175, 175)
260+ 202 -> Just (255, 175, 215)
261+ 203 -> Just (255, 175, 255)
262+ 204 -> Just (255, 215, 0)
263+ 205 -> Just (255, 215, 95)
264+ 206 -> Just (255, 215, 135)
265+ 207 -> Just (255, 215, 175)
266+ 208 -> Just (255, 215, 215)
267+ 209 -> Just (255, 215, 255)
268+ 210 -> Just (255, 255, 0)
269+ 211 -> Just (255, 255, 95)
270+ 212 -> Just (255, 255, 135)
271+ 213 -> Just (255, 255, 175)
272+ 214 -> Just (255, 255, 215)
273+ 215 -> Just (255, 255, 255)
274+ 216 -> Just (8, 8, 8)
275+ 217 -> Just (18, 18, 18)
276+ 218 -> Just (28, 28, 28)
277+ 219 -> Just (38, 38, 38)
278+ 220 -> Just (48, 48, 48)
279+ 221 -> Just (58, 58, 58)
280+ 222 -> Just (68, 68, 68)
281+ 223 -> Just (78, 78, 78)
282+ 224 -> Just (88, 88, 88)
283+ 225 -> Just (98, 98, 98)
284+ 226 -> Just (108, 108, 108)
285+ 227 -> Just (118, 118, 118)
286+ 228 -> Just (128, 128, 128)
287+ 229 -> Just (138, 138, 138)
288+ 230 -> Just (148, 148, 148)
289+ 231 -> Just (158, 158, 158)
290+ 232 -> Just (168, 168, 168)
291+ 233 -> Just (178, 178, 178)
292+ 234 -> Just (188, 188, 188)
293+ 235 -> Just (198, 198, 198)
294+ 236 -> Just (208, 208, 208)
295+ 237 -> Just (218, 218, 218)
296+ 238 -> Just (228, 228, 228)
297+ 239 -> Just (238, 238, 238)
298+ _ -> Nothing
src/Graphics/Vty/Config.hs+290−111
@@ -3,21 +3,13 @@
3{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}
3{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}
4{-# LANGUAGE TypeOperators #-}
4{-# LANGUAGE TypeOperators #-}
5{-# LANGUAGE ScopedTypeVariables #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE LambdaCase, MultiWayIf #-}
7+-- | Vty supports a configuration file format and associated 'Config'
6−-- | A 'Config' can be provided to mkVty to customize the applications use of vty. A config file can
8+-- data type. The 'Config' can be provided to 'mkVty' to customize the
7−-- be used to customize vty for a user's system.
9+-- application's use of Vty.
8--
10--
9−-- The 'Config' provided is mappend'd to 'Config's loaded from @'getAppUserDataDirectory'/config@
10−-- and @$VTY_CONFIG_FILE@. The @$VTY_CONFIG_FILE@ takes precedence over the @config@ file or the
11−-- application provided 'Config'.
12−--
13−-- Lines in config files that fail to parse are ignored. Later entries take precedence over
11+-- Lines in config files that fail to parse are ignored. Later entries
14−-- earlier.
12+-- take precedence over earlier ones.
15−--
16−-- For all directives:
17−--
18−-- @
19−-- string := \"\\\"\" chars+ \"\\\"\"
20−-- @
21--
13--
22-- = Debug
14-- = Debug
23--
15--
@@ -29,8 +21,8 @@
29-- \"debugLog\" string
21-- \"debugLog\" string
30-- @
22-- @
31--
23--
32−-- The value of the environment variable @VTY_DEBUG_LOG@ is equivalent to a debugLog entry at the
24+-- The value of the environment variable @VTY_DEBUG_LOG@ is equivalent
33−-- end of the last config file.
25+-- to a debugLog entry at the end of the last config file.
34--
26--
35-- = Input Processing
27-- = Input Processing
36--
28--
@@ -40,14 +32,14 @@
40--
32--
41-- @
33-- @
42-- \"map\" term string key modifier_list
34-- \"map\" term string key modifier_list
43−-- where
35+-- where
44-- key := KEsc | KChar Char | KBS ... (same as 'Key')
36-- key := KEsc | KChar Char | KBS ... (same as 'Key')
45-- modifier_list := \"[\" modifier+ \"]\"
37-- modifier_list := \"[\" modifier+ \"]\"
46-- modifier := MShift | MCtrl | MMeta | MAlt
38-- modifier := MShift | MCtrl | MMeta | MAlt
47-- term := "_" | string
39-- term := "_" | string
48-- @
40-- @
49--
41--
50−-- EG: If the contents are
42+-- E.g., if the contents are
51--
43--
52-- @
44-- @
53-- map _ \"\\ESC[B\" KUp []
45-- map _ \"\\ESC[B\" KUp []
@@ -55,16 +47,41 @@
55-- map \"xterm\" \"\\ESC[D\" KLeft []
47-- map \"xterm\" \"\\ESC[D\" KLeft []
56-- @
48-- @
57--
49--
58−-- Then the bytes @\"\\ESC[B\"@ will result in the KUp event on all terminals. The bytes
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
59−-- @\"\\ESC[1;3B\"@ will result in the event KDown with the MAlt modifier on all terminals.
52+-- with the MAlt modifier on all terminals. The bytes @\"\\ESC[D\"@ will
60−-- The bytes @\"\\ESC[D\"@ will result in the KLeft event when @TERM@ is @xterm@.
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+-- @
61--
69--
62−-- If a debug log is requested then vty will output the current input table to the log in the above
70+-- E.g.,
63−-- format.
64--
71--
65−-- Set VTY_DEBUG_LOG. Run vty. Check debug log for incorrect mappings. Add corrected mappings to
72+-- @
66−-- .vty/config
73+-- widthMap \"xterm\" \"\/home\/user\/.vty\/xterm\_map.dat\"
74+-- @
67--
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.
68module Graphics.Vty.Config
85module Graphics.Vty.Config
69 ( InputMap
86 ( InputMap
70 , Config(..)
87 , Config(..)
@@ -74,13 +91,22 @@ module Graphics.Vty.Config
74 , standardIOConfig
91 , standardIOConfig
75 , runParseConfig
92 , runParseConfig
76 , parseConfigFile
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+ )
77− ) where
107+where
78
108
79−#if __GLASGOW_HASKELL__ > 704
80import Prelude
109import Prelude
81−#else
82−import Prelude hiding (catch)
83−#endif
84
110
85import Control.Applicative hiding (many)
111import Control.Applicative hiding (many)
86
112
@@ -88,113 +114,186 @@ import Control.Exception (catch, IOException, Exception(..), throwIO)
88import Control.Monad (liftM, guard, void)
114import Control.Monad (liftM, guard, void)
89
115
90import qualified Data.ByteString as BS
116import qualified Data.ByteString as BS
117+#if !(MIN_VERSION_base(4,8,0))
91−import Data.Default
118+import Data.Monoid (Monoid(..))
119+#endif
120+#if !(MIN_VERSION_base(4,11,0))
92−import Data.Monoid
121+import Data.Semigroup (Semigroup(..))
122+#endif
93import Data.Typeable (Typeable)
123import Data.Typeable (Typeable)
94
124
95import Graphics.Vty.Input.Events
125import Graphics.Vty.Input.Events
126+import Graphics.Vty.Attributes.Color (ColorMode(..), detectColorMode)
96
127
97import GHC.Generics
128import GHC.Generics
98
129
99−import System.Directory (getAppUserDataDirectory)
130+import System.Directory ( getAppUserDataDirectory, doesFileExist
131+ , createDirectoryIfMissing
132+ )
100−import System.Posix.Env (getEnv)
133+import System.Environment (lookupEnv)
134+import System.FilePath ((</>), takeDirectory)
101import System.Posix.IO (stdInput, stdOutput)
135import System.Posix.IO (stdInput, stdOutput)
102import System.Posix.Types (Fd(..))
136import System.Posix.Types (Fd(..))
137+import Foreign.C.Types (CInt(..), CChar(..))
103
138
104import Text.Parsec hiding ((<|>))
139import Text.Parsec hiding ((<|>))
105import Text.Parsec.Token ( GenLanguageDef(..) )
140import Text.Parsec.Token ( GenLanguageDef(..) )
106import qualified Text.Parsec.Token as P
141import qualified Text.Parsec.Token as P
107
142
108-- | Type of errors that can be thrown when configuring VTY
143-- | Type of errors that can be thrown when configuring VTY
109−data VtyConfigurationError
144+data VtyConfigurationError =
145+ VtyMissingTermEnvVar
110− = VtyMissingTermEnvVar -- ^ TERM environment variable not set
146+ -- ^ TERM environment variable not set
111− deriving (Show, Eq, Typeable)
147+ deriving (Show, Eq, Typeable)
112
148
113instance Exception VtyConfigurationError where
149instance Exception VtyConfigurationError where
114−#if MIN_VERSION_base(4,8,0)
115− displayException VtyMissingTermEnvVar = "TERM environment variable not set"
150+ displayException VtyMissingTermEnvVar = "TERM environment variable not set"
116−#endif
117
151
118−-- | Mappings from input bytes to event in the order specified. Later entries take precedence over
152+-- | Mappings from input bytes to event in the order specified. Later
119−-- earlier in the case multiple entries have the same byte string.
153+-- entries take precedence over earlier in the case multiple entries
154+-- have the same byte string.
120type InputMap = [(Maybe String, String, Event)]
155type InputMap = [(Maybe String, String, Event)]
121
156
157+-- | A Vty configuration.
122−data Config = Config
158+data Config =
123− {
124− -- | The default is 1 character.
125− vmin :: Maybe Int
159+ Config { vmin :: Maybe Int
160+ -- ^ The default is 1 character.
161+ , vtime :: Maybe Int
126− -- | The default is 100 milliseconds, 0.1 seconds.
162+ -- ^ The default is 100 milliseconds, 0.1 seconds.
127− , vtime :: Maybe Int
163+ , mouseMode :: Maybe Bool
128− -- | The default is False.
164+ -- ^ The default is False.
129− , mouseMode :: Maybe Bool
165+ , bracketedPasteMode :: Maybe Bool
130− -- | The default is False.
166+ -- ^ The default is False.
131− , bracketedPasteMode :: Maybe Bool
167+ , debugLog :: Maybe FilePath
132− -- | Debug information is appended to this file if not Nothing.
168+ -- ^ Debug information is appended to this file if not
133− , debugLog :: Maybe FilePath
169+ -- Nothing.
170+ , inputMap :: InputMap
134− -- | The (input byte, output event) pairs extend the internal input table of VTY and the table
171+ -- ^ The (input byte, output event) pairs extend the internal
135− -- from terminfo.
172+ -- input table of VTY and the table from terminfo.
136− --
173+ --
137− -- See "Graphics.Vty.Config" module documentation for documentation of the @map@ directive.
174+ -- See "Graphics.Vty.Config" module documentation for
138− , inputMap :: InputMap
175+ -- documentation of the @map@ directive.
176+ , inputFd :: Maybe Fd
139− -- | The input file descriptor to use. The default is 'System.Posix.IO.stdInput'
177+ -- ^ The input file descriptor to use. The default is
178+ -- 'System.Posix.IO.stdInput'
140− , inputFd :: Maybe Fd
179+ , outputFd :: Maybe Fd
141− -- | The output file descriptor to use. The default is 'System.Posix.IO.stdOutput'
180+ -- ^ The output file descriptor to use. The default is
142− , outputFd :: Maybe Fd
181+ -- 'System.Posix.IO.stdOutput'
182+ , termName :: Maybe String
143− -- | The terminal name used to look up terminfo capabilities.
183+ -- ^ The terminal name used to look up terminfo capabilities.
144− -- The default is the value of the TERM environment variable.
184+ -- The default is the value of the TERM environment variable.
145− , termName :: Maybe String
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+ }
146− } deriving (Show, Eq)
202+ deriving (Show, Eq)
147−
203+
204+defaultConfig :: Config
205+defaultConfig = mempty
206+
148−instance Default Config where
207+instance Semigroup Config where
149− def = mempty
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+ }
150
224
151instance Monoid Config where
225instance Monoid Config where
152− mempty = Config
226+ mempty =
153− { vmin = Nothing
227+ Config { vmin = Nothing
154− , vtime = Nothing
228+ , vtime = Nothing
155− , mouseMode = Nothing
229+ , mouseMode = Nothing
156− , bracketedPasteMode = Nothing
230+ , bracketedPasteMode = Nothing
157− , debugLog = mempty
231+ , debugLog = mempty
158− , inputMap = mempty
232+ , inputMap = mempty
159− , inputFd = Nothing
233+ , inputFd = Nothing
160− , outputFd = Nothing
234+ , outputFd = Nothing
161− , termName = Nothing
235+ , termName = Nothing
236+ , termWidthMaps = []
237+ , allowCustomUnicodeWidthTables = Nothing
238+ , colorMode = Nothing
162− }
239+ }
240+#if !(MIN_VERSION_base(4,11,0))
163− mappend c0 c1 = Config
241+ mappend = (<>)
164− -- latter config takes priority for everything but inputMap
242+#endif
165− { vmin = vmin c1 <|> vmin c0
243+
166− , vtime = vtime c1 <|> vtime c0
244+vtyDataDirectory :: IO FilePath
167− , mouseMode = mouseMode c1
245+vtyDataDirectory = getAppUserDataDirectory "vty"
168− , bracketedPasteMode = bracketedPasteMode c1
246+
169− , debugLog = debugLog c1 <|> debugLog c0
247+vtyConfigPath :: IO FilePath
170− , inputMap = inputMap c0 <> inputMap c1
248+vtyConfigPath = do
171− , inputFd = inputFd c1 <|> inputFd c0
249+ dir <- vtyDataDirectory
172− , outputFd = outputFd c1 <|> outputFd c0
250+ return $ dir </> "config"
173− , termName = termName c1 <|> termName c0
251+
174− }
252+vtyConfigFileEnvName :: String
175−
253+vtyConfigFileEnvName = "VTY_CONFIG_FILE"
254+
176−-- | Config from @'getAppUserDataDirectory'/config@ and @$VTY_CONFIG_FILE@
255+-- | Load a configuration from 'vtyConfigPath' and @$VTY_CONFIG_FILE@.
177userConfig :: IO Config
256userConfig :: IO Config
178userConfig = do
257userConfig = do
179− configFile <- (mappend <$> getAppUserDataDirectory "vty" <*> pure "/config") >>= parseConfigFile
258+ configFile <- vtyConfigPath >>= parseConfigFile
180− overrideConfig <- maybe (return def) parseConfigFile =<< getEnv "VTY_CONFIG_FILE"
259+ overrideConfig <- maybe (return defaultConfig) parseConfigFile =<<
260+ lookupEnv vtyConfigFileEnvName
181 let base = configFile <> overrideConfig
261 let base = configFile <> overrideConfig
182 mappend base <$> overrideEnvConfig
262 mappend base <$> overrideEnvConfig
183
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+
184overrideEnvConfig :: IO Config
282overrideEnvConfig :: IO Config
185overrideEnvConfig = do
283overrideEnvConfig = do
186− d <- getEnv "VTY_DEBUG_LOG"
284+ d <- lookupEnv "VTY_DEBUG_LOG"
187− return $ def { debugLog = d }
285+ return $ defaultConfig { debugLog = d }
188
286
189−-- | Configures VTY using defaults suitable for terminals. This action
287+-- | Configures VTY using defaults suitable for terminals. This function
190-- can raise 'VtyConfigurationError'.
288-- can raise 'VtyConfigurationError'.
191standardIOConfig :: IO Config
289standardIOConfig :: IO Config
192standardIOConfig = do
290standardIOConfig = do
193− mb <- getEnv "TERM"
291+ mb <- lookupEnv termVariable
194 case mb of
292 case mb of
195 Nothing -> throwIO VtyMissingTermEnvVar
293 Nothing -> throwIO VtyMissingTermEnvVar
196− Just t ->
294+ Just t -> do
295+ mcolorMode <- detectColorMode t
197− return def
296+ return defaultConfig
198 { vmin = Just 1
297 { vmin = Just 1
199 , mouseMode = Just False
298 , mouseMode = Just False
200 , bracketedPasteMode = Just False
299 , bracketedPasteMode = Just False
@@ -202,25 +301,24 @@ standardIOConfig = do
202 , inputFd = Just stdInput
301 , inputFd = Just stdInput
203 , outputFd = Just stdOutput
302 , outputFd = Just stdOutput
204 , termName = Just t
303 , termName = Just t
304+ , colorMode = Just mcolorMode
205 }
305 }
206
306
207parseConfigFile :: FilePath -> IO Config
307parseConfigFile :: FilePath -> IO Config
208parseConfigFile path = do
308parseConfigFile path = do
209 catch (runParseConfig path <$> BS.readFile path)
309 catch (runParseConfig path <$> BS.readFile path)
210− (\(_ :: IOException) -> return def)
310+ (\(_ :: IOException) -> return defaultConfig)
211
311
212runParseConfig :: String -> BS.ByteString -> Config
312runParseConfig :: String -> BS.ByteString -> Config
213runParseConfig name cfgTxt =
313runParseConfig name cfgTxt =
214 case runParser parseConfig () name cfgTxt of
314 case runParser parseConfig () name cfgTxt of
215 Right cfg -> cfg
315 Right cfg -> cfg
216− Left{} -> def
316+ Left{} -> defaultConfig
217
317
218------------------------------------------------------------------------
318------------------------------------------------------------------------
219
319
220type Parser = Parsec BS.ByteString ()
320type Parser = Parsec BS.ByteString ()
221
321
222−-- I tried to use the haskellStyle here but that was specialized (without requirement?) to the
223−-- String stream type.
224configLanguage :: Monad m => P.GenLanguageDef BS.ByteString () m
322configLanguage :: Monad m => P.GenLanguageDef BS.ByteString () m
225configLanguage = LanguageDef
323configLanguage = LanguageDef
226 { commentStart = "{-"
324 { commentStart = "{-"
@@ -247,13 +345,20 @@ mapDecl = do
247 bytes <- P.stringLiteral configLexer
345 bytes <- P.stringLiteral configLexer
248 key <- parseValue
346 key <- parseValue
249 modifiers <- parseValue
347 modifiers <- parseValue
250− return def { inputMap = [(termIdent, bytes, EvKey key modifiers)] }
348+ return defaultConfig { inputMap = [(termIdent, bytes, EvKey key modifiers)] }
251
349
252debugLogDecl :: Parser Config
350debugLogDecl :: Parser Config
253debugLogDecl = do
351debugLogDecl = do
254 "debugLog" <- P.identifier configLexer
352 "debugLog" <- P.identifier configLexer
255 path <- P.stringLiteral configLexer
353 path <- P.stringLiteral configLexer
256− return def { debugLog = Just path }
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)] }
257
362
258ignoreLine :: Parser ()
363ignoreLine :: Parser ()
259ignoreLine = void $ manyTill anyChar newline
364ignoreLine = void $ manyTill anyChar newline
@@ -261,8 +366,8 @@ ignoreLine = void $ manyTill anyChar newline
261parseConfig :: Parser Config
366parseConfig :: Parser Config
262parseConfig = liftM mconcat $ many $ do
367parseConfig = liftM mconcat $ many $ do
263 P.whiteSpace configLexer
368 P.whiteSpace configLexer
264− let directives = [try mapDecl, try debugLogDecl]
369+ let directives = [try mapDecl, try debugLogDecl, try widthMapDecl]
265− choice directives <|> (ignoreLine >> return def)
370+ choice directives <|> (ignoreLine >> return defaultConfig)
266
371
267class Parse a where parseValue :: Parser a
372class Parse a where parseValue :: Parser a
268instance Parse Char where parseValue = P.charLiteral configLexer
373instance Parse Char where parseValue = P.charLiteral configLexer
@@ -307,3 +412,77 @@ instance (GParseAlts f, GParseAlts g) => GParseAlts (f :+: g) where
307 gparseAlts con = L1 <$> gparseAlts con <|> R1 <$> gparseAlts con
412 gparseAlts con = L1 <$> gparseAlts con <|> R1 <$> gparseAlts con
308
413
309instance GParseAlts V1 where gparseAlts _ = fail "GParse: V1"
414instance 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+13−14
@@ -1,29 +1,29 @@
1-- Copyright 2009-2010 Corey O'Connor
1-- Copyright 2009-2010 Corey O'Connor
2−module Graphics.Vty.Debug ( module Graphics.Vty.Debug
2+module Graphics.Vty.Debug
3+ ( module Graphics.Vty.Debug
3− , module Graphics.Vty.Debug.Image
4+ , module Graphics.Vty.Debug.Image
4− )
5+ )
5where
6where
6
7
7−import Graphics.Vty.Prelude
8−
9import Graphics.Vty.Attributes
8import Graphics.Vty.Attributes
9+import Graphics.Vty.Image (DisplayRegion)
10import Graphics.Vty.Debug.Image
10import Graphics.Vty.Debug.Image
11import Graphics.Vty.Span
11import Graphics.Vty.Span
12
12
13−import qualified Data.Vector as Vector
13+import qualified Data.Vector as Vector
14
14
15−rowOpsEffectedColumns :: DisplayOps -> [Int]
15+rowOpsAffectedColumns :: DisplayOps -> [Int]
16−rowOpsEffectedColumns ops
16+rowOpsAffectedColumns ops
17− = Vector.toList $ Vector.map spanOpsEffectedColumns ops
17+ = Vector.toList $ Vector.map spanOpsAffectedColumns ops
18
18
19allSpansHaveWidth :: DisplayOps -> Int -> Bool
19allSpansHaveWidth :: DisplayOps -> Int -> Bool
20allSpansHaveWidth ops expected
20allSpansHaveWidth ops expected
21− = all (== expected) $ Vector.toList $ Vector.map spanOpsEffectedColumns ops
21+ = all (== expected) $ Vector.toList $ Vector.map spanOpsAffectedColumns ops
22
22
23−spanOpsEffectedRows :: DisplayOps -> Int
23+spanOpsAffectedRows :: DisplayOps -> Int
24−spanOpsEffectedRows ops
24+spanOpsAffectedRows ops
25 = toEnum $ length (filter (not . null . Vector.toList) (Vector.toList ops))
25 = toEnum $ length (filter (not . null . Vector.toList) (Vector.toList ops))
26−
26+
27type SpanConstructLog = [SpanConstructEvent]
27type SpanConstructLog = [SpanConstructEvent]
28data SpanConstructEvent = SpanSetAttr Attr
28data SpanConstructEvent = SpanSetAttr Attr
29
29
@@ -37,4 +37,3 @@ data MockWindow = MockWindow Int Int
37
37
38regionForWindow :: MockWindow -> DisplayRegion
38regionForWindow :: MockWindow -> DisplayRegion
39regionForWindow (MockWindow w h) = (w,h)
39regionForWindow (MockWindow w h) = (w,h)
40−
src/Graphics/Vty/Debug/Image.hs+1−1
@@ -19,7 +19,7 @@ data ImageOp = ImageOp ImageEndo ImageEndo
19type ImageEndo = Image -> Image
19type ImageEndo = Image -> Image
20
20
21debugImageOps :: [ImageOp]
21debugImageOps :: [ImageOp]
22−debugImageOps =
22+debugImageOps =
23 [ idImageOp
23 [ idImageOp
24 -- , renderSingleColumnCharOp
24 -- , renderSingleColumnCharOp
25 -- , renderDoubleColumnCharOp
25 -- , renderDoubleColumnCharOp
src/Graphics/Vty/DisplayAttributes.hs+64−33
@@ -1,31 +1,30 @@
1-- Copyright 2009-2010 Corey O'Connor
1-- Copyright 2009-2010 Corey O'Connor
2−{-# LANGUAGE CPP #-}
3{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE CPP #-}
4
4
5−#ifndef MIN_VERSION_base
6−#defined MIN_VERSION_base(x,y,z) 1
7−#endif
8−
9−module Graphics.Vty.DisplayAttributes
5+module Graphics.Vty.DisplayAttributes where
10− where
11
6
12import Graphics.Vty.Attributes
7import Graphics.Vty.Attributes
13
8
14import Data.Bits ((.&.))
9import Data.Bits ((.&.))
15−
10+import Data.ByteString (ByteString)
16−#if !(MIN_VERSION_base(4,8,0))
11+#if !(MIN_VERSION_base(4,11,0))
17−import Data.Monoid (Monoid(..), mconcat)
12+import Data.Semigroup (Semigroup(..))
18#endif
13#endif
14+import Data.Text (Text)
15+import Data.Text.Encoding (encodeUtf8)
19
16
20−-- | Given the previously applied display attributes as a FixedAttr and the current display
17+-- | Given the previously applied display attributes as a FixedAttr and
18+-- the current display attributes as an Attr produces a FixedAttr that
21−-- attributes as an Attr produces a FixedAttr that represents the current display attributes. This
19+-- represents the current display attributes. This is done by using the
22−-- is done by using the previously applied display attributes to remove the "KeepCurrent"
20+-- previously applied display attributes to remove the "KeepCurrent"
23-- abstraction.
21-- abstraction.
24fixDisplayAttr :: FixedAttr -> Attr -> FixedAttr
22fixDisplayAttr :: FixedAttr -> Attr -> FixedAttr
25fixDisplayAttr fattr attr
23fixDisplayAttr fattr attr
26 = FixedAttr (fixStyle (fixedStyle fattr) (attrStyle attr))
24 = FixedAttr (fixStyle (fixedStyle fattr) (attrStyle attr))
27 (fixColor (fixedForeColor fattr) (attrForeColor attr))
25 (fixColor (fixedForeColor fattr) (attrForeColor attr))
28 (fixColor (fixedBackColor fattr) (attrBackColor attr))
26 (fixColor (fixedBackColor fattr) (attrBackColor attr))
27+ (fixURL (fixedURL fattr) (attrURL attr))
29 where
28 where
30 fixStyle _s Default = defaultStyleMask
29 fixStyle _s Default = defaultStyleMask
31 fixStyle s KeepCurrent = s
30 fixStyle s KeepCurrent = s
@@ -33,43 +32,56 @@ fixDisplayAttr fattr attr
33 fixColor _c Default = Nothing
32 fixColor _c Default = Nothing
34 fixColor c KeepCurrent = c
33 fixColor c KeepCurrent = c
35 fixColor _c (SetTo c) = Just 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
36
38
37−-- | difference between two display attributes. Used in the calculation of the operations required
39+-- | difference between two display attributes. Used in the calculation
38−-- to go from one display attribute to the next.
40+-- of the operations required to go from one display attribute to the
41+-- next.
39--
42--
40−-- Previously, vty would reset display attributes to default then apply the new display attributes.
43+-- Previously, vty would reset display attributes to default then apply
44+-- the new display attributes. This turned out to be very expensive: A
41−-- This turned out to be very expensive: A *lot* more data would be sent to the terminal than
45+-- *lot* more data would be sent to the terminal than required.
42−-- required.
43data DisplayAttrDiff = DisplayAttrDiff
46data DisplayAttrDiff = DisplayAttrDiff
44 { styleDiffs :: [StyleStateChange]
47 { styleDiffs :: [StyleStateChange]
45 , foreColorDiff :: DisplayColorDiff
48 , foreColorDiff :: DisplayColorDiff
46 , backColorDiff :: DisplayColorDiff
49 , backColorDiff :: DisplayColorDiff
50+ , urlDiff :: URLDiff
47 }
51 }
48 deriving (Show)
52 deriving (Show)
49
53
50−instance Monoid DisplayAttrDiff where
54+instance Semigroup DisplayAttrDiff where
51− mempty = DisplayAttrDiff [] NoColorChange NoColorChange
52− mappend d0 d1 =
55+ d0 <> d1 =
53 let ds = simplifyStyleDiffs (styleDiffs d0) (styleDiffs d1)
56 let ds = simplifyStyleDiffs (styleDiffs d0) (styleDiffs d1)
54 fcd = simplifyColorDiffs (foreColorDiff d0) (foreColorDiff d1)
57 fcd = simplifyColorDiffs (foreColorDiff d0) (foreColorDiff d1)
55 bcd = simplifyColorDiffs (backColorDiff d0) (backColorDiff d1)
58 bcd = simplifyColorDiffs (backColorDiff d0) (backColorDiff d1)
59+ ud = simplifyUrlDiffs (urlDiff d0) (urlDiff d1)
56− in DisplayAttrDiff ds fcd bcd
60+ in DisplayAttrDiff ds fcd bcd ud
61+
62+instance Monoid DisplayAttrDiff where
63+ mempty = DisplayAttrDiff [] NoColorChange NoColorChange NoLinkChange
64+#if !(MIN_VERSION_base(4,11,0))
65+ mappend = (<>)
66+#endif
57
67
58-- | Used in the computation of a final style attribute change.
68-- | Used in the computation of a final style attribute change.
59−--
60−-- TODO(corey): not really a simplify but a monoid instance.
61simplifyStyleDiffs :: [StyleStateChange] -> [StyleStateChange] -> [StyleStateChange]
69simplifyStyleDiffs :: [StyleStateChange] -> [StyleStateChange] -> [StyleStateChange]
62simplifyStyleDiffs cs0 cs1 = cs0 `mappend` cs1
70simplifyStyleDiffs cs0 cs1 = cs0 `mappend` cs1
63
71
64−-- | Consider two display color attributes diffs. What display color attribute diff are these
72+-- | Consider two display color attributes diffs. What display color
65−-- equivalent to?
73+-- attribute diff are these equivalent to?
66−--
67−-- TODO(corey): not really a simplify but a monoid instance.
68simplifyColorDiffs :: DisplayColorDiff -> DisplayColorDiff -> DisplayColorDiff
74simplifyColorDiffs :: DisplayColorDiff -> DisplayColorDiff -> DisplayColorDiff
69simplifyColorDiffs _cd ColorToDefault = ColorToDefault
75simplifyColorDiffs _cd ColorToDefault = ColorToDefault
70simplifyColorDiffs cd NoColorChange = cd
76simplifyColorDiffs cd NoColorChange = cd
71simplifyColorDiffs _cd (SetColor !c) = SetColor c
77simplifyColorDiffs _cd (SetColor !c) = SetColor c
72
78
79+-- | Consider two URL changes, which are mostly going to be the latter
80+-- unless the latter specifies no change.
81+simplifyUrlDiffs :: URLDiff -> URLDiff -> URLDiff
82+simplifyUrlDiffs ud NoLinkChange = ud
83+simplifyUrlDiffs _ ud = ud
84+
73-- | Difference between two display color attribute changes.
85-- | Difference between two display color attribute changes.
74data DisplayColorDiff
86data DisplayColorDiff
75 = ColorToDefault
87 = ColorToDefault
@@ -77,11 +89,15 @@ data DisplayColorDiff
77 | SetColor !Color
89 | SetColor !Color
78 deriving (Show, Eq)
90 deriving (Show, Eq)
79
91
80−-- | Style attribute changes are transformed into a sequence of apply/removes of the individual
92+-- | Style attribute changes are transformed into a sequence of
81−-- attributes.
93+-- apply/removes of the individual attributes.
82−data StyleStateChange
94+data StyleStateChange
83 = ApplyStandout
95 = ApplyStandout
84 | RemoveStandout
96 | RemoveStandout
97+ | ApplyItalic
98+ | RemoveItalic
99+ | ApplyStrikethrough
100+ | RemoveStrikethrough
85 | ApplyUnderline
101 | ApplyUnderline
86 | RemoveUnderline
102 | RemoveUnderline
87 | ApplyReverseVideo
103 | ApplyReverseVideo
@@ -94,15 +110,28 @@ data StyleStateChange
94 | RemoveBold
110 | RemoveBold
95 deriving (Show, Eq)
111 deriving (Show, Eq)
96
112
113+-- Setting and unsetting hyperlinks
114+data URLDiff
115+ = LinkTo !ByteString
116+ | NoLinkChange
117+ | EndLink
118+ deriving (Show, Eq)
119+
97−-- | Determines the diff between two display&color attributes. This diff determines the operations
120+-- | Determines the diff between two display&color attributes. This diff
98−-- that actually get output to the terminal.
121+-- determines the operations that actually get output to the terminal.
99displayAttrDiffs :: FixedAttr -> FixedAttr -> DisplayAttrDiff
122displayAttrDiffs :: FixedAttr -> FixedAttr -> DisplayAttrDiff
100displayAttrDiffs attr attr' = DisplayAttrDiff
123displayAttrDiffs attr attr' = DisplayAttrDiff
101 { styleDiffs = diffStyles (fixedStyle attr) (fixedStyle attr')
124 { styleDiffs = diffStyles (fixedStyle attr) (fixedStyle attr')
102 , foreColorDiff = diffColor (fixedForeColor attr) (fixedForeColor attr')
125 , foreColorDiff = diffColor (fixedForeColor attr) (fixedForeColor attr')
103 , backColorDiff = diffColor (fixedBackColor attr) (fixedBackColor attr')
126 , backColorDiff = diffColor (fixedBackColor attr) (fixedBackColor attr')
127+ , urlDiff = diffURL (fixedURL attr) (fixedURL attr')
104 }
128 }
105
129
130+diffURL :: Maybe Text -> Maybe Text -> URLDiff
131+diffURL Nothing Nothing = NoLinkChange
132+diffURL (Just _) Nothing = EndLink
133+diffURL _ (Just url) = LinkTo (encodeUtf8 url)
134+
106diffColor :: Maybe Color -> Maybe Color -> DisplayColorDiff
135diffColor :: Maybe Color -> Maybe Color -> DisplayColorDiff
107diffColor Nothing (Just c') = SetColor c'
136diffColor Nothing (Just c') = SetColor c'
108diffColor (Just c) (Just c')
137diffColor (Just c) (Just c')
@@ -116,6 +145,8 @@ diffStyles prev cur
116 = mconcat
145 = mconcat
117 [ styleDiff standout ApplyStandout RemoveStandout
146 [ styleDiff standout ApplyStandout RemoveStandout
118 , styleDiff underline ApplyUnderline RemoveUnderline
147 , styleDiff underline ApplyUnderline RemoveUnderline
148+ , styleDiff italic ApplyItalic RemoveItalic
149+ , styleDiff strikethrough ApplyStrikethrough RemoveStrikethrough
119 , styleDiff reverseVideo ApplyReverseVideo RemoveReverseVideo
150 , styleDiff reverseVideo ApplyReverseVideo RemoveReverseVideo
120 , styleDiff blink ApplyBlink RemoveBlink
151 , styleDiff blink ApplyBlink RemoveBlink
121 , styleDiff dim ApplyDim RemoveDim
152 , styleDiff dim ApplyDim RemoveDim
src/Graphics/Vty/Error.hs+3−3
@@ -1,8 +1,8 @@
1−module Graphics.Vty.Error
1+module Graphics.Vty.Error where
2− where
3
2
4-- | The type of exceptions specific to vty.
3-- | The type of exceptions specific to vty.
5--
4--
6−-- These have fully qualified names by default since, IMO, exception handling requires this.
5+-- These have fully qualified names by default since, IMO, exception
6+-- handling requires this.
7data VtyException
7data VtyException
8 = VtyFailure String -- ^ Uncategorized failure specific to vty.
8 = VtyFailure String -- ^ Uncategorized failure specific to vty.
src/Graphics/Vty/Image.hs+198−119
@@ -1,48 +1,63 @@
1-- Copyright 2009-2010 Corey O'Connor
1-- Copyright 2009-2010 Corey O'Connor
2−{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
4{-# LANGUAGE DisambiguateRecordFields #-}
3{-# LANGUAGE DisambiguateRecordFields #-}
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.
5−module Graphics.Vty.Image ( DisplayText
7+module Graphics.Vty.Image
8+ (
9+ -- * Images
6− , Image
10+ Image
7− , imageWidth
11+ , imageWidth
8− , imageHeight
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
9− , horizJoin
26+ , horizJoin
10− , (<|>)
27+ , (<|>)
11− , vertJoin
28+ , vertJoin
12− , (<->)
29+ , (<->)
13− , horizCat
30+ , horizCat
14− , vertCat
31+ , vertCat
15− , backgroundFill
32+ -- * Image modifications
16− , text
17− , text'
18− , char
19− , string
20− , iso10646String
21− , utf8String
22− , utf8Bytestring
23− , utf8Bytestring'
24− , charFill
25− , emptyImage
26− , safeWcwidth
27− , safeWcswidth
28− , wcwidth
29− , wcswidth
30− , crop
33+ , crop
31− , cropRight
34+ , cropRight
32− , cropLeft
35+ , cropLeft
33− , cropBottom
36+ , cropBottom
34− , cropTop
37+ , cropTop
35− , pad
38+ , pad
36− , resize
39+ , resize
37− , resizeWidth
40+ , resizeWidth
38− , resizeHeight
41+ , resizeHeight
39− , translate
42+ , translate
40− , translateX
43+ , translateX
41− , translateY
44+ , translateY
42− -- | The possible display attributes used in constructing an `Image`.
45+ -- * Character width functions
43− , module Graphics.Vty.Attributes
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
44− )
59+ )
45− where
60+where
46
61
47import Graphics.Vty.Attributes
62import Graphics.Vty.Attributes
48import Graphics.Vty.Image.Internal
63import Graphics.Vty.Image.Internal
@@ -56,115 +71,163 @@ import qualified Data.Text.Lazy as TL
56import qualified Data.Text.Lazy.Encoding as TL
71import qualified Data.Text.Lazy.Encoding as TL
57import Data.Word
72import Data.Word
58
73
74+-- | A region of the display (first width, then height)
75+type DisplayRegion = (Int,Int)
76+
77+regionWidth :: DisplayRegion -> Int
78+regionWidth = fst
79+
80+regionHeight :: DisplayRegion -> Int
81+regionHeight = snd
82+
59infixr 5 <|>
83infixr 5 <|>
60infixr 4 <->
84infixr 4 <->
61
85
62−-- | An area of the picture's bacground (See Background) of w columns and h rows.
86+-- | An area of the picture's background (See 'Background').
63−backgroundFill :: Int -> Int -> Image
87+backgroundFill :: Int
88+ -- ^ Fill width in columns
89+ -> Int
90+ -- ^ Fill height in rows
91+ -> Image
64−backgroundFill w h
92+backgroundFill w h
65 | w == 0 = EmptyImage
93 | w == 0 = EmptyImage
66 | h == 0 = EmptyImage
94 | h == 0 = EmptyImage
67 | otherwise = BGFill w h
95 | otherwise = BGFill w h
68
96
69−-- | Combines two images horizontally. Alias for horizJoin
97+-- | Combines two images horizontally. This is an alias for 'horizJoin'.
70--
98--
71-- infixr 5
99-- infixr 5
72(<|>) :: Image -> Image -> Image
100(<|>) :: Image -> Image -> Image
73(<|>) = horizJoin
101(<|>) = horizJoin
74
102
75−-- | Combines two images vertically. Alias for vertJoin
103+-- | Combines two images vertically. This is an alias for 'vertJoin'.
76--
104--
77-- infixr 4
105-- infixr 4
78(<->) :: Image -> Image -> Image
106(<->) :: Image -> Image -> Image
79(<->) = vertJoin
107(<->) = vertJoin
80
108
81−-- | Compose any number of images horizontally.
109+-- | Compose any number of images together horizontally, with the first
110+-- in the list being leftmost.
82horizCat :: [Image] -> Image
111horizCat :: [Image] -> Image
83horizCat = foldr horizJoin EmptyImage
112horizCat = foldr horizJoin EmptyImage
84
113
85−-- | Compose any number of images vertically.
114+-- | Compose any number of images vertically, with the first in the list
115+-- being topmost.
86vertCat :: [Image] -> Image
116vertCat :: [Image] -> Image
87vertCat = foldr vertJoin EmptyImage
117vertCat = foldr vertJoin EmptyImage
88
118
89−-- | A Data.Text.Lazy value
119+-- | Make an 'Image' from a lazy text value. The text value should be
120+-- sanitized of escape sequences (ASCII 27) and carriage returns;
121+-- otherwise layout and attribute problems may result.
90text :: Attr -> TL.Text -> Image
122text :: Attr -> TL.Text -> Image
91−text a txt = let displayWidth = safeWcswidth (TL.unpack txt)
123+text a txt = let displayWidth = safeWctlwidth txt
92 in HorizText a txt displayWidth (fromIntegral $! TL.length txt)
124 in HorizText a txt displayWidth (fromIntegral $! TL.length txt)
93
125
94−-- | A Data.Text value
126+-- | Make an 'Image' from a text value. The text value should be
127+-- sanitized of escape sequences (ASCII 27) and carriage returns;
128+-- otherwise layout and attribute problems may result.
95text' :: Attr -> T.Text -> Image
129text' :: Attr -> T.Text -> Image
96−text' a txt = let displayWidth = safeWcswidth (T.unpack txt)
130+text' a txt = let displayWidth = safeWctwidth txt
97 in HorizText a (TL.fromStrict txt) displayWidth (T.length txt)
131 in HorizText a (TL.fromStrict txt) displayWidth (T.length txt)
98
132
99−-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
133+-- | Make an image from a single character. This is a standard Haskell
100−-- the ISO-10646 encoding.
134+-- 31-bit character assumed to be in the ISO-10646 encoding.
101char :: Attr -> Char -> Image
135char :: Attr -> Char -> Image
102char a c =
136char a c =
103 let displayWidth = safeWcwidth c
137 let displayWidth = safeWcwidth c
104 in HorizText a (TL.singleton c) displayWidth 1
138 in HorizText a (TL.singleton c) displayWidth 1
105
139
140+-- | Make an image from a string of characters laid out on a single
106−-- | A string of characters layed out on a single row with the same display attribute. The string is
141+-- row with the same display attribute. The string is assumed to be a
107−-- assumed to be a sequence of ISO-10646 characters.
142+-- sequence of ISO-10646 characters. The input string should be
143+-- sanitized of escape sequences (ASCII 27) and carriage returns;
144+-- otherwise layout and attribute problems may result.
108--
145--
109−-- Note: depending on how the Haskell compiler represents string literals a string literal in a
146+-- Note: depending on how the Haskell compiler represents string
147+-- literals, a string literal in a UTF-8 encoded source file, for
110−-- UTF-8 encoded source file, for example, may be represented as a ISO-10646 string.
148+-- example, may be represented as a ISO-10646 string. That is, I think,
111−-- That is, I think, the case with GHC 6.10. This means, for the most part, you don't need to worry
149+-- the case with GHC 6.10. This means, for the most part, you don't need
112−-- about the encoding format when outputting string literals. Just provide the string literal
150+-- to worry about the encoding format when outputting string literals.
113−-- directly to iso10646String or string.
151+-- Just provide the string literal directly to iso10646String or string.
114−--
115iso10646String :: Attr -> String -> Image
152iso10646String :: Attr -> String -> Image
116iso10646String a str =
153iso10646String a str =
117 let displayWidth = safeWcswidth str
154 let displayWidth = safeWcswidth str
118 in HorizText a (TL.pack str) displayWidth (length str)
155 in HorizText a (TL.pack str) displayWidth (length str)
119
156
120−-- | Alias for iso10646String. Since the usual case is that a literal string like "foo" is
157+-- | Make an 'Image' from a 'String'.
121−-- represented internally as a list of ISO 10646 31 bit characters.
122--
158--
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.
162+--
123−-- Note: Keep in mind that GHC will compile source encoded as UTF-8 but the literal strings, while
163+-- Note: Keep in mind that GHC will compile source encoded as UTF-8
164+-- but the literal strings, while UTF-8 encoded in the source, will be
124−-- UTF-8 encoded in the source, will be transcoded to a ISO 10646 31 bit characters runtime
165+-- transcoded to a ISO 10646 31 bit characters runtime representation.
125−-- representation.
126string :: Attr -> String -> Image
166string :: Attr -> String -> Image
127string = iso10646String
167string = iso10646String
128
168
129−-- | A string of characters layed out on a single row. The input is assumed to be the bytes for
169+-- | Make an 'Image' from a string of characters layed out on a single
130−-- UTF-8 encoded text.
170+-- row. The input is assumed to be the bytes for UTF-8 encoded text.
131utf8String :: Attr -> [Word8] -> Image
171utf8String :: Attr -> [Word8] -> Image
132utf8String a bytes = utf8Bytestring a (BL.pack bytes)
172utf8String a bytes = utf8Bytestring a (BL.pack bytes)
133
173
134−-- | Renders a UTF-8 encoded lazy bytestring.
174+-- | Make an 'Image' from a UTF-8 encoded lazy bytestring.
135utf8Bytestring :: Attr -> BL.ByteString -> Image
175utf8Bytestring :: Attr -> BL.ByteString -> Image
136utf8Bytestring a bs = text a (TL.decodeUtf8 bs)
176utf8Bytestring a bs = text a (TL.decodeUtf8 bs)
137
177
138−-- | Renders a UTF-8 encoded strict bytestring.
178+-- | Make an 'Image' from a UTF-8 encoded strict bytestring.
139utf8Bytestring' :: Attr -> B.ByteString -> Image
179utf8Bytestring' :: Attr -> B.ByteString -> Image
140utf8Bytestring' a bs = text' a (T.decodeUtf8 bs)
180utf8Bytestring' a bs = text' a (T.decodeUtf8 bs)
141
181
142−-- | creates a fill of the specified character. The dimensions are in number of characters wide and
182+-- | Make an image filling a region with the specified character.
143−-- number of rows high.
183+--
184+-- If either the width or height are less than or equal to 0, then
185+-- the result is the empty image.
144−charFill :: Integral d => Attr -> Char -> d -> d -> Image
186+charFill :: Integral d
145−charFill _a _c 0 _h = EmptyImage
187+ => Attr
146−charFill _a _c _w 0 = EmptyImage
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
147−charFill a c w h =
196+charFill a c w h
197+ | w <= 0 || h <= 0 = EmptyImage
198+ | otherwise = vertCat
199+ $ replicate (fromIntegral h)
148− vertCat $ replicate (fromIntegral h) $ HorizText a txt displayWidth charWidth
200+ $ HorizText a txt displayWidth charWidth
149− where
201+ where
150− txt = TL.replicate (fromIntegral w) (TL.singleton c)
202+ txt = TL.replicate charWidth (TL.singleton c)
151− displayWidth = safeWcwidth c * (fromIntegral w)
203+ displayWidth = safeWcwidth c * charWidth
204+
205+ charWidth :: Num a => a
152− charWidth = fromIntegral w
206+ charWidth = fromIntegral w
153−
207+
154−-- | The empty image. Useful for fold combinators. These occupy no space nor define any display
208+-- | The empty image. Useful for fold combinators. These occupy no space
155−-- attributes.
209+-- and do not affect display attributes.
156−emptyImage :: Image
210+emptyImage :: Image
157emptyImage = EmptyImage
211emptyImage = EmptyImage
158
212
159−-- | pad the given image. This adds background character fills to the left, top, right, bottom.
213+-- | Pad the given image. This adds background character fills to the
160−-- The pad values are how many display columns or rows to add.
214+-- left, top, right, bottom.
161−pad :: Int -> Int -> Int -> Int -> Image -> Image
215+pad :: Int
216+ -- ^ How much padding to add to the left side of the image.
217+ -> Int
218+ -- ^ How much padding to add to the top of the image.
219+ -> Int
220+ -- ^ How much padding to add to the right side of the image.
221+ -> Int
222+ -- ^ How much padding to add to the bottom of the image.
223+ -> Image
224+ -- ^ The image to pad.
225+ -> Image
162pad 0 0 0 0 i = i
226pad 0 0 0 0 i = i
163pad inL inT inR inB inImage
227pad inL inT inR inB inImage
164 | inL < 0 || inT < 0 || inR < 0 || inB < 0 = error "cannot pad by negative amount"
228 | inL < 0 || inT < 0 || inR < 0 || inB < 0 = error "cannot pad by negative amount"
165 | otherwise = go inL inT inR inB inImage
229 | otherwise = go inL inT inR inB inImage
166− where
230+ where
167− -- TODO: uh.
168 go 0 0 0 0 i = i
231 go 0 0 0 0 i = i
169 go 0 0 0 b i = VertJoin i (BGFill w b) w h
232 go 0 0 0 b i = VertJoin i (BGFill w b) w h
170 where w = imageWidth i
233 where w = imageWidth i
@@ -179,40 +242,53 @@ pad inL inT inR inB inImage
179 where w = imageWidth i + l
242 where w = imageWidth i + l
180 h = imageHeight i
243 h = imageHeight i
181
244
182−-- | translates an image by padding or cropping the left and top. First param is amount to translate
245+-- | Translates an image by padding or cropping the left and top.
183−-- left. Second param is amount to translate top.
184--
246--
185−-- This can have an unexpected effect: Translating an image to less than (0,0) then to greater than
247+-- If translation offsets are negative then the image is cropped.
186−-- (0,0) will crop the image.
187−translate :: Int -> Int -> Image -> Image
248+translate :: Int
249+ -- ^ The horizontal translation offset (can be negative)
250+ -> Int
251+ -- ^ The vertical translation offset (can be negative)
252+ -> Image
253+ -- ^ The image to translate.
254+ -> Image
188translate x y i = translateX x (translateY y i)
255translate x y i = translateX x (translateY y i)
189
256
190−-- | translates an image by padding or cropping the left
257+-- | Translates an image by padding or cropping its left side.
191translateX :: Int -> Image -> Image
258translateX :: Int -> Image -> Image
192translateX x i
259translateX x i
260+ | x < 0 && (abs x > imageWidth i) = emptyImage
193 | x < 0 = let s = abs x in CropLeft i s (imageWidth i - s) (imageHeight i)
261 | x < 0 = let s = abs x in CropLeft i s (imageWidth i - s) (imageHeight i)
194 | x == 0 = i
262 | x == 0 = i
195 | otherwise = let h = imageHeight i in HorizJoin (BGFill x h) i (imageWidth i + x) h
263 | otherwise = let h = imageHeight i in HorizJoin (BGFill x h) i (imageWidth i + x) h
196
264
197−-- | translates an image by padding or cropping the top
265+-- | Translates an image by padding or cropping its top.
198translateY :: Int -> Image -> Image
266translateY :: Int -> Image -> Image
199translateY y i
267translateY y i
268+ | y < 0 && (abs y > imageHeight i) = emptyImage
200 | y < 0 = let s = abs y in CropTop i s (imageWidth i) (imageHeight i - s)
269 | y < 0 = let s = abs y in CropTop i s (imageWidth i) (imageHeight i - s)
201 | y == 0 = i
270 | y == 0 = i
202 | otherwise = let w = imageWidth i in VertJoin (BGFill w y) i w (imageHeight i + y)
271 | otherwise = let w = imageWidth i in VertJoin (BGFill w y) i w (imageHeight i + y)
203
272
204−-- | Ensure an image is no larger than the provided size. If the image is larger then crop the right
273+-- | Ensure an image is no larger than the provided size. If the image
205−-- or bottom.
274+-- is larger then crop the right or bottom.
206--
275--
207−-- This is transformed to a vertical crop from the bottom followed by horizontal crop from the
276+-- This is equivalent to a vertical crop from the bottom followed by
208−-- right.
277+-- horizontal crop from the right.
209−crop :: Int -> Int -> Image -> Image
278+crop :: Int
279+ -- ^ Cropping width
280+ -> Int
281+ -- ^ Cropping height
282+ -> Image
283+ -- ^ The image to crop
284+ -> Image
210crop 0 _ _ = EmptyImage
285crop 0 _ _ = EmptyImage
211crop _ 0 _ = EmptyImage
286crop _ 0 _ = EmptyImage
212crop w h i = cropBottom h (cropRight w i)
287crop w h i = cropBottom h (cropRight w i)
213
288
214−-- | crop the display height. If the image is less than or equal in height then this operation has
289+-- | Crop an image's height. If the image's height is less than or equal
290+-- to the specified height then this operation has no effect. Otherwise
215−-- no effect. Otherwise the image is cropped from the bottom.
291+-- the image is cropped from the bottom.
216cropBottom :: Int -> Image -> Image
292cropBottom :: Int -> Image -> Image
217cropBottom 0 _ = EmptyImage
293cropBottom 0 _ = EmptyImage
218cropBottom h inI
294cropBottom h inI
@@ -227,8 +303,9 @@ cropBottom h inI
227 | h >= imageHeight i = i
303 | h >= imageHeight i = i
228 | otherwise = CropBottom i (imageWidth i) h
304 | otherwise = CropBottom i (imageWidth i) h
229
305
230−-- | ensure the image is no wider than the given width. If the image is wider then crop the right
306+-- | Crop an image's width. If the image's width is less than or equal
231−-- side.
307+-- to the specified width then this operation has no effect. Otherwise
308+-- the image is cropped from the right.
232cropRight :: Int -> Image -> Image
309cropRight :: Int -> Image -> Image
233cropRight 0 _ = EmptyImage
310cropRight 0 _ = EmptyImage
234cropRight w inI
311cropRight w inI
@@ -243,8 +320,9 @@ cropRight w inI
243 | w >= imageWidth i = i
320 | w >= imageWidth i = i
244 | otherwise = CropRight i w (imageHeight i)
321 | otherwise = CropRight i w (imageHeight i)
245
322
246−-- | ensure the image is no wider than the given width. If the image is wider then crop the left
323+-- | Crop an image's width. If the image's width is less than or equal
247−-- side.
324+-- to the specified width then this operation has no effect. Otherwise
325+-- the image is cropped from the left.
248cropLeft :: Int -> Image -> Image
326cropLeft :: Int -> Image -> Image
249cropLeft 0 _ = EmptyImage
327cropLeft 0 _ = EmptyImage
250cropLeft w inI
328cropLeft w inI
@@ -261,8 +339,9 @@ cropLeft w inI
261 | imageWidth i <= w = i
339 | imageWidth i <= w = i
262 | otherwise = CropLeft i (imageWidth i - w) w (imageHeight i)
340 | otherwise = CropLeft i (imageWidth i - w) w (imageHeight i)
263
341
264−-- | crop the display height. If the image is less than or equal in height then this operation has
342+-- | Crop an image's height. If the image's height is less than or equal
343+-- to the specified height then this operation has no effect. Otherwise
265−-- no effect. Otherwise the image is cropped from the top.
344+-- the image is cropped from the top.
266cropTop :: Int -> Image -> Image
345cropTop :: Int -> Image -> Image
267cropTop 0 _ = EmptyImage
346cropTop 0 _ = EmptyImage
268cropTop h inI
347cropTop h inI
@@ -279,24 +358,24 @@ cropTop h inI
279 | imageHeight i <= h = i
358 | imageHeight i <= h = i
280 | otherwise = CropTop i (imageHeight i - h) (imageWidth i) h
359 | otherwise = CropTop i (imageHeight i - h) (imageWidth i) h
281
360
282−-- | Generic resize. Pads and crops as required to assure the given display width and height.
361+-- | Generic resize. Pads and crops are added to ensure that the
362+-- resulting image matches the specified dimensions. This is biased to
283−-- This is biased to pad/crop the right and bottom.
363+-- pad/crop the right and bottom.
284resize :: Int -> Int -> Image -> Image
364resize :: Int -> Int -> Image -> Image
285resize w h i = resizeHeight h (resizeWidth w i)
365resize w h i = resizeHeight h (resizeWidth w i)
286
366
287−-- | Resize the width. Pads and crops as required to assure the given display width.
367+-- | Resize the width. Pads and crops as required to assure the given
288−-- This is biased to pad/crop the right.
368+-- display width. This is biased to pad/crop on the right.
289resizeWidth :: Int -> Image -> Image
369resizeWidth :: Int -> Image -> Image
290resizeWidth w i = case w `compare` imageWidth i of
370resizeWidth w i = case w `compare` imageWidth i of
291 LT -> cropRight w i
371 LT -> cropRight w i
292 EQ -> i
372 EQ -> i
293 GT -> i <|> BGFill (w - imageWidth i) (imageHeight i)
373 GT -> i <|> BGFill (w - imageWidth i) (imageHeight i)
294
374
295−-- | Resize the height. Pads and crops as required to assure the given display height.
375+-- | Resize the height. Pads and crops as required to assure the given
296−-- This is biased to pad/crop the bottom.
376+-- display height. This is biased to pad/crop on the bottom.
297resizeHeight :: Int -> Image -> Image
377resizeHeight :: Int -> Image -> Image
298resizeHeight h i = case h `compare` imageHeight i of
378resizeHeight h i = case h `compare` imageHeight i of
299 LT -> cropBottom h i
379 LT -> cropBottom h i
300 EQ -> i
380 EQ -> i
301 GT -> i <-> BGFill (imageWidth i) (h - imageHeight i)
381 GT -> i <-> BGFill (imageWidth i) (h - imageHeight i)
302−
src/Graphics/Vty/Image/Internal.hs+72−92
@@ -3,10 +3,6 @@
3{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE DeriveGeneric #-}
4{-# OPTIONS_HADDOCK hide #-}
4{-# OPTIONS_HADDOCK hide #-}
5
5
6−#ifndef MIN_VERSION_base
7−#defined MIN_VERSION_base(x,y,z) 1
8−#endif
9−
10module Graphics.Vty.Image.Internal where
6module Graphics.Vty.Image.Internal where
11
7
12import Graphics.Vty.Attributes
8import Graphics.Vty.Attributes
@@ -16,24 +12,14 @@ import GHC.Generics
16
12
17import Control.DeepSeq
13import Control.DeepSeq
18
14
19−import qualified Data.Text.Lazy as TL
20−
21−#if !(MIN_VERSION_base(4,8,0))
15+#if !(MIN_VERSION_base(4,11,0))
22−import Data.Monoid
16+import Data.Semigroup (Semigroup(..))
23#endif
17#endif
18+import qualified Data.Text.Lazy as TL
24
19
25-- | A display text is a Data.Text.Lazy
20-- | A display text is a Data.Text.Lazy
26−--
27−-- TODO(corey): hm. there is an explicit equation for each type which goes to a lazy text. Each
28−-- application probably uses a single type. Perhaps parameterize the entire vty interface by the
29−-- input text type?
30−-- TODO: Try using a builder instead of a TL.Text instance directly. That might improve performance
31−-- for the usual case of appending a bunch of characters with the same attribute together.
32type DisplayText = TL.Text
21type DisplayText = TL.Text
33
22
34−-- TODO: store a skip list in HorizText(?)
35−-- TODO: represent display strings containing chars that are not 1 column chars as a separate
36−-- display string value?
37clipText :: DisplayText -> Int -> Int -> DisplayText
23clipText :: DisplayText -> Int -> Int -> DisplayText
38clipText txt leftSkip rightClip =
24clipText txt leftSkip rightClip =
39 -- CPS would clarify this I think
25 -- CPS would clarify this I think
@@ -42,8 +28,8 @@ clipText txt leftSkip rightClip =
42 (toTake,padSuffix) = clipForCharWidth rightClip txt' 0
28 (toTake,padSuffix) = clipForCharWidth rightClip txt' 0
43 txt'' = TL.append (TL.take toTake txt') (if padSuffix then TL.singleton '…' else TL.empty)
29 txt'' = TL.append (TL.take toTake txt') (if padSuffix then TL.singleton '…' else TL.empty)
44 -- Note: some characters and zero-width and combining characters
30 -- Note: some characters and zero-width and combining characters
45− -- combine to the left, so keep taking characters even if the width
31+ -- combine to the left, so keep taking characters even if the
46− -- is zero.
32+ -- width is zero.
47 clipForCharWidth w t n
33 clipForCharWidth w t n
48 | TL.null t = (n, False)
34 | TL.null t = (n, False)
49 | w < cw = (n, w /= 0)
35 | w < cw = (n, w /= 0)
@@ -51,8 +37,8 @@ clipText txt leftSkip rightClip =
51 where cw = safeWcwidth (TL.head t)
37 where cw = safeWcwidth (TL.head t)
52 in txt''
38 in txt''
53
39
54−-- | This is the internal representation of Images. Use the constructors in "Graphics.Vty.Image" to
40+-- | This is the internal representation of Images. Use the constructors
55−-- create instances.
41+-- in "Graphics.Vty.Image" to create instances.
56--
42--
57-- Images are:
43-- Images are:
58--
44--
@@ -65,49 +51,61 @@ clipText txt leftSkip rightClip =
65-- * a cropped image
51-- * a cropped image
66--
52--
67-- * an empty image of no size or content.
53-- * an empty image of no size or content.
68−data Image =
54+data Image =
69 -- | A horizontal text span has a row height of 1.
55 -- | A horizontal text span has a row height of 1.
70 HorizText
56 HorizText
71 { attr :: Attr
57 { attr :: Attr
72− -- | The text to display. The display width of the text is always outputWidth.
58+ -- | The text to display. The display width of the text is always
59+ -- outputWidth.
73 , displayText :: DisplayText
60 , displayText :: DisplayText
74 -- | The number of display columns for the text.
61 -- | The number of display columns for the text.
75 , outputWidth :: Int
62 , outputWidth :: Int
76 -- | the number of characters in the text.
63 -- | the number of characters in the text.
77 , charWidth :: Int
64 , charWidth :: Int
78 }
65 }
79− -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
66+ -- | A horizontal join can be constructed between any two images.
67+ -- However a HorizJoin instance is required to be between two images
80− -- required to be between two images of equal height. The horizJoin constructor adds background
68+ -- of equal height. The horizJoin constructor adds background fills
81− -- fills to the provided images that assure this is true for the HorizJoin value produced.
69+ -- to the provided images that assure this is true for the HorizJoin
70+ -- value produced.
82 | HorizJoin
71 | HorizJoin
83− { partLeft :: Image
72+ { partLeft :: Image
84 , partRight :: Image
73 , partRight :: Image
74+ , outputWidth :: Int
85− , outputWidth :: Int -- ^ imageWidth partLeft == imageWidth partRight. Always > 0
75+ -- ^ imageWidth partLeft == imageWidth partRight. Always > 0
76+ , outputHeight :: Int
86− , outputHeight :: Int -- ^ imageHeight partLeft == imageHeight partRight. Always > 0
77+ -- ^ imageHeight partLeft == imageHeight partRight. Always > 0
87 }
78 }
88− -- | A veritical join can be constructed between any two images. However a VertJoin instance is
79+ -- | A veritical join can be constructed between any two images.
80+ -- However a VertJoin instance is required to be between two images
89− -- required to be between two images of equal width. The vertJoin constructor adds background
81+ -- of equal width. The vertJoin constructor adds background fills
90− -- fills to the provides images that assure this is true for the VertJoin value produced.
82+ -- to the provides images that assure this is true for the VertJoin
83+ -- value produced.
91 | VertJoin
84 | VertJoin
92 { partTop :: Image
85 { partTop :: Image
93 , partBottom :: Image
86 , partBottom :: Image
87+ , outputWidth :: Int
94− , outputWidth :: Int -- ^ imageWidth partTop == imageWidth partBottom. always > 0
88+ -- ^ imageWidth partTop == imageWidth partBottom. always > 0
89+ , outputHeight :: Int
95− , outputHeight :: Int -- ^ imageHeight partTop == imageHeight partBottom. always > 1
90+ -- ^ imageHeight partTop == imageHeight partBottom. always > 1
96 }
91 }
97− -- | A background fill will be filled with the background char. The background char is
92+ -- | A background fill will be filled with the background char. The
98− -- defined as a property of the Picture this Image is used to form.
93+ -- background char is defined as a property of the Picture this
94+ -- Image is used to form.
99 | BGFill
95 | BGFill
100 { outputWidth :: Int -- ^ always > 0
96 { outputWidth :: Int -- ^ always > 0
101 , outputHeight :: Int -- ^ always > 0
97 , outputHeight :: Int -- ^ always > 0
102 }
98 }
103− -- | Crop an image horizontally to a size by reducing the size from the right.
99+ -- | Crop an image horizontally to a size by reducing the size from
100+ -- the right.
104 | CropRight
101 | CropRight
105 { croppedImage :: Image
102 { croppedImage :: Image
106 -- | Always < imageWidth croppedImage > 0
103 -- | Always < imageWidth croppedImage > 0
107 , outputWidth :: Int
104 , outputWidth :: Int
108 , outputHeight :: Int -- ^ imageHeight croppedImage
105 , outputHeight :: Int -- ^ imageHeight croppedImage
109 }
106 }
110− -- | Crop an image horizontally to a size by reducing the size from the left.
107+ -- | Crop an image horizontally to a size by reducing the size from
108+ -- the left.
111 | CropLeft
109 | CropLeft
112 { croppedImage :: Image
110 { croppedImage :: Image
113 -- | Always < imageWidth croppedImage > 0
111 -- | Always < imageWidth croppedImage > 0
@@ -116,7 +114,8 @@ data Image =
116 , outputWidth :: Int
114 , outputWidth :: Int
117 , outputHeight :: Int
115 , outputHeight :: Int
118 }
116 }
119− -- | Crop an image vertically to a size by reducing the size from the bottom
117+ -- | Crop an image vertically to a size by reducing the size from
118+ -- the bottom
120 | CropBottom
119 | CropBottom
121 { croppedImage :: Image
120 { croppedImage :: Image
122 -- | imageWidth croppedImage
121 -- | imageWidth croppedImage
@@ -124,7 +123,8 @@ data Image =
124 -- | height image is cropped to. Always < imageHeight croppedImage > 0
123 -- | height image is cropped to. Always < imageHeight croppedImage > 0
125 , outputHeight :: Int
124 , outputHeight :: Int
126 }
125 }
127− -- | Crop an image vertically to a size by reducing the size from the top
126+ -- | Crop an image vertically to a size by reducing the size from
127+ -- the top
128 | CropTop
128 | CropTop
129 { croppedImage :: Image
129 { croppedImage :: Image
130 -- | Always < imageHeight croppedImage > 0
130 -- | Always < imageHeight croppedImage > 0
@@ -136,43 +136,17 @@ data Image =
136 }
136 }
137 -- | The empty image
137 -- | The empty image
138 --
138 --
139− -- The combining operators identity constant.
139+ -- The combining operators identity constant.
140 -- EmptyImage <|> a = a
140 -- EmptyImage <|> a = a
141 -- EmptyImage <-> a = a
141 -- EmptyImage <-> a = a
142− --
142+ --
143 -- Any image of zero size equals the empty image.
143 -- Any image of zero size equals the empty image.
144 | EmptyImage
144 | EmptyImage
145− deriving (Eq, Generic)
145+ deriving (Eq, Generic, Show, Read)
146−
147−instance Show Image where
148− show ( HorizText { attr, displayText, outputWidth, charWidth } )
149− = "HorizText " ++ show displayText
150− ++ "@(" ++ show attr ++ ","
151− ++ show outputWidth ++ ","
152− ++ show charWidth ++ ")"
153− show ( BGFill { outputWidth, outputHeight } )
154− = "BGFill (" ++ show outputWidth ++ "," ++ show outputHeight ++ ")"
155− show ( HorizJoin { partLeft = l, partRight = r, outputWidth = c } )
156− = "HorizJoin " ++ show c ++ " (" ++ show l ++ " <|> " ++ show r ++ ")"
157− show ( VertJoin { partTop = t, partBottom = b, outputWidth = c, outputHeight = r } )
158− = "VertJoin [" ++ show c ++ ", " ++ show r ++ "] (" ++ show t ++ ") <-> (" ++ show b ++ ")"
159− show ( CropRight { croppedImage, outputWidth, outputHeight } )
160− = "CropRight [" ++ show outputWidth ++ "," ++ show outputHeight ++ "]"
161− ++ " (" ++ show croppedImage ++ ")"
162− show ( CropLeft { croppedImage, leftSkip, outputWidth, outputHeight } )
163− = "CropLeft [" ++ show leftSkip ++ "," ++ show outputWidth ++ "," ++ show outputHeight ++ "]"
164− ++ " (" ++ show croppedImage ++ ")"
165− show ( CropBottom { croppedImage, outputWidth, outputHeight } )
166− = "CropBottom [" ++ show outputWidth ++ "," ++ show outputHeight ++ "]"
167− ++ " (" ++ show croppedImage ++ ")"
168− show ( CropTop { croppedImage, topSkip, outputWidth, outputHeight } )
169− = "CropTop [" ++ show topSkip ++ "," ++ show outputWidth ++ "," ++ show outputHeight ++ "]"
170− ++ " (" ++ show croppedImage ++ ")"
171− show ( EmptyImage ) = "EmptyImage"
172
146
173-- | pretty print just the structure of an image.
147-- | pretty print just the structure of an image.
174ppImageStructure :: Image -> String
148ppImageStructure :: Image -> String
175−ppImageStructure inImg = go 0 inImg
149+ppImageStructure = go 0
176 where
150 where
177 go indent img = tab indent ++ pp indent img
151 go indent img = tab indent ++ pp indent img
178 tab indent = concat $ replicate indent " "
152 tab indent = concat $ replicate indent " "
@@ -198,7 +172,7 @@ ppImageStructure inImg = go 0 inImg
198 = "CropTop("++ show outputWidth ++ "," ++ show topSkip ++ "->" ++ show outputHeight ++ ")\n"
172 = "CropTop("++ show outputWidth ++ "," ++ show topSkip ++ "->" ++ show outputHeight ++ ")\n"
199 ++ go (i+1) croppedImage
173 ++ go (i+1) croppedImage
200 pp _ EmptyImage = "EmptyImage"
174 pp _ EmptyImage = "EmptyImage"
201−
175+
202instance NFData Image where
176instance NFData Image where
203 rnf EmptyImage = ()
177 rnf EmptyImage = ()
204 rnf (CropRight i w h) = i `deepseq` w `seq` h `seq` ()
178 rnf (CropRight i w h) = i `deepseq` w `seq` h `seq` ()
@@ -210,7 +184,8 @@ instance NFData Image where
210 rnf (HorizJoin l r w h) = l `deepseq` r `deepseq` w `seq` h `seq` ()
184 rnf (HorizJoin l r w h) = l `deepseq` r `deepseq` w `seq` h `seq` ()
211 rnf (HorizText a s w cw) = a `seq` s `deepseq` w `seq` cw `seq` ()
185 rnf (HorizText a s w cw) = a `seq` s `deepseq` w `seq` cw `seq` ()
212
186
213−-- | The width of an Image. This is the number display columns the image will occupy.
187+-- | The width of an Image. This is the number display columns the image
188+-- will occupy.
214imageWidth :: Image -> Int
189imageWidth :: Image -> Int
215imageWidth HorizText { outputWidth = w } = w
190imageWidth HorizText { outputWidth = w } = w
216imageWidth HorizJoin { outputWidth = w } = w
191imageWidth HorizJoin { outputWidth = w } = w
@@ -222,7 +197,8 @@ imageWidth CropBottom { outputWidth = w } = w
222imageWidth CropTop { outputWidth = w } = w
197imageWidth CropTop { outputWidth = w } = w
223imageWidth EmptyImage = 0
198imageWidth EmptyImage = 0
224
199
225−-- | The height of an Image. This is the number of display rows the image will occupy.
200+-- | The height of an Image. This is the number of display rows the
201+-- image will occupy.
226imageHeight :: Image -> Int
202imageHeight :: Image -> Int
227imageHeight HorizText {} = 1
203imageHeight HorizText {} = 1
228imageHeight HorizJoin { outputHeight = h } = h
204imageHeight HorizJoin { outputHeight = h } = h
@@ -234,27 +210,32 @@ imageHeight CropBottom { outputHeight = h } = h
234imageHeight CropTop { outputHeight = h } = h
210imageHeight CropTop { outputHeight = h } = h
235imageHeight EmptyImage = 0
211imageHeight EmptyImage = 0
236
212
213+-- | Append in the 'Semigroup' instance is equivalent to '<->'.
214+instance Semigroup Image where
215+ (<>) = vertJoin
216+
237−-- | Append in the Monoid instance is equivalent to <->.
217+-- | Append in the 'Monoid' instance is equivalent to '<->'.
238instance Monoid Image where
218instance Monoid Image where
239 mempty = EmptyImage
219 mempty = EmptyImage
220+#if !(MIN_VERSION_base(4,11,0))
240− mappend = vertJoin
221+ mappend = (<>)
222+#endif
241
223
242-- | combines two images side by side
224-- | combines two images side by side
243--
225--
244−-- Combines text chunks where possible. Assures outputWidth and outputHeight properties are not
226+-- Combines text chunks where possible. Assures outputWidth and
245−-- violated.
227+-- outputHeight properties are not violated.
246−--
247−-- The result image will have a width equal to the sum of the two images width. And the height will
248−-- equal the largest height of the two images. The area not defined in one image due to a height
249−-- missmatch will be filled with the background pattern.
250--
228--
251−-- TODO: the bg fill is biased towards top to bottom languages(?)
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.
252horizJoin :: Image -> Image -> Image
233horizJoin :: Image -> Image -> Image
253horizJoin EmptyImage i = i
234horizJoin EmptyImage i = i
254horizJoin i EmptyImage = i
235horizJoin i EmptyImage = i
255horizJoin i0@(HorizText a0 t0 w0 cw0) i1@(HorizText a1 t1 w1 cw1)
236horizJoin i0@(HorizText a0 t0 w0 cw0) i1@(HorizText a1 t1 w1 cw1)
256 | a0 == a1 = HorizText a0 (TL.append t0 t1) (w0 + w1) (cw0 + cw1)
237 | a0 == a1 = HorizText a0 (TL.append t0 t1) (w0 + w1) (cw0 + cw1)
257− -- TODO: assumes horiz text height is always 1
238+ -- assumes horiz text height is always 1
258 | otherwise = HorizJoin i0 i1 (w0 + w1) 1
239 | otherwise = HorizJoin i0 i1 (w0 + w1) 1
259horizJoin i0 i1
240horizJoin i0 i1
260 -- If the images are of the same height then no padding is required
241 -- If the images are of the same height then no padding is required
@@ -276,19 +257,19 @@ horizJoin _ _ = error "horizJoin applied to undefined values."
276
257
277-- | combines two images vertically
258-- | combines two images vertically
278--
259--
279−-- The result image will have a height equal to the sum of the heights of both images.
260+-- The result image will have a height equal to the sum of the heights
280−-- The width will equal the largest width of the two images.
261+-- of both images. The width will equal the largest width of the two
281−-- The area not defined in one image due to a width missmatch will be filled with the background
262+-- images. The area not defined in one image due to a width mismatch
282−-- pattern.
263+-- will be filled with the background pattern.
283−--
284−-- TODO: the bg fill is biased towards right to left languages(?)
285vertJoin :: Image -> Image -> Image
264vertJoin :: Image -> Image -> Image
286vertJoin EmptyImage i = i
265vertJoin EmptyImage i = i
287vertJoin i EmptyImage = i
266vertJoin i EmptyImage = i
288vertJoin i0 i1
267vertJoin i0 i1
289− -- If the images are of the same width then no background padding is required
268+ -- If the images are of the same width then no background padding is
269+ -- required
290 | w0 == w1 = VertJoin i0 i1 w0 h
270 | w0 == w1 = VertJoin i0 i1 w0 h
291− -- Otherwise one of the images needs to be padded to the size of the other image.
271+ -- Otherwise one of the images needs to be padded to the size of the
272+ -- other image.
292 | w0 < w1
273 | w0 < w1
293 = let padAmount = w1 - w0
274 = let padAmount = w1 - w0
294 in VertJoin (HorizJoin i0 (BGFill padAmount h0) w1 h0) i1 w1 h
275 in VertJoin (HorizJoin i0 (BGFill padAmount h0) w1 h0) i1 w1 h
@@ -302,4 +283,3 @@ vertJoin i0 i1
302 h1 = imageHeight i1
283 h1 = imageHeight i1
303 h = h0 + h1
284 h = h0 + h1
304vertJoin _ _ = error "vertJoin applied to undefined values."
285vertJoin _ _ = error "vertJoin applied to undefined values."
305−
src/Graphics/Vty/Inline.hs+62−55
@@ -1,45 +1,41 @@
1−-- | The inline module provides a limited interface to changing the style of terminal output. The
1+-- | The inline module provides a limited interface to changing the
2+-- style of terminal output. The intention is for this interface to be
2−-- intention is for this interface to be used inline with other output systems.
3+-- used inline with other output systems.
3--
4--
4−-- The changes specified by the InlineM monad are applied to the terminals display attributes. These
5+-- The changes specified by the InlineM monad are applied to the
6+-- terminal's display attributes. These display attributes affect the
5−-- display attributes effect the display of all following text output to the terminal file
7+-- display of all following text output to the terminal file descriptor.
6−-- descriptor.
7--
8--
8−-- For example, in an IO monad the following code with print the text \"Not styled. \" Followed by the
9+-- For example, in an IO monad the following code will print the text
10+-- \"Not styled. \" Followed by the text \" Styled! \" drawn over a red
9−-- text \" Styled! \" drawn over a red background and underlined.
11+-- background and underlined.
10--
12--
11-- @
13-- @
12-- putStr \"Not styled. \"
14-- putStr \"Not styled. \"
13-- putAttrChange_ $ do
15-- putAttrChange_ $ do
14−-- backColor red
16+-- backColor red
15-- applyStyle underline
17-- applyStyle underline
16-- putStr \" Styled! \"
18-- putStr \" Styled! \"
17-- putAttrChange_ $ defaultAll
19-- putAttrChange_ $ defaultAll
18-- putStrLn \"Not styled.\"
20-- putStrLn \"Not styled.\"
19-- @
21-- @
20--
22--
21−-- 'putAttrChange' outputs the control codes to the terminal device 'Handle'. This is a duplicate
23+-- 'putAttrChange' emits the control codes to the terminal device
24+-- attached to 'Handle'. This is a duplicate of the 'stdout' handle when
22−-- of the 'stdout' handle when the 'terminalHandle' was (first) acquired. If 'stdout' has since been
25+-- the 'terminalHandle' was (first) acquired. If 'stdout' has since been
23−-- changed then 'putStr', 'putStrLn', 'print' etc.. will output to a different 'Handle' than
26+-- changed then 'putStr', 'putStrLn', 'print' etc. will output to a
24−-- 'putAttrChange'
27+-- different 'Handle' than 'putAttrChange'.
25--
28--
26-- Copyright 2009-2010 Corey O'Connor
29-- Copyright 2009-2010 Corey O'Connor
27−{-# LANGUAGE CPP #-}
28−{-# LANGUAGE BangPatterns #-}
29−
30−#ifndef MIN_VERSION_base
31−#defined MIN_VERSION_base(x,y,z) 1
32−#endif
33−
34−module Graphics.Vty.Inline ( module Graphics.Vty.Inline
30+module Graphics.Vty.Inline
31+ ( module Graphics.Vty.Inline
35− , withVty
32+ , withVty
36− )
33+ )
37− where
34+where
38
35
39import Graphics.Vty
36import Graphics.Vty
40import Graphics.Vty.DisplayAttributes
37import Graphics.Vty.DisplayAttributes
41import Graphics.Vty.Inline.Unsafe
38import Graphics.Vty.Inline.Unsafe
42−import Graphics.Vty.Output.Interface
43
39
44import Blaze.ByteString.Builder (writeToByteString)
40import Blaze.ByteString.Builder (writeToByteString)
45
41
@@ -50,44 +46,55 @@ import Data.IORef
50
46
51import System.IO
47import System.IO
52
48
53−#if !(MIN_VERSION_base(4,8,0))
49+type InlineM v = State InlineState v
54−import Control.Applicative
55−import Data.Monoid ( mappend )
56−#endif
57
50
58−type InlineM v = State Attr v
51+data InlineState =
52+ InlineState { inlineAttr :: Attr
53+ , inlineUrlsEnabled :: Bool
54+ }
59
55
60−-- | Set the background color to the provided 'Color'
56+-- | Set the background color to the provided 'Color'.
61backColor :: Color -> InlineM ()
57backColor :: Color -> InlineM ()
62−backColor c = modify $ flip mappend ( currentAttr `withBackColor` c )
58+backColor c = modify $ \s ->
59+ s { inlineAttr = inlineAttr s `withBackColor` c
60+ }
63
61
64−-- | Set the foreground color to the provided 'Color'
62+-- | Set the foreground color to the provided 'Color'.
65foreColor :: Color -> InlineM ()
63foreColor :: Color -> InlineM ()
66−foreColor c = modify $ flip mappend ( currentAttr `withForeColor` c )
64+foreColor c = modify $ \s ->
65+ s { inlineAttr = inlineAttr s `withForeColor` c
66+ }
67
67
68−-- | Attempt to change the 'Style' of the following text.
68+-- | Attempt to change the 'Style' of the following text..
69--
69--
70−-- If the terminal does not support the style change no error is produced. The style can still be
70+-- If the terminal does not support the style change then no error is
71−-- removed.
71+-- produced. The style can still be removed.
72applyStyle :: Style -> InlineM ()
72applyStyle :: Style -> InlineM ()
73−applyStyle s = modify $ flip mappend ( currentAttr `withStyle` s )
73+applyStyle st = modify $ \s ->
74+ s { inlineAttr = inlineAttr s `withStyle` st
75+ }
74
76
75−-- | Attempt to remove the specified 'Style' from the display of the following text.
77+-- | Attempt to remove the specified 'Style' from the display of the
78+-- following text.
76--
79--
77−-- This will fail if applyStyle for the given style has not been previously called.
80+-- This will fail if 'applyStyle' for the given style has not been
81+-- previously called.
78removeStyle :: Style -> InlineM ()
82removeStyle :: Style -> InlineM ()
79−removeStyle sMask = modify $ \attr ->
83+removeStyle sMask = modify $ \s ->
84+ s { inlineAttr =
80− let style' = case attrStyle attr of
85+ let style' = case attrStyle (inlineAttr s) of
81− Default -> error $ "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."
86+ Default -> error "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."
82− KeepCurrent -> error $ "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."
87+ KeepCurrent -> error "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."
83− SetTo s -> s .&. complement sMask
88+ SetTo st -> st .&. complement sMask
84− in attr { attrStyle = SetTo style' }
89+ in (inlineAttr s) { attrStyle = SetTo style' }
85−
90+ }
91+
86−-- | Reset the display attributes
92+-- | Reset the display attributes.
87defaultAll :: InlineM ()
93defaultAll :: InlineM ()
88−defaultAll = put defAttr
94+defaultAll = modify $ \s -> s { inlineAttr = defAttr }
89
95
90−-- | Apply the provided display attribute changes to the given terminal output device.
96+-- | Apply the provided display attribute changes to the given terminal
97+-- output device.
91--
98--
92-- This does not flush the terminal.
99-- This does not flush the terminal.
93putAttrChange :: ( Applicative m, MonadIO m ) => Output -> InlineM () -> m ()
100putAttrChange :: ( Applicative m, MonadIO m ) => Output -> InlineM () -> m ()
@@ -97,19 +104,19 @@ putAttrChange out c = liftIO $ do
97 mfattr <- prevFattr <$> readIORef (assumedStateRef out)
104 mfattr <- prevFattr <$> readIORef (assumedStateRef out)
98 fattr <- case mfattr of
105 fattr <- case mfattr of
99 Nothing -> do
106 Nothing -> do
100− liftIO $ outputByteBuffer out $ writeToByteString $ writeDefaultAttr dc
107+ liftIO $ outputByteBuffer out $ writeToByteString $ writeDefaultAttr dc False
101− return $ FixedAttr defaultStyleMask Nothing Nothing
108+ return $ FixedAttr defaultStyleMask Nothing Nothing Nothing
102 Just v -> return v
109 Just v -> return v
103− let attr = execState c currentAttr
110+ let InlineState attr urlsEnabled = execState c (InlineState currentAttr False)
104 attr' = limitAttrForDisplay out attr
111 attr' = limitAttrForDisplay out attr
105 fattr' = fixDisplayAttr fattr attr'
112 fattr' = fixDisplayAttr fattr attr'
106 diffs = displayAttrDiffs fattr fattr'
113 diffs = displayAttrDiffs fattr fattr'
107− outputByteBuffer out $ writeToByteString $ writeSetAttr dc fattr attr' diffs
114+ outputByteBuffer out $ writeToByteString $ writeSetAttr dc urlsEnabled fattr attr' diffs
108 modifyIORef (assumedStateRef out) $ \s -> s { prevFattr = Just fattr' }
115 modifyIORef (assumedStateRef out) $ \s -> s { prevFattr = Just fattr' }
109 inlineHack dc
116 inlineHack dc
110
117
111−-- | Apply the provided display attributes changes to the terminal output device that was current at
118+-- | Apply the provided display attributes changes to the terminal
112−-- the time this was first used. Which, for most use cases, is the current terminal.
119+-- output device.
113--
120--
114-- This will flush the terminal output.
121-- This will flush the terminal output.
115putAttrChange_ :: ( Applicative m, MonadIO m ) => InlineM () -> m ()
122putAttrChange_ :: ( Applicative m, MonadIO m ) => InlineM () -> m ()
src/Graphics/Vty/Inline/Unsafe.hs+4−14
@@ -1,16 +1,9 @@
1−{-# LANGUAGE CPP #-}
2{-# OPTIONS_HADDOCK hide #-}
1{-# OPTIONS_HADDOCK hide #-}
3
2
4−#ifndef MIN_VERSION_base
5−#defined MIN_VERSION_base(x,y,z) 1
6−#endif
7−
8module Graphics.Vty.Inline.Unsafe where
3module Graphics.Vty.Inline.Unsafe where
9
4
10import Graphics.Vty
5import Graphics.Vty
11
6
12−import Data.Default
13−import Data.Monoid
14import Data.IORef
7import Data.IORef
15
8
16import GHC.IO.Handle (hDuplicate)
9import GHC.IO.Handle (hDuplicate)
@@ -21,10 +14,6 @@ import System.IO.Unsafe
21
14
22import System.Posix.IO (handleToFd)
15import System.Posix.IO (handleToFd)
23
16
24−#if !(MIN_VERSION_base(4,8,0))
25−import Control.Applicative
26−#endif
27−
28globalVty :: IORef (Maybe Vty)
17globalVty :: IORef (Maybe Vty)
29{-# NOINLINE globalVty #-}
18{-# NOINLINE globalVty #-}
30globalVty = unsafePerformIO $ newIORef Nothing
19globalVty = unsafePerformIO $ newIORef Nothing
@@ -39,10 +28,11 @@ mkDupeConfig = do
39 hSetBuffering stdin NoBuffering
28 hSetBuffering stdin NoBuffering
40 stdinDupe <- hDuplicate stdin >>= handleToFd
29 stdinDupe <- hDuplicate stdin >>= handleToFd
41 stdoutDupe <- hDuplicate stdout >>= handleToFd
30 stdoutDupe <- hDuplicate stdout >>= handleToFd
42− return $ def { inputFd = Just stdinDupe, outputFd = Just stdoutDupe }
31+ return $ defaultConfig { inputFd = Just stdinDupe, outputFd = Just stdoutDupe }
43
32
44−-- | This will create a Vty instance using 'mkVty' and execute an IO action provided that instance.
33+-- | This will create a Vty instance using 'mkVty' and execute an IO
34+-- action provided that instance. The created Vty instance will be
45−-- The created Vty instance will be stored to the unsafe 'IORef' 'globalVty'.
35+-- stored to the unsafe 'IORef' 'globalVty'.
46--
36--
47-- This instance will use duplicates of the stdin and stdout Handles.
37-- This instance will use duplicates of the stdin and stdout Handles.
48withVty :: (Vty -> IO b) -> IO b
38withVty :: (Vty -> IO b) -> IO b
src/Graphics/Vty/Input.hs+97−127
@@ -1,135 +1,125 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE RecordWildCards #-}
1+{-# LANGUAGE RecordWildCards, CPP #-}
3
2
4−#ifndef MIN_VERSION_base
3+-- | This module provides the input layer for Vty, including methods
5−#defined MIN_VERSION_base(x,y,z) 1
6−#endif
7−
8−-- | The input layer for VTY. This provides methods for initializing an 'Input' structure which can
4+-- for initializing an 'Input' structure and reading 'Event's from the
9−-- then be used to read 'Event's from the terminal.
5+-- terminal.
10−--
6+--
11−-- The history of terminals has resulted in a broken input process. Some keys and combinations will
7+-- Note that due to the evolution of terminal emulators, some keys
12−-- not reliably map to the expected events by any terminal program. Even those not using vty. There
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
13−-- is no 1:1 mapping from key events to bytes read from the terminal input device. In very limited
10+-- bytes read from the terminal input device. In very limited cases the
14−-- cases the terminal and vty's input process can be customized to resolve these issues.
11+-- terminal and vty's input process can be customized to resolve these
15−--
16−-- See "Graphics.Vty.Config" for how to configure vty's input processing. Customizing terminfo and
12+-- issues; see "Graphics.Vty.Config" for how to configure vty's input
17−-- the terminal is beyond the scope of this documentation.
13+-- processing.
18--
14--
19-- = VTY's Implementation
15-- = VTY's Implementation
20--
16--
21−-- One can get the brain rot trying to understand all this. So, as far as I can care...
22−--
23-- There are two input modes:
17-- There are two input modes:
24--
18--
25−-- 1. 7 bit
19+-- 1. 7-bit
26--
20--
27−-- 2. 8 bit
21+-- 2. 8-bit
28--
22--
29−-- 7 bit input is the default and the expected in most use cases. This is what vty uses.
23+-- The 7-bit input mode is the default and the expected mode in most use
24+-- cases. This is what Vty uses.
30--
25--
31−-- == 7 bit input encoding
26+-- == 7-bit input encoding
32--
27--
33−-- Control key combinations are represented by masking the two high bits of the 7bit input. Back in
28+-- Control key combinations are represented by masking the two high bits
29+-- of the 7-bit input. Historically the control key actually grounded
34−-- the day the control key actually grounded the two high bit wires: 6 and 7. This is why
30+-- the two high bit wires: 6 and 7. This is why control key combos
35−-- control key combos map to single character events: The input bytes are identical. The input byte
31+-- map to single character events: the input bytes are identical. The
36−-- is the bit encoding of the character with bits 6 and 7 masked. Bit 6 is set by shift. Bit 6 and
32+-- input byte is the bit encoding of the character with bits 6 and 7
37−-- 7 are masked by control. EG:
33+-- masked. Bit 6 is set by shift. Bit 6 and 7 are masked by control. For
34+-- example,
38--
35--
39−-- * Control-I is 'i', `01101001`, has bit 6 and 7 masked to become `00001001`. Which is the ASCII
36+-- * Control-I is 'i', `01101001`, and has bit 6 and 7 masked to become
40−-- and UTF-8 encoding of the tab key.
37+-- `00001001`, which is the ASCII and UTF-8 encoding of the Tab key.
41--
38--
42−-- * Control+Shift-C is 'C', `01000011`, with bit 6 and 7 set to zero which makes `0000011` and
39+-- * Control+Shift-C is 'C', `01000011`, with bit 6 and 7 set to zero
43−-- is the "End of Text" code.
40+-- which is `0000011` and is the "End of Text" code.
44--
41--
45−-- * Hypothesis: This is why capital-A, 'A', has value 65 in ASCII: This is the value 1 with bit 7
42+-- * Hypothesis: This is why capital-A, 'A', has value 65 in ASCII: this
46−-- set and 6 unset.
43+-- is the value 1 with bit 7 set and 6 unset.
47--
44--
48−-- * Hypothesis: Bit 6 is unset by upper case letters because, initially, there were only upper case
45+-- * Hypothesis: Bit 6 is unset by upper case letters because,
49−-- letters used and a 5 bit encoding.
46+-- initially, there were only upper case letters used and a 5 bit
47+-- encoding.
50--
48--
51−-- == 8 bit encoding
49+-- == 8-bit encoding
52--
50--
53−-- The 8th bit was originally used for parity checking. Useless for emulators. Some terminal
51+-- The 8th bit was originally used for parity checking which is useless
52+-- for terminal emulators. Some terminal emulators support an 8-bit
54−-- emulators support a 8 bit input encoding. While this provides some advantages the actual usage is
53+-- input encoding. While this provides some advantages, the actual usage
55−-- low. Most systems use 7bit mode but recognize 8bit control characters when escaped. This is what
54+-- is low. Most systems use 7-bit mode but recognize 8-bit control
56−-- vty does.
55+-- characters when escaped. This is what Vty does.
57--
56--
58-- == Escaped Control Keys
57-- == Escaped Control Keys
59--
58--
60−-- Using 7 bit input encoding the @ESC@ byte can signal the start of an encoded control key. To
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−-- differentiate a single @ESC@ eventfrom a control key the timing of the input is used.
61+-- control key, the timing of the input is used.
62--
62--
63−-- 1. @ESC@ individually: @ESC@ byte; no bytes for 'singleEscPeriod'.
63+-- 1. @ESC@ individually: @ESC@ byte; no bytes following for a period of
64+-- 'VMIN' milliseconds.
64--
65--
65−-- 2. control keys that contain @ESC@ in their encoding: The @ESC byte; followed by more bytes read
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
66−-- within 'singleEscPeriod'. All bytes up until the next valid input block are passed to the
68+-- up until the next valid input block are passed to the classifier.
67−-- classifier.
68--
69--
69−-- If the current runtime is the threaded runtime then the terminal's @VMIN@ and @VTIME@ behavior
70+-- If the current runtime is the threaded runtime then the terminal's
71+-- @VMIN@ and @VTIME@ behavior reliably implement the above rules. If
70−-- reliably implement the above rules. If the current runtime does not support forkOS then there is
72+-- the current runtime does not support 'forkOS' then there is currently
71−-- currently no implementation.
73+-- no implementation.
72−--
73−-- Vty used to emulate @VMIN@ and @VTIME@. This was a input loop which did tricky things with
74−-- non-blocking reads and timers. The implementation was not reliable. A reliable implementation is
75−-- possible, but there are no plans to implement this.
76--
74--
77-- == Unicode Input and Escaped Control Key Sequences
75-- == Unicode Input and Escaped Control Key Sequences
78--
76--
79−-- The input encoding determines how UTF-8 encoded characters are recognize.
77+-- The input encoding determines how UTF-8 encoded characters are
78+-- recognized.
80--
79--
81−-- * 7 bit mode: UTF-8 can be input unambiguiously. UTF-8 input is a superset of ASCII. UTF-8 does
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−-- not overlap escaped control key sequences. However, the escape key must be differentiated from
82+-- sequences. However, the escape key must be differentiated from
83-- escaped control key sequences by the timing of the input bytes.
83-- escaped control key sequences by the timing of the input bytes.
84--
84--
85−-- * 8 bit mode: UTF-8 cannot be input unambiguously. This does not require using the timing of
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
86−-- input bytes to differentiate the escape key. Many terminals do not support 8 bit mode.
87+-- key. Many terminals do not support 8-bit mode.
87--
88--
88-- == Terminfo
89-- == Terminfo
89--
90--
90−-- The terminfo system is used to determine how some keys are encoded. Terminfo is incomplete. In
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
91−-- some cases terminfo is incorrect. Vty assumes terminfo is correct but provides a mechanism to
93+-- assumes terminfo is correct but provides a mechanism to override
92−-- override terminfo. See "Graphics.Vty.Config" specifically 'inputOverrides'.
94+-- terminfo; see "Graphics.Vty.Config", specifically 'inputOverrides'.
93--
95--
94-- == Terminal Input is Broken
96-- == Terminal Input is Broken
95--
97--
96−-- Clearly terminal input has fundemental issues. There is no easy way to reliably resolve these
98+-- Clearly terminal input has fundamental issues. There is no easy way
97−-- issues.
99+-- to reliably resolve these issues.
98−--
99−-- One resolution would be to ditch standard terminal interfaces entirely and just go directly to
100−-- scancodes. A reasonable option for vty if everybody used the linux kernel console. I hear GUIs
101−-- are popular these days. Sadly, GUI terminal emulators don't provide access to scancodes AFAIK.
102−--
103−-- All is lost? Not really. "Graphics.Vty.Config" supports customizing the input byte to event
104−-- mapping and xterm supports customizing the scancode to input byte mapping. With a lot of work a
105−-- user's system can be set up to encode all the key combos in an almost-sane manner.
106−--
107−-- There are other tricky work arounds that can be done. I have no interest in implementing most of
108−-- these. They are not really worth the time.
109--
100--
110−-- == Terminal Output is Also Broken
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.
111--
105--
112−-- This isn't the only odd aspect of terminals due to historical aspects that no longer apply. EG:
106+-- The "Graphics.Vty.Config" module supports customizing the
113−-- Some terminfo capabilities specify millisecond delays. (Capabilities are how terminfo describes
107+-- input-byte-to-event mapping and xterm supports customizing the
114−-- the control sequence to output red, for instance) This is to account for the slow speed of
108+-- scancode-to-input-byte mapping. With a lot of work a user's system
115−-- hardcopy teletype interfaces. Cause, uh, we totally still use those.
109+-- can be set up to encode all the key combos in an almost-sane manner.
116−--
117−-- The output encoding of colors and attributes are also rife with issues.
118--
110--
119-- == See also
111-- == See also
120--
112--
121-- * http://www.leonerd.org.uk/hacks/fixterms/
113-- * http://www.leonerd.org.uk/hacks/fixterms/
122−--
123−-- In my experience this cannot resolve the issues without changes to the terminal emulator and
124−-- device.
125−module Graphics.Vty.Input ( Key(..)
114+module Graphics.Vty.Input
115+ ( Key(..)
126− , Modifier(..)
116+ , Modifier(..)
127− , Button(..)
117+ , Button(..)
128− , Event(..)
118+ , Event(..)
129− , Input(..)
119+ , Input(..)
130− , inputForConfig
120+ , inputForConfig
131− )
121+ )
132− where
122+where
133
123
134import Graphics.Vty.Config
124import Graphics.Vty.Config
135import Graphics.Vty.Input.Events
125import Graphics.Vty.Input.Events
@@ -142,42 +132,19 @@ import Lens.Micro
142import qualified System.Console.Terminfo as Terminfo
132import qualified System.Console.Terminfo as Terminfo
143import System.Posix.Signals.Exts
133import System.Posix.Signals.Exts
144
134
145−#if !(MIN_VERSION_base(4,8,0))
135+#if !MIN_VERSION_base(4,11,0)
146−import Data.Functor ((<$>))
147−import Data.Monoid
148−#else
149import Data.Monoid ((<>))
136import Data.Monoid ((<>))
150#endif
137#endif
151
138
152−-- | Set up the terminal with file descriptor `inputFd` for input. Returns a 'Input'.
139+-- | Set up the terminal with file descriptor `inputFd` for input.
153−--
140+-- Returns an 'Input'.
154−-- The table used to determine the 'Events' to produce for the input bytes comes from
155−-- 'classifyMapForTerm'. Which is then overridden by the the applicable entries from
156−-- 'inputMap'.
157−--
158−-- The terminal device is configured with the attributes:
159−--
160−-- * IXON disabled
161−-- - disables software flow control on outgoing data. This stops the process from being
162−-- suspended if the output terminal cannot keep up. I presume this has little effect these
163−-- days. I hope this means that output will be buffered if the terminal cannot keep up. In the
164−-- old days the output might of been dropped?
165−--
166−-- "raw" mode is used for input.
167−--
168−-- * ISIG disabled
169−-- - enables keyboard combinations that result in signals. TODO: should probably be a dynamic
170−-- option.
171--
141--
172−-- * ECHO disabled
142+-- The table used to determine the 'Events' to produce for the input
173−-- - input is not echod to the output. TODO: should be a dynamic option.
143+-- bytes comes from 'classifyMapForTerm' which is then overridden by
174−--
144+-- the the applicable entries from the configuration's 'inputMap'.
175−-- * ICANON disabled
176−-- - canonical mode (line mode) input is not used. TODO: should be a dynamic option.
177−--
178−-- * IEXTEN disabled
179−-- - extended functions are disabled. TODO: I don't know what those are.
180--
145--
146+-- The terminal device's mode flags are configured by the
147+-- 'attributeControl' function.
181inputForConfig :: Config -> IO Input
148inputForConfig :: Config -> IO Input
182inputForConfig config@Config{ termName = Just termName
149inputForConfig config@Config{ termName = Just termName
183 , inputFd = Just termFd
150 , inputFd = Just termFd
@@ -187,20 +154,23 @@ inputForConfig config@Config{ termName = Just termName
187 terminal <- Terminfo.setupTerm termName
154 terminal <- Terminfo.setupTerm termName
188 let inputOverrides = [(s,e) | (t,s,e) <- inputMap, t == Nothing || t == Just termName]
155 let inputOverrides = [(s,e) | (t,s,e) <- inputMap, t == Nothing || t == Just termName]
189 activeInputMap = classifyMapForTerm termName terminal `mappend` inputOverrides
156 activeInputMap = classifyMapForTerm termName terminal `mappend` inputOverrides
190− (setAttrs,unsetAttrs) <- attributeControl termFd
157+ (setAttrs, unsetAttrs) <- attributeControl termFd
191 setAttrs
158 setAttrs
192 input <- initInput config activeInputMap
159 input <- initInput config activeInputMap
193 let pokeIO = Catch $ do
160 let pokeIO = Catch $ do
194− let e = error "vty internal failure: this value should not propagate to users"
195 setAttrs
161 setAttrs
196− atomically $ writeTChan (input^.eventChannel) (EvResize e e)
162+ atomically $ writeTChan (input^.eventChannel) ResumeAfterSignal
197 _ <- installHandler windowChange pokeIO Nothing
163 _ <- installHandler windowChange pokeIO Nothing
198 _ <- installHandler continueProcess pokeIO Nothing
164 _ <- installHandler continueProcess pokeIO Nothing
165+
166+ let restore = unsetAttrs
167+
199 return $ input
168 return $ input
200 { shutdownInput = do
169 { shutdownInput = do
201 shutdownInput input
170 shutdownInput input
202 _ <- installHandler windowChange Ignore Nothing
171 _ <- installHandler windowChange Ignore Nothing
203 _ <- installHandler continueProcess Ignore Nothing
172 _ <- installHandler continueProcess Ignore Nothing
204− unsetAttrs
173+ restore
174+ , restoreInputState = restoreInputState input >> restore
205 }
175 }
206inputForConfig config = (<> config) <$> standardIOConfig >>= inputForConfig
176inputForConfig config = (<> config) <$> standardIOConfig >>= inputForConfig
src/Graphics/Vty/Input/Classify.hs+69−45
@@ -1,84 +1,108 @@
1{-# OPTIONS_HADDOCK hide #-}
1{-# OPTIONS_HADDOCK hide #-}
2−-- This makes a kind of tri. Has space efficiency issues with large input blocks.
2+-- This makes a kind of trie. Has space efficiency issues with large
3−-- Likely building a parser and just applying that would be better.
3+-- input blocks. Likely building a parser and just applying that would
4−-- I did not write this so I might just rewrite it for better understanding. Which is not the best
4+-- be better.
5−-- of reasons.
6−-- TODO: measure and rewrite if required.
7−-- TODO: The ClassifyMap interface requires this code to always assure later entries override
8−-- earlier.
9module Graphics.Vty.Input.Classify
5module Graphics.Vty.Input.Classify
10− ( classify
6+ ( classify
11− , KClass(..)
7+ , KClass(..)
8+ , ClassifierState(..)
9+ )
12− ) where
10+where
13
11
14import Graphics.Vty.Input.Events
12import Graphics.Vty.Input.Events
15import Graphics.Vty.Input.Mouse
13import Graphics.Vty.Input.Mouse
14+import Graphics.Vty.Input.Focus
16import Graphics.Vty.Input.Paste
15import Graphics.Vty.Input.Paste
17import Graphics.Vty.Input.Classify.Types
16import Graphics.Vty.Input.Classify.Types
18
17
19import Codec.Binary.UTF8.Generic (decode)
18import Codec.Binary.UTF8.Generic (decode)
20
19
21−import Data.List (inits)
20+import Control.Arrow (first)
22import qualified Data.Map as M( fromList, lookup )
21import qualified Data.Map as M( fromList, lookup )
23import Data.Maybe ( mapMaybe )
22import Data.Maybe ( mapMaybe )
24import qualified Data.Set as S( fromList, member )
23import qualified Data.Set as S( fromList, member )
25
24
26−import Data.Char
27import Data.Word
25import Data.Word
28
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+
29−compile :: ClassifyMap -> [Char] -> KClass
42+compile :: ClassifyMap -> ByteString -> KClass
30compile table = cl' where
43compile table = cl' where
31 -- take all prefixes and create a set of these
44 -- take all prefixes and create a set of these
32− prefixSet = S.fromList $ concatMap (init . inits . fst) $ table
45+ prefixSet = S.fromList $ concatMap (init . BS.inits . BS8.pack . fst) table
33 maxValidInputLength = maximum (map (length . fst) table)
46 maxValidInputLength = maximum (map (length . fst) table)
34− eventForInput = M.fromList table
47+ eventForInput = M.fromList $ map (first BS8.pack) table
35− cl' [] = Prefix
48+ cl' inputBlock | BS8.null inputBlock = Prefix
36 cl' inputBlock = case M.lookup inputBlock eventForInput of
49 cl' inputBlock = case M.lookup inputBlock eventForInput of
37− -- if the inputBlock is exactly what is expected for an event then consume the whole
50+ -- if the inputBlock is exactly what is expected for an
38− -- block and return the event
51+ -- event then consume the whole block and return the event
39− Just e -> Valid e []
52+ Just e -> Valid e BS8.empty
40 Nothing -> case S.member inputBlock prefixSet of
53 Nothing -> case S.member inputBlock prefixSet of
41 True -> Prefix
54 True -> Prefix
42− -- look up progressively smaller tails of the input block until an event is found
55+ -- look up progressively smaller tails of the input
56+ -- block until an event is found The assumption is that
43− -- The assumption is that the event that consumes the most input bytes should be
57+ -- the event that consumes the most input bytes should
44− -- produced.
58+ -- be produced.
45 -- The test verifyFullSynInputToEvent2x verifies this.
59 -- The test verifyFullSynInputToEvent2x verifies this.
46− -- H: There will always be one match. The prefixSet contains, by definition, all
60+ -- H: There will always be one match. The prefixSet
47− -- prefixes of an event.
61+ -- contains, by definition, all prefixes of an event.
48 False ->
62 False ->
49− let inputPrefixes = reverse $ take maxValidInputLength $ tail $ inits inputBlock
63+ let inputPrefixes = reverse . take maxValidInputLength . tail . BS8.inits $ inputBlock
50 in case mapMaybe (\s -> (,) s `fmap` M.lookup s eventForInput) inputPrefixes of
64 in case mapMaybe (\s -> (,) s `fmap` M.lookup s eventForInput) inputPrefixes of
51− (s,e) : _ -> Valid e (drop (length s) inputBlock)
65+ (s,e) : _ -> Valid e (BS8.drop (BS8.length s) inputBlock)
52 -- neither a prefix or a full event.
66 -- neither a prefix or a full event.
53− -- TODO: debug log
54 [] -> Invalid
67 [] -> Invalid
55
68
56−classify :: ClassifyMap -> [Char] -> KClass
69+classify :: ClassifyMap -> ClassifierState -> ByteString -> KClass
57−classify table =
70+classify table = process
71+ where
58− let standardClassifier = compile table
72+ standardClassifier = compile table
73+
74+ process ClassifierStart s =
59− in \s -> case s of
75+ case BS.uncons s of
60− _ | bracketedPasteStarted s ->
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 =
61 if bracketedPasteFinished s
86 if bracketedPasteFinished s
62− then parseBracketedPaste s
87+ then parseBracketedPaste $ BS.concat $ p:reverse (s:ps)
63− else Prefix
88+ else Chunk
64− _ | isMouseEvent s -> classifyMouseEvent s
89+ process ClassifierInChunk{} _ = Invalid
65− c:cs | ord c >= 0xC2 -> classifyUtf8 c cs
66− _ -> standardClassifier s
67
90
68−classifyUtf8 :: Char -> [Char] -> KClass
91+classifyUtf8 :: Word8 -> ByteString -> KClass
69classifyUtf8 c cs =
92classifyUtf8 c cs =
70− let n = utf8Length (ord c)
93+ let n = utf8Length c
71− (codepoint,rest) = splitAt n (c:cs)
94+ (codepoint,rest) = BS8.splitAt (n - 1) cs
72
95
73 codepoint8 :: [Word8]
96 codepoint8 :: [Word8]
74− codepoint8 = map (fromIntegral . ord) codepoint
97+ codepoint8 = c:BS.unpack codepoint
75
98
76 in case decode codepoint8 of
99 in case decode codepoint8 of
77− _ | n < length codepoint -> Prefix
100+ _ | n < BS.length codepoint + 1 -> Prefix
78− Just (unicodeChar, _) -> Valid (EvKey (KChar unicodeChar) []) rest
101+ Just (unicodeChar, _) -> Valid (EvKey (KChar unicodeChar) []) rest
79− Nothing -> Invalid -- something bad happened; just ignore and continue.
102+ -- something bad happened; just ignore and continue.
103+ Nothing -> Invalid
80
104
81−utf8Length :: (Num t, Ord a, Num a) => a -> t
105+utf8Length :: Word8 -> Int
82utf8Length c
106utf8Length c
83 | c < 0x80 = 1
107 | c < 0x80 = 1
84 | c < 0xE0 = 2
108 | c < 0xE0 = 2
src/Graphics/Vty/Input/Classify/Parse.hs+18−14
@@ -1,13 +1,14 @@
1-- | This module provides a simple parser for parsing input event
1-- | This module provides a simple parser for parsing input event
2-- control sequences.
2-- control sequences.
3module Graphics.Vty.Input.Classify.Parse
3module Graphics.Vty.Input.Classify.Parse
4− ( Parser
4+ ( Parser
5− , runParser
5+ , runParser
6− , failParse
6+ , failParse
7− , readInt
7+ , readInt
8− , readChar
8+ , readChar
9− , expectChar
9+ , expectChar
10+ )
10− ) where
11+where
11
12
12import Graphics.Vty.Input.Events
13import Graphics.Vty.Input.Events
13import Graphics.Vty.Input.Classify.Types
14import Graphics.Vty.Input.Classify.Types
@@ -15,12 +16,15 @@ import Graphics.Vty.Input.Classify.Types
15import Control.Monad.Trans.Maybe
16import Control.Monad.Trans.Maybe
16import Control.Monad.State
17import Control.Monad.State
17
18
19+import qualified Data.ByteString.Char8 as BS8
20+import Data.ByteString.Char8 (ByteString)
21+
18−type Parser a = MaybeT (State String) a
22+type Parser a = MaybeT (State ByteString) a
19
23
20-- | Run a parser on a given input string. If the parser fails, return
24-- | Run a parser on a given input string. If the parser fails, return
21-- 'Invalid'. Otherwise return the valid event ('Valid') and the
25-- 'Invalid'. Otherwise return the valid event ('Valid') and the
22-- remaining unparsed characters.
26-- remaining unparsed characters.
23−runParser :: String -> Parser Event -> KClass
27+runParser :: ByteString -> Parser Event -> KClass
24runParser s parser =
28runParser s parser =
25 case runState (runMaybeT parser) s of
29 case runState (runMaybeT parser) s of
26 (Nothing, _) -> Invalid
30 (Nothing, _) -> Invalid
@@ -35,9 +39,9 @@ failParse = fail "invalid parse"
35-- return '123' and consume those characters.
39-- return '123' and consume those characters.
36readInt :: Parser Int
40readInt :: Parser Int
37readInt = do
41readInt = do
38− s <- get
42+ s <- BS8.unpack <$> get
39 case (reads :: ReadS Int) s of
43 case (reads :: ReadS Int) s of
40− [(i, rest)] -> put rest >> return i
44+ [(i, rest)] -> put (BS8.pack rest) >> return i
41 _ -> failParse
45 _ -> failParse
42
46
43-- | Read a character from the input stream. If one cannot be read (e.g.
47-- | Read a character from the input stream. If one cannot be read (e.g.
@@ -45,9 +49,9 @@ readInt = do
45readChar :: Parser Char
49readChar :: Parser Char
46readChar = do
50readChar = do
47 s <- get
51 s <- get
48− case s of
52+ case BS8.uncons s of
49− c:rest -> put rest >> return c
53+ Just (c,rest) -> put rest >> return c
50− _ -> failParse
54+ Nothing -> failParse
51
55
52-- | Read a character from the input stream and fail parsing if it is
56-- | Read a character from the input stream and fail parsing if it is
53-- not the specified character.
57-- not the specified character.
src/Graphics/Vty/Input/Classify/Types.hs+10−3
@@ -1,13 +1,17 @@
1-- | This module exports the input classification type to avoid import
1-- | This module exports the input classification type to avoid import
2-- cycles between other modules that need this.
2-- cycles between other modules that need this.
3+{-# LANGUAGE StrictData #-}
3module Graphics.Vty.Input.Classify.Types
4module Graphics.Vty.Input.Classify.Types
4− ( KClass(..)
5+ ( KClass(..)
6+ )
5− ) where
7+where
6
8
7import Graphics.Vty.Input.Events
9import Graphics.Vty.Input.Events
8
10
11+import Data.ByteString.Char8 (ByteString)
12+
9data KClass
13data KClass
10− = Valid Event [Char]
14+ = Valid Event ByteString
11 -- ^ A valid event was parsed. Any unused characters from the input
15 -- ^ A valid event was parsed. Any unused characters from the input
12 -- stream are also provided.
16 -- stream are also provided.
13 | Invalid
17 | Invalid
@@ -15,4 +19,7 @@ data KClass
15 | Prefix
19 | Prefix
16 -- ^ The input characters form the prefix of a valid event character
20 -- ^ The input characters form the prefix of a valid event character
17 -- sequence.
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.
18 deriving(Show, Eq)
25 deriving(Show, Eq)
src/Graphics/Vty/Input/Events.hs+38−14
@@ -1,34 +1,44 @@
1{-# Language DeriveGeneric #-}
1{-# Language DeriveGeneric #-}
2+{-# Language StrictData #-}
2module Graphics.Vty.Input.Events where
3module Graphics.Vty.Input.Events where
3
4
5+import Control.DeepSeq
4import Data.ByteString
6import Data.ByteString
5import GHC.Generics
7import GHC.Generics
6
8
7-- | Representations of non-modifier keys.
9-- | Representations of non-modifier keys.
8--
10--
9−-- * KFun is indexed from 0 to 63. Range of supported FKeys varies by terminal and keyboard.
11+-- * KFun is indexed from 0 to 63. Range of supported FKeys varies by
12+-- terminal and keyboard.
10--
13--
11−-- * KUpLeft, KUpRight, KDownLeft, KDownRight, KCenter support varies by terminal and keyboard.
14+-- * KUpLeft, KUpRight, KDownLeft, KDownRight, KCenter support varies by
15+-- terminal and keyboard.
12--
16--
13−-- * Actually, support for most of these but KEsc, KChar, KBS, and KEnter vary by terminal and
17+-- * Actually, support for most of these but KEsc, KChar, KBS, and
14−-- keyboard.
18+-- KEnter vary by terminal and keyboard.
15−data Key = KEsc | KChar Char | KBS | KEnter
19+data Key = KEsc | KChar {-# UNPACK #-} Char | KBS | KEnter
16 | KLeft | KRight | KUp | KDown
20 | KLeft | KRight | KUp | KDown
17 | KUpLeft | KUpRight | KDownLeft | KDownRight | KCenter
21 | KUpLeft | KUpRight | KDownLeft | KDownRight | KCenter
18− | KFun Int | KBackTab | KPrtScr | KPause | KIns
22+ | KFun {-# UNPACK #-} Int | KBackTab | KPrtScr | KPause | KIns
19 | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KMenu
23 | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KMenu
20 deriving (Eq,Show,Read,Ord,Generic)
24 deriving (Eq,Show,Read,Ord,Generic)
21
25
26+instance NFData Key
27+
22−-- | Modifier keys. Key codes are interpreted such that users are more likely to
28+-- | Modifier keys. Key codes are interpreted such that users are more
23−-- have Meta than Alt; for instance on the PC Linux console, 'MMeta' will
29+-- likely to have Meta than Alt; for instance on the PC Linux console,
24−-- generally correspond to the physical Alt key.
30+-- 'MMeta' will generally correspond to the physical Alt key.
25data Modifier = MShift | MCtrl | MMeta | MAlt
31data Modifier = MShift | MCtrl | MMeta | MAlt
26 deriving (Eq,Show,Read,Ord,Generic)
32 deriving (Eq,Show,Read,Ord,Generic)
27
33
34+instance NFData Modifier
35+
28-- | Mouse buttons.
36-- | Mouse buttons.
29−data Button = BLeft | BMiddle | BRight
37+data Button = BLeft | BMiddle | BRight | BScrollUp | BScrollDown
30 deriving (Eq,Show,Read,Ord,Generic)
38 deriving (Eq,Show,Read,Ord,Generic)
31
39
40+instance NFData Button
41+
32-- | Events.
42-- | Events.
33data Event
43data Event
34 = EvKey Key [Modifier]
44 = EvKey Key [Modifier]
@@ -42,10 +52,8 @@ data Event
42 -- without specifying which one; in that case, Nothing is provided.
52 -- without specifying which one; in that case, Nothing is provided.
43 -- Otherwise Just the button released is included in the event.
53 -- Otherwise Just the button released is included in the event.
44 | EvResize Int Int
54 | EvResize Int Int
45− -- ^ If read from 'eventChannel' this is the size at the time of the
55+ -- The terminal window was resized and the size is provided in the
46− -- signal. If read from 'nextEvent' this is the size at the time the
56+ -- integer fields (width, height).
47− -- event was processed by Vty. Typically these are the same, but if
48− -- somebody is resizing the terminal quickly they can be different.
49 | EvPaste ByteString
57 | EvPaste ByteString
50 -- ^ A paste event occurs when a bracketed paste input sequence is
58 -- ^ A paste event occurs when a bracketed paste input sequence is
51 -- received. For terminals that support bracketed paste mode, these
59 -- received. For terminals that support bracketed paste mode, these
@@ -54,6 +62,22 @@ data Event
54 -- input (which is probably bad, so beware!) Note that the data is
62 -- input (which is probably bad, so beware!) Note that the data is
55 -- provided in raw form and you'll have to decode (e.g. as UTF-8) if
63 -- provided in raw form and you'll have to decode (e.g. as UTF-8) if
56 -- that's what your application expects.
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.
57 deriving (Eq,Show,Read,Ord,Generic)
69 deriving (Eq,Show,Read,Ord,Generic)
58
70
71+instance NFData Event
72+
59type ClassifyMap = [(String,Event)]
73type 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.hs+109−47
@@ -1,14 +1,18 @@
1−{-# LANGUAGE RecordWildCards #-}
1+{-# LANGUAGE CPP #-}
2−{-# OPTIONS_HADDOCK hide #-}
3{-# LANGUAGE NoMonomorphismRestriction #-}
2{-# LANGUAGE NoMonomorphismRestriction #-}
3+{-# LANGUAGE RecordWildCards #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
4{-# LANGUAGE TemplateHaskell #-}
5{-# LANGUAGE TemplateHaskell #-}
5−{-# LANGUAGE CPP #-}
6+{-# OPTIONS_HADDOCK hide #-}
6−-- | The input layer used to be a single function that correctly accounted for the non-threaded
7+-- | The input layer used to be a single function that correctly
8+-- accounted for the non-threaded runtime by emulating the terminal
7−-- runtime by emulating the terminal VMIN adn VTIME handling. This has been removed and replace with
9+-- VMIN adn VTIME handling. This has been removed and replace with a
8−-- a more straightforward parser. The non-threaded runtime is no longer supported.
10+-- more straightforward parser. The non-threaded runtime is no longer
11+-- supported.
9--
12--
10−-- This is an example of an algorithm where code coverage could be high, even 100%, but the
13+-- This is an example of an algorithm where code coverage could be high,
11−-- behavior is still under tested. I should collect more of these examples...
14+-- even 100%, but the behavior is still under tested. I should collect
15+-- more of these examples...
12--
16--
13-- reference: http://www.unixwiz.net/techtips/termios-vmin-vtime.html
17-- reference: http://www.unixwiz.net/techtips/termios-vmin-vtime.html
14module Graphics.Vty.Input.Loop where
18module Graphics.Vty.Input.Loop where
@@ -21,8 +25,7 @@ import Control.Applicative
21import Control.Concurrent
25import Control.Concurrent
22import Control.Concurrent.STM
26import Control.Concurrent.STM
23import Control.Exception (mask, try, SomeException)
27import Control.Exception (mask, try, SomeException)
24−import Data.Monoid
25−import Lens.Micro
28+import Lens.Micro hiding ((<>~))
26import Lens.Micro.Mtl
29import Lens.Micro.Mtl
27import Lens.Micro.TH
30import Lens.Micro.TH
28import Control.Monad (when, mzero, forM_)
31import Control.Monad (when, mzero, forM_)
@@ -31,12 +34,15 @@ import Control.Monad.Trans.State (StateT(..), evalStateT)
31import Control.Monad.State.Class (MonadState, modify)
34import Control.Monad.State.Class (MonadState, modify)
32import Control.Monad.Trans.Reader (ReaderT(..))
35import Control.Monad.Trans.Reader (ReaderT(..))
33
36
34−import Data.Char
37+import qualified Data.ByteString.Char8 as BS8
38+import qualified Data.ByteString as BS
39+import Data.ByteString.Char8 (ByteString)
35import Data.IORef
40import Data.IORef
36import Data.Word (Word8)
41import Data.Word (Word8)
37
42
38−import Foreign ( allocaArray, peekArray, Ptr )
43+import Foreign (allocaArray)
39import Foreign.C.Types (CInt(..))
44import Foreign.C.Types (CInt(..))
45+import Foreign.Ptr (Ptr, castPtr)
40
46
41import System.IO
47import System.IO
42import System.Posix.IO (fdReadBuf, setFdOption, FdOption(..))
48import System.Posix.IO (fdReadBuf, setFdOption, FdOption(..))
@@ -46,12 +52,18 @@ import System.Posix.Types (Fd(..))
46import Text.Printf (hPrintf)
52import Text.Printf (hPrintf)
47
53
48data Input = Input
54data Input = Input
49− { -- | Channel of events direct from input processing. Unlike 'nextEvent' this will not refresh
55+ { -- | Channel of events direct from input processing. Unlike
50− -- the display if the next event is an 'EvResize'.
56+ -- 'nextEvent' this will not refresh the display if the next event
57+ -- is an 'EvResize'.
51− _eventChannel :: TChan Event
58+ _eventChannel :: TChan InternalEvent
52− -- | Shuts down the input processing. This should return the terminal input state to before
59+ -- | Shuts down the input processing. As part of shutting down the
53− -- the input initialized.
60+ -- input, this should also restore the input state.
54 , shutdownInput :: IO ()
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 ()
55 -- | Changes to this value are reflected after the next event.
67 -- | Changes to this value are reflected after the next event.
56 , _configRef :: IORef Config
68 , _configRef :: IORef Config
57 -- | input debug log
69 -- | input debug log
@@ -68,10 +80,11 @@ data InputBuffer = InputBuffer
68makeLenses ''InputBuffer
80makeLenses ''InputBuffer
69
81
70data InputState = InputState
82data InputState = InputState
71− { _unprocessedBytes :: String
83+ { _unprocessedBytes :: ByteString
84+ , _classifierState :: ClassifierState
72 , _appliedConfig :: Config
85 , _appliedConfig :: Config
73 , _inputBuffer :: InputBuffer
86 , _inputBuffer :: InputBuffer
74− , _classifier :: String -> KClass
87+ , _classifier :: ClassifierState -> ByteString -> KClass
75 }
88 }
76
89
77makeLenses ''InputState
90makeLenses ''InputState
@@ -86,7 +99,8 @@ logMsg msg = do
86 Just h -> liftIO $ hPutStrLn h msg >> hFlush h
99 Just h -> liftIO $ hPutStrLn h msg >> hFlush h
87
100
88-- this must be run on an OS thread dedicated to this input handling.
101-- this must be run on an OS thread dedicated to this input handling.
89−-- otherwise the terminal timing read behavior will block the execution of the lightweight threads.
102+-- otherwise the terminal timing read behavior will block the execution
103+-- of the lightweight threads.
90loopInputProcessor :: InputM ()
104loopInputProcessor :: InputM ()
91loopInputProcessor = do
105loopInputProcessor = do
92 readFromDevice >>= addBytesToProcess
106 readFromDevice >>= addBytesToProcess
@@ -95,20 +109,20 @@ loopInputProcessor = do
95 dropInvalid
109 dropInvalid
96 loopInputProcessor
110 loopInputProcessor
97
111
98−addBytesToProcess :: String -> InputM ()
112+addBytesToProcess :: ByteString -> InputM ()
99addBytesToProcess block = unprocessedBytes <>= block
113addBytesToProcess block = unprocessedBytes <>= block
100
114
101emit :: Event -> InputM ()
115emit :: Event -> InputM ()
102emit event = do
116emit event = do
103 logMsg $ "parsed event: " ++ show event
117 logMsg $ "parsed event: " ++ show event
104− view eventChannel >>= liftIO . atomically . flip writeTChan event
118+ view eventChannel >>= liftIO . atomically . flip writeTChan (InputEvent event)
105
119
106−-- The timing requirements are assured by the VMIN and VTIME set for the device.
120+-- The timing requirements are assured by the VMIN and VTIME set for the
121+-- device.
107--
122--
108−-- Precondition: Under the threaded runtime. Only current use is from a forkOS thread. That case
123+-- Precondition: Under the threaded runtime. Only current use is from a
109−-- satisfies precondition.
124+-- forkOS thread. That case satisfies precondition.
110−-- TODO: When under the non-threaded runtime emulate VMIN and VTIME
111−readFromDevice :: InputM String
125+readFromDevice :: InputM ByteString
112readFromDevice = do
126readFromDevice = do
113 newConfig <- view configRef >>= liftIO . readIORef
127 newConfig <- view configRef >>= liftIO . readIORef
114 oldConfig <- use appliedConfig
128 oldConfig <- use appliedConfig
@@ -120,16 +134,18 @@ readFromDevice = do
120 bufferPtr <- use $ inputBuffer.ptr
134 bufferPtr <- use $ inputBuffer.ptr
121 maxBytes <- use $ inputBuffer.size
135 maxBytes <- use $ inputBuffer.size
122 stringRep <- liftIO $ do
136 stringRep <- liftIO $ do
123− -- The killThread used in shutdownInput will not interrupt the foreign call fdReadBuf uses
137+ -- The killThread used in shutdownInput will not interrupt the
138+ -- foreign call fdReadBuf uses this provides a location to be
124− -- this provides a location to be interrupted prior to the foreign call. If there is input
139+ -- interrupted prior to the foreign call. If there is input on
125− -- on the FD then the fdReadBuf will return in a finite amount of time due to the vtime
140+ -- the FD then the fdReadBuf will return in a finite amount of
126− -- terminal setting.
141+ -- time due to the vtime terminal setting.
127 threadWaitRead fd
142 threadWaitRead fd
128 bytesRead <- fdReadBuf fd bufferPtr (fromIntegral maxBytes)
143 bytesRead <- fdReadBuf fd bufferPtr (fromIntegral maxBytes)
129 if bytesRead > 0
144 if bytesRead > 0
130− then fmap (map $ chr . fromIntegral) $ peekArray (fromIntegral bytesRead) bufferPtr
145+ then BS.packCStringLen (castPtr bufferPtr, fromIntegral bytesRead)
131− else return []
146+ else return BS.empty
147+ when (not $ BS.null stringRep) $
132− when (not $ null stringRep) $ logMsg $ "input bytes: " ++ show stringRep
148+ logMsg $ "input bytes: " ++ show (BS8.unpack stringRep)
133 return stringRep
149 return stringRep
134
150
135applyConfig :: Fd -> Config -> IO ()
151applyConfig :: Fd -> Config -> IO ()
@@ -140,41 +156,86 @@ applyConfig _ _ = fail "(vty) applyConfig was not provided a complete configurat
140parseEvent :: InputM Event
156parseEvent :: InputM Event
141parseEvent = do
157parseEvent = do
142 c <- use classifier
158 c <- use classifier
159+ s <- use classifierState
143 b <- use unprocessedBytes
160 b <- use unprocessedBytes
144− case c b of
161+ case c s b of
145 Valid e remaining -> do
162 Valid e remaining -> do
146 logMsg $ "valid parse: " ++ show e
163 logMsg $ "valid parse: " ++ show e
147 logMsg $ "remaining: " ++ show remaining
164 logMsg $ "remaining: " ++ show remaining
165+ classifierState .= ClassifierStart
148 unprocessedBytes .= remaining
166 unprocessedBytes .= remaining
149 return e
167 return e
150− _ -> mzero
168+ _ -> mzero
151
169
152dropInvalid :: InputM ()
170dropInvalid :: InputM ()
153dropInvalid = do
171dropInvalid = do
154 c <- use classifier
172 c <- use classifier
173+ s <- use classifierState
155 b <- use unprocessedBytes
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
156− when (c b == Invalid) $ do
182+ Invalid -> do
157− logMsg "dropping input bytes"
183+ logMsg "dropping input bytes"
184+ classifierState .= ClassifierStart
158− unprocessedBytes .= []
185+ unprocessedBytes .= BS8.empty
186+ _ -> return ()
159
187
160runInputProcessorLoop :: ClassifyMap -> Input -> IO ()
188runInputProcessorLoop :: ClassifyMap -> Input -> IO ()
161runInputProcessorLoop classifyTable input = do
189runInputProcessorLoop classifyTable input = do
162 let bufferSize = 1024
190 let bufferSize = 1024
163 allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do
191 allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do
192+ s0 <- InputState BS8.empty ClassifierStart
164− s0 <- InputState [] <$> readIORef (_configRef input)
193+ <$> readIORef (_configRef input)
165− <*> pure (InputBuffer bufferPtr bufferSize)
194+ <*> pure (InputBuffer bufferPtr bufferSize)
166− <*> pure (classify classifyTable)
195+ <*> pure (classify classifyTable)
167 runReaderT (evalStateT loopInputProcessor s0) input
196 runReaderT (evalStateT loopInputProcessor s0) input
168
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)
169attributeControl :: Fd -> IO (IO (), IO ())
223attributeControl :: Fd -> IO (IO (), IO ())
170attributeControl fd = do
224attributeControl fd = do
171 original <- getTerminalAttributes fd
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
172− let vtyMode = foldl withoutMode original [ StartStopOutput, KeyboardInterrupts
231+ , KeyboardInterrupts -- ISIG
232+ , EnableEcho -- ECHO
233+ , ProcessInput -- ICANON
173− , EnableEcho, ProcessInput, ExtendedFunctions
234+ , ExtendedFunctions -- IEXTEN
174− ]
235+ ]
175 let setAttrs = setTerminalAttributes fd vtyMode Immediately
236 let setAttrs = setTerminalAttributes fd vtyMode Immediately
176 unsetAttrs = setTerminalAttributes fd original Immediately
237 unsetAttrs = setTerminalAttributes fd original Immediately
177− return (setAttrs,unsetAttrs)
238+ return (setAttrs, unsetAttrs)
178
239
179logInitialInputState :: Input -> ClassifyMap -> IO()
240logInitialInputState :: Input -> ClassifyMap -> IO()
180logInitialInputState input classifyTable = case _inputDebug input of
241logInitialInputState input classifyTable = case _inputDebug input of
@@ -182,7 +243,7 @@ logInitialInputState input classifyTable = case _inputDebug input of
182 Just h -> do
243 Just h -> do
183 Config{ vmin = Just theVmin
244 Config{ vmin = Just theVmin
184 , vtime = Just theVtime
245 , vtime = Just theVtime
185− , termName = Just theTerm, .. } <- readIORef $ _configRef input
246+ , termName = Just theTerm } <- readIORef $ _configRef input
186 _ <- hPrintf h "initial (vmin,vtime): %s\n" (show (theVmin, theVtime))
247 _ <- hPrintf h "initial (vmin,vtime): %s\n" (show (theVmin, theVtime))
187 forM_ classifyTable $ \i -> case i of
248 forM_ classifyTable $ \i -> case i of
188 (inBytes, EvKey k mods) -> hPrintf h "map %s %s %s %s\n" (show theTerm)
249 (inBytes, EvKey k mods) -> hPrintf h "map %s %s %s %s\n" (show theTerm)
@@ -198,6 +259,7 @@ initInput config classifyTable = do
198 applyConfig fd config
259 applyConfig fd config
199 stopSync <- newEmptyMVar
260 stopSync <- newEmptyMVar
200 input <- Input <$> atomically newTChan
261 input <- Input <$> atomically newTChan
262+ <*> pure (return ())
201 <*> pure (return ())
263 <*> pure (return ())
202 <*> newIORef config
264 <*> newIORef config
203 <*> maybe (return Nothing)
265 <*> maybe (return Nothing)
src/Graphics/Vty/Input/Mouse.hs+34−23
@@ -3,21 +3,24 @@
3--
3--
4-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
4-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
5module Graphics.Vty.Input.Mouse
5module Graphics.Vty.Input.Mouse
6− ( requestMouseEvents
6+ ( requestMouseEvents
7− , disableMouseEvents
7+ , disableMouseEvents
8− , isMouseEvent
8+ , isMouseEvent
9− , classifyMouseEvent
9+ , classifyMouseEvent
10+ )
10− ) where
11+where
11
12
12import Graphics.Vty.Input.Events
13import Graphics.Vty.Input.Events
13import Graphics.Vty.Input.Classify.Types
14import Graphics.Vty.Input.Classify.Types
14import Graphics.Vty.Input.Classify.Parse
15import Graphics.Vty.Input.Classify.Parse
15
16
16import Control.Monad.State
17import Control.Monad.State
17−import Data.List (isPrefixOf)
18import Data.Maybe (catMaybes)
18import Data.Maybe (catMaybes)
19import Data.Bits ((.&.))
19import Data.Bits ((.&.))
20
20
21+import qualified Data.ByteString.Char8 as BS8
22+import Data.ByteString.Char8 (ByteString)
23+
21-- A mouse event in SGR extended mode is
24-- A mouse event in SGR extended mode is
22--
25--
23-- '\ESC' '[' '<' B ';' X ';' Y ';' ('M'|'m')
26-- '\ESC' '[' '<' B ';' X ';' Y ';' ('M'|'m')
@@ -31,28 +34,28 @@ import Data.Bits ((.&.))
31
34
32-- | These sequences set xterm-based terminals to send mouse event
35-- | These sequences set xterm-based terminals to send mouse event
33-- sequences.
36-- sequences.
34−requestMouseEvents :: String
37+requestMouseEvents :: ByteString
35−requestMouseEvents = "\ESC[?1000h\ESC[?1002h\ESC[?1006h\ESC[?1005h"
38+requestMouseEvents = BS8.pack "\ESC[?1000h\ESC[?1002h\ESC[?1006h"
36
39
37-- | These sequences disable mouse events.
40-- | These sequences disable mouse events.
38−disableMouseEvents :: String
41+disableMouseEvents :: ByteString
39−disableMouseEvents = "\ESC[?1000l\ESC[?1002l\ESC[?1006l\ESC[?1005l"
42+disableMouseEvents = BS8.pack "\ESC[?1000l\ESC[?1002l\ESC[?1006l"
40
43
41-- | Does the specified string begin with a mouse event?
44-- | Does the specified string begin with a mouse event?
42−isMouseEvent :: String -> Bool
45+isMouseEvent :: ByteString -> Bool
43isMouseEvent s = isSGREvent s || isNormalEvent s
46isMouseEvent s = isSGREvent s || isNormalEvent s
44
47
45−isSGREvent :: String -> Bool
48+isSGREvent :: ByteString -> Bool
46−isSGREvent = isPrefixOf sgrPrefix
49+isSGREvent = BS8.isPrefixOf sgrPrefix
47
50
48−sgrPrefix :: String
51+sgrPrefix :: ByteString
49−sgrPrefix = "\ESC[M"
52+sgrPrefix = BS8.pack "\ESC[M"
50
53
51−isNormalEvent :: String -> Bool
54+isNormalEvent :: ByteString -> Bool
52−isNormalEvent = isPrefixOf normalPrefix
55+isNormalEvent = BS8.isPrefixOf normalPrefix
53
56
54−normalPrefix :: String
57+normalPrefix :: ByteString
55−normalPrefix = "\ESC[<"
58+normalPrefix = BS8.pack "\ESC[<"
56
59
57-- Modifier bits:
60-- Modifier bits:
58shiftBit :: Int
61shiftBit :: Int
@@ -66,7 +69,7 @@ ctrlBit = 16
66
69
67-- These bits indicate the buttons involved:
70-- These bits indicate the buttons involved:
68buttonMask :: Int
71buttonMask :: Int
69−buttonMask = 3
72+buttonMask = 67
70
73
71leftButton :: Int
74leftButton :: Int
72leftButton = 0
75leftButton = 0
@@ -77,11 +80,17 @@ middleButton = 1
77rightButton :: Int
80rightButton :: Int
78rightButton = 2
81rightButton = 2
79
82
83+scrollUp :: Int
84+scrollUp = 64
85+
86+scrollDown :: Int
87+scrollDown = 65
88+
80hasBitSet :: Int -> Int -> Bool
89hasBitSet :: Int -> Int -> Bool
81hasBitSet val bit = val .&. bit > 0
90hasBitSet val bit = val .&. bit > 0
82
91
83−-- | Attempt to lassify an input string as a mouse event.
92+-- | Attempt to classify an input string as a mouse event.
84−classifyMouseEvent :: String -> KClass
93+classifyMouseEvent :: ByteString -> KClass
85classifyMouseEvent s = runParser s $ do
94classifyMouseEvent s = runParser s $ do
86 when (not $ isMouseEvent s) failParse
95 when (not $ isMouseEvent s) failParse
87
96
@@ -93,12 +102,14 @@ classifyMouseEvent s = runParser s $ do
93 'M' -> classifyNormalMouseEvent
102 'M' -> classifyNormalMouseEvent
94 _ -> failParse
103 _ -> failParse
95
104
96−-- Given a modifer/button value, determine which button was indicated
105+-- Given a modifier/button value, determine which button was indicated
97getSGRButton :: Int -> Parser Button
106getSGRButton :: Int -> Parser Button
98getSGRButton mods =
107getSGRButton mods =
99 let buttonMap = [ (leftButton, BLeft)
108 let buttonMap = [ (leftButton, BLeft)
100 , (middleButton, BMiddle)
109 , (middleButton, BMiddle)
101 , (rightButton, BRight)
110 , (rightButton, BRight)
111+ , (scrollUp, BScrollUp)
112+ , (scrollDown, BScrollDown)
102 ]
113 ]
103 in case lookup (mods .&. buttonMask) buttonMap of
114 in case lookup (mods .&. buttonMask) buttonMap of
104 Nothing -> failParse
115 Nothing -> failParse
src/Graphics/Vty/Input/Paste.hs+21−28
@@ -2,47 +2,40 @@
2--
2--
3-- http://cirw.in/blog/bracketed-paste
3-- http://cirw.in/blog/bracketed-paste
4module Graphics.Vty.Input.Paste
4module Graphics.Vty.Input.Paste
5− ( parseBracketedPaste
5+ ( parseBracketedPaste
6− , bracketedPasteStarted
6+ , bracketedPasteStarted
7− , bracketedPasteFinished
7+ , bracketedPasteFinished
8+ )
8− ) where
9+where
9
10
10import qualified Data.ByteString.Char8 as BS8
11import qualified Data.ByteString.Char8 as BS8
12+import Data.ByteString.Char8 (ByteString)
11
13
12import Graphics.Vty.Input.Events
14import Graphics.Vty.Input.Events
13import Graphics.Vty.Input.Classify.Types
15import Graphics.Vty.Input.Classify.Types
14
16
15−import Data.List (isPrefixOf, isInfixOf)
17+bracketedPasteStart :: ByteString
18+bracketedPasteStart = BS8.pack "\ESC[200~"
16
19
17−bracketedPasteStart :: String
18−bracketedPasteStart = "\ESC[200~"
19−
20−bracketedPasteEnd :: String
20+bracketedPasteEnd :: ByteString
21−bracketedPasteEnd = "\ESC[201~"
21+bracketedPasteEnd = BS8.pack "\ESC[201~"
22
22
23-- | Does the input start a bracketed paste?
23-- | Does the input start a bracketed paste?
24−bracketedPasteStarted :: String -> Bool
24+bracketedPasteStarted :: ByteString -> Bool
25−bracketedPasteStarted = isPrefixOf bracketedPasteStart
25+bracketedPasteStarted = BS8.isPrefixOf bracketedPasteStart
26
26
27-- | Does the input contain a complete bracketed paste?
27-- | Does the input contain a complete bracketed paste?
28−bracketedPasteFinished :: String -> Bool
28+bracketedPasteFinished :: ByteString -> Bool
29−bracketedPasteFinished = isInfixOf bracketedPasteEnd
29+bracketedPasteFinished = BS8.isInfixOf bracketedPasteEnd
30
30
31-- | Parse a bracketed paste. This should only be called on a string if
31-- | Parse a bracketed paste. This should only be called on a string if
32-- both 'bracketedPasteStarted' and 'bracketedPasteFinished' return
32-- both 'bracketedPasteStarted' and 'bracketedPasteFinished' return
33-- 'True'.
33-- 'True'.
34−parseBracketedPaste :: String -> KClass
34+parseBracketedPaste :: ByteString -> KClass
35parseBracketedPaste s =
35parseBracketedPaste s =
36− let (p, rest) = takeUntil (drop (length bracketedPasteStart) s) bracketedPasteEnd
37− rest' = if bracketedPasteEnd `isPrefixOf` rest
38− then drop (length bracketedPasteEnd) rest
39− else rest
40− in Valid (EvPaste $ BS8.pack p) rest'
36+ Valid (EvPaste p) (BS8.drop endLen rest')
41−
37+ where
42−takeUntil :: (Eq a) => [a] -> [a] -> ([a],[a])
38+ startLen = BS8.length bracketedPasteStart
43−takeUntil [] _ = ([], [])
39+ endLen = BS8.length bracketedPasteEnd
44−takeUntil cs sub
40+ (_, rest ) = BS8.breakSubstring bracketedPasteStart s
45− | length cs < length sub = (cs, [])
41+ (p, rest') = BS8.breakSubstring bracketedPasteEnd . BS8.drop startLen $ rest
46− | take (length sub) cs == sub = ([], drop (length sub) cs)
47− | otherwise = let (pre, suf) = takeUntil (tail cs) sub
48− in (head cs:pre, suf)
src/Graphics/Vty/Input/Terminfo.hs+34−33
@@ -7,8 +7,8 @@ import qualified Graphics.Vty.Input.Terminfo.ANSIVT as ANSIVT
7import Control.Arrow
7import Control.Arrow
8import System.Console.Terminfo
8import System.Console.Terminfo
9
9
10−-- | queries the terminal for all capability based input sequences then adds on a terminal dependent
10+-- | Queries the terminal for all capability-based input sequences and
11−-- input sequence mapping.
11+-- then adds on a terminal-dependent input sequence mapping.
12--
12--
13-- For reference see:
13-- For reference see:
14--
14--
@@ -20,29 +20,29 @@ import System.Console.Terminfo
20--
20--
21-- * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
21-- * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
22--
22--
23−-- This is painful. Terminfo is incomplete. The vim source implies that terminfo is also incorrect.
23+-- Terminfo is incomplete. The vim source implies that terminfo is also
24−-- Vty assumes that the an internal terminfo table added to the system provided terminfo table is
24+-- incorrect. Vty assumes that the internal terminfo table added to the
25−-- correct.
25+-- system-provided terminfo table is correct.
26--
26--
27−-- 1. build terminfo table for all caps. Missing caps are not added.
27+-- The procedure used here is:
28--
28--
29−-- 2. add tables for visible chars, esc, del plus ctrl and meta
29+-- 1. Build terminfo table for all caps. Missing caps are not added.
30--
30--
31−-- 3. add internally defined table for given terminal type.
31+-- 2. Add tables for visible chars, esc, del, ctrl, and meta.
32--
32--
33−-- Precedence is currently implicit in the 'compile' algorithm. Which is a bit odd.
33+-- 3. Add internally-defined table for given terminal type.
34--
34--
35−-- \todo terminfo meta is not supported.
35+-- Precedence is currently implicit in the 'compile' algorithm.
36−-- \todo no 8bit
37classifyMapForTerm :: String -> Terminal -> ClassifyMap
36classifyMapForTerm :: String -> Terminal -> ClassifyMap
38classifyMapForTerm termName term =
37classifyMapForTerm termName term =
39 concat $ capsClassifyMap term keysFromCapsTable
38 concat $ capsClassifyMap term keysFromCapsTable
40 : universalTable
39 : universalTable
41 : termSpecificTables termName
40 : termSpecificTables termName
42
41
43−-- | key table applicable to all terminals.
42+-- | The key table applicable to all terminals.
44--
43--
45−-- TODO: some probably only applicable to ANSI/VT100 terminals.
44+-- Note that some of these entries are probably only applicable to
45+-- ANSI/VT100 terminals.
46universalTable :: ClassifyMap
46universalTable :: ClassifyMap
47universalTable = concat [visibleChars, ctrlChars, ctrlMetaChars, specialSupportKeys]
47universalTable = concat [visibleChars, ctrlChars, ctrlMetaChars, specialSupportKeys]
48
48
@@ -50,31 +50,31 @@ capsClassifyMap :: Terminal -> [(String,Event)] -> ClassifyMap
50capsClassifyMap terminal table = [(x,y) | (Just x,y) <- map extractCap table]
50capsClassifyMap terminal table = [(x,y) | (Just x,y) <- map extractCap table]
51 where extractCap = first (getCapability terminal . tiGetStr)
51 where extractCap = first (getCapability terminal . tiGetStr)
52
52
53−-- | tables specific to a given terminal that are not derivable from terminfo.
53+-- | Tables specific to a given terminal that are not derivable from
54+-- terminfo.
54--
55--
55−-- TODO: Adds the ANSI/VT100/VT50 tables regardless of term identifier.
56+-- Note that this adds the ANSI/VT100/VT50 tables regardless of term
57+-- identifier.
56termSpecificTables :: String -> [ClassifyMap]
58termSpecificTables :: String -> [ClassifyMap]
57termSpecificTables _termName = ANSIVT.classifyTable
59termSpecificTables _termName = ANSIVT.classifyTable
58
60
59-- | Visible characters in the ISO-8859-1 and UTF-8 common set.
61-- | Visible characters in the ISO-8859-1 and UTF-8 common set.
60--
62--
61−-- we limit to < 0xC1. The UTF8 sequence detector will catch all values 0xC2 and above before this
63+-- We limit to < 0xC1. The UTF8 sequence detector will catch all values
62−-- classify table is reached.
64+-- 0xC2 and above before this classify table is reached.
63−--
64−-- TODO: resolve
65−-- 1. start at ' '. The earlier characters are all 'ctrlChar'
66visibleChars :: ClassifyMap
65visibleChars :: ClassifyMap
67visibleChars = [ ([x], EvKey (KChar x) [])
66visibleChars = [ ([x], EvKey (KChar x) [])
68 | x <- [' ' .. toEnum 0xC1]
67 | x <- [' ' .. toEnum 0xC1]
69 ]
68 ]
70
69
71−-- | Non visible characters in the ISO-8859-1 and UTF-8 common set translated to ctrl + char.
70+-- | Non-printable characters in the ISO-8859-1 and UTF-8 common set
71+-- translated to ctrl + char.
72--
72--
73−-- \todo resolve CTRL-i is the same as tab
73+-- This treats CTRL-i the same as tab.
74ctrlChars :: ClassifyMap
74ctrlChars :: ClassifyMap
75ctrlChars =
75ctrlChars =
76 [ ([toEnum x],EvKey (KChar y) [MCtrl])
76 [ ([toEnum x],EvKey (KChar y) [MCtrl])
77− | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_'])
77+ | (x,y) <- zip [0..31] ('@':['a'..'z']++['['..'_'])
78 , y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
78 , y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
79 , y /= 'h' -- CTRL-h should not hide BS
79 , y /= 'h' -- CTRL-h should not hide BS
80 ]
80 ]
@@ -83,22 +83,23 @@ ctrlChars =
83ctrlMetaChars :: ClassifyMap
83ctrlMetaChars :: ClassifyMap
84ctrlMetaChars = map (\(s,EvKey c m) -> ('\ESC':s, EvKey c (MMeta:m))) ctrlChars
84ctrlMetaChars = map (\(s,EvKey c m) -> ('\ESC':s, EvKey c (MMeta:m))) ctrlChars
85
85
86−-- | esc, meta esc, delete, meta delete, enter, meta enter
86+-- | Esc, meta-esc, delete, meta-delete, enter, meta-enter.
87specialSupportKeys :: ClassifyMap
87specialSupportKeys :: ClassifyMap
88specialSupportKeys =
88specialSupportKeys =
89+ [ ("\ESC\ESC[5~",EvKey KPageUp [MMeta])
90+ , ("\ESC\ESC[6~",EvKey KPageDown [MMeta])
89− [ -- special support for ESC
91+ -- special support for ESC
90− ("\ESC",EvKey KEsc []), ("\ESC\ESC",EvKey KEsc [MMeta])
92+ , ("\ESC",EvKey KEsc []), ("\ESC\ESC",EvKey KEsc [MMeta])
91 -- Special support for backspace
93 -- Special support for backspace
92− , ("\DEL",EvKey KBS []), ("\ESC\DEL",EvKey KBS [MMeta])
94+ , ("\DEL",EvKey KBS []), ("\ESC\DEL",EvKey KBS [MMeta]), ("\b",EvKey KBS [])
93 -- Special support for Enter
95 -- Special support for Enter
94 , ("\ESC\^J",EvKey KEnter [MMeta]), ("\^J",EvKey KEnter [])
96 , ("\ESC\^J",EvKey KEnter [MMeta]), ("\^J",EvKey KEnter [])
95 -- explicit support for tab
97 -- explicit support for tab
96 , ("\t", EvKey (KChar '\t') [])
98 , ("\t", EvKey (KChar '\t') [])
97 ]
99 ]
98
100
99−-- | classify table directly generated from terminfo cap strings
101+-- | A classification table directly generated from terminfo cap
100−--
102+-- strings. These are:
101−-- these are:
102--
103--
103-- * ka1 - keypad up-left
104-- * ka1 - keypad up-left
104--
105--
@@ -113,7 +114,7 @@ specialSupportKeys =
113-- * kcbt - back tab
114-- * kcbt - back tab
114--
115--
115-- * kc1 - keypad left-down
116-- * kc1 - keypad left-down
116−--
117+--
117-- * kc3 - keypad right-down
118-- * kc3 - keypad right-down
118--
119--
119-- * kdch1 - delete
120-- * kdch1 - delete
@@ -121,7 +122,7 @@ specialSupportKeys =
121-- * kcud1 - down
122-- * kcud1 - down
122--
123--
123-- * kend - end
124-- * kend - end
124−--
125+--
125-- * kent - enter
126-- * kent - enter
126--
127--
127-- * kf0 - kf63 - function keys
128-- * kf0 - kf63 - function keys
@@ -180,6 +181,6 @@ keysFromCapsTable =
180 , ("kcuu1", EvKey KUp [])
181 , ("kcuu1", EvKey KUp [])
181 ] ++ functionKeyCapsTable
182 ] ++ functionKeyCapsTable
182
183
183−-- | cap names for function keys
184+-- | Cap names for function keys.
184functionKeyCapsTable :: ClassifyMap
185functionKeyCapsTable :: ClassifyMap
185functionKeyCapsTable = flip map [0..63] $ \n -> ("kf" ++ show n, EvKey (KFun n) [])
186functionKeyCapsTable = flip map [0..63] $ \n -> ("kf" ++ show n, EvKey (KFun n) [])
src/Graphics/Vty/Input/Terminfo/ANSIVT.hs+21−20
@@ -1,15 +1,15 @@
1−-- | Input mappings for ANSI/VT100/VT50 terminals that is missing from terminfo.
1+-- | Input mappings for ANSI/VT100/VT50 terminals that is missing from
2+-- terminfo.
2−--
3+--
3−-- Or that are sent regardless of terminfo by terminal emulators. EG: Terminal emulators will often
4+-- Or that are sent regardless of terminfo by terminal emulators. EG:
5+-- Terminal emulators will often use VT50 input bytes regardless of
4−-- use VT50 input bytes regardless of declared terminal type. This provides compatibility with
6+-- declared terminal type. This provides compatibility with programs
5−-- programs that don't follow terminfo.
7+-- that don't follow terminfo.
6module Graphics.Vty.Input.Terminfo.ANSIVT where
8module Graphics.Vty.Input.Terminfo.ANSIVT where
7
9
8import Graphics.Vty.Input.Events
10import Graphics.Vty.Input.Events
9
11
10-- | Encoding for navigation keys.
12-- | Encoding for navigation keys.
11−--
12−-- TODO: This is not the same as the input bytes pulled from teh caps table.
13navKeys0 :: ClassifyMap
13navKeys0 :: ClassifyMap
14navKeys0 =
14navKeys0 =
15 [ k "G" KCenter
15 [ k "G" KCenter
@@ -30,8 +30,10 @@ navKeys1 =
30 [("\ESC[" ++ charCnt ++ show mc++c,EvKey s m)
30 [("\ESC[" ++ charCnt ++ show mc++c,EvKey s m)
31 | charCnt <- ["1;", ""], -- we can have a count or not
31 | charCnt <- ["1;", ""], -- we can have a count or not
32 (m,mc) <- [([MShift],2::Int), ([MCtrl],5), ([MMeta],3),
32 (m,mc) <- [([MShift],2::Int), ([MCtrl],5), ([MMeta],3),
33+ -- modifiers and their codes
33− ([MShift, MCtrl],6), ([MShift, MMeta],4)], -- modifiers and their codes
34+ ([MShift, MCtrl],6), ([MShift, MMeta],4)],
35+ -- directions and their codes
34− (c,s) <- [("A", KUp), ("B", KDown), ("C", KRight), ("D", KLeft), ("H", KHome), ("F", KEnd)] -- directions and their codes
36+ (c,s) <- [("A", KUp), ("B", KDown), ("C", KRight), ("D", KLeft), ("H", KHome), ("F", KEnd)]
35 ]
37 ]
36
38
37-- | encoding for ins, del, pageup, pagedown, home, end
39-- | encoding for ins, del, pageup, pagedown, home, end
@@ -49,26 +51,26 @@ navKeys3 =
49 [KIns,KDel,KPageUp,KPageDown,KHome,KEnd]
51 [KIns,KDel,KPageUp,KPageDown,KHome,KEnd]
50
52
51-- | encoding for shift plus function keys
53-- | encoding for shift plus function keys
52−--
54+--
53−-- According to
55+-- According to
54--
56--
55-- * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
57-- * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
56--
58--
57-- This encoding depends on the terminal.
59-- This encoding depends on the terminal.
58functionKeys1 :: ClassifyMap
60functionKeys1 :: ClassifyMap
59functionKeys1 =
61functionKeys1 =
60− let f ff nrs m = [ ("\ESC["++show n++"~",EvKey (KFun $ n-(nrs!!0)+ff) m) | n <- nrs ] in
62+ let f ff nrs m = [ ("\ESC["++show n++"~",EvKey (KFun $ n-head nrs+ff) m) | n <- nrs ] in
61 concat [f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ]
63 concat [f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ]
62
64
63-- | encoding for meta plus char
65-- | encoding for meta plus char
64--
66--
65−-- TODO: resolve -
66−--
67−-- 1. removed 'ESC' from second list due to duplication with "special_support_keys".
67+-- 1. removed 'ESC' from second list due to duplication with
68+-- "special_support_keys".
68−-- 2. removed '[' from second list due to conflict with 7-bit encoding for ESC. Whether meta+[ is
69+-- 2. removed '[' from second list due to conflict with 7-bit encoding
69−-- the same as ESC should examine km and current encoding.
70+-- for ESC. Whether meta+[ is the same as ESC should examine km and
71+-- current encoding.
70−-- 3. stopped enumeration at '~' instead of '\DEL'. The latter is mapped to KBS by
72+-- 3. stopped enumeration at '~' instead of '\DEL'. The latter is mapped
71−-- special_support_keys.
73+-- to KBS by special_support_keys.
72functionKeys2 :: ClassifyMap
74functionKeys2 :: ClassifyMap
73functionKeys2 = [ ('\ESC':[x],EvKey (KChar x) [MMeta])
75functionKeys2 = [ ('\ESC':[x],EvKey (KChar x) [MMeta])
74 | x <- '\t':[' ' .. '~']
76 | x <- '\t':[' ' .. '~']
@@ -84,4 +86,3 @@ classifyTable =
84 , functionKeys1
86 , functionKeys1
85 , functionKeys2
87 , functionKeys2
86 ]
88 ]
87−
src/Graphics/Vty/Output.hs+53−52
@@ -1,70 +1,69 @@
1−{-# LANGUAGE RecordWildCards #-}
1+{-# LANGUAGE RecordWildCards, CPP #-}
2−{-# LANGUAGE NamedFieldPuns #-}
2+-- | This module provides functions for accessing the current terminal
3−-- | Output interface.
3+-- or a specific terminal device.
4--
4--
5−-- Access to the current terminal or a specific terminal device.
5+-- See also:
6--
6--
7−-- See also:
7+-- 1. "Graphics.Vty.Output": This instantiates an abtract interface
8+-- to the terminal based on the @TERM@ and @COLORTERM@ environment
9+-- variables.
8--
10--
9−-- 1. "Graphics.Vty.Output": This instantiates an abtract interface to the terminal interface based
11+-- 2. "Graphics.Vty.Output.Interface": Defines the generic interface all
10−-- on the TERM and COLORTERM environment variables.
12+-- terminal modules need to implement.
11--
13--
12−-- 2. "Graphics.Vty.Output.Interface": Defines the generic interface all terminals need to implement.
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.
13--
17--
14−-- 3. "Graphics.Vty.Output.TerminfoBased": Defines a terminal instance that uses terminfo for all
15−-- control strings. No attempt is made to change the character set to UTF-8 for these terminals.
16−-- I don't know a way to reliably determine if that is required or how to do so.
17−--
18−-- 4. "Graphics.Vty.Output.XTermColor": This module contains an interface suitable for xterm-like
18+-- 4. "Graphics.Vty.Output.XTermColor": This module contains an
19+-- interface suitable for xterm-like terminals. These are the terminals
19−-- terminals. These are the terminals where TERM == xterm. This does use terminfo for as many
20+-- where @TERM@ begins with @xterm@. This does use terminfo for as many
20−-- control codes as possible.
21+-- control codes as possible.
21−module Graphics.Vty.Output ( module Graphics.Vty.Output
22+module Graphics.Vty.Output
22− , Output(..) -- \todo hide constructors
23+ ( outputForConfig
23− , AssumedState(..)
24+ , setCursorPos
24− , DisplayContext(..) -- \todo hide constructors
25+ , hideCursor
25− , outputPicture
26+ , showCursor
26− , displayContext
27− )
27+ )
28− where
28+where
29
29
30−import Graphics.Vty.Prelude
30+import Control.Monad (when)
31
31
32import Graphics.Vty.Config
32import Graphics.Vty.Config
33−
33+import Graphics.Vty.Image (regionWidth, regionHeight)
34import Graphics.Vty.Output.Interface
34import Graphics.Vty.Output.Interface
35import Graphics.Vty.Output.XTermColor as XTermColor
35import Graphics.Vty.Output.XTermColor as XTermColor
36import Graphics.Vty.Output.TerminfoBased as TerminfoBased
36import Graphics.Vty.Output.TerminfoBased as TerminfoBased
37
37
38import Blaze.ByteString.Builder (writeToByteString)
38import Blaze.ByteString.Builder (writeToByteString)
39
39
40−import Control.Monad.Trans
41−
42import Data.List (isPrefixOf)
40import Data.List (isPrefixOf)
41+
42+#if !MIN_VERSION_base(4,11,0)
43import Data.Monoid ((<>))
43import Data.Monoid ((<>))
44+#endif
44
45
45−-- | Returns a `Output` for the terminal specified in `Config`
46+-- | Returns an `Output` for the terminal specified in `Config`.
46--
47--
47−-- The specific Output implementation used is hidden from the API user. All terminal implementations
48+-- The specific Output implementation used is hidden from the API user.
49+-- All terminal implementations are assumed to perform more, or less,
48−-- are assumed to perform more, or less, the same. Currently, all implementations use terminfo for at
50+-- the same. Currently, all implementations use terminfo for at least
49−-- least some terminal specific information.
51+-- some terminal specific information.
50--
52--
51−-- Specifics about it being based on terminfo are hidden from the API user. If a terminal
52−-- implementation is developed for a terminal without terminfo support then Vty should work as
53+-- If a terminal implementation is developed for a terminal without
53−-- expected on that terminal.
54+-- terminfo support then Vty should work as expected on that terminal.
54--
55--
55-- Selection of a terminal is done as follows:
56-- Selection of a terminal is done as follows:
56--
57--
57-- * If TERM contains "xterm" or "screen", use XTermColor.
58-- * If TERM contains "xterm" or "screen", use XTermColor.
58-- * otherwise use the TerminfoBased driver.
59-- * otherwise use the TerminfoBased driver.
59−--
60−-- \todo add an implementation for windows that does not depend on terminfo. Should be installable
61−-- with only what is provided in the haskell platform. Use ansi-terminal
62outputForConfig :: Config -> IO Output
60outputForConfig :: Config -> IO Output
63−outputForConfig Config{ outputFd = Just fd, termName = Just termName, .. } = do
61+outputForConfig Config{ outputFd = Just fd, termName = Just termName
62+ , colorMode = Just colorMode, .. } = do
64 t <- if "xterm" `isPrefixOf` termName || "screen" `isPrefixOf` termName
63 t <- if "xterm" `isPrefixOf` termName || "screen" `isPrefixOf` termName
65− then XTermColor.reserveTerminal termName fd
64+ then XTermColor.reserveTerminal termName fd colorMode
66 -- Not an xterm-like terminal. try for generic terminfo.
65 -- Not an xterm-like terminal. try for generic terminfo.
67− else TerminfoBased.reserveTerminal termName fd
66+ else TerminfoBased.reserveTerminal termName fd colorMode
68
67
69 case mouseMode of
68 case mouseMode of
70 Just s -> setMode t Mouse s
69 Just s -> setMode t Mouse s
@@ -79,28 +78,30 @@ outputForConfig config = (<> config) <$> standardIOConfig >>= outputForConfig
79
78
80-- | Sets the cursor position to the given output column and row.
79-- | Sets the cursor position to the given output column and row.
81--
80--
82−-- This is not necessarially the same as the character position with the same coordinates.
81+-- This is not necessarily the same as the character position with the
83−-- Characters can be a variable number of columns in width.
82+-- same coordinates. Characters can be a variable number of columns in
83+-- width.
84--
84--
85−-- Currently, the only way to set the cursor position to a given character coordinate is to specify
85+-- Currently, the only way to set the cursor position to a given
86+-- character coordinate is to specify the coordinate in the Picture
86−-- the coordinate in the Picture instance provided to outputPicture or refresh.
87+-- instance provided to 'outputPicture' or 'refresh'.
87−setCursorPos :: MonadIO m => Output -> Int -> Int -> m ()
88+setCursorPos :: Output -> Int -> Int -> IO ()
88setCursorPos t x y = do
89setCursorPos t x y = do
89 bounds <- displayBounds t
90 bounds <- displayBounds t
90 when (x >= 0 && x < regionWidth bounds && y >= 0 && y < regionHeight bounds) $ do
91 when (x >= 0 && x < regionWidth bounds && y >= 0 && y < regionHeight bounds) $ do
91 dc <- displayContext t bounds
92 dc <- displayContext t bounds
92− liftIO $ outputByteBuffer t $ writeToByteString $ writeMoveCursor dc x y
93+ outputByteBuffer t $ writeToByteString $ writeMoveCursor dc x y
93
94
94−-- | Hides the cursor
95+-- | Hides the cursor.
95−hideCursor :: MonadIO m => Output -> m ()
96+hideCursor :: Output -> IO ()
96hideCursor t = do
97hideCursor t = do
97 bounds <- displayBounds t
98 bounds <- displayBounds t
98 dc <- displayContext t bounds
99 dc <- displayContext t bounds
99− liftIO $ outputByteBuffer t $ writeToByteString $ writeHideCursor dc
100+ outputByteBuffer t $ writeToByteString $ writeHideCursor dc
100
101
101−-- | Shows the cursor
102+-- | Shows the cursor.
102−showCursor :: MonadIO m => Output -> m ()
103+showCursor :: Output -> IO ()
103showCursor t = do
104showCursor t = do
104 bounds <- displayBounds t
105 bounds <- displayBounds t
105 dc <- displayContext t bounds
106 dc <- displayContext t bounds
106− liftIO $ outputByteBuffer t $ writeToByteString $ writeShowCursor dc
107+ outputByteBuffer t $ writeToByteString $ writeShowCursor dc
src/Graphics/Vty/Output/Interface.hs+176−132
@@ -1,21 +1,25 @@
1-- Copyright Corey O'Connor
1-- Copyright Corey O'Connor
2−-- General philosophy is: MonadIO is for equations exposed to clients.
3−{-# LANGUAGE CPP #-}
4−{-# LANGUAGE BangPatterns #-}
5{-# LANGUAGE RankNTypes #-}
2{-# LANGUAGE RankNTypes #-}
6{-# LANGUAGE ExistentialQuantification #-}
3{-# LANGUAGE ExistentialQuantification #-}
7{-# LANGUAGE GADTs #-}
4{-# LANGUAGE GADTs #-}
8{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleContexts #-}
9−
6+{-# LANGUAGE ScopedTypeVariables #-}
10−#ifndef MIN_VERSION_base
7+-- | This module provides an abstract interface for performing terminal
11−#defined MIN_VERSION_base(x,y,z) 1
8+-- output. The only user-facing part of this API is 'Output'.
12−#endif
13−
14module Graphics.Vty.Output.Interface
9module Graphics.Vty.Output.Interface
10+ ( Output(..)
11+ , AssumedState(..)
12+ , DisplayContext(..)
13+ , Mode(..)
14+ , displayContext
15+ , outputPicture
16+ , initialAssumedState
17+ , limitAttrForDisplay
18+ )
15where
19where
16
20
17−import Graphics.Vty.Prelude
21+import Graphics.Vty.Attributes
18−
22+import Graphics.Vty.Image (DisplayRegion, regionHeight)
19import Graphics.Vty.Picture
23import Graphics.Vty.Picture
20import Graphics.Vty.PictureToSpans
24import Graphics.Vty.PictureToSpans
21import Graphics.Vty.Span
25import Graphics.Vty.Span
@@ -25,72 +29,97 @@ import Graphics.Vty.DisplayAttributes
25import Blaze.ByteString.Builder (Write, writeToByteString)
29import Blaze.ByteString.Builder (Write, writeToByteString)
26import Blaze.ByteString.Builder.ByteString (writeByteString)
30import Blaze.ByteString.Builder.ByteString (writeByteString)
27
31
28−import Control.Monad.Trans
29−
30import qualified Data.ByteString as BS
32import qualified Data.ByteString as BS
31import Data.IORef
33import Data.IORef
32import qualified Data.Text.Encoding as T
34import qualified Data.Text.Encoding as T
33import qualified Data.Text.Lazy as TL
35import qualified Data.Text.Lazy as TL
34import qualified Data.Vector as Vector
36import qualified Data.Vector as Vector
35
37
36−#if !(MIN_VERSION_base(4,8,0))
37−import Data.Monoid (mempty, mappend)
38−#endif
39−
40-- | Modal terminal features that can be enabled and disabled.
38-- | Modal terminal features that can be enabled and disabled.
41data Mode = Mouse
39data Mode = Mouse
40+ -- ^ Mouse mode (whether the terminal is configured to provide
41+ -- mouse input events)
42 | BracketedPaste
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.
43 deriving (Eq, Read, Show)
54 deriving (Eq, Read, Show)
44
55
56+-- | The Vty terminal output interface.
45data Output = Output
57data Output = Output
46− { -- | Text identifier for the output device. Used for debugging.
58+ { -- | Text identifier for the output device. Used for debugging.
47 terminalID :: String
59 terminalID :: String
60+ -- | Release the terminal just prior to application exit and reset
61+ -- it to its state prior to application startup.
48− , releaseTerminal :: forall m. MonadIO m => m ()
62+ , releaseTerminal :: IO ()
49− -- | Clear the display and initialize the terminal to some initial display state.
63+ -- | Clear the display and initialize the terminal to some initial
64+ -- display state.
50− --
65+ --
51− -- The expectation of a program is that the display starts in some initial state.
66+ -- The expectation of a program is that the display starts in some
52− -- The initial state would consist of fixed values:
67+ -- The initial state. initial state would consist of fixed values:
53− --
68+ --
54− -- - cursor at top left
69+ -- - cursor at top left
55− -- - UTF-8 character encoding
70+ -- - UTF-8 character encoding
56− -- - drawing characteristics are the default
71+ -- - drawing characteristics are the default
57− --
58− -- The abstract operation I think all these behaviors are instances of is reserving exclusive
59− -- access to a display such that:
60− --
61− -- - The previous state cannot be determined
62− -- - When exclusive access to a display is released the display returns to the previous state.
63− , reserveDisplay :: forall m. MonadIO m => m ()
72+ , reserveDisplay :: IO ()
64− -- | Return the display to the state before `reserveDisplay`
73+ -- | Return the display to the state before `reserveDisplay` If no
65− -- If no previous state then set the display state to the initial state.
74+ -- previous state then set the display state to the initial state.
66− , releaseDisplay :: forall m. MonadIO m => m ()
75+ , releaseDisplay :: IO ()
76+ -- | Sets the current display bounds (width, height).
77+ , setDisplayBounds :: (Int, Int) -> IO ()
67− -- | Returns the current display bounds.
78+ -- | Returns the current display bounds.
68− , displayBounds :: forall m. MonadIO m => m DisplayRegion
79+ , displayBounds :: IO DisplayRegion
69− -- | Output the byte string to the terminal device.
80+ -- | Output the bytestring to the terminal device.
70 , outputByteBuffer :: BS.ByteString -> IO ()
81 , outputByteBuffer :: BS.ByteString -> IO ()
71− -- | Maximum number of colors supported by the context.
72− , contextColorCount :: Int
73− -- | if the cursor can be shown / hidden
82+ -- | Specifies whether the cursor can be shown / hidden.
74 , supportsCursorVisibility :: Bool
83 , supportsCursorVisibility :: Bool
75− -- | Indicates support for terminal modes for this output device
84+ -- | Indicates support for terminal modes for this output device.
76 , supportsMode :: Mode -> Bool
85 , supportsMode :: Mode -> Bool
77− -- | Enables or disables a mode (does nothing if the mode is unsupported)
86+ -- | Enables or disables a mode (does nothing if the mode is
87+ -- unsupported).
78− , setMode :: forall m. MonadIO m => Mode -> Bool -> m ()
88+ , setMode :: Mode -> Bool -> IO ()
79− -- | Returns whether a mode is enabled
89+ -- | Returns whether a mode is enabled.
80− , getModeStatus :: forall m. MonadIO m => Mode -> m Bool
90+ , getModeStatus :: Mode -> IO Bool
81 , assumedStateRef :: IORef AssumedState
91 , assumedStateRef :: IORef AssumedState
82− -- | Acquire display access to the given region of the display.
92+ -- | Acquire display access to the given region of the display.
83− -- Currently all regions have the upper left corner of (0,0) and the lower right corner at
93+ -- Currently all regions have the upper left corner of (0,0) and
94+ -- the lower right corner at (max displayWidth providedWidth, max
84− -- (max displayWidth providedWidth, max displayHeight providedHeight)
95+ -- displayHeight providedHeight)
85− , mkDisplayContext :: forall m. MonadIO m => Output -> DisplayRegion -> m DisplayContext
96+ , mkDisplayContext :: Output -> DisplayRegion -> IO DisplayContext
86− -- | Ring the terminal bell if supported.
97+ -- | Ring the terminal bell if supported.
87− , ringTerminalBell :: forall m. MonadIO m => m ()
98+ , ringTerminalBell :: IO ()
88− -- | Returns whether the terminal has an audio bell feature.
99+ -- | Returns whether the terminal has an audio bell feature.
89− , supportsBell :: forall m. MonadIO m => m Bool
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
90 }
119 }
91
120
92−displayContext :: MonadIO m => Output -> DisplayRegion -> m DisplayContext
121+displayContext :: Output -> DisplayRegion -> IO DisplayContext
93−displayContext t = liftIO . mkDisplayContext t t
122+displayContext t = mkDisplayContext t t
94
123
95data AssumedState = AssumedState
124data AssumedState = AssumedState
96 { prevFattr :: Maybe FixedAttr
125 { prevFattr :: Maybe FixedAttr
@@ -102,69 +131,71 @@ initialAssumedState = AssumedState Nothing Nothing
102
131
103data DisplayContext = DisplayContext
132data DisplayContext = DisplayContext
104 { contextDevice :: Output
133 { contextDevice :: Output
105− -- | Provide the bounds of the display context.
134+ -- | Provide the bounds of the display context.
106 , contextRegion :: DisplayRegion
135 , contextRegion :: DisplayRegion
107− -- | sets the output position to the specified row and column. Where the number of bytes
136+ -- | Sets the output position to the specified row and column
137+ -- where the number of bytes required for the control codes can be
108− -- required for the control codes can be specified seperate from the actual byte sequence.
138+ -- specified seperate from the actual byte sequence.
109 , writeMoveCursor :: Int -> Int -> Write
139 , writeMoveCursor :: Int -> Int -> Write
110 , writeShowCursor :: Write
140 , writeShowCursor :: Write
111 , writeHideCursor :: Write
141 , writeHideCursor :: Write
112− -- | Assure the specified output attributes will be applied to all the following text until the
142+ -- Ensure that the specified output attributes will be applied to
143+ -- all the following text until the next output attribute change
113− -- next output attribute change. Where the number of bytes required for the control codes can
144+ -- where the number of bytes required for the control codes can be
114− -- be specified seperate from the actual byte sequence. The required number of bytes must be
145+ -- specified seperately from the actual byte sequence. The required
146+ -- number of bytes must be at least the maximum number of bytes
115− -- at least the maximum number of bytes required by any attribute changes. The serialization
147+ -- required by any attribute changes. The serialization equations
116− -- equations must provide the ptr to the next byte to be specified in the output buffer.
148+ -- must provide the ptr to the next byte to be specified in the
149+ -- output buffer.
117 --
150 --
118− -- The currently applied display attributes are provided as well. The Attr data type can
151+ -- The currently applied display attributes are provided as well.
152+ -- The Attr data type can specify the style or color should not be
119− -- specify the style or color should not be changed from the currently applied display
153+ -- changed from the currently applied display attributes. In order
120− -- attributes. In order to support this the currently applied display attributes are required.
154+ -- to support this the currently applied display attributes are
121− -- In addition it may be possible to optimize the state changes based off the currently applied
155+ -- required. In addition it may be possible to optimize the state
122− -- display attributes.
156+ -- changes based off the currently applied display attributes.
123− , writeSetAttr :: FixedAttr -> Attr -> DisplayAttrDiff -> Write
157+ , writeSetAttr :: Bool -> FixedAttr -> Attr -> DisplayAttrDiff -> Write
124− -- | Reset the display attributes to the default display attributes
158+ -- | Reset the display attributes to the default display attributes.
125− , writeDefaultAttr :: Write
159+ , writeDefaultAttr :: Bool -> Write
126 , writeRowEnd :: Write
160 , writeRowEnd :: Write
127 -- | See `Graphics.Vty.Output.XTermColor.inlineHack`
161 -- | See `Graphics.Vty.Output.XTermColor.inlineHack`
128 , inlineHack :: IO ()
162 , inlineHack :: IO ()
129 }
163 }
130
164
131−-- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.
165+-- | All terminals serialize UTF8 text to the terminal device exactly as
166+-- serialized in memory.
132writeUtf8Text :: BS.ByteString -> Write
167writeUtf8Text :: BS.ByteString -> Write
133writeUtf8Text = writeByteString
168writeUtf8Text = writeByteString
134
169
135-- | Displays the given `Picture`.
170-- | Displays the given `Picture`.
136--
171--
137−-- 0. The image is cropped to the display size.
172+-- 1. The image is cropped to the display size.
138--
173--
139−-- 1. Converted into a sequence of attribute changes and text spans.
174+-- 2. Converted into a sequence of attribute changes and text spans.
140−--
141−-- 2. The cursor is hidden.
142--
175--
143−-- 3. Serialized to the display.
176+-- 3. The cursor is hidden.
144--
177--
178+-- 4. Serialized to the display.
179+--
145−-- 4. The cursor is then shown and positioned or kept hidden.
180+-- 5. The cursor is then shown and positioned or kept hidden.
146−--
147−-- todo: specify possible IO exceptions.
148−-- abstract from IO monad to a MonadIO instance.
149−outputPicture :: MonadIO m => DisplayContext -> Picture -> m ()
181+outputPicture :: DisplayContext -> Picture -> IO ()
150−outputPicture dc pic = liftIO $ do
182+outputPicture dc pic = do
183+ urlsEnabled <- getModeStatus (contextDevice dc) Hyperlink
151 as <- readIORef (assumedStateRef $ contextDevice dc)
184 as <- readIORef (assumedStateRef $ contextDevice dc)
152 let manipCursor = supportsCursorVisibility (contextDevice dc)
185 let manipCursor = supportsCursorVisibility (contextDevice dc)
153 r = contextRegion dc
186 r = contextRegion dc
154 ops = displayOpsForPic pic r
187 ops = displayOpsForPic pic r
155− initialAttr = FixedAttr defaultStyleMask Nothing Nothing
188+ initialAttr = FixedAttr defaultStyleMask Nothing Nothing Nothing
156− -- Diff the previous output against the requested output. Differences are currently on a per-row
189+ -- Diff the previous output against the requested output.
157− -- basis.
190+ -- Differences are currently on a per-row basis.
158− -- \todo handle resizes that crop the dominate directions better.
159 diffs :: [Bool] = case prevOutputOps as of
191 diffs :: [Bool] = case prevOutputOps as of
160− Nothing -> replicate (fromEnum $ regionHeight $ effectedRegion ops) True
192+ Nothing -> replicate (fromEnum $ regionHeight $ affectedRegion ops) True
161− Just previousOps -> if effectedRegion previousOps /= effectedRegion ops
193+ Just previousOps -> if affectedRegion previousOps /= affectedRegion ops
162 then replicate (displayOpsRows ops) True
194 then replicate (displayOpsRows ops) True
163− else zipWith (/=) (Vector.toList previousOps)
195+ else Vector.toList $ Vector.zipWith (/=) previousOps ops
164− (Vector.toList ops)
165 -- build the Write corresponding to the output image
196 -- build the Write corresponding to the output image
166 out = (if manipCursor then writeHideCursor dc else mempty)
197 out = (if manipCursor then writeHideCursor dc else mempty)
167− `mappend` writeOutputOps dc initialAttr diffs ops
198+ `mappend` writeOutputOps urlsEnabled dc initialAttr diffs ops
168 `mappend`
199 `mappend`
169 (let (w,h) = contextRegion dc
200 (let (w,h) = contextRegion dc
170 clampX = max 0 . min (w-1)
201 clampX = max 0 . min (w-1)
@@ -175,9 +206,15 @@ outputPicture dc pic = liftIO $ do
175 AbsoluteCursor x y ->
206 AbsoluteCursor x y ->
176 writeShowCursor dc `mappend`
207 writeShowCursor dc `mappend`
177 writeMoveCursor dc (clampX x) (clampY y)
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)
178 Cursor x y ->
215 Cursor x y ->
179 let m = cursorOutputMap ops $ picCursor pic
216 let m = cursorOutputMap ops $ picCursor pic
180− (ox, oy) = charToOutputPos m (x,y)
217+ (ox, oy) = charToOutputPos m (clampX x, clampY y)
181 in writeShowCursor dc `mappend`
218 in writeShowCursor dc `mappend`
182 writeMoveCursor dc (clampX ox) (clampY oy)
219 writeMoveCursor dc (clampX ox) (clampY oy)
183 )
220 )
@@ -187,15 +224,15 @@ outputPicture dc pic = liftIO $ do
187 let as' = as { prevOutputOps = Just ops }
224 let as' = as { prevOutputOps = Just ops }
188 writeIORef (assumedStateRef $ contextDevice dc) as'
225 writeIORef (assumedStateRef $ contextDevice dc) as'
189
226
190−writeOutputOps :: DisplayContext -> FixedAttr -> [Bool] -> DisplayOps -> Write
227+writeOutputOps :: Bool -> DisplayContext -> FixedAttr -> [Bool] -> DisplayOps -> Write
191−writeOutputOps dc initialAttr diffs ops =
228+writeOutputOps urlsEnabled dc initialAttr diffs ops =
192 let (_, out, _) = Vector.foldl' writeOutputOps'
229 let (_, out, _) = Vector.foldl' writeOutputOps'
193 (0, mempty, diffs)
230 (0, mempty, diffs)
194 ops
231 ops
195 in out
232 in out
196− where
233+ where
197 writeOutputOps' (y, out, True : diffs') spanOps
234 writeOutputOps' (y, out, True : diffs') spanOps
198− = let spanOut = writeSpanOps dc y initialAttr spanOps
235+ = let spanOut = writeSpanOps urlsEnabled dc y initialAttr spanOps
199 out' = out `mappend` spanOut
236 out' = out `mappend` spanOut
200 in (y+1, out', diffs')
237 in (y+1, out', diffs')
201 writeOutputOps' (y, out, False : diffs') _spanOps
238 writeOutputOps' (y, out, False : diffs') _spanOps
@@ -203,33 +240,34 @@ writeOutputOps dc initialAttr diffs ops =
203 writeOutputOps' (_y, _out, []) _spanOps
240 writeOutputOps' (_y, _out, []) _spanOps
204 = error "vty - output spans without a corresponding diff."
241 = error "vty - output spans without a corresponding diff."
205
242
206−writeSpanOps :: DisplayContext -> Int -> FixedAttr -> SpanOps -> Write
243+writeSpanOps :: Bool -> DisplayContext -> Int -> FixedAttr -> SpanOps -> Write
207−writeSpanOps dc y initialAttr spanOps =
244+writeSpanOps urlsEnabled dc y initialAttr spanOps =
208 -- The first operation is to set the cursor to the start of the row
245 -- The first operation is to set the cursor to the start of the row
209− let start = writeMoveCursor dc 0 y `mappend` writeDefaultAttr dc
246+ let start = writeMoveCursor dc 0 y `mappend` writeDefaultAttr dc urlsEnabled
210 -- then the span ops are serialized in the order specified
247 -- then the span ops are serialized in the order specified
211− in fst $ Vector.foldl' (\(out, fattr) op -> case writeSpanOp dc op fattr of
248+ in fst $ Vector.foldl' (\(out, fattr) op -> case writeSpanOp urlsEnabled dc op fattr of
212 (opOut, fattr') -> (out `mappend` opOut, fattr')
249 (opOut, fattr') -> (out `mappend` opOut, fattr')
213 )
250 )
214 (start, initialAttr)
251 (start, initialAttr)
215 spanOps
252 spanOps
216
253
217−writeSpanOp :: DisplayContext -> SpanOp -> FixedAttr -> (Write, FixedAttr)
254+writeSpanOp :: Bool -> DisplayContext -> SpanOp -> FixedAttr -> (Write, FixedAttr)
218−writeSpanOp dc (TextSpan attr _ _ str) fattr =
255+writeSpanOp urlsEnabled dc (TextSpan attr _ _ str) fattr =
219 let attr' = limitAttrForDisplay (contextDevice dc) attr
256 let attr' = limitAttrForDisplay (contextDevice dc) attr
220 fattr' = fixDisplayAttr fattr attr'
257 fattr' = fixDisplayAttr fattr attr'
221 diffs = displayAttrDiffs fattr fattr'
258 diffs = displayAttrDiffs fattr fattr'
222− out = writeSetAttr dc fattr attr' diffs
259+ out = writeSetAttr dc urlsEnabled fattr attr' diffs
223 `mappend` writeUtf8Text (T.encodeUtf8 $ TL.toStrict str)
260 `mappend` writeUtf8Text (T.encodeUtf8 $ TL.toStrict str)
224 in (out, fattr')
261 in (out, fattr')
225−writeSpanOp _dc (Skip _) _fattr = error "writeSpanOp for Skip"
262+writeSpanOp _ _ (Skip _) _fattr = error "writeSpanOp for Skip"
226−writeSpanOp dc (RowEnd _) fattr = (writeDefaultAttr dc `mappend` writeRowEnd dc, fattr)
263+writeSpanOp urlsEnabled dc (RowEnd _) fattr = (writeDefaultAttr dc urlsEnabled `mappend` writeRowEnd dc, fattr)
227
264
228−-- | The cursor position is given in X,Y character offsets. Due to multi-column characters this
265+-- | The cursor position is given in X,Y character offsets. Due to
229−-- needs to be translated to column, row positions.
266+-- multi-column characters this needs to be translated to column, row
267+-- positions.
230data CursorOutputMap = CursorOutputMap
268data CursorOutputMap = CursorOutputMap
231 { charToOutputPos :: (Int, Int) -> (Int, Int)
269 { charToOutputPos :: (Int, Int) -> (Int, Int)
232− }
270+ }
233
271
234cursorOutputMap :: DisplayOps -> Cursor -> CursorOutputMap
272cursorOutputMap :: DisplayOps -> Cursor -> CursorOutputMap
235cursorOutputMap spanOps _cursor = CursorOutputMap
273cursorOutputMap spanOps _cursor = CursorOutputMap
@@ -239,18 +277,18 @@ cursorOutputMap spanOps _cursor = CursorOutputMap
239cursorColumnOffset :: DisplayOps -> Int -> Int -> Int
277cursorColumnOffset :: DisplayOps -> Int -> Int -> Int
240cursorColumnOffset ops cx cy =
278cursorColumnOffset ops cx cy =
241 let cursorRowOps = Vector.unsafeIndex ops (fromEnum cy)
279 let cursorRowOps = Vector.unsafeIndex ops (fromEnum cy)
242− (outOffset, _, _)
280+ (outOffset, _, _)
243− = Vector.foldl' ( \(d, currentCx, done) op ->
281+ = Vector.foldl' ( \(d, currentCx, done) op ->
244 if done then (d, currentCx, done) else case spanOpHasWidth op of
282 if done then (d, currentCx, done) else case spanOpHasWidth op of
245 Nothing -> (d, currentCx, False)
283 Nothing -> (d, currentCx, False)
246 Just (cw, ow) -> case compare cx (currentCx + cw) of
284 Just (cw, ow) -> case compare cx (currentCx + cw) of
247 GT -> ( d + ow
285 GT -> ( d + ow
248 , currentCx + cw
286 , currentCx + cw
249− , False
287+ , False
250 )
288 )
251 EQ -> ( d + ow
289 EQ -> ( d + ow
252 , currentCx + cw
290 , currentCx + cw
253− , True
291+ , True
254 )
292 )
255 LT -> ( d + columnsToCharOffset (cx - currentCx) op
293 LT -> ( d + columnsToCharOffset (cx - currentCx) op
256 , currentCx + cw
294 , currentCx + cw
@@ -261,27 +299,33 @@ cursorColumnOffset ops cx cy =
261 cursorRowOps
299 cursorRowOps
262 in outOffset
300 in outOffset
263
301
264−-- | Not all terminals support all display attributes. This filters a display attribute to what the
302+-- | Not all terminals support all display attributes. This filters a
265−-- given terminal can display.
303+-- display attribute to what the given terminal can display.
266limitAttrForDisplay :: Output -> Attr -> Attr
304limitAttrForDisplay :: Output -> Attr -> Attr
267−limitAttrForDisplay t attr
305+limitAttrForDisplay t attr
268 = attr { attrForeColor = clampColor $ attrForeColor attr
306 = attr { attrForeColor = clampColor $ attrForeColor attr
269 , attrBackColor = clampColor $ attrBackColor attr
307 , attrBackColor = clampColor $ attrBackColor attr
270 }
308 }
271 where
309 where
272 clampColor Default = Default
310 clampColor Default = Default
273 clampColor KeepCurrent = KeepCurrent
311 clampColor KeepCurrent = KeepCurrent
274− clampColor (SetTo c) = clampColor' c
312+ clampColor (SetTo c) = clampColor' (outputColorMode t) c
313+
275− clampColor' (ISOColor v)
314+ clampColor' NoColor _ = Default
276− | contextColorCount t < 8 = Default
315+
316+ clampColor' ColorMode8 (ISOColor v)
277− | contextColorCount t < 16 && v >= 8 = SetTo $ ISOColor (v - 8)
317+ | v >= 8 = SetTo $ ISOColor (v - 8)
278− | otherwise = SetTo $ ISOColor v
318+ | otherwise = SetTo $ ISOColor v
279− clampColor' (Color240 v)
319+ clampColor' ColorMode8 _ = Default
280− -- TODO: Choose closes ISO color?
320+
281− | contextColorCount t < 8 = Default
321+ clampColor' ColorMode16 c@(ISOColor _) = SetTo c
282− | contextColorCount t < 16 = Default
322+ clampColor' ColorMode16 _ = Default
283− | contextColorCount t <= 256 = SetTo $ Color240 v
323+
324+ clampColor' (ColorMode240 _) c@(ISOColor _) = SetTo c
325+ clampColor' (ColorMode240 colorCount) c@(Color240 n)
326+ | n <= colorCount = SetTo c
284− | otherwise
327+ | otherwise = Default
285− = let p :: Double = fromIntegral v / 240.0
328+ clampColor' colorMode@(ColorMode240 _) (RGBColor r g b) =
286− v' = floor $ p * (fromIntegral $ contextColorCount t)
329+ clampColor' colorMode (color240 r g b)
287− in SetTo $ Color240 v'
330+
331+ clampColor' FullColor c = SetTo c
src/Graphics/Vty/Output/Mock.hs+40−25
@@ -1,13 +1,17 @@
1-- Copyright Corey O'Connor
1-- Copyright Corey O'Connor
2{-# LANGUAGE FlexibleInstances #-}
2{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE TypeFamilies #-}
3{-# LANGUAGE TypeFamilies #-}
4−-- | This provides a mock terminal implementation that is nice for testing. This transforms the
4+-- | This provides a mock terminal implementation that is nice for
5−-- output operations to visible characters. Which is nice for some tests.
5+-- testing. This transforms the output operations to visible characters
6+-- which is useful for testing.
6−module Graphics.Vty.Output.Mock ( MockData, mockTerminal )
7+module Graphics.Vty.Output.Mock
8+ ( MockData
9+ , mockTerminal
10+ )
7− where
11+where
8−
9−import Graphics.Vty.Prelude
10
12
13+import Graphics.Vty.Image (DisplayRegion)
14+import Graphics.Vty.Attributes.Color (ColorMode(ColorMode16))
11import Graphics.Vty.Output.Interface
15import Graphics.Vty.Output.Interface
12
16
13import Blaze.ByteString.Builder.Word (writeWord8)
17import Blaze.ByteString.Builder.Word (writeWord8)
@@ -20,18 +24,20 @@ import qualified Data.String.UTF8 as UTF8
20
24
21type MockData = IORef (UTF8.UTF8 BS.ByteString)
25type MockData = IORef (UTF8.UTF8 BS.ByteString)
22
26
23−-- | The mock display terminal produces a string representation of the requested picture. There is
27+-- | The mock display terminal produces a string representation of
28+-- the requested picture. There is *not* an isomorphism between the
24−-- *not* an isomorphism between the string representation and the picture. The string
29+-- string representation and the picture. The string representation is
25−-- representation is a simplification of the picture that is only useful in debugging VTY without
30+-- a simplification of the picture that is only useful in debugging VTY
26−-- considering terminal specific issues.
31+-- without considering terminal specific issues.
27--
32--
28−-- The mock implementation is useful in manually determining if the sequence of terminal operations
33+-- The mock implementation is useful in manually determining if the
34+-- sequence of terminal operations matches the expected sequence. The
29−-- matches the expected sequence. So requirement of the produced representation is simplicity in
35+-- requirement of the produced representation is simplicity in parsing
30−-- parsing the text representation and determining how the picture was mapped to terminal
36+-- the text representation and determining how the picture was mapped to
31−-- operations.
37+-- terminal operations.
32--
38--
33−-- The string representation is a sequence of identifiers where each identifier is the name of an
39+-- The string representation is a sequence of identifiers where each
34−-- operation in the algebra.
40+-- identifier is the name of an operation in the algebra.
35mockTerminal :: (Applicative m, MonadIO m) => DisplayRegion -> m (MockData, Output)
41mockTerminal :: (Applicative m, MonadIO m) => DisplayRegion -> m (MockData, Output)
36mockTerminal r = liftIO $ do
42mockTerminal r = liftIO $ do
37 outRef <- newIORef undefined
43 outRef <- newIORef undefined
@@ -43,30 +49,39 @@ mockTerminal r = liftIO $ do
43 , releaseDisplay = return ()
49 , releaseDisplay = return ()
44 , ringTerminalBell = return ()
50 , ringTerminalBell = return ()
45 , supportsBell = return False
51 , supportsBell = return False
52+ , supportsItalics = return False
53+ , supportsStrikethrough = return False
54+ , setDisplayBounds = const $ return ()
46 , displayBounds = return r
55 , displayBounds = return r
47 , outputByteBuffer = \bytes -> do
56 , outputByteBuffer = \bytes -> do
48 putStrLn $ "mock outputByteBuffer of " ++ show (BS.length bytes) ++ " bytes"
57 putStrLn $ "mock outputByteBuffer of " ++ show (BS.length bytes) ++ " bytes"
49 writeIORef outRef $ UTF8.fromRep bytes
58 writeIORef outRef $ UTF8.fromRep bytes
50− , contextColorCount = 16
51 , supportsCursorVisibility = True
59 , supportsCursorVisibility = True
52 , supportsMode = const False
60 , supportsMode = const False
53 , setMode = const $ const $ return ()
61 , setMode = const $ const $ return ()
54 , getModeStatus = const $ return False
62 , getModeStatus = const $ return False
55 , assumedStateRef = newAssumedStateRef
63 , assumedStateRef = newAssumedStateRef
64+ , outputColorMode = ColorMode16
56 , mkDisplayContext = \tActual rActual -> return $ DisplayContext
65 , mkDisplayContext = \tActual rActual -> return $ DisplayContext
57 { contextRegion = rActual
66 { contextRegion = rActual
58 , contextDevice = tActual
67 , contextDevice = tActual
59− -- A cursor move is always visualized as the single character 'M'
68+ -- A cursor move is always visualized as the single
69+ -- character 'M'
60 , writeMoveCursor = \_x _y -> writeWord8 $ toEnum $ fromEnum 'M'
70 , writeMoveCursor = \_x _y -> writeWord8 $ toEnum $ fromEnum 'M'
61− -- Show cursor is always visualized as the single character 'S'
71+ -- Show cursor is always visualized as the single
72+ -- character 'S'
62 , writeShowCursor = writeWord8 $ toEnum $ fromEnum 'S'
73 , writeShowCursor = writeWord8 $ toEnum $ fromEnum 'S'
63− -- Hide cursor is always visualized as the single character 'H'
74+ -- Hide cursor is always visualized as the single
75+ -- character 'H'
64 , writeHideCursor = writeWord8 $ toEnum $ fromEnum 'H'
76 , writeHideCursor = writeWord8 $ toEnum $ fromEnum 'H'
65− -- An attr change is always visualized as the single character 'A'
77+ -- An attr change is always visualized as the single
78+ -- character 'A'
66− , writeSetAttr = \_fattr _diffs _attr -> writeWord8 $ toEnum $ fromEnum 'A'
79+ , writeSetAttr = \_ _fattr _diffs _attr -> writeWord8 $ toEnum $ fromEnum 'A'
67− -- default attr is always visualized as the single character 'D'
80+ -- default attr is always visualized as the single
81+ -- character 'D'
68− , writeDefaultAttr = writeWord8 $ toEnum $ fromEnum 'D'
82+ , writeDefaultAttr = const $ writeWord8 $ toEnum $ fromEnum 'D'
69− -- row end is always visualized as the single character 'E'
83+ -- row end is always visualized as the single character
84+ -- 'E'
70 , writeRowEnd = writeWord8 $ toEnum $ fromEnum 'E'
85 , writeRowEnd = writeWord8 $ toEnum $ fromEnum 'E'
71 , inlineHack = return ()
86 , inlineHack = return ()
72 }
87 }
src/Graphics/Vty/Output/TerminfoBased.hs+290−161
@@ -1,42 +1,40 @@
1−{-# LANGUAGE RecordWildCards #-}
2−{-# LANGUAGE CPP #-}
3{-# LANGUAGE FlexibleInstances #-}
1{-# LANGUAGE FlexibleInstances #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE CPP #-}
4{-# OPTIONS_GHC -D_XOPEN_SOURCE=500 -fno-warn-warnings-deprecations #-}
4{-# OPTIONS_GHC -D_XOPEN_SOURCE=500 -fno-warn-warnings-deprecations #-}
5{-# CFILES gwinsz.c #-}
5{-# CFILES gwinsz.c #-}
6
6
7−#ifndef MIN_VERSION_base
8−#defined MIN_VERSION_base(x,y,z) 1
9−#endif
10−
11−-- | Terminfo based terminal handling.
7+-- | Terminfo-based terminal output driver.
12−--
13−-- The color handling assumes tektronix like. No HP support provided. If the terminal is not one I
14−-- have easy access to then color support is entirely based of the docs. Probably with some
15−-- assumptions mixed in.
16--
8--
17-- Copyright Corey O'Connor (coreyoconnor@gmail.com)
9-- Copyright Corey O'Connor (coreyoconnor@gmail.com)
18−module Graphics.Vty.Output.TerminfoBased ( reserveTerminal )
10+module Graphics.Vty.Output.TerminfoBased
11+ ( reserveTerminal
12+ , setWindowSize
13+ )
19− where
14+where
20−
15+
21−import Graphics.Vty.Prelude
16+import Control.Monad (when)
22−
17+import Data.Bits (shiftL, (.&.))
18+import qualified Data.ByteString as BS
23import Data.ByteString.Internal (toForeignPtr)
19import Data.ByteString.Internal (toForeignPtr)
24import Data.Terminfo.Parse
20import Data.Terminfo.Parse
25import Data.Terminfo.Eval
21import Data.Terminfo.Eval
26
22
27import Graphics.Vty.Attributes
23import Graphics.Vty.Attributes
24+import Graphics.Vty.Image (DisplayRegion)
28import Graphics.Vty.DisplayAttributes
25import Graphics.Vty.DisplayAttributes
29import Graphics.Vty.Output.Interface
26import Graphics.Vty.Output.Interface
30
27
31−import Blaze.ByteString.Builder (Write, writeToByteString)
28+import Blaze.ByteString.Builder (Write, writeToByteString, writeStorable, writeWord8)
32−
33−import Control.Monad.Trans
34
29
35−import Data.Bits ((.&.))
36import Data.IORef
30import Data.IORef
37import Data.Maybe (isJust, isNothing, fromJust)
31import Data.Maybe (isJust, isNothing, fromJust)
38import Data.Word
32import Data.Word
39
33
34+#if !MIN_VERSION_base(4,8,0)
35+import Data.Foldable (foldMap)
36+#endif
37+
40import Foreign.C.Types ( CInt(..), CLong(..) )
38import Foreign.C.Types ( CInt(..), CLong(..) )
41import Foreign.ForeignPtr (withForeignPtr)
39import Foreign.ForeignPtr (withForeignPtr)
42import Foreign.Ptr (Ptr, plusPtr)
40import Foreign.Ptr (Ptr, plusPtr)
@@ -45,18 +43,12 @@ import qualified System.Console.Terminfo as Terminfo
45import System.Posix.IO (fdWriteBuf)
43import System.Posix.IO (fdWriteBuf)
46import System.Posix.Types (Fd(..))
44import System.Posix.Types (Fd(..))
47
45
48−#if !(MIN_VERSION_base(4,8,0))
49−import Data.Foldable (foldMap)
50−import Data.Monoid
51−#endif
52−
53−data TerminfoCaps = TerminfoCaps
46+data TerminfoCaps = TerminfoCaps
54 { smcup :: Maybe CapExpression
47 { smcup :: Maybe CapExpression
55 , rmcup :: Maybe CapExpression
48 , rmcup :: Maybe CapExpression
56 , cup :: CapExpression
49 , cup :: CapExpression
57 , cnorm :: Maybe CapExpression
50 , cnorm :: Maybe CapExpression
58 , civis :: Maybe CapExpression
51 , civis :: Maybe CapExpression
59− , supportsNoColors :: Bool
60 , useAltColorMap :: Bool
52 , useAltColorMap :: Bool
61 , setForeColor :: CapExpression
53 , setForeColor :: CapExpression
62 , setBackColor :: CapExpression
54 , setBackColor :: CapExpression
@@ -71,15 +63,21 @@ data DisplayAttrCaps = DisplayAttrCaps
71 { setAttrStates :: Maybe CapExpression
63 { setAttrStates :: Maybe CapExpression
72 , enterStandout :: Maybe CapExpression
64 , enterStandout :: Maybe CapExpression
73 , exitStandout :: Maybe CapExpression
65 , exitStandout :: Maybe CapExpression
66+ , enterItalic :: Maybe CapExpression
67+ , exitItalic :: Maybe CapExpression
68+ , enterStrikethrough :: Maybe CapExpression
69+ , exitStrikethrough :: Maybe CapExpression
74 , enterUnderline :: Maybe CapExpression
70 , enterUnderline :: Maybe CapExpression
75 , exitUnderline :: Maybe CapExpression
71 , exitUnderline :: Maybe CapExpression
76 , enterReverseVideo :: Maybe CapExpression
72 , enterReverseVideo :: Maybe CapExpression
77 , enterDimMode :: Maybe CapExpression
73 , enterDimMode :: Maybe CapExpression
78 , enterBoldMode :: Maybe CapExpression
74 , enterBoldMode :: Maybe CapExpression
79 }
75 }
80−
76+
77+-- kinda like:
81−-- kinda like: https://code.google.com/p/vim/source/browse/src/fileio.c#10422
78+-- https://code.google.com/p/vim/source/browse/src/fileio.c#10422
82−-- fdWriteBuf will throw on error. Unless the error is EINTR. On EINTR the write will be retried.
79+-- fdWriteBuf will throw on error. Unless the error is EINTR. On EINTR
80+-- the write will be retried.
83fdWriteAll :: Fd -> Ptr Word8 -> Int -> Int -> IO Int
81fdWriteAll :: Fd -> Ptr Word8 -> Int -> Int -> IO Int
84fdWriteAll outFd ptr len count
82fdWriteAll outFd ptr len count
85 | len < 0 = fail "fdWriteAll: len is less than 0"
83 | len < 0 = fail "fdWriteAll: len is less than 0"
@@ -95,71 +93,95 @@ sendCapToTerminal :: Output -> CapExpression -> [CapParam] -> IO ()
95sendCapToTerminal t cap capParams = do
93sendCapToTerminal t cap capParams = do
96 outputByteBuffer t $ writeToByteString $ writeCapExpr cap capParams
94 outputByteBuffer t $ writeToByteString $ writeCapExpr cap capParams
97
95
96+-- | Constructs an output driver that uses terminfo for all control
98−{- | Uses terminfo for all control codes. While this should provide the most compatible terminal
97+-- codes. While this should provide the most compatible terminal,
99− - terminfo does not support some features that would increase efficiency and improve compatibility:
98+-- terminfo does not support some features that would increase
100− -
99+-- efficiency and improve compatibility:
100+--
101− - * determine the character encoding supported by the terminal. Should this be taken from the LANG
101+-- * determining the character encoding supported by the terminal.
102− - environment variable?
102+-- Should this be taken from the LANG environment variable?
103− -
103+--
104− - * Provide independent string capabilities for all display attributes.
104+-- * Providing independent string capabilities for all display
105− -
105+-- attributes.
106− - todo: Some display attributes like underline and bold have independent string capabilities that
107− - should be used instead of the generic "sgr" string capability.
108− -}
109−reserveTerminal :: ( Applicative m, MonadIO m ) => String -> Fd -> m Output
106+reserveTerminal :: String -> Fd -> ColorMode -> IO Output
110−reserveTerminal termName outFd = liftIO $ do
107+reserveTerminal termName outFd colorMode = do
111 ti <- Terminfo.setupTerm termName
108 ti <- Terminfo.setupTerm termName
112 -- assumes set foreground always implies set background exists.
109 -- assumes set foreground always implies set background exists.
113− -- if set foreground is not set then all color changing style attributes are filtered.
110+ -- if set foreground is not set then all color changing style
111+ -- attributes are filtered.
114 msetaf <- probeCap ti "setaf"
112 msetaf <- probeCap ti "setaf"
115 msetf <- probeCap ti "setf"
113 msetf <- probeCap ti "setf"
116− let (noColors, useAlt, setForeCap)
114+ let (useAlt, setForeCap)
117 = case msetaf of
115 = case msetaf of
118− Just setaf -> (False, False, setaf)
116+ Just setaf -> (False, setaf)
119 Nothing -> case msetf of
117 Nothing -> case msetf of
120− Just setf -> (False, True, setf)
118+ Just setf -> (True, setf)
121− Nothing -> (True, True, error $ "no fore color support for terminal " ++ termName)
119+ Nothing -> (True, error $ "no fore color support for terminal " ++ termName)
122 msetab <- probeCap ti "setab"
120 msetab <- probeCap ti "setab"
123 msetb <- probeCap ti "setb"
121 msetb <- probeCap ti "setb"
124− let set_back_cap
122+ let setBackCap
125 = case msetab of
123 = case msetab of
124+ Just setab -> setab
126 Nothing -> case msetb of
125 Nothing -> case msetb of
127 Just setb -> setb
126 Just setb -> setb
128 Nothing -> error $ "no back color support for terminal " ++ termName
127 Nothing -> error $ "no back color support for terminal " ++ termName
129− Just setab -> setab
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+
130 terminfoCaps <- pure TerminfoCaps
147 terminfoCaps <- pure TerminfoCaps
131 <*> probeCap ti "smcup"
148 <*> probeCap ti "smcup"
132 <*> probeCap ti "rmcup"
149 <*> probeCap ti "rmcup"
133 <*> requireCap ti "cup"
150 <*> requireCap ti "cup"
134 <*> probeCap ti "cnorm"
151 <*> probeCap ti "cnorm"
135 <*> probeCap ti "civis"
152 <*> probeCap ti "civis"
136− <*> pure noColors
137 <*> pure useAlt
153 <*> pure useAlt
138 <*> pure setForeCap
154 <*> pure setForeCap
139− <*> pure set_back_cap
155+ <*> pure setBackCap
140 <*> requireCap ti "sgr0"
156 <*> requireCap ti "sgr0"
141 <*> requireCap ti "clear"
157 <*> requireCap ti "clear"
142 <*> requireCap ti "el"
158 <*> requireCap ti "el"
143 <*> currentDisplayAttrCaps ti
159 <*> currentDisplayAttrCaps ti
144 <*> probeCap ti "bel"
160 <*> probeCap ti "bel"
145− newAssumedStateRef <- newIORef initialAssumedState
146 let t = Output
161 let t = Output
147 { terminalID = termName
162 { terminalID = termName
148− , releaseTerminal = liftIO $ do
163+ , releaseTerminal = do
149 sendCap setDefaultAttr []
164 sendCap setDefaultAttr []
150 maybeSendCap cnorm []
165 maybeSendCap cnorm []
151 , supportsBell = return $ isJust $ ringBellAudio terminfoCaps
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))
152− , ringTerminalBell = liftIO $ maybeSendCap ringBellAudio []
171+ , ringTerminalBell = maybeSendCap ringBellAudio []
153− , reserveDisplay = liftIO $ do
172+ , reserveDisplay = do
154− -- If there is no support for smcup: Clear the screen and then move the mouse to the
173+ -- If there is no support for smcup: Clear the screen
174+ -- and then move the mouse to the home position to
155− -- home position to approximate the behavior.
175+ -- approximate the behavior.
156 maybeSendCap smcup []
176 maybeSendCap smcup []
157 sendCap clearScreen []
177 sendCap clearScreen []
158− , releaseDisplay = liftIO $ do
178+ , releaseDisplay = do
159 maybeSendCap rmcup []
179 maybeSendCap rmcup []
160 maybeSendCap cnorm []
180 maybeSendCap cnorm []
181+ , setDisplayBounds = \(w, h) ->
182+ setWindowSize outFd (w, h)
161 , displayBounds = do
183 , displayBounds = do
162− rawSize <- liftIO $ getWindowSize outFd
184+ rawSize <- getWindowSize outFd
163 case rawSize of
185 case rawSize of
164 (w, h) | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show rawSize
186 (w, h) | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show rawSize
165 | otherwise -> return (w,h)
187 | otherwise -> return (w,h)
@@ -170,51 +192,48 @@ reserveTerminal termName outFd = liftIO $ do
170 when (toEnum len /= actualLen) $ fail $ "Graphics.Vty.Output: outputByteBuffer "
192 when (toEnum len /= actualLen) $ fail $ "Graphics.Vty.Output: outputByteBuffer "
171 ++ "length mismatch. " ++ show len ++ " /= " ++ show actualLen
193 ++ "length mismatch. " ++ show len ++ " /= " ++ show actualLen
172 ++ " Please report this bug to vty project."
194 ++ " Please report this bug to vty project."
173− , contextColorCount
174− = case supportsNoColors terminfoCaps of
175− False -> case Terminfo.getCapability ti (Terminfo.tiGetNum "colors" ) of
176− Nothing -> 8
177− Just v -> toEnum v
178− True -> 1
179 , supportsCursorVisibility = isJust $ civis terminfoCaps
195 , supportsCursorVisibility = isJust $ civis terminfoCaps
180− , supportsMode = const False
196+ , supportsMode = terminfoModeSupported
181− , setMode = const $ const $ return ()
197+ , setMode = terminfoSetMode
182− , getModeStatus = const $ return False
198+ , getModeStatus = terminfoModeStatus
183 , assumedStateRef = newAssumedStateRef
199 , assumedStateRef = newAssumedStateRef
200+ , outputColorMode = colorMode
184− -- I think fix would help assure tActual is the only reference. I was having issues
201+ -- I think fix would help assure tActual is the only
185− -- tho.
202+ -- reference. I was having issues tho.
186− , mkDisplayContext = \tActual -> liftIO . terminfoDisplayContext tActual terminfoCaps
203+ , mkDisplayContext = (`terminfoDisplayContext` terminfoCaps)
187 }
204 }
188 sendCap s = sendCapToTerminal t (s terminfoCaps)
205 sendCap s = sendCapToTerminal t (s terminfoCaps)
189 maybeSendCap s = when (isJust $ s terminfoCaps) . sendCap (fromJust . s)
206 maybeSendCap s = when (isJust $ s terminfoCaps) . sendCap (fromJust . s)
190 return t
207 return t
191
208
192−requireCap :: (Applicative m, MonadIO m) => Terminfo.Terminal -> String -> m CapExpression
209+requireCap :: Terminfo.Terminal -> String -> IO CapExpression
193−requireCap ti capName
210+requireCap ti capName
194 = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
211 = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
195 Nothing -> fail $ "Terminal does not define required capability \"" ++ capName ++ "\""
212 Nothing -> fail $ "Terminal does not define required capability \"" ++ capName ++ "\""
196 Just capStr -> parseCap capStr
213 Just capStr -> parseCap capStr
197
214
198−probeCap :: (Applicative m, MonadIO m) => Terminfo.Terminal -> String -> m (Maybe CapExpression)
215+probeCap :: Terminfo.Terminal -> String -> IO (Maybe CapExpression)
199−probeCap ti capName
216+probeCap ti capName
200 = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
217 = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of
201 Nothing -> return Nothing
218 Nothing -> return Nothing
202 Just capStr -> Just <$> parseCap capStr
219 Just capStr -> Just <$> parseCap capStr
203
220
204−parseCap :: (Applicative m, MonadIO m) => String -> m CapExpression
221+parseCap :: String -> IO CapExpression
205parseCap capStr = do
222parseCap capStr = do
206 case parseCapExpression capStr of
223 case parseCapExpression capStr of
207 Left e -> fail $ show e
224 Left e -> fail $ show e
208 Right cap -> return cap
225 Right cap -> return cap
209
226
210−currentDisplayAttrCaps :: ( Applicative m, MonadIO m )
227+currentDisplayAttrCaps :: Terminfo.Terminal -> IO DisplayAttrCaps
211− => Terminfo.Terminal
212− -> m DisplayAttrCaps
213−currentDisplayAttrCaps ti
228+currentDisplayAttrCaps ti
214− = pure DisplayAttrCaps
229+ = pure DisplayAttrCaps
215 <*> probeCap ti "sgr"
230 <*> probeCap ti "sgr"
216 <*> probeCap ti "smso"
231 <*> probeCap ti "smso"
217 <*> probeCap ti "rmso"
232 <*> probeCap ti "rmso"
233+ <*> probeCap ti "sitm"
234+ <*> probeCap ti "ritm"
235+ <*> probeCap ti "smxx"
236+ <*> probeCap ti "rmxx"
218 <*> probeCap ti "smul"
237 <*> probeCap ti "smul"
219 <*> probeCap ti "rmul"
238 <*> probeCap ti "rmul"
220 <*> probeCap ti "rev"
239 <*> probeCap ti "rev"
@@ -224,10 +243,17 @@ currentDisplayAttrCaps ti
224foreign import ccall "gwinsz.h vty_c_get_window_size" c_getWindowSize :: Fd -> IO CLong
243foreign import ccall "gwinsz.h vty_c_get_window_size" c_getWindowSize :: Fd -> IO CLong
225
244
226getWindowSize :: Fd -> IO (Int,Int)
245getWindowSize :: Fd -> IO (Int,Int)
227−getWindowSize fd = do
246+getWindowSize fd = do
228 (a,b) <- (`divMod` 65536) `fmap` c_getWindowSize fd
247 (a,b) <- (`divMod` 65536) `fmap` c_getWindowSize fd
229 return (fromIntegral b, fromIntegral a)
248 return (fromIntegral b, fromIntegral a)
230
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+
231terminfoDisplayContext :: Output -> TerminfoCaps -> DisplayRegion -> IO DisplayContext
257terminfoDisplayContext :: Output -> TerminfoCaps -> DisplayRegion -> IO DisplayContext
232terminfoDisplayContext tActual terminfoCaps r = return dc
258terminfoDisplayContext tActual terminfoCaps r = return dc
233 where dc = DisplayContext
259 where dc = DisplayContext
@@ -241,123 +267,204 @@ terminfoDisplayContext tActual terminfoCaps r = return dc
241 Nothing -> error "this terminal does not support hide cursor"
267 Nothing -> error "this terminal does not support hide cursor"
242 Just c -> writeCapExpr c []
268 Just c -> writeCapExpr c []
243 , writeSetAttr = terminfoWriteSetAttr dc terminfoCaps
269 , writeSetAttr = terminfoWriteSetAttr dc terminfoCaps
270+ , writeDefaultAttr = \urlsEnabled ->
244− , writeDefaultAttr = writeCapExpr (setDefaultAttr terminfoCaps) []
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+ )
245 , writeRowEnd = writeCapExpr (clearEol terminfoCaps) []
277 , writeRowEnd = writeCapExpr (clearEol terminfoCaps) []
246 , inlineHack = return ()
278 , inlineHack = return ()
247 }
279 }
248
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+
249-- | Portably setting the display attributes is a giant pain in the ass.
295-- | Portably setting the display attributes is a giant pain in the ass.
250--
296--
251−-- If the terminal supports the sgr capability (which sets the on/off state of each style
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
252−-- directly ; and, for no good reason, resets the colors to the default) this procedure is used:
299+-- colors to the default) this procedure is used:
253--
300--
254-- 0. set the style attributes. This resets the fore and back color.
301-- 0. set the style attributes. This resets the fore and back color.
255--
302--
256-- 1, If a foreground color is to be set then set the foreground color
303-- 1, If a foreground color is to be set then set the foreground color
257--
304--
258-- 2. likewise with the background color
305-- 2. likewise with the background color
259−--
260−-- If the terminal does not support the sgr cap then:
261−-- if there is a change from an applied color to the default (in either the fore or back color)
262−-- then:
263--
306--
264−-- 0. reset all display attributes (sgr0)
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:
265--
310--
266−-- 1. enter required style modes
311+-- 0. reset all display attributes (sgr0)
267--
312--
268−-- 2. set the fore color if required
313+-- 1. enter required style modes
269--
314--
270−-- 3. set the back color if required
315+-- 2. set the fore color if required
271--
316--
317+-- 3. set the back color if required
318+--
272−-- Entering the required style modes could require a reset of the display attributes. If this is
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
273−-- the case then the back and fore colors always need to be set if not default.
321+-- always need to be set if not default.
274--
322--
275-- This equation implements the above logic.
323-- This equation implements the above logic.
276--
324--
277−-- \todo This assumes the removal of color changes in the display attributes is done as expected
325+-- Note that this assumes the removal of color changes in the
326+-- display attributes is done as expected with noColors == True. See
278−-- with noColors == True. See `limitAttrForDisplay`
327+-- `limitAttrForDisplay`.
279--
328--
280−-- \todo This assumes that fewer state changes, followed by fewer bytes, is what to optimize. I
329+-- Note that this optimizes for fewer state changes followed by fewer
281−-- haven't measured this or even examined terminal implementations. *shrug*
330+-- bytes.
282−terminfoWriteSetAttr :: DisplayContext -> TerminfoCaps -> FixedAttr -> Attr -> DisplayAttrDiff -> Write
331+terminfoWriteSetAttr :: DisplayContext -> TerminfoCaps -> Bool -> FixedAttr -> Attr -> DisplayAttrDiff -> Write
283−terminfoWriteSetAttr dc terminfoCaps prevAttr reqAttr diffs = do
332+terminfoWriteSetAttr dc terminfoCaps urlsEnabled prevAttr reqAttr diffs =
284− case (foreColorDiff diffs == ColorToDefault) || (backColorDiff diffs == ColorToDefault) of
333+ urlAttrs urlsEnabled `mappend` case (foreColorDiff diffs == ColorToDefault) || (backColorDiff diffs == ColorToDefault) of
285− -- The only way to reset either color, portably, to the default is to use either the set
334+ -- The only way to reset either color, portably, to the default
286− -- state capability or the set default capability.
335+ -- is to use either the set state capability or the set default
336+ -- capability.
287− True -> do
337+ True -> do
288 case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps)
338 case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps)
289− (fixedStyle attr )
339+ (fixedStyle attr)
290 (styleToApplySeq $ fixedStyle attr) of
340 (styleToApplySeq $ fixedStyle attr) of
291 -- only way to reset a color to the defaults
341 -- only way to reset a color to the defaults
292− EnterExitSeq caps -> writeDefaultAttr dc
342+ EnterExitSeq caps -> writeDefaultAttr dc urlsEnabled
293− `mappend`
343+ `mappend`
294 foldMap (\cap -> writeCapExpr cap []) caps
344 foldMap (\cap -> writeCapExpr cap []) caps
295 `mappend`
345 `mappend`
296 setColors
346 setColors
297 -- implicitly resets the colors to the defaults
347 -- implicitly resets the colors to the defaults
298− SetState state -> writeCapExpr (fromJust $ setAttrStates
348+ SetState state -> writeCapExpr (fromJust $ setAttrStates
299− $ displayAttrCaps
349+ $ displayAttrCaps terminfoCaps
300− $ terminfoCaps
301 )
350 )
302 (sgrArgsForState state)
351 (sgrArgsForState state)
303− `mappend`
352+ `mappend` setItalics
353+ `mappend` setStrikethrough
304− setColors
354+ `mappend` setColors
305− -- Otherwise the display colors are not changing or changing between two non-default
355+ -- Otherwise the display colors are not changing or changing
306− -- points.
356+ -- between two non-default points.
307 False -> do
357 False -> do
308− -- Still, it could be the case that the change in display attributes requires the
358+ -- Still, it could be the case that the change in display
359+ -- attributes requires the colors to be reset because the
309− -- colors to be reset because the required capability was not available.
360+ -- required capability was not available.
310 case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps)
361 case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps)
311 (fixedStyle attr)
362 (fixedStyle attr)
312 (styleDiffs diffs) of
363 (styleDiffs diffs) of
313− -- Really, if terminals were re-implemented with modern concepts instead of bowing
364+ -- Really, if terminals were re-implemented with modern
365+ -- concepts instead of bowing down to 40 yr old dumb
314− -- down to 40 yr old dumb terminal requirements this would be the only case ever
366+ -- terminal requirements this would be the only case
315− -- reached! Changes the style and color states according to the differences with
367+ -- ever reached! Changes the style and color states
368+ -- according to the differences with the currently
316− -- the currently applied states.
369+ -- applied states.
317 EnterExitSeq caps -> foldMap (\cap -> writeCapExpr cap []) caps
370 EnterExitSeq caps -> foldMap (\cap -> writeCapExpr cap []) caps
318 `mappend`
371 `mappend`
319− writeColorDiff setForeColor (foreColorDiff diffs)
372+ writeColorDiff Foreground (foreColorDiff diffs)
320 `mappend`
373 `mappend`
321− writeColorDiff setBackColor (backColorDiff diffs)
374+ writeColorDiff Background (backColorDiff diffs)
322 -- implicitly resets the colors to the defaults
375 -- implicitly resets the colors to the defaults
323− SetState state -> writeCapExpr (fromJust $ setAttrStates
376+ SetState state -> writeCapExpr (fromJust $ setAttrStates
324 $ displayAttrCaps terminfoCaps
377 $ displayAttrCaps terminfoCaps
325 )
378 )
326 (sgrArgsForState state)
379 (sgrArgsForState state)
380+ `mappend` setItalics
381+ `mappend` setStrikethrough
327 `mappend` setColors
382 `mappend` setColors
328− where
383+ where
384+ urlAttrs True = writeURLEscapes (urlDiff diffs)
385+ urlAttrs False = mempty
329 colorMap = case useAltColorMap terminfoCaps of
386 colorMap = case useAltColorMap terminfoCaps of
330 False -> ansiColorIndex
387 False -> ansiColorIndex
331 True -> altColorIndex
388 True -> altColorIndex
332 attr = fixDisplayAttr prevAttr reqAttr
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
333 setColors =
404 setColors =
334 (case fixedForeColor attr of
405 (case fixedForeColor attr of
335− Just c -> writeCapExpr (setForeColor terminfoCaps)
406+ Just c -> writeColor Foreground c
336− [toEnum $ colorMap c]
337 Nothing -> mempty)
407 Nothing -> mempty)
338 `mappend`
408 `mappend`
339 (case fixedBackColor attr of
409 (case fixedBackColor attr of
340− Just c -> writeCapExpr (setBackColor terminfoCaps)
410+ Just c -> writeColor Background c
341− [toEnum $ colorMap c]
342 Nothing -> mempty)
411 Nothing -> mempty)
343− writeColorDiff _f NoColorChange
412+ writeColorDiff _side NoColorChange
344 = mempty
413 = mempty
345− writeColorDiff _f ColorToDefault
414+ writeColorDiff _side ColorToDefault
346 = error "ColorToDefault is not a possible case for applyColorDiffs"
415 = error "ColorToDefault is not a possible case for applyColorDiffs"
347− writeColorDiff f (SetColor c)
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 =
348− = writeCapExpr (f terminfoCaps) [toEnum $ colorMap c]
426+ writeCapExpr (setSideColor side terminfoCaps) [toEnum $ colorMap c]
349−
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+
350−-- | The color table used by a terminal is a 16 color set followed by a 240 color set that might not
453+-- | The color table used by a terminal is a 16 color set followed by a
351−-- be supported by the terminal.
454+-- 240 color set that might not be supported by the terminal.
352--
455--
353−-- This takes a Color which clearly identifies which pallete to use and computes the index
456+-- This takes a Color which clearly identifies which palette to use and
354−-- into the full 256 color pallete.
457+-- computes the index into the full 256 color palette.
355ansiColorIndex :: Color -> Int
458ansiColorIndex :: Color -> Int
356ansiColorIndex (ISOColor v) = fromEnum v
459ansiColorIndex (ISOColor v) = fromEnum v
357ansiColorIndex (Color240 v) = 16 + fromEnum v
460ansiColorIndex (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+ ]
358
465
359-- | For terminals without setaf/setab
466-- | For terminals without setaf/setab
360−--
467+--
361-- See table in `man terminfo`
468-- See table in `man terminfo`
362-- Will error if not in table.
469-- Will error if not in table.
363altColorIndex :: Color -> Int
470altColorIndex :: Color -> Int
@@ -371,21 +478,28 @@ altColorIndex (ISOColor 6) = 3
371altColorIndex (ISOColor 7) = 7
478altColorIndex (ISOColor 7) = 7
372altColorIndex (ISOColor v) = fromEnum v
479altColorIndex (ISOColor v) = fromEnum v
373altColorIndex (Color240 v) = 16 + fromEnum v
480altColorIndex (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+ ]
374
485
375−{- | The sequence of terminfo caps to apply a given style are determined according to these rules.
486+{- | The sequence of terminfo caps to apply a given style are determined
376− -
487+ - according to these rules.
377− - 1. The assumption is that it's preferable to use the simpler enter/exit mode capabilities than
378− - the full set display attribute state capability.
379 -
488 -
380− - 2. If a mode is supposed to be removed but there is not an exit capability defined then the
489+ - 1. The assumption is that it's preferable to use the simpler
381− - display attributes are reset to defaults then the display attribute state is set.
490+ - enter/exit mode capabilities than the full set display attribute
491+ - state capability.
382 -
492 -
383− - 3. If a mode is supposed to be applied but there is not an enter capability defined then then
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
384− - display attribute state is set if possible. Otherwise the mode is not applied.
495+ - then the display attribute state is set.
385 -
496 -
497+ - 3. If a mode is supposed to be applied but there is not an enter
386− - 4. If the display attribute state is being set then just update the arguments to that for any
498+ - capability defined then then display attribute state is set if
387− - apply/remove.
499+ - possible. Otherwise the mode is not applied.
388 -
500 -
501+ - 4. If the display attribute state is being set then just update the
502+ - arguments to that for any apply/remove.
389 -}
503 -}
390data DisplayAttrSeq
504data DisplayAttrSeq
391 = EnterExitSeq [CapExpression]
505 = EnterExitSeq [CapExpression]
@@ -394,6 +508,8 @@ data DisplayAttrSeq
394data DisplayAttrState = DisplayAttrState
508data DisplayAttrState = DisplayAttrState
395 { applyStandout :: Bool
509 { applyStandout :: Bool
396 , applyUnderline :: Bool
510 , applyUnderline :: Bool
511+ , applyItalic :: Bool
512+ , applyStrikethrough :: Bool
397 , applyReverseVideo :: Bool
513 , applyReverseVideo :: Bool
398 , applyBlink :: Bool
514 , applyBlink :: Bool
399 , applyDim :: Bool
515 , applyDim :: Bool
@@ -415,19 +531,25 @@ sgrArgsForState attrState = map (\b -> if b then 1 else 0)
415
531
416reqDisplayCapSeqFor :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq
532reqDisplayCapSeqFor :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq
417reqDisplayCapSeqFor caps s diffs
533reqDisplayCapSeqFor caps s diffs
418− -- if the state transition implied by any diff cannot be supported with an enter/exit mode cap
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
419− -- then either the state needs to be set or the attribute change ignored.
536+ -- or the attribute change ignored.
420 = case (any noEnterExitCap diffs, isJust $ setAttrStates caps) of
537 = case (any noEnterExitCap diffs, isJust $ setAttrStates caps) of
421 -- If all the diffs have an enter-exit cap then just use those
538 -- If all the diffs have an enter-exit cap then just use those
422 ( False, _ ) -> EnterExitSeq $ map enterExitCap diffs
539 ( False, _ ) -> EnterExitSeq $ map enterExitCap diffs
423− -- If not all the diffs have an enter-exit cap and there is no set state cap then filter out
540+ -- If not all the diffs have an enter-exit cap and there is no
424− -- all unsupported diffs and just apply the rest
541+ -- set state cap then filter out all unsupported diffs and just
542+ -- apply the rest
425− ( True, False ) -> EnterExitSeq $ map enterExitCap
543+ ( True, False ) -> EnterExitSeq $ map enterExitCap
426 $ filter (not . noEnterExitCap) diffs
544 $ filter (not . noEnterExitCap) diffs
427− -- if not all the diffs have an enter-exit can and there is a set state cap then just use
545+ -- if not all the diffs have an enter-exit can and there is a
428− -- the set state cap.
546+ -- set state cap then just use the set state cap.
429 ( True, True ) -> SetState $ stateForStyle s
547 ( True, True ) -> SetState $ stateForStyle s
430 where
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
431 noEnterExitCap ApplyStandout = isNothing $ enterStandout caps
553 noEnterExitCap ApplyStandout = isNothing $ enterStandout caps
432 noEnterExitCap RemoveStandout = isNothing $ exitStandout caps
554 noEnterExitCap RemoveStandout = isNothing $ exitStandout caps
433 noEnterExitCap ApplyUnderline = isNothing $ enterUnderline caps
555 noEnterExitCap ApplyUnderline = isNothing $ enterUnderline caps
@@ -440,6 +562,10 @@ reqDisplayCapSeqFor caps s diffs
440 noEnterExitCap RemoveDim = True
562 noEnterExitCap RemoveDim = True
441 noEnterExitCap ApplyBold = isNothing $ enterBoldMode caps
563 noEnterExitCap ApplyBold = isNothing $ enterBoldMode caps
442 noEnterExitCap RemoveBold = True
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
443 enterExitCap ApplyStandout = fromJust $ enterStandout caps
569 enterExitCap ApplyStandout = fromJust $ enterStandout caps
444 enterExitCap RemoveStandout = fromJust $ exitStandout caps
570 enterExitCap RemoveStandout = fromJust $ exitStandout caps
445 enterExitCap ApplyUnderline = fromJust $ enterUnderline caps
571 enterExitCap ApplyUnderline = fromJust $ enterUnderline caps
@@ -453,6 +579,8 @@ stateForStyle :: Style -> DisplayAttrState
453stateForStyle s = DisplayAttrState
579stateForStyle s = DisplayAttrState
454 { applyStandout = isStyleSet standout
580 { applyStandout = isStyleSet standout
455 , applyUnderline = isStyleSet underline
581 , applyUnderline = isStyleSet underline
582+ , applyItalic = isStyleSet italic
583+ , applyStrikethrough = isStyleSet strikethrough
456 , applyReverseVideo = isStyleSet reverseVideo
584 , applyReverseVideo = isStyleSet reverseVideo
457 , applyBlink = isStyleSet blink
585 , applyBlink = isStyleSet blink
458 , applyDim = isStyleSet dim
586 , applyDim = isStyleSet dim
@@ -464,14 +592,15 @@ styleToApplySeq :: Style -> [StyleStateChange]
464styleToApplySeq s = concat
592styleToApplySeq s = concat
465 [ applyIfRequired ApplyStandout standout
593 [ applyIfRequired ApplyStandout standout
466 , applyIfRequired ApplyUnderline underline
594 , applyIfRequired ApplyUnderline underline
595+ , applyIfRequired ApplyItalic italic
596+ , applyIfRequired ApplyStrikethrough strikethrough
467 , applyIfRequired ApplyReverseVideo reverseVideo
597 , applyIfRequired ApplyReverseVideo reverseVideo
468 , applyIfRequired ApplyBlink blink
598 , applyIfRequired ApplyBlink blink
469 , applyIfRequired ApplyDim dim
599 , applyIfRequired ApplyDim dim
470− , applyIfRequired ApplyBlink bold
600+ , applyIfRequired ApplyBold bold
471 ]
601 ]
472− where
602+ where
473− applyIfRequired op flag
603+ applyIfRequired op flag
474 = if 0 == (flag .&. s)
604 = if 0 == (flag .&. s)
475 then []
605 then []
476 else [op]
606 else [op]
477−
src/Graphics/Vty/Output/XTermColor.hs+53−37
@@ -1,56 +1,72 @@
1−{-# LANGUAGE CPP #-}
1+{-# Language CPP #-}
2−
3−#ifndef MIN_VERSION_base
4−#defined MIN_VERSION_base(x,y,z) 1
5−#endif
6−
7-- Copyright 2009-2010 Corey O'Connor
2-- Copyright 2009-2010 Corey O'Connor
3+-- | Xterm output driver. This uses the Terminfo driver with some
4+-- extensions for Xterm.
8−module Graphics.Vty.Output.XTermColor ( reserveTerminal )
5+module Graphics.Vty.Output.XTermColor
6+ ( reserveTerminal
7+ )
9− where
8+where
10
9
11import Graphics.Vty.Output.Interface
10import Graphics.Vty.Output.Interface
12import Graphics.Vty.Input.Mouse
11import Graphics.Vty.Input.Mouse
12+import Graphics.Vty.Input.Focus
13+import Graphics.Vty.Attributes.Color (ColorMode)
13import qualified Graphics.Vty.Output.TerminfoBased as TerminfoBased
14import qualified Graphics.Vty.Output.TerminfoBased as TerminfoBased
14
15
15import Blaze.ByteString.Builder (writeToByteString)
16import Blaze.ByteString.Builder (writeToByteString)
16import Blaze.ByteString.Builder.Word (writeWord8)
17import Blaze.ByteString.Builder.Word (writeWord8)
17
18
19+import qualified Data.ByteString.Char8 as BS8
20+import Data.ByteString.Char8 (ByteString)
21+import Foreign.Ptr (castPtr)
22+
18import Control.Monad (void, when)
23import Control.Monad (void, when)
19import Control.Monad.Trans
24import Control.Monad.Trans
25+import Data.Char (toLower)
20import Data.IORef
26import Data.IORef
21
27
22−import System.Posix.IO (fdWrite)
28+import System.Posix.IO (fdWriteBuf)
23−import System.Posix.Types (Fd)
29+import System.Posix.Types (ByteCount, Fd)
24import System.Posix.Env (getEnv)
30import System.Posix.Env (getEnv)
25
31
26−#if !(MIN_VERSION_base(4,8,0))
27−import Control.Applicative
28−import Data.Foldable (foldMap)
29−#endif
30−
31import Data.List (isInfixOf)
32import Data.List (isInfixOf)
32import Data.Maybe (catMaybes)
33import Data.Maybe (catMaybes)
34+
35+#if !MIN_VERSION_base(4,11,0)
33import Data.Monoid ((<>))
36import 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)
34
44
35−-- | Initialize the display to UTF-8.
45+-- | Construct an Xterm output driver. Initialize the display to UTF-8.
36−reserveTerminal :: ( Applicative m, MonadIO m ) => String -> Fd -> m Output
46+reserveTerminal :: ( Applicative m, MonadIO m ) => String -> Fd -> ColorMode -> m Output
37−reserveTerminal variant outFd = liftIO $ do
47+reserveTerminal variant outFd colorMode = liftIO $ do
38 let flushedPut = void . fdWrite outFd
48 let flushedPut = void . fdWrite outFd
39− -- If the terminal variant is xterm-color use xterm instead since, more often than not,
49+ -- If the terminal variant is xterm-color use xterm instead since,
40− -- xterm-color is broken.
50+ -- more often than not, xterm-color is broken.
41 let variant' = if variant == "xterm-color" then "xterm" else variant
51 let variant' = if variant == "xterm-color" then "xterm" else variant
42
52
43 utf8a <- utf8Active
53 utf8a <- utf8Active
44 when (not utf8a) $ flushedPut setUtf8CharSet
54 when (not utf8a) $ flushedPut setUtf8CharSet
45− t <- TerminfoBased.reserveTerminal variant' outFd
55+ t <- TerminfoBased.reserveTerminal variant' outFd colorMode
46
56
47 mouseModeStatus <- newIORef False
57 mouseModeStatus <- newIORef False
58+ focusModeStatus <- newIORef False
48 pasteModeStatus <- newIORef False
59 pasteModeStatus <- newIORef False
49
60
50 let xtermSetMode t' m newStatus = do
61 let xtermSetMode t' m newStatus = do
51 curStatus <- getModeStatus t' m
62 curStatus <- getModeStatus t' m
52 when (newStatus /= curStatus) $
63 when (newStatus /= curStatus) $
53 case m of
64 case m of
65+ Focus -> liftIO $ do
66+ case newStatus of
67+ True -> flushedPut requestFocusEvents
68+ False -> flushedPut disableFocusEvents
69+ writeIORef focusModeStatus newStatus
54 Mouse -> liftIO $ do
70 Mouse -> liftIO $ do
55 case newStatus of
71 case newStatus of
56 True -> flushedPut requestMouseEvents
72 True -> flushedPut requestMouseEvents
@@ -61,9 +77,12 @@ reserveTerminal variant outFd = liftIO $ do
61 True -> flushedPut enableBracketedPastes
77 True -> flushedPut enableBracketedPastes
62 False -> flushedPut disableBracketedPastes
78 False -> flushedPut disableBracketedPastes
63 writeIORef pasteModeStatus newStatus
79 writeIORef pasteModeStatus newStatus
80+ Hyperlink -> setMode t Hyperlink newStatus
64
81
65 xtermGetMode Mouse = liftIO $ readIORef mouseModeStatus
82 xtermGetMode Mouse = liftIO $ readIORef mouseModeStatus
83+ xtermGetMode Focus = liftIO $ readIORef focusModeStatus
66 xtermGetMode BracketedPaste = liftIO $ readIORef pasteModeStatus
84 xtermGetMode BracketedPaste = liftIO $ readIORef pasteModeStatus
85+ xtermGetMode Hyperlink = getModeStatus t Hyperlink
67
86
68 let t' = t
87 let t' = t
69 { terminalID = terminalID t ++ " (xterm-color)"
88 { terminalID = terminalID t ++ " (xterm-color)"
@@ -71,6 +90,7 @@ reserveTerminal variant outFd = liftIO $ do
71 when (not utf8a) $ liftIO $ flushedPut setDefaultCharSet
90 when (not utf8a) $ liftIO $ flushedPut setDefaultCharSet
72 setMode t' BracketedPaste False
91 setMode t' BracketedPaste False
73 setMode t' Mouse False
92 setMode t' Mouse False
93+ setMode t' Focus False
74 releaseTerminal t
94 releaseTerminal t
75 , mkDisplayContext = \tActual r -> do
95 , mkDisplayContext = \tActual r -> do
76 dc <- mkDisplayContext t tActual r
96 dc <- mkDisplayContext t tActual r
@@ -84,32 +104,28 @@ reserveTerminal variant outFd = liftIO $ do
84utf8Active :: IO Bool
104utf8Active :: IO Bool
85utf8Active = do
105utf8Active = do
86 let vars = ["LC_ALL", "LANG", "LC_CTYPE"]
106 let vars = ["LC_ALL", "LANG", "LC_CTYPE"]
87− results <- catMaybes <$> mapM getEnv vars
107+ results <- map (toLower <$>) . catMaybes <$> mapM getEnv vars
88− let matches = filter ("UTF8" `isInfixOf`) results <>
108+ let matches = filter ("utf8" `isInfixOf`) results <>
89− filter ("UTF-8" `isInfixOf`) results
109+ filter ("utf-8" `isInfixOf`) results
90 return $ not $ null matches
110 return $ not $ null matches
91
111
92-- | Enable bracketed paste mode:
112-- | Enable bracketed paste mode:
93-- http://cirw.in/blog/bracketed-paste
113-- http://cirw.in/blog/bracketed-paste
94−enableBracketedPastes :: String
114+enableBracketedPastes :: ByteString
95−enableBracketedPastes = "\ESC[?2004h"
115+enableBracketedPastes = BS8.pack "\ESC[?2004h"
96
116
97-- | Disable bracketed paste mode:
117-- | Disable bracketed paste mode:
98−disableBracketedPastes :: String
118+disableBracketedPastes :: ByteString
99−disableBracketedPastes = "\ESC[?2004l"
119+disableBracketedPastes = BS8.pack "\ESC[?2004l"
100
120
101-- | These sequences set xterm based terminals to UTF-8 output.
121-- | These sequences set xterm based terminals to UTF-8 output.
102--
122--
103−-- \todo I don't know of a terminfo cap that is equivalent to this.
123+-- There is no known terminfo capability equivalent to this.
104−setUtf8CharSet, setDefaultCharSet :: String
124+setUtf8CharSet, setDefaultCharSet :: ByteString
105−setUtf8CharSet = "\ESC%G"
125+setUtf8CharSet = BS8.pack "\ESC%G"
106−setDefaultCharSet = "\ESC%@"
126+setDefaultCharSet = BS8.pack "\ESC%@"
107−
127+
108−-- | I think xterm is broken: Reseting the background color as the first bytes serialized on a
109−-- new line does not effect the background color xterm uses to clear the line. Which is used
110−-- *after* the next newline.
111xtermInlineHack :: Output -> IO ()
128xtermInlineHack :: Output -> IO ()
112xtermInlineHack t = do
129xtermInlineHack t = do
113 let writeReset = foldMap (writeWord8.toEnum.fromEnum) "\ESC[K"
130 let writeReset = foldMap (writeWord8.toEnum.fromEnum) "\ESC[K"
114 outputByteBuffer t $ writeToByteString writeReset
131 outputByteBuffer t $ writeToByteString writeReset
115−
src/Graphics/Vty/Picture.hs+65−42
@@ -1,25 +1,36 @@
1−{-# LANGUAGE BangPatterns #-}
1+-- A 'Picture' is a background paired with a set of 'Image' layers. The
2−-- | The 'Picture' data structure is representative of the final terminal view.
2+-- 'Picture' data structure is representative of the final terminal
3−--
3+-- view.
4−-- A 'Picture' is a background paired with a layer of 'Image's.
5−module Graphics.Vty.Picture ( module Graphics.Vty.Picture
4+module Graphics.Vty.Picture
6− , module Graphics.Vty.Image
5+ ( Picture(..)
6+ , Cursor(..)
7+ , Background(..)
8+ , emptyPicture
9+ , addToTop
10+ , addToBottom
11+ , picForImage
12+ , picForLayers
13+ , picImage
7− )
14+ )
8− where
15+where
9
16
10import Graphics.Vty.Image
17import Graphics.Vty.Image
18+import Graphics.Vty.Attributes
11
19
12import Control.DeepSeq
20import Control.DeepSeq
13
21
14−-- | The type of images to be displayed using 'update'.
22+-- | A Vty picture.
15--
23--
16−-- Can be constructed directly or using `picForImage`. Which provides an initial instance with
24+-- These can be constructed directly or using `picForImage`.
17−-- reasonable defaults for picCursor and picBackground.
18data Picture = Picture
25data Picture = Picture
19 { picCursor :: Cursor
26 { picCursor :: Cursor
27+ -- ^ The picture's cursor.
20 , picLayers :: [Image]
28 , picLayers :: [Image]
29+ -- ^ The picture's image layers (top-most first).
21 , picBackground :: Background
30 , picBackground :: Background
22− }
31+ -- ^ The picture's background to be displayed in locations with no
32+ -- Image data.
33+ } deriving Eq
23
34
24instance Show Picture where
35instance Show Picture where
25 show (Picture _ layers _ ) = "Picture ?? " ++ show layers ++ " ??"
36 show (Picture _ layers _ ) = "Picture ?? " ++ show layers ++ " ??"
@@ -27,66 +38,73 @@ instance Show Picture where
27instance NFData Picture where
38instance NFData Picture where
28 rnf (Picture c l b) = c `deepseq` l `deepseq` b `deepseq` ()
39 rnf (Picture c l b) = c `deepseq` l `deepseq` b `deepseq` ()
29
40
30−-- | a picture with no cursor, background or image layers
41+-- | A picture with no cursor, background or image layers.
31emptyPicture :: Picture
42emptyPicture :: Picture
32emptyPicture = Picture NoCursor [] ClearBackground
43emptyPicture = Picture NoCursor [] ClearBackground
33
44
34−-- | The given 'Image' is added as the top layer of the 'Picture'
45+-- | Add an 'Image' as the top-most layer of a 'Picture'.
35addToTop :: Picture -> Image -> Picture
46addToTop :: Picture -> Image -> Picture
36addToTop p i = p {picLayers = i : picLayers p}
47addToTop p i = p {picLayers = i : picLayers p}
37
48
38−-- | The given 'Image' is added as the bottom layer of the 'Picture'
49+-- | Add an 'Image' as the bottom-most layer of a 'Picture'.
39addToBottom :: Picture -> Image -> Picture
50addToBottom :: Picture -> Image -> Picture
40addToBottom p i = p {picLayers = picLayers p ++ [i]}
51addToBottom p i = p {picLayers = picLayers p ++ [i]}
41
52
42−-- | Create a picture for display for the given image. The picture will not have a displayed cursor
53+-- | Create a picture from the given image. The picture will not have a
43−-- and no background pattern (ClearBackground) will be used.
54+-- displayed cursor and no background pattern (ClearBackground) will be
55+-- used.
44picForImage :: Image -> Picture
56picForImage :: Image -> Picture
45−picForImage i = Picture
57+picForImage i = Picture
46 { picCursor = NoCursor
58 { picCursor = NoCursor
47 , picLayers = [i]
59 , picLayers = [i]
48 , picBackground = ClearBackground
60 , picBackground = ClearBackground
49 }
61 }
50
62
51−-- | Create a picture for display with the given layers. Ordered top to bottom.
63+-- | Create a picture with the given layers, top-most first.
52--
64--
53−-- The picture will not have a displayed cursor and no background apttern (ClearBackgroun) will be
65+-- The picture will not have a displayed cursor and no background
54−-- used.
66+-- pattern (ClearBackgroun) will be used.
55−--
56−-- The first 'Image' is the top layer.
57picForLayers :: [Image] -> Picture
67picForLayers :: [Image] -> Picture
58−picForLayers is = Picture
68+picForLayers is = Picture
59 { picCursor = NoCursor
69 { picCursor = NoCursor
60 , picLayers = is
70 , picLayers = is
61 , picBackground = ClearBackground
71 , picBackground = ClearBackground
62 }
72 }
63
73
64−-- | A picture can be configured either to not show the cursor or show the cursor at the specified
74+-- | A picture can be configured to hide the cursor or to show the
65−-- character position.
75+-- cursor at the specified character position.
66--
76--
67−-- There is not a 1 to 1 map from character positions to a row and column on the screen due to
77+-- There is not a 1:1 map from character positions to a row and column
68−-- characters that take more than 1 column.
78+-- on the screen due to characters that take more than 1 column.
69−--
70−-- todo: The Cursor can be given a (character,row) offset outside of the visible bounds of the
71−-- output region. In this case the cursor will not be shown.
72data Cursor =
79data Cursor =
73− -- | Hide the cursor
80+ -- | Hide the cursor
74− NoCursor
81+ NoCursor
82+ -- | Set the terminal's cursor position without displaying a cursor
83+ -- character. This is important for accessibility with screen
84+ -- readers where a cursor position needs to be reported but we may
85+ -- not want to show a block cursor in that location for cosmetic
86+ -- reasons. The boolean argument indicates whether the positioning
87+ -- should be absolute as with 'AbsoluteCursor' ('True') or logical
88+ -- as with 'Cursor' ('False').
89+ | PositionOnly !Bool !Int !Int
75− -- | Show the cursor at the given logical column accounting for char width and row
90+ -- | Show the cursor at the given logical column accounting for
91+ -- character width in the presence of multi-column characters.
76 | Cursor !Int !Int
92 | Cursor !Int !Int
77− -- | Show the cursor at the given absolute terminal column and row
93+ -- | Show the cursor at the given absolute terminal column and row
78 | AbsoluteCursor !Int !Int
94 | AbsoluteCursor !Int !Int
95+ deriving Eq
79
96
80instance NFData Cursor where
97instance NFData Cursor where
81 rnf c = c `seq` ()
98 rnf c = c `seq` ()
82
99
83−-- | A 'Picture' has a background pattern. The background is either ClearBackground. Which shows the
100+-- | A 'Picture' has a background pattern. The background is either:
84−-- layer below or is blank if the bottom layer. Or the background pattern is a character and a
101+--
85−-- display attribute. If the display attribute used previously should be used for a background fill
102+-- * ClearBackground, which shows the layer below or is blank if the
86−-- then use `currentAttr` for the background attribute.
103+-- bottom layer
104+-- * A character and a display attribute
87--
105--
88−-- \todo The current attribute is always set to the default attributes at the start of updating the
106+-- If the display attribute used previously should be used for a
89−-- screen to a picture.
107+-- background fill then use `currentAttr` for the background attribute.
90data Background
108data Background
91 = Background
109 = Background
92 { backgroundChar :: Char
110 { backgroundChar :: Char
@@ -98,11 +116,16 @@ data Background
98 --
116 --
99 -- * End of line if there are no remaining non-skip ops.
117 -- * End of line if there are no remaining non-skip ops.
100 | ClearBackground
118 | ClearBackground
119+ deriving Eq
101
120
102instance NFData Background where
121instance NFData Background where
103 rnf (Background c a) = c `seq` a `seq` ()
122 rnf (Background c a) = c `seq` a `seq` ()
104 rnf ClearBackground = ()
123 rnf ClearBackground = ()
105
124
125+-- | Return the top-most 'Image' layer for a picture. This is unsafe for
126+-- 'Picture's without at least one layer.
127+--
128+-- This is provided for compatibility with applications that do not use
106−-- | Compatibility with applications that do not use more than a single layer.
129+-- more than a single layer.
107picImage :: Picture -> Image
130picImage :: Picture -> Image
108picImage = head . picLayers
131picImage = head . picLayers
src/Graphics/Vty/PictureToSpans.hs+68−87
@@ -1,21 +1,14 @@
1-- Copyright Corey O'Connor<coreyoconnor@gmail.com>
1-- Copyright Corey O'Connor<coreyoconnor@gmail.com>
2{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE CPP #-}
4{-# LANGUAGE ExistentialQuantification #-}
3{-# LANGUAGE ExistentialQuantification #-}
5{-# LANGUAGE NamedFieldPuns #-}
4{-# LANGUAGE NamedFieldPuns #-}
6{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE RankNTypes #-}
7{-# LANGUAGE TemplateHaskell #-}
6{-# LANGUAGE TemplateHaskell #-}
8
7
9−#ifndef MIN_VERSION_base
10−#defined MIN_VERSION_base(x,y,z) 1
11−#endif
12−
13−{- | Transforms an image into rows of operations.
8+-- | Transforms an image into rows of operations.
14− -}
15module Graphics.Vty.PictureToSpans where
9module Graphics.Vty.PictureToSpans where
16
10
17−import Graphics.Vty.Prelude
11+import Graphics.Vty.Attributes (Attr, currentAttr)
18−
19import Graphics.Vty.Image
12import Graphics.Vty.Image
20import Graphics.Vty.Image.Internal
13import Graphics.Vty.Image.Internal
21import Graphics.Vty.Picture
14import Graphics.Vty.Picture
@@ -26,12 +19,7 @@ import Lens.Micro.Mtl
26import Lens.Micro.TH
19import Lens.Micro.TH
27import Control.Monad.Reader
20import Control.Monad.Reader
28import Control.Monad.State.Strict hiding ( state )
21import Control.Monad.State.Strict hiding ( state )
29−
30−#if __GLASGOW_HASKELL__ < 708
31−import Control.Monad.ST.Strict hiding ( unsafeIOToST )
32−#else
33import Control.Monad.ST.Strict
22import Control.Monad.ST.Strict
34−#endif
35
23
36import qualified Data.Vector as Vector hiding ( take, replicate )
24import qualified Data.Vector as Vector hiding ( take, replicate )
37import Data.Vector.Mutable ( MVector(..))
25import Data.Vector.Mutable ( MVector(..))
@@ -39,29 +27,27 @@ import qualified Data.Vector.Mutable as MVector
39
27
40import qualified Data.Text.Lazy as TL
28import qualified Data.Text.Lazy as TL
41
29
42−#if !(MIN_VERSION_base(4,8,0))
43−import Data.Monoid (mappend)
44−#endif
45−
46type MRowOps s = MVector s SpanOps
30type MRowOps s = MVector s SpanOps
47
31
48type MSpanOps s = MVector s SpanOp
32type MSpanOps s = MVector s SpanOp
49
33
50-- transform plus clip. More or less.
34-- transform plus clip. More or less.
51data BlitState = BlitState
35data BlitState = BlitState
52− -- we always snoc to the operation vectors. Thus the columnOffset = length of row at rowOffset
36+ -- we always snoc to the operation vectors. Thus the columnOffset =
37+ -- length of row at rowOffset although, one possibility is to merge
53− -- although, one possibility is to merge layers right in snocOp (naming it something else, of
38+ -- layers right in snocOp (naming it something else, of course). In
54− -- course). In which case columnnOffset would be applicable.
39+ -- which case columnnOffset would be applicable. Right now we need
55− -- Right now we need it to exist.
40+ -- it to exist.
56 { _columnOffset :: Int
41 { _columnOffset :: Int
57 , _rowOffset :: Int
42 , _rowOffset :: Int
58− -- clip coordinate space is in image space. Which means it's >= 0 and < imageWidth.
43+ -- clip coordinate space is in image space. Which means it's >= 0
44+ -- and < imageWidth.
59 , _skipColumns :: Int
45 , _skipColumns :: Int
60 -- >= 0 and < imageHeight
46 -- >= 0 and < imageHeight
61 , _skipRows :: Int
47 , _skipRows :: Int
62− -- includes consideration of skipColumns. In display space.
48+ -- includes consideration of skipColumns. In display space. The
63− -- The number of columns from the next column to be defined to the end of the display for the
49+ -- number of columns from the next column to be defined to the end
64− -- row.
50+ -- of the display for the row.
65 , _remainingColumns :: Int
51 , _remainingColumns :: Int
66 -- includes consideration of skipRows. In display space.
52 -- includes consideration of skipRows. In display space.
67 , _remainingRows :: Int
53 , _remainingRows :: Int
@@ -78,31 +64,29 @@ makeLenses ''BlitEnv
78
64
79type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
65type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
80
66
81−-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
67+-- | Produces the span ops that will render the given picture, possibly
82−-- specified region.
68+-- cropped or padded, into the specified region.
83displayOpsForPic :: Picture -> DisplayRegion -> DisplayOps
69displayOpsForPic :: Picture -> DisplayRegion -> DisplayOps
84displayOpsForPic pic r = Vector.create (combinedOpsForLayers pic r)
70displayOpsForPic pic r = Vector.create (combinedOpsForLayers pic r)
85
71
86−-- | Returns the DisplayOps for an image rendered to a window the size of the image.
72+-- | Returns the DisplayOps for an image rendered to a window the size
73+-- of the image.
87--
74--
88−-- largerly used only for debugging.
75+-- largely used only for debugging.
89displayOpsForImage :: Image -> DisplayOps
76displayOpsForImage :: Image -> DisplayOps
90displayOpsForImage i = displayOpsForPic (picForImage i) (imageWidth i, imageHeight i)
77displayOpsForImage i = displayOpsForPic (picForImage i) (imageWidth i, imageHeight i)
91
78
92-- | Produces the span ops for each layer then combines them.
79-- | Produces the span ops for each layer then combines them.
93−--
94−-- TODO: a fold over a builder function. start with span ops that are a bg fill of the entire
95−-- region.
96combinedOpsForLayers :: Picture -> DisplayRegion -> ST s (MRowOps s)
80combinedOpsForLayers :: Picture -> DisplayRegion -> ST s (MRowOps s)
97combinedOpsForLayers pic r
81combinedOpsForLayers pic r
98 | regionWidth r == 0 || regionHeight r == 0 = MVector.new 0
82 | regionWidth r == 0 || regionHeight r == 0 = MVector.new 0
99 | otherwise = do
83 | otherwise = do
100− layerOps <- mapM (\layer -> buildSpans layer r) (picLayers pic)
84+ layerOps <- mapM (`buildSpans` r) (picLayers pic)
101 case layerOps of
85 case layerOps of
102 [] -> fail "empty picture"
86 [] -> fail "empty picture"
103 [ops] -> substituteSkips (picBackground pic) ops
87 [ops] -> substituteSkips (picBackground pic) ops
104− -- instead of merging ops after generation the merging can be performed as part of
88+ -- instead of merging ops after generation the merging can
105− -- snocOp.
89+ -- be performed as part of snocOp.
106 topOps : lowerOps -> do
90 topOps : lowerOps -> do
107 ops <- foldM mergeUnder topOps lowerOps
91 ops <- foldM mergeUnder topOps lowerOps
108 substituteSkips (picBackground pic) ops
92 substituteSkips (picBackground pic) ops
@@ -111,21 +95,23 @@ substituteSkips :: Background -> MRowOps s -> ST s (MRowOps s)
111substituteSkips ClearBackground ops = do
95substituteSkips ClearBackground ops = do
112 forM_ [0 .. MVector.length ops - 1] $ \row -> do
96 forM_ [0 .. MVector.length ops - 1] $ \row -> do
113 rowOps <- MVector.read ops row
97 rowOps <- MVector.read ops row
114− -- the image operations assure that background fills are combined.
98+ -- the image operations assure that background fills are
115− -- clipping a background fill does not split the background fill.
99+ -- combined. clipping a background fill does not split the
116− -- merging of image layers can split a skip, but only by the insertion of a non skip.
100+ -- background fill. merging of image layers can split a skip,
101+ -- but only by the insertion of a non skip. all this combines to
117− -- all this combines to mean we can check the last operation and remove it if it's a skip
102+ -- mean we can check the last operation and remove it if it's a
118− -- todo: or does it?
103+ -- skip
119 let rowOps' = case Vector.last rowOps of
104 let rowOps' = case Vector.last rowOps of
120 Skip w -> Vector.init rowOps `Vector.snoc` RowEnd w
105 Skip w -> Vector.init rowOps `Vector.snoc` RowEnd w
121 _ -> rowOps
106 _ -> rowOps
122− -- now all the skips can be replaced by replications of ' ' of the required width.
107+ -- now all the skips can be replaced by replications of ' ' of
108+ -- the required width.
123 let rowOps'' = swapSkipsForSingleColumnCharSpan ' ' currentAttr rowOps'
109 let rowOps'' = swapSkipsForSingleColumnCharSpan ' ' currentAttr rowOps'
124 MVector.write ops row rowOps''
110 MVector.write ops row rowOps''
125 return ops
111 return ops
126substituteSkips (Background {backgroundChar, backgroundAttr}) ops = do
112substituteSkips (Background {backgroundChar, backgroundAttr}) ops = do
127− -- At this point we decide if the background character is single column or not.
113+ -- At this point we decide if the background character is single
128− -- obviously, single column is easier.
114+ -- column or not. obviously, single column is easier.
129 case safeWcwidth backgroundChar of
115 case safeWcwidth backgroundChar of
130 w | w == 0 -> fail $ "invalid background character " ++ show backgroundChar
116 w | w == 0 -> fail $ "invalid background character " ++ show backgroundChar
131 | w == 1 -> do
117 | w == 1 -> do
@@ -149,12 +135,12 @@ mergeUnder upper lower = do
149 MVector.write upper row rowOps
135 MVector.write upper row rowOps
150 return upper
136 return upper
151
137
152−-- fugly
153mergeRowUnder :: SpanOps -> SpanOps -> SpanOps
138mergeRowUnder :: SpanOps -> SpanOps -> SpanOps
154−mergeRowUnder upperRowOps lowerRowOps =
139+mergeRowUnder upperRowOps =
155− onUpperOp Vector.empty (Vector.head upperRowOps) (Vector.tail upperRowOps) lowerRowOps
140+ onUpperOp Vector.empty (Vector.head upperRowOps) (Vector.tail upperRowOps)
156 where
141 where
157− -- H: it will never be the case that we are out of upper ops before lower ops.
142+ -- H: it will never be the case that we are out of upper ops
143+ -- before lower ops.
158 onUpperOp :: SpanOps -> SpanOp -> SpanOps -> SpanOps -> SpanOps
144 onUpperOp :: SpanOps -> SpanOp -> SpanOps -> SpanOps -> SpanOps
159 onUpperOp outOps op@(TextSpan _ w _ _) upperOps lowerOps =
145 onUpperOp outOps op@(TextSpan _ w _ _) upperOps lowerOps =
160 let lowerOps' = dropOps w lowerOps
146 let lowerOps' = dropOps w lowerOps
@@ -189,30 +175,32 @@ swapSkipsForCharSpan w c a = Vector.map f
189 in TextSpan a ow cw txt
175 in TextSpan a ow cw txt
190 f v = v
176 f v = v
191
177
192−-- | Builds a vector of row operations that will output the given picture to the terminal.
178+-- | Builds a vector of row operations that will output the given
179+-- picture to the terminal.
193--
180--
194-- Crops to the given display region.
181-- Crops to the given display region.
195−--
196−-- \todo I'm pretty sure there is an algorithm that does not require a mutable buffer.
197buildSpans :: Image -> DisplayRegion -> ST s (MRowOps s)
182buildSpans :: Image -> DisplayRegion -> ST s (MRowOps s)
198buildSpans image outRegion = do
183buildSpans image outRegion = do
199 -- First we create a mutable vector for each rows output operations.
184 -- First we create a mutable vector for each rows output operations.
200 outOps <- MVector.replicate (regionHeight outRegion) Vector.empty
185 outOps <- MVector.replicate (regionHeight outRegion) Vector.empty
201− -- \todo I think building the span operations in display order would provide better performance.
186+ -- It's possible that building the span operations in display order
202− -- However, I got stuck trying to implement an algorithm that did this. This will be considered
187+ -- would provide better performance.
203− -- as a possible future optimization.
204 --
188 --
205− -- A depth first traversal of the image is performed. ordered according to the column range
189+ -- A depth first traversal of the image is performed. ordered
190+ -- according to the column range defined by the image from least
206− -- defined by the image from least to greatest. The output row ops will at least have the
191+ -- to greatest. The output row ops will at least have the region
207− -- region of the image specified. Iterate over all output rows and output background fills for
192+ -- of the image specified. Iterate over all output rows and output
208− -- all unspecified columns.
193+ -- background fills for all unspecified columns.
209 --
194 --
210− -- The images are made into span operations from left to right. It's possible that this could
195+ -- The images are made into span operations from left to right. It's
211− -- easily be made to assure top to bottom output as well.
196+ -- possible that this could easily be made to assure top to bottom
197+ -- output as well.
212 when (regionHeight outRegion > 0 && regionWidth outRegion > 0) $ do
198 when (regionHeight outRegion > 0 && regionWidth outRegion > 0) $ do
213− -- The ops builder recursively descends the image and outputs span ops that would
199+ -- The ops builder recursively descends the image and outputs
200+ -- span ops that would display that image. The number of columns
214− -- display that image. The number of columns remaining in this row before exceeding the
201+ -- remaining in this row before exceeding the bounds is also
215− -- bounds is also provided. This is used to clip the span ops produced to the display.
202+ -- provided. This is used to clip the span ops produced to the
203+ -- display.
216 let fullBuild = do
204 let fullBuild = do
217 startImageBuild image
205 startImageBuild image
218 -- Fill in any unspecified columns with a skip.
206 -- Fill in any unspecified columns with a skip.
@@ -223,7 +211,8 @@ buildSpans image outRegion = do
223 return ()
211 return ()
224 return outOps
212 return outOps
225
213
226−-- | Add the operations required to build a given image to the current set of row operations.
214+-- | Add the operations required to build a given image to the current
215+-- set of row operations.
227startImageBuild :: Image -> BlitM s ()
216startImageBuild :: Image -> BlitM s ()
228startImageBuild image = do
217startImageBuild image = do
229 outOfBounds <- isOutOfBounds image <$> get
218 outOfBounds <- isOutOfBounds image <$> get
@@ -237,17 +226,16 @@ isOutOfBounds i s
237 | s ^. skipRows >= imageHeight i = True
226 | s ^. skipRows >= imageHeight i = True
238 | otherwise = False
227 | otherwise = False
239
228
240−-- | This adds an image that might be partially clipped to the output ops.
229+-- | This adds an image that might be partially clipped to the output
241−--
230+-- ops.
242−-- This is a very touchy algorithm. Too touchy. For instance, the CropRight and CropBottom
243−-- implementations are odd. They pass the current tests but something seems terribly wrong about all
244−-- this.
245--
231--
246−-- \todo prove this cannot be called in an out of bounds case.
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.
247addMaybeClipped :: forall s . Image -> BlitM s ()
235addMaybeClipped :: forall s . Image -> BlitM s ()
248addMaybeClipped EmptyImage = return ()
236addMaybeClipped EmptyImage = return ()
249addMaybeClipped (HorizText a textStr ow _cw) = do
237addMaybeClipped (HorizText a textStr ow _cw) = do
250− -- TODO: assumption that text spans are only 1 row high.
238+ -- This assumes that text spans are only 1 row high.
251 s <- use skipRows
239 s <- use skipRows
252 when (s < 1) $ do
240 when (s < 1) $ do
253 leftClip <- use skipColumns
241 leftClip <- use skipColumns
@@ -297,7 +285,7 @@ addMaybeClipped CropTop {croppedImage, topSkip} = do
297 skipRows += topSkip
285 skipRows += topSkip
298 addMaybeClipped croppedImage
286 addMaybeClipped croppedImage
299
287
300−addMaybeClippedJoin :: forall s . String
288+addMaybeClippedJoin :: forall s . String
301 -> Lens BlitState BlitState Int Int
289 -> Lens BlitState BlitState Int Int
302 -> Lens BlitState BlitState Int Int
290 -> Lens BlitState BlitState Int Int
303 -> Lens BlitState BlitState Int Int
291 -> Lens BlitState BlitState Int Int
@@ -310,12 +298,11 @@ addMaybeClippedJoin name skip remaining offset i0Dim i0 i1 size = do
310 state <- get
298 state <- get
311 when (state^.remaining <= 0) $ fail $ name ++ " with remaining <= 0"
299 when (state^.remaining <= 0) $ fail $ name ++ " with remaining <= 0"
312 case state^.skip of
300 case state^.skip of
313− s -- TODO: check if clipped in other dim. if not use addUnclipped
314− | s > size -> put $ state & skip -~ size
301+ s | s > size -> put $ state & skip %~ subtract size
315− | s == 0 -> if state^.remaining > i0Dim
302+ | s == 0 -> if state^.remaining > i0Dim
316 then do
303 then do
317 addMaybeClipped i0
304 addMaybeClipped i0
318− put $ state & offset +~ i0Dim & remaining -~ i0Dim
305+ put $ state & offset %~ (+ i0Dim) & remaining %~ subtract i0Dim
319 addMaybeClipped i1
306 addMaybeClipped i1
320 else addMaybeClipped i0
307 else addMaybeClipped i0
321 | s < i0Dim ->
308 | s < i0Dim ->
@@ -324,10 +311,10 @@ addMaybeClippedJoin name skip remaining offset i0Dim i0 i1 size = do
324 then addMaybeClipped i0
311 then addMaybeClipped i0
325 else do
312 else do
326 addMaybeClipped i0
313 addMaybeClipped i0
327− put $ state & offset +~ i0Dim' & remaining -~ i0Dim' & skip .~ 0
314+ put $ state & offset %~ (+ i0Dim') & remaining %~ subtract i0Dim' & skip .~ 0
328 addMaybeClipped i1
315 addMaybeClipped i1
329 | s >= i0Dim -> do
316 | s >= i0Dim -> do
330− put $ state & skip -~ i0Dim
317+ put $ state & skip %~ subtract i0Dim
331 addMaybeClipped i1
318 addMaybeClipped i1
332 _ -> fail $ name ++ " has unhandled skip class"
319 _ -> fail $ name ++ " has unhandled skip class"
333
320
@@ -336,14 +323,14 @@ addUnclippedText a txt = do
336 let op = TextSpan a usedDisplayColumns
323 let op = TextSpan a usedDisplayColumns
337 (fromIntegral $ TL.length txt)
324 (fromIntegral $ TL.length txt)
338 txt
325 txt
339− usedDisplayColumns = wcswidth $ TL.unpack txt
326+ usedDisplayColumns = wctlwidth txt
340 use rowOffset >>= snocOp op
327 use rowOffset >>= snocOp op
341
328
342addRowCompletion :: DisplayRegion -> Int -> BlitM s ()
329addRowCompletion :: DisplayRegion -> Int -> BlitM s ()
343addRowCompletion displayRegion row = do
330addRowCompletion displayRegion row = do
344 allRowOps <- view mrowOps
331 allRowOps <- view mrowOps
345 rowOps <- lift $ lift $ MVector.read allRowOps row
332 rowOps <- lift $ lift $ MVector.read allRowOps row
346− let endX = spanOpsEffectedColumns rowOps
333+ let endX = spanOpsAffectedColumns rowOps
347 when (endX < regionWidth displayRegion) $ do
334 when (endX < regionWidth displayRegion) $ do
348 let ow = regionWidth displayRegion - endX
335 let ow = regionWidth displayRegion - endX
349 snocOp (Skip ow) row
336 snocOp (Skip ow) row
@@ -356,12 +343,6 @@ snocOp !op !row = do
356 lift $ lift $ do
343 lift $ lift $ do
357 ops <- MVector.read theMrowOps row
344 ops <- MVector.read theMrowOps row
358 let ops' = Vector.snoc ops op
345 let ops' = Vector.snoc ops op
359− when (spanOpsEffectedColumns ops' > regionWidth theRegion)
346+ when (spanOpsAffectedColumns ops' > regionWidth theRegion)
360 $ fail $ "row " ++ show row ++ " now exceeds region width"
347 $ fail $ "row " ++ show row ++ " now exceeds region width"
361 MVector.write theMrowOps row ops'
348 MVector.write theMrowOps row ops'
362−
363−(-~) :: Num a => ASetter s t a a -> a -> s -> t
364−l -~ n = over l (subtract n)
365−
366−(+~) :: Num a => ASetter s t a a -> a -> s -> t
367−l +~ n = over l (n +)
src/Graphics/Vty/Prelude.hsdeleted−18
@@ -1,18 +0,0 @@
1−-- | Prelude for Vty modules. Not particularly useful outside of Vty.
2−module Graphics.Vty.Prelude ( module Graphics.Vty.Prelude
3− , module Control.Applicative
4− , module Control.Monad
5− )
6−where
7−
8−import Control.Applicative hiding ((<|>))
9−import Control.Monad
10−
11−-- | Named alias for a Int pair
12−type DisplayRegion = (Int,Int)
13−
14−regionWidth :: DisplayRegion -> Int
15−regionWidth = fst
16−
17−regionHeight :: DisplayRegion -> Int
18−regionHeight = snd
src/Graphics/Vty/Span.hs+46−55
@@ -1,23 +1,15 @@
1-- Copyright Corey O'Connor
1-- Copyright Corey O'Connor
2−{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE NamedFieldPuns #-}
4{-# LANGUAGE GADTs #-}
2{-# LANGUAGE GADTs #-}
5−{- | A picture is translated into a sequences of state changes and character spans.
3+-- | A picture is translated into a sequences of state changes and
6− - State changes are currently limited to new attribute values. The attribute is applied to all
4+-- character spans. The attribute is applied to all following spans,
7− - following spans. Including spans of the next row. The nth element of the sequence represents the
5+-- including spans of the next row. The nth element of the sequence
8− - nth row (from top to bottom) of the picture to render.
6+-- represents the nth row (from top to bottom) of the picture to render.
9− -
7+--
10− - A span op sequence will be defined for all rows and columns (and no more) of the region provided
8+-- A span op sequence will be defined for all rows and columns (and no
11− - with the picture to `spansForPic`.
9+-- more) of the region provided with the picture to 'spansForPic'.
12− -
13− - todo: Partition attribute changes into multiple categories according to the serialized
14− - representation of the various attributes.
15− -}
16−module Graphics.Vty.Span
10+module Graphics.Vty.Span where
17− where
18−
19−import Graphics.Vty.Prelude
20
11
12+import Graphics.Vty.Attributes (Attr)
21import Graphics.Vty.Image
13import Graphics.Vty.Image
22import Graphics.Vty.Image.Internal ( clipText )
14import Graphics.Vty.Image.Internal ( clipText )
23
15
@@ -25,40 +17,40 @@ import qualified Data.Text.Lazy as TL
25import Data.Vector (Vector)
17import Data.Vector (Vector)
26import qualified Data.Vector as Vector
18import qualified Data.Vector as Vector
27
19
28−-- | This represents an operation on the terminal. Either an attribute change or the output of a
20+-- | This represents an operation on the terminal: either an attribute
29−-- text string.
21+-- change or the output of a text string.
30data SpanOp =
22data SpanOp =
31− -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
23+ -- | A span of UTF-8 text occupies a specific number of screen space
24+ -- columns. A single UTF character does not necessarily represent 1
32− -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
25+ -- colunm. See Codec.Binary.UTF8.Width TextSpan [Attr] [output width
33− -- TextSpan [Attr] [output width in columns] [number of characters] [data]
26+ -- in columns] [number of characters] [data]
34− TextSpan
27+ TextSpan
35 { textSpanAttr :: !Attr
28 { textSpanAttr :: !Attr
36 , textSpanOutputWidth :: !Int
29 , textSpanOutputWidth :: !Int
37 , textSpanCharWidth :: !Int
30 , textSpanCharWidth :: !Int
38 , textSpanText :: DisplayText
31 , textSpanText :: DisplayText
39 }
32 }
40− -- | Skips the given number of columns
33+ -- | Skips the given number of columns.
41− -- A skip is transparent.... maybe? I am not sure how attribute changes interact.
42− -- todo: separate from this type.
43 | Skip !Int
34 | Skip !Int
44− -- | Marks the end of a row. specifies how many columns are remaining. These columns will not be
35+ -- | Marks the end of a row. Specifies how many columns are
36+ -- remaining. These columns will not be explicitly overwritten with
45− -- explicitly overwritten with the span ops. The terminal is require to assure the remaining
37+ -- the span ops. The terminal is require to assure the remaining
46 -- columns are clear.
38 -- columns are clear.
47− -- todo: separate from this type.
48 | RowEnd !Int
39 | RowEnd !Int
49 deriving Eq
40 deriving Eq
50
41
51−-- | vector of span operations. executed in succession. This represents the operations required to
42+-- | A vector of span operations executed in succession. This represents
43+-- the operations required to render a row of the terminal. The
52−-- render a row of the terminal. The operations in one row may effect subsequent rows.
44+-- operations in one row may affect subsequent rows. For example,
53−-- EG: Setting the foreground color in one row will effect all subsequent rows until the foreground
45+-- setting the foreground color in one row will affect all subsequent
54−-- color is changed.
46+-- rows until the foreground color is changed.
55type SpanOps = Vector SpanOp
47type SpanOps = Vector SpanOp
56
48
57dropOps :: Int -> SpanOps -> SpanOps
49dropOps :: Int -> SpanOps -> SpanOps
58dropOps w = snd . splitOpsAt w
50dropOps w = snd . splitOpsAt w
59
51
60splitOpsAt :: Int -> SpanOps -> (SpanOps, SpanOps)
52splitOpsAt :: Int -> SpanOps -> (SpanOps, SpanOps)
61−splitOpsAt inW inOps = splitOpsAt' inW inOps
53+splitOpsAt = splitOpsAt'
62 where
54 where
63 splitOpsAt' 0 ops = (Vector.empty, ops)
55 splitOpsAt' 0 ops = (Vector.empty, ops)
64 splitOpsAt' remainingColumns ops = case Vector.head ops of
56 splitOpsAt' remainingColumns ops = case Vector.head ops of
@@ -89,9 +81,9 @@ splitOpsAt inW inOps = splitOpsAt' inW inOps
89 , Vector.cons (Skip (w - remainingColumns)) (Vector.tail ops)
81 , Vector.cons (Skip (w - remainingColumns)) (Vector.tail ops)
90 )
82 )
91 RowEnd _ -> error "cannot split ops containing a row end"
83 RowEnd _ -> error "cannot split ops containing a row end"
92−
93
84
94−-- | vector of span operation vectors for display. One per row of the output region.
85+-- | A vector of span operation vectors for display, one per row of the
86+-- output region.
95type DisplayOps = Vector SpanOps
87type DisplayOps = Vector SpanOps
96
88
97instance Show SpanOp where
89instance Show SpanOp where
@@ -99,40 +91,39 @@ instance Show SpanOp where
99 show (Skip ow) = "Skip(" ++ show ow ++ ")"
91 show (Skip ow) = "Skip(" ++ show ow ++ ")"
100 show (RowEnd ow) = "RowEnd(" ++ show ow ++ ")"
92 show (RowEnd ow) = "RowEnd(" ++ show ow ++ ")"
101
93
102−-- | Number of columns the DisplayOps are defined for
94+-- | The number of columns the DisplayOps are defined for.
103--
95--
104−-- All spans are verified to define same number of columns. See: VerifySpanOps
96+-- All spans are verified to define same number of columns.
105displayOpsColumns :: DisplayOps -> Int
97displayOpsColumns :: DisplayOps -> Int
106−displayOpsColumns ops
98+displayOpsColumns ops
107 | Vector.length ops == 0 = 0
99 | Vector.length ops == 0 = 0
108 | otherwise = Vector.length $ Vector.head ops
100 | otherwise = Vector.length $ Vector.head ops
109
101
110−-- | Number of rows the DisplayOps are defined for
102+-- | The number of rows the DisplayOps are defined for.
111displayOpsRows :: DisplayOps -> Int
103displayOpsRows :: DisplayOps -> Int
112−displayOpsRows ops = Vector.length ops
104+displayOpsRows = Vector.length
113
105
114−effectedRegion :: DisplayOps -> DisplayRegion
106+affectedRegion :: DisplayOps -> DisplayRegion
115−effectedRegion ops = (displayOpsColumns ops, displayOpsRows ops)
107+affectedRegion ops = (displayOpsColumns ops, displayOpsRows ops)
116
108
117−-- | The number of columns a SpanOps effects.
109+-- | The number of columns a SpanOps affects.
118−spanOpsEffectedColumns :: SpanOps -> Int
110+spanOpsAffectedColumns :: SpanOps -> Int
119−spanOpsEffectedColumns inOps = Vector.foldl' spanOpsEffectedColumns' 0 inOps
111+spanOpsAffectedColumns inOps = Vector.foldl' spanOpsAffectedColumns' 0 inOps
120− where
112+ where
121− spanOpsEffectedColumns' t (TextSpan _ w _ _ ) = t + w
113+ spanOpsAffectedColumns' t (TextSpan _ w _ _ ) = t + w
122− spanOpsEffectedColumns' t (Skip w) = t + w
114+ spanOpsAffectedColumns' t (Skip w) = t + w
123− spanOpsEffectedColumns' t (RowEnd w) = t + w
115+ spanOpsAffectedColumns' t (RowEnd w) = t + w
124
116
125−-- | The width of a single SpanOp in columns
117+-- | The width of a single SpanOp in columns.
126spanOpHasWidth :: SpanOp -> Maybe (Int, Int)
118spanOpHasWidth :: SpanOp -> Maybe (Int, Int)
127spanOpHasWidth (TextSpan _ ow cw _) = Just (cw, ow)
119spanOpHasWidth (TextSpan _ ow cw _) = Just (cw, ow)
128spanOpHasWidth (Skip ow) = Just (ow,ow)
120spanOpHasWidth (Skip ow) = Just (ow,ow)
129spanOpHasWidth (RowEnd ow) = Just (ow,ow)
121spanOpHasWidth (RowEnd ow) = Just (ow,ow)
130
122
131−-- | returns the number of columns to the character at the given position in the span op
123+-- | The number of columns to the character at the given position in the
124+-- span op.
132columnsToCharOffset :: Int -> SpanOp -> Int
125columnsToCharOffset :: Int -> SpanOp -> Int
133columnsToCharOffset cx (TextSpan _ _ _ utf8Str) =
126columnsToCharOffset cx (TextSpan _ _ _ utf8Str) =
134− let str = TL.unpack utf8Str
127+ wctlwidth (TL.take (fromIntegral cx) utf8Str)
135− in wcswidth (take cx str)
136columnsToCharOffset cx (Skip _) = cx
128columnsToCharOffset cx (Skip _) = cx
137columnsToCharOffset cx (RowEnd _) = cx
129columnsToCharOffset cx (RowEnd _) = cx
138−
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)
stack.yamldeleted−40
@@ -1,40 +0,0 @@
1−# For more information, see: http://docs.haskellstack.org/en/stable/yaml_configuration.html
2−
3−# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)
4−resolver: lts-5.11
5−
6−# Local packages, usually specified by relative directory name
7−packages:
8−- '.'
9−- test/
10−
11−# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)
12−extra-deps: []
13−
14−# Override default flag values for local packages and extra-deps
15−flags: {}
16−
17−# Extra package databases containing global packages
18−extra-package-dbs: []
19−
20−nix:
21− enable: true
22− packages: [ncurses]
23−
24−# Control whether we use the GHC we find on the path
25−# system-ghc: true
26−
27−# Require a specific version of stack, using version ranges
28−# require-stack-version: -any # Default
29−# require-stack-version: >= 1.0.0
30−
31−# Override the architecture used by stack, especially useful on Windows
32−# arch: i386
33−# arch: x86_64
34−
35−# Extra directories used by stack for building
36−# extra-include-dirs: [/path/to/dir]
37−# extra-lib-dirs: [/path/to/dir]
38−
39−# Allow a newer minor version of GHC than the snapshot specifies
40−# compiler-check: newer-minor
test/BenchImageFuzz.hs+1−3
@@ -8,8 +8,6 @@ import Verify
8import Control.Applicative
8import Control.Applicative
9import Control.Monad
9import Control.Monad
10
10
11−import Data.Default (def)
12−
13import System.Random
11import System.Random
14
12
15rand :: Arbitrary a => IO a
13rand :: Arbitrary a => IO a
@@ -21,7 +19,7 @@ randomImage = rand
21randomPicture = picForImage <$> randomImage
19randomPicture = picForImage <$> randomImage
22
20
23bench0 = do
21bench0 = do
24− vty <- mkVty def
22+ vty <- mkVty defaultConfig
25 (w,h) <- displayBounds $ outputIface vty
23 (w,h) <- displayBounds $ outputIface vty
26 let pictures = replicateM 3000 randomPicture
24 let pictures = replicateM 3000 randomPicture
27 bench ps = do
25 bench ps = do
test/BenchNoDiffOpt.hs+3−4
@@ -9,17 +9,16 @@ import Control.Concurrent( threadDelay )
9import Control.Monad( liftM2 )
9import Control.Monad( liftM2 )
10
10
11import qualified Data.ByteString.Char8 as B
11import qualified Data.ByteString.Char8 as B
12−import Data.Default (def)
13import Data.List
12import Data.List
14
13
15import System.Environment( getArgs )
14import System.Environment( getArgs )
16import System.IO
15import System.IO
17import System.Random
16import System.Random
18
17
19−bench0 = do
18+bench0 = do
20 let fixedGen = mkStdGen 0
19 let fixedGen = mkStdGen 0
21 setStdGen fixedGen
20 setStdGen fixedGen
22− vty <- mkVty def
21+ vty <- mkVty defaultConfig
23 (w,h) <- displayBounds $ outputIface vty
22 (w,h) <- displayBounds $ outputIface vty
24 let images = return $ (image0, image1)
23 let images = return $ (image0, image1)
25 image0 = charFill defAttr 'X' w h
24 image0 = charFill defAttr 'X' w h
@@ -29,7 +28,7 @@ bench0 = do
29 shutdown vty
28 shutdown vty
30 return $ Bench images bench
29 return $ Bench images bench
31
30
32−flipOut vty n image0 image1 =
31+flipOut vty n image0 image1 =
33 let !pLeft = picForImage image0
32 let !pLeft = picForImage image0
34 !pRight = picForImage image1
33 !pRight = picForImage image1
35 wLeft 0 = return ()
34 wLeft 0 = return ()
test/BenchRenderChar.hs+7−9
@@ -1,7 +1,7 @@
1−{- benchmarks composing images using the renderChar operation.
1+-- benchmarks composing images using the renderChar operation. This is
2− - This is what Yi uses in Yi.UI.Vty.drawText. Ideally a sequence of renderChar images horizontally
2+-- what Yi uses in Yi.UI.Vty.drawText. Ideally a sequence of renderChar
3− - composed should provide no worse performance than a fill render op.
3+-- images horizontally composed should provide no worse performance than
4− -}
4+-- a fill render op.
5module BenchRenderChar where
5module BenchRenderChar where
6
6
7import Graphics.Vty
7import Graphics.Vty
@@ -9,10 +9,8 @@ import Verify
9
9
10import Control.Monad ( forM_ )
10import Control.Monad ( forM_ )
11
11
12−import Data.Default (def)
13−
14bench0 = do
12bench0 = do
15− vty <- mkVty def
13+ vty <- mkVty defaultConfig
16 (w,h) <- displayBounds $ outputIface vty
14 (w,h) <- displayBounds $ outputIface vty
17 let testChars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
15 let testChars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
18 bench d = do
16 bench d = do
@@ -23,12 +21,12 @@ bench0 = do
23 shutdown vty
21 shutdown vty
24 return $ Bench testChars bench
22 return $ Bench testChars bench
25
23
26−testImageUsingChar c w h
24+testImageUsingChar c w h
27 = vertCat $ replicate (fromIntegral h)
25 = vertCat $ replicate (fromIntegral h)
28 $ horizCat $ map (char defAttr) (replicate (fromIntegral w) c)
26 $ horizCat $ map (char defAttr) (replicate (fromIntegral w) c)
29
27
30bench1 = do
28bench1 = do
31− vty <- mkVty def
29+ vty <- mkVty defaultConfig
32 (w,h) <- displayBounds $ outputIface vty
30 (w,h) <- displayBounds $ outputIface vty
33 let testChars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
31 let testChars = return $ take 500 $ cycle $ [ c | c <- ['a'..'z']]
34 bench d = do
32 bench d = do
test/BenchVerticalScroll.hs+4−6
@@ -6,7 +6,6 @@ import Verify
6import Control.Concurrent( threadDelay )
6import Control.Concurrent( threadDelay )
7import Control.Monad( liftM2 )
7import Control.Monad( liftM2 )
8
8
9−import Data.Default (def)
10import Data.List
9import Data.List
11import Data.Word
10import Data.Word
12
11
@@ -14,10 +13,10 @@ import System.Environment( getArgs )
14import System.IO
13import System.IO
15import System.Random
14import System.Random
16
15
17−bench0 = do
16+bench0 = do
18 let fixedGen = mkStdGen 0
17 let fixedGen = mkStdGen 0
19 setStdGen fixedGen
18 setStdGen fixedGen
20− return $ Bench (return ()) (\() -> mkVty def >>= liftM2 (>>) run shutdown)
19+ return $ Bench (return ()) (\() -> mkVty defaultConfig >>= liftM2 (>>) run shutdown)
21
20
22run vt = mapM_ (\p -> update vt p) . benchgen =<< displayBounds (outputIface vt)
21run vt = mapM_ (\p -> update vt p) . benchgen =<< displayBounds (outputIface vt)
23
22
@@ -49,13 +48,12 @@ pad :: Int -> Image -> Image
49pad ml img = img <|> charFill defAttr ' ' (ml - imageWidth img) 1
48pad ml img = img <|> charFill defAttr ' ' (ml - imageWidth img) 1
50
49
51clines :: StdGen -> Int -> [Image]
50clines :: StdGen -> Int -> [Image]
52−clines g maxll = map (pad maxll . horizCat . map (uncurry string))
51+clines g maxll = map (pad maxll . horizCat . map (uncurry string))
53 $ fold (length . snd) (lengths maxll g1) (nums g2)
52 $ fold (length . snd) (lengths maxll g1) (nums g2)
54 where (g1,g2) = split g
53 where (g1,g2) = split g
55
54
56benchgen :: DisplayRegion -> [Picture]
55benchgen :: DisplayRegion -> [Picture]
57−benchgen (w,h)
56+benchgen (w,h)
58 = take 2000 $ map ((\i -> picForImage i) . vertCat . take (fromEnum h))
57 = take 2000 $ map ((\i -> picForImage i) . vertCat . take (fromEnum h))
59 $ tails
58 $ tails
60 $ clines (mkStdGen 80) w
59 $ clines (mkStdGen 80) w
61−
test/ControlTable.hs+1−1
@@ -5,7 +5,7 @@ import Graphics.Vty.ControlStrings
5import System.Console.Terminfo
5import System.Console.Terminfo
6
6
7main = do
7main = do
8− terminal <- setupTermFromEnv
8+ terminal <- setupTermFromEnv
9 controlTable <- init_controlTable terminal
9 controlTable <- init_controlTable terminal
10 putStrLn $ "ANSI terminal show cursor string: " ++ show cvis
10 putStrLn $ "ANSI terminal show cursor string: " ++ show cvis
11 putStrLn $ "Current terminal show cursor string: " ++ show (showCursorStr controlTable)
11 putStrLn $ "Current terminal show cursor string: " ++ show (showCursorStr controlTable)
test/EventEcho.hs+1−2
@@ -6,7 +6,6 @@ import Control.Applicative
6import Control.Arrow
6import Control.Arrow
7import Control.Monad.RWS
7import Control.Monad.RWS
8
8
9−import Data.Default (def)
10import Data.Sequence (Seq, (<|) )
9import Data.Sequence (Seq, (<|) )
11import qualified Data.Sequence as Seq
10import qualified Data.Sequence as Seq
12import Data.Foldable
11import Data.Foldable
@@ -16,7 +15,7 @@ eventBufferSize = 1000
16type App = RWST Vty () (Seq String) IO
15type App = RWST Vty () (Seq String) IO
17
16
18main = do
17main = do
19− vty <- mkVty def
18+ vty <- mkVty defaultConfig
20 _ <- execRWST (vtyInteract False) vty Seq.empty
19 _ <- execRWST (vtyInteract False) vty Seq.empty
21 shutdown vty
20 shutdown vty
22
21
test/HereDoc.hs+2−4
@@ -1,8 +1,7 @@
1{-# LANGUAGE QuasiQuotes #-}
1{-# LANGUAGE QuasiQuotes #-}
2{-# LANGUAGE TemplateHaskell #-}
2{-# LANGUAGE TemplateHaskell #-}
3−{- From http://www.reddit.com/r/haskell/comments/8ereh/a_here_document_syntax/
3+-- From http://www.reddit.com/r/haskell/comments/8ereh/a_here_document_syntax/
4− - copyright unknown?
4+-- copyright unknown?
5− -}
6module HereDoc (heredoc) where
5module HereDoc (heredoc) where
7
6
8import Language.Haskell.TH.Quote
7import Language.Haskell.TH.Quote
@@ -11,4 +10,3 @@ import Language.Haskell.TH.Lib
11
10
12heredoc :: QuasiQuoter
11heredoc :: QuasiQuoter
13heredoc = QuasiQuoter (litE . stringL) (litP . stringL) undefined undefined
12heredoc = QuasiQuoter (litE . stringL) (litP . stringL) undefined undefined
14−
test/Issue18.hs+4−6
@@ -1,5 +1,4 @@
1−module Main
1+module Main where
2− where
3
2
4import Graphics.Vty
3import Graphics.Vty
5import Graphics.Vty.Debug
4import Graphics.Vty.Debug
@@ -13,8 +12,8 @@ main = do
13 play vty sx sy
12 play vty sx sy
14
13
15play :: Vty -> Int -> Int -> IO ()
14play :: Vty -> Int -> Int -> IO ()
16−play vty sx sy =
15+play vty sx sy =
17− let
16+ let
18 testScreen = pic {
17 testScreen = pic {
19 pCursor = NoCursor
18 pCursor = NoCursor
20 , pImage = box 10 10 }
19 , pImage = box 10 10 }
@@ -31,6 +30,5 @@ box w h =
31 vertLine = renderFill attr '|' 1 (h - 2)
30 vertLine = renderFill attr '|' 1 (h - 2)
32 horizLine = corner <|> renderHFill attr '-' (w - 2) <|> corner
31 horizLine = corner <|> renderHFill attr '-' (w - 2) <|> corner
33 centerArea = vertLine <|> renderFill attr 'X' (w - 2) (h - 2) <|> vertLine
32 centerArea = vertLine <|> renderFill attr 'X' (w - 2) (h - 2) <|> vertLine
34− in
33+ in
35 horizLine <-> centerArea <-> horizLine
34 horizLine <-> centerArea <-> horizLine
36−
test/Makefile+2−2
@@ -45,7 +45,7 @@ all : $(VERIF_TESTS)
45
45
46.PHONY: $(TESTS)
46.PHONY: $(TESTS)
47.SECONDEXPANSION:
47.SECONDEXPANSION:
48−$(TESTS) :
48+$(TESTS) :
49 @echo running test $@
49 @echo running test $@
50 @mkdir -p results/$@
50 @mkdir -p results/$@
51 ( ghc $(GHC_PROF_ARGS) $@ \
51 ( ghc $(GHC_PROF_ARGS) $@ \
@@ -54,7 +54,7 @@ $(TESTS) :
54 )
54 )
55
55
56.PHONY: interactive_terminal_test
56.PHONY: interactive_terminal_test
57−interactive_terminal_test :
57+interactive_terminal_test :
58 ghc $(GHC_ARGS) $@ && ./$@
58 ghc $(GHC_ARGS) $@ && ./$@
59
59
60.PHONY: core
60.PHONY: core
test/MinimalExample.hs+1−3
@@ -2,10 +2,8 @@ module Main where
2
2
3import Graphics.Vty
3import Graphics.Vty
4
4
5−import Data.Default
6−
7main = do
5main = do
8− vty <- mkVty def
6+ vty <- mkVty defaultConfig
9 let line0 = string (def `withForeColor` green) "first line"
7 let line0 = string (def `withForeColor` green) "first line"
10 line1 = string (def `withBackColor` blue) "second line"
8 line1 = string (def `withBackColor` blue) "second line"
11 img = line0 <-> line1
9 img = line0 <-> line1
test/Rogue.hs+1−3
@@ -1,10 +1,8 @@
1−{-# LANGUAGE TemplateHaskell #-}
2module Main where
1module Main where
3
2
4import Graphics.Vty
3import Graphics.Vty
5
4
6import Data.Array
5import Data.Array
7−import Data.Default (def)
8
6
9import Control.Applicative
7import Control.Applicative
10import Control.Monad
8import Control.Monad
@@ -43,7 +41,7 @@ type Coord = (Int, Int)
43
41
44main :: IO ()
42main :: IO ()
45main = do
43main = do
46− vty <- mkVty def
44+ vty <- mkVty defaultConfig
47 level0 <- mkLevel 1
45 level0 <- mkLevel 1
48 let world0 = World (Player (levelStart level0)) level0
46 let world0 = World (Player (levelStart level0)) level0
49 (_finalWorld, ()) <- execRWST play vty world0
47 (_finalWorld, ()) <- execRWST play vty world0
test/Test2.hs+1
@@ -1,4 +1,5 @@
1module Main where
1module Main where
2+
2import Graphics.Vty
3import Graphics.Vty
3
4
4main = do
5main = do
test/Verify.hs+20−31
@@ -1,29 +1,29 @@
1{-# LANGUAGE RankNTypes #-}
1{-# LANGUAGE RankNTypes #-}
2{-# LANGUAGE GADTs #-}
2{-# LANGUAGE GADTs #-}
3{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE RecordWildCards #-}
4−{-# LANGUAGE CPP #-}
5{-# LANGUAGE TypeSynonymInstances #-}
4{-# LANGUAGE TypeSynonymInstances #-}
6{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE StandaloneDeriving #-}
6{-# LANGUAGE StandaloneDeriving #-}
8{-# LANGUAGE DisambiguateRecordFields #-}
7{-# LANGUAGE DisambiguateRecordFields #-}
9{-# LANGUAGE NamedFieldPuns #-}
8{-# LANGUAGE NamedFieldPuns #-}
10−module Verify ( module Verify
9+module Verify
10+ ( module Verify
11− , module Control.Applicative
11+ , module Control.Applicative
12− , module Control.DeepSeq
12+ , module Control.DeepSeq
13− , module Control.Exception
13+ , module Control.Exception
14− , module Control.Monad
14+ , module Control.Monad
15− , module Test.QuickCheck
15+ , module Test.QuickCheck
16− , module Test.QuickCheck.Modifiers
16+ , module Test.QuickCheck.Modifiers
17− , module Text.Printf
17+ , module Text.Printf
18− , succeeded
18+ , succeeded
19− , failed
19+ , failed
20− , monadicIO
20+ , monadicIO
21− , liftIO
21+ , liftIO
22− , liftBool
22+ , liftBool
23− , Test(..)
23+ , Test(..)
24− , Prop.Result(..)
24+ , Prop.Result(..)
25− )
25+ )
26− where
26+where
27
27
28import Control.Exception ( bracket, try, SomeException(..) )
28import Control.Exception ( bracket, try, SomeException(..) )
29
29
@@ -35,7 +35,7 @@ import qualified Test.QuickCheck as QC
35import Test.QuickCheck.Modifiers
35import Test.QuickCheck.Modifiers
36import Test.QuickCheck.Property hiding ( Result(..) )
36import Test.QuickCheck.Property hiding ( Result(..) )
37import qualified Test.QuickCheck.Property as Prop
37import qualified Test.QuickCheck.Property as Prop
38−import Test.QuickCheck.Monadic ( monadicIO )
38+import Test.QuickCheck.Monadic ( monadicIO )
39
39
40import Text.Printf
40import Text.Printf
41
41
@@ -56,7 +56,7 @@ verify testName p = Test $ TestInstance
56 case qcResult of
56 case qcResult of
57 QC.Success {..} -> return $ Finished TS.Pass
57 QC.Success {..} -> return $ Finished TS.Pass
58 QC.Failure {numShrinks,reason} -> return $ Finished
58 QC.Failure {numShrinks,reason} -> return $ Finished
59− $ TS.Fail $ "After "
59+ $ TS.Fail $ "After "
60 ++ show numShrinks ++ " shrinks determined failure to be: "
60 ++ show numShrinks ++ " shrinks determined failure to be: "
61 ++ show reason
61 ++ show reason
62 _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"
62 _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"
@@ -85,16 +85,5 @@ instance Arbitrary DoubleColumnChar where
85liftIOResult :: Testable prop => IO prop -> Property
85liftIOResult :: Testable prop => IO prop -> Property
86liftIOResult = ioProperty
86liftIOResult = ioProperty
87
87
88−#if __GLASGOW_HASKELL__ <= 701
89−instance Random Word where
90− random g =
91− let (i :: Int, g') = random g
92− in (toEnum i, g')
93− randomR (l,h) g =
94− let (i :: Int, g') = randomR (fromEnum l,fromEnum h) g
95− in (toEnum i, g')
96−#endif
97−
98data Bench where
88data Bench where
99 Bench :: forall v . NFData v => IO v -> (v -> IO ()) -> Bench
89 Bench :: forall v . NFData v => IO v -> (v -> IO ()) -> Bench
100−
test/Verify/Data/Terminfo/Parse.hs+17−17
@@ -1,7 +1,8 @@
1−module Verify.Data.Terminfo.Parse ( module Verify.Data.Terminfo.Parse
1+module Verify.Data.Terminfo.Parse
2+ ( module Verify.Data.Terminfo.Parse
2− , module Data.Terminfo.Parse
3+ , module Data.Terminfo.Parse
3− )
4+ )
4− where
5+where
5
6
6import Data.Terminfo.Parse
7import Data.Terminfo.Parse
7import Data.Terminfo.Eval
8import Data.Terminfo.Eval
@@ -19,22 +20,22 @@ data NonParamCapString = NonParamCapString String
19 deriving Show
20 deriving Show
20
21
21instance Arbitrary NonParamCapString where
22instance Arbitrary NonParamCapString where
22− arbitrary
23+ arbitrary
23 = ( do
24 = ( do
24 s <- listOf1 $ (choose (0, 255) >>= return . toEnum) `suchThat` (/= '%')
25 s <- listOf1 $ (choose (0, 255) >>= return . toEnum) `suchThat` (/= '%')
25 return $ NonParamCapString s
26 return $ NonParamCapString s
26− ) `suchThat` ( \(NonParamCapString str) -> length str < 255 )
27+ ) `suchThat` ( \(NonParamCapString str) -> length str < 255 )
27
28
28data LiteralPercentCap = LiteralPercentCap String [Word8]
29data LiteralPercentCap = LiteralPercentCap String [Word8]
29 deriving ( Show )
30 deriving ( Show )
30
31
31instance Arbitrary LiteralPercentCap where
32instance Arbitrary LiteralPercentCap where
32− arbitrary
33+ arbitrary
33 = ( do
34 = ( do
34 NonParamCapString s <- arbitrary
35 NonParamCapString s <- arbitrary
35 (s', bytes) <- insertEscapeOp "%" [toEnum $ fromEnum '%'] s
36 (s', bytes) <- insertEscapeOp "%" [toEnum $ fromEnum '%'] s
36 return $ LiteralPercentCap s' bytes
37 return $ LiteralPercentCap s' bytes
37− ) `suchThat` ( \(LiteralPercentCap str _) -> length str < 255 )
38+ ) `suchThat` ( \(LiteralPercentCap str _) -> length str < 255 )
38
39
39data IncFirstTwoCap = IncFirstTwoCap String [Word8]
40data IncFirstTwoCap = IncFirstTwoCap String [Word8]
40 deriving ( Show )
41 deriving ( Show )
@@ -45,7 +46,7 @@ instance Arbitrary IncFirstTwoCap where
45 NonParamCapString s <- arbitrary
46 NonParamCapString s <- arbitrary
46 (s', bytes) <- insertEscapeOp "i" [] s
47 (s', bytes) <- insertEscapeOp "i" [] s
47 return $ IncFirstTwoCap s' bytes
48 return $ IncFirstTwoCap s' bytes
48− ) `suchThat` ( \(IncFirstTwoCap str _) -> length str < 255 )
49+ ) `suchThat` ( \(IncFirstTwoCap str _) -> length str < 255 )
49
50
50data PushParamCap = PushParamCap String Int [Word8]
51data PushParamCap = PushParamCap String Int [Word8]
51 deriving ( Show )
52 deriving ( Show )
@@ -57,7 +58,7 @@ instance Arbitrary PushParamCap where
57 n <- choose (1,9)
58 n <- choose (1,9)
58 (s', bytes) <- insertEscapeOp ("p" ++ show n) [] s
59 (s', bytes) <- insertEscapeOp ("p" ++ show n) [] s
59 return $ PushParamCap s' n bytes
60 return $ PushParamCap s' n bytes
60− ) `suchThat` ( \(PushParamCap str _ _) -> length str < 255 )
61+ ) `suchThat` ( \(PushParamCap str _ _) -> length str < 255 )
61
62
62data DecPrintCap = DecPrintCap String Int [Word8]
63data DecPrintCap = DecPrintCap String Int [Word8]
63 deriving ( Show )
64 deriving ( Show )
@@ -69,7 +70,7 @@ instance Arbitrary DecPrintCap where
69 n <- choose (1,9)
70 n <- choose (1,9)
70 (s', bytes) <- insertEscapeOp ("p" ++ show n ++ "%d") [] s
71 (s', bytes) <- insertEscapeOp ("p" ++ show n ++ "%d") [] s
71 return $ DecPrintCap s' n bytes
72 return $ DecPrintCap s' n bytes
72− ) `suchThat` ( \(DecPrintCap str _ _) -> length str < 255 )
73+ ) `suchThat` ( \(DecPrintCap str _ _) -> length str < 255 )
73
74
74insertEscapeOp opStr replBytes s = do
75insertEscapeOp opStr replBytes s = do
75 insertPoints <- listOf1 $ elements [0 .. length s - 1]
76 insertPoints <- listOf1 $ elements [0 .. length s - 1]
@@ -92,21 +93,20 @@ bytesForRange cap offset count
92 = Vector.toList $ Vector.take count $ Vector.drop offset $ capBytes cap
93 = Vector.toList $ Vector.take count $ Vector.drop offset $ capBytes cap
93
94
94collectBytes :: CapExpression -> [Word8]
95collectBytes :: CapExpression -> [Word8]
95−collectBytes e = concat [ bytes
96+collectBytes e = concat [ bytes
96 | Bytes offset count <- capOps e
97 | Bytes offset count <- capOps e
97 , let bytes = bytesForRange e offset count
98 , let bytes = bytesForRange e offset count
98 ]
99 ]
99−
100+
100
101
101verifyBytesEqual :: [Word8] -> [Word8] -> Result
102verifyBytesEqual :: [Word8] -> [Word8] -> Result
102−verifyBytesEqual outBytes expectedBytes
103+verifyBytesEqual outBytes expectedBytes
103 = if outBytes == expectedBytes
104 = if outBytes == expectedBytes
104 then succeeded
105 then succeeded
105− else failed
106+ else failed
106− { reason = "outBytes ["
107+ { reason = "outBytes ["
107 ++ hexDump outBytes
108 ++ hexDump outBytes
108 ++ "] /= expectedBytes ["
109 ++ "] /= expectedBytes ["
109 ++ hexDump expectedBytes
110 ++ hexDump expectedBytes
110 ++ "]"
111 ++ "]"
111 }
112 }
112−
test/Verify/Graphics/Vty/Attributes.hs+7−7
@@ -1,7 +1,8 @@
1−module Verify.Graphics.Vty.Attributes ( module Verify.Graphics.Vty.Attributes
1+module Verify.Graphics.Vty.Attributes
2+ ( module Verify.Graphics.Vty.Attributes
2− , module Graphics.Vty.Attributes
3+ , module Graphics.Vty.Attributes
3− )
4+ )
4− where
5+where
5
6
6import Graphics.Vty.Attributes
7import Graphics.Vty.Attributes
7import Verify
8import Verify
@@ -40,8 +41,8 @@ allStyles =
40
41
41-- Limit the possible attributes to just a few for now.
42-- Limit the possible attributes to just a few for now.
42possibleAttrMods :: [ AttrOp ]
43possibleAttrMods :: [ AttrOp ]
43−possibleAttrMods =
44+possibleAttrMods =
44− [ idOp
45+ [ idOp
45 ] ++ map setForeColorOp allColors
46 ] ++ map setForeColorOp allColors
46 ++ map setBackColorOp allColors
47 ++ map setBackColorOp allColors
47 ++ map setStyleOp allStyles
48 ++ map setStyleOp allStyles
@@ -70,4 +71,3 @@ idOp = AttrOp "id" id
70
71
71applyOp :: AttrOp -> Attr -> Attr
72applyOp :: AttrOp -> Attr -> Attr
72applyOp (AttrOp _ f) a = f a
73applyOp (AttrOp _ f) a = f a
73−
test/Verify/Graphics/Vty/DisplayAttributes.hs+5−5
@@ -1,8 +1,8 @@
1−module Verify.Graphics.Vty.DisplayAttributes ( module Verify.Graphics.Vty.DisplayAttributes
1+module Verify.Graphics.Vty.DisplayAttributes
2+ ( module Verify.Graphics.Vty.DisplayAttributes
2− , module Graphics.Vty.DisplayAttributes
3+ , module Graphics.Vty.DisplayAttributes
3− )
4+ )
4− where
5+where
5
6
6import Graphics.Vty.DisplayAttributes
7import Graphics.Vty.DisplayAttributes
7import Verify
8import Verify
8−
test/Verify/Graphics/Vty/Image.hs+18−18
@@ -1,10 +1,11 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3{-# LANGUAGE DisambiguateRecordFields #-}
3{-# LANGUAGE DisambiguateRecordFields #-}
4−module Verify.Graphics.Vty.Image ( module Verify.Graphics.Vty.Image
4+module Verify.Graphics.Vty.Image
5+ ( module Verify.Graphics.Vty.Image
5− , module Graphics.Vty.Image
6+ , module Graphics.Vty.Image
6− )
7+ )
7− where
8+where
8
9
9import Verify.Graphics.Vty.Attributes
10import Verify.Graphics.Vty.Attributes
10import Graphics.Vty.Image
11import Graphics.Vty.Image
@@ -26,7 +27,7 @@ instance Show UnitImage where
26data DefaultImage = DefaultImage Image
27data DefaultImage = DefaultImage Image
27
28
28instance Show DefaultImage where
29instance Show DefaultImage where
29− show (DefaultImage i)
30+ show (DefaultImage i)
30 = "DefaultImage (" ++ show i ++ ") " ++ show (imageWidth i, imageHeight i)
31 = "DefaultImage (" ++ show i ++ ") " ++ show (imageWidth i, imageHeight i)
31
32
32instance Arbitrary DefaultImage where
33instance Arbitrary DefaultImage where
@@ -34,15 +35,15 @@ instance Arbitrary DefaultImage where
34 i <- return $ char defAttr 'X'
35 i <- return $ char defAttr 'X'
35 return $ DefaultImage i
36 return $ DefaultImage i
36
37
37−data SingleRowSingleAttrImage
38+data SingleRowSingleAttrImage
38− = SingleRowSingleAttrImage
39+ = SingleRowSingleAttrImage
39 { expectedAttr :: Attr
40 { expectedAttr :: Attr
40 , expectedColumns :: Int
41 , expectedColumns :: Int
41 , rowImage :: Image
42 , rowImage :: Image
42 }
43 }
43
44
44instance Show SingleRowSingleAttrImage where
45instance Show SingleRowSingleAttrImage where
45− show (SingleRowSingleAttrImage attr columns image)
46+ show (SingleRowSingleAttrImage attr columns image)
46 = "SingleRowSingleAttrImage (" ++ show attr ++ ") " ++ show columns ++ " ( " ++ show image ++ " )"
47 = "SingleRowSingleAttrImage (" ++ show attr ++ ") " ++ show columns ++ " ( " ++ show image ++ " )"
47
48
48newtype WidthResize = WidthResize (Image -> (Image, Int))
49newtype WidthResize = WidthResize (Image -> (Image, Int))
@@ -84,14 +85,14 @@ instance Arbitrary ImageResize where
84 , do
85 , do
85 ImageResize f <- arbitrary
86 ImageResize f <- arbitrary
86 WidthResize g <- arbitrary
87 WidthResize g <- arbitrary
87− return $! ImageResize $! \i ->
88+ return $! ImageResize $! \i ->
88 let (i0, (_, outHeight)) = f i
89 let (i0, (_, outHeight)) = f i
89 gI = g i0
90 gI = g i0
90 in (fst gI, (snd gI, outHeight))
91 in (fst gI, (snd gI, outHeight))
91 , do
92 , do
92 ImageResize f <- arbitrary
93 ImageResize f <- arbitrary
93 HeightResize g <- arbitrary
94 HeightResize g <- arbitrary
94− return $! ImageResize $! \i ->
95+ return $! ImageResize $! \i ->
95 let (i0, (outWidth, _)) = f i
96 let (i0, (outWidth, _)) = f i
96 gI = g i0
97 gI = g i0
97 in (fst gI, (outWidth, snd gI))
98 in (fst gI, (outWidth, snd gI))
@@ -100,17 +101,17 @@ instance Arbitrary ImageResize where
100
101
101instance Arbitrary SingleRowSingleAttrImage where
102instance Arbitrary SingleRowSingleAttrImage where
102 arbitrary = do
103 arbitrary = do
103− -- The text must contain at least one character. Otherwise the image simplifies to the
104+ -- The text must contain at least one character. Otherwise the
105+ -- image simplifies to the IdImage which has a height of 0. If
104− -- IdImage which has a height of 0. If this is to represent a single row then the height
106+ -- this is to represent a single row then the height must be 1
105− -- must be 1
106 singleColumnRowText <- Verify.resize 16 (listOf1 arbitrary)
107 singleColumnRowText <- Verify.resize 16 (listOf1 arbitrary)
107 a <- arbitrary
108 a <- arbitrary
108 let outImage = horizCat $ [char a c | SingleColumnChar c <- singleColumnRowText]
109 let outImage = horizCat $ [char a c | SingleColumnChar c <- singleColumnRowText]
109 outWidth = length singleColumnRowText
110 outWidth = length singleColumnRowText
110 return $ SingleRowSingleAttrImage a outWidth outImage
111 return $ SingleRowSingleAttrImage a outWidth outImage
111
112
112−data SingleRowTwoAttrImage
113+data SingleRowTwoAttrImage
113− = SingleRowTwoAttrImage
114+ = SingleRowTwoAttrImage
114 { part0 :: SingleRowSingleAttrImage
115 { part0 :: SingleRowSingleAttrImage
115 , part1 :: SingleRowSingleAttrImage
116 , part1 :: SingleRowSingleAttrImage
116 , joinImage :: Image
117 , joinImage :: Image
@@ -122,8 +123,8 @@ instance Arbitrary SingleRowTwoAttrImage where
122 p1 <- arbitrary
123 p1 <- arbitrary
123 return $ SingleRowTwoAttrImage p0 p1 (rowImage p0 <|> rowImage p1)
124 return $ SingleRowTwoAttrImage p0 p1 (rowImage p0 <|> rowImage p1)
124
125
125−data SingleAttrSingleSpanStack = SingleAttrSingleSpanStack
126+data SingleAttrSingleSpanStack = SingleAttrSingleSpanStack
126− { stackImage :: Image
127+ { stackImage :: Image
127 , stackSourceImages :: [SingleRowSingleAttrImage]
128 , stackSourceImages :: [SingleRowSingleAttrImage]
128 , stackWidth :: Int
129 , stackWidth :: Int
129 , stackHeight :: Int
130 , stackHeight :: Int
@@ -215,4 +216,3 @@ instance Arbitrary Translation where
215 y <- arbitrary `suchThat` (> 0)
216 y <- arbitrary `suchThat` (> 0)
216 let i' = translate x y i
217 let i' = translate x y i
217 return $ Translation i (x,y) i'
218 return $ Translation i (x,y) i'
218−
test/Verify/Graphics/Vty/Output.hs+5−7
@@ -4,18 +4,17 @@ import Control.Applicative ((<$>))
4
4
5import Graphics.Vty.Output.Mock
5import Graphics.Vty.Output.Mock
6
6
7−import qualified Data.ByteString as BS
8import Data.IORef
7import Data.IORef
9import qualified Data.String.UTF8 as UTF8
8import qualified Data.String.UTF8 as UTF8
10
9
11import Test.QuickCheck.Property
10import Test.QuickCheck.Property
12
11
13−-- A list of terminals that should be supported.
12+-- A list of terminals that should be supported. This started with a
14−-- This started with a list of terminals ubuntu supported. Then those terminals that really could
13+-- list of terminals ubuntu supported. Then those terminals that really
15−-- not be supported were removed. Then a few more were pruned until a reasonable looking set was
14+-- could not be supported were removed. Then a few more were pruned
16−-- made.
15+-- until a reasonable looking set was made.
17terminalsOfInterest :: [String]
16terminalsOfInterest :: [String]
18−terminalsOfInterest =
17+terminalsOfInterest =
19 [ "vt100"
18 [ "vt100"
20 , "vt220"
19 , "vt220"
21 , "vt102"
20 , "vt102"
@@ -58,4 +57,3 @@ compareMockOutput mockData expectedStr = do
58 ++ expectedStr
57 ++ expectedStr
59 }
58 }
60 else return succeeded
59 else return succeeded
61−
test/Verify/Graphics/Vty/Picture.hs+13−13
@@ -1,8 +1,9 @@
1{-# LANGUAGE DisambiguateRecordFields #-}
1{-# LANGUAGE DisambiguateRecordFields #-}
2−module Verify.Graphics.Vty.Picture ( module Verify.Graphics.Vty.Picture
2+module Verify.Graphics.Vty.Picture
3+ ( module Verify.Graphics.Vty.Picture
3− , module Graphics.Vty.Picture
4+ , module Graphics.Vty.Picture
4− )
5+ )
5− where
6+where
6
7
7import Verify.Graphics.Vty.Prelude
8import Verify.Graphics.Vty.Prelude
8
9
@@ -14,9 +15,9 @@ import Verify.Graphics.Vty.Image
14
15
15import Verify
16import Verify
16
17
17−data DefaultPic = DefaultPic
18+data DefaultPic = DefaultPic
18 { defaultPic :: Picture
19 { defaultPic :: Picture
19− , defaultWin :: MockWindow
20+ , defaultWin :: MockWindow
20 }
21 }
21
22
22instance Show DefaultPic where
23instance Show DefaultPic where
@@ -27,10 +28,10 @@ instance Arbitrary DefaultPic where
27 arbitrary = do
28 arbitrary = do
28 DefaultImage image <- arbitrary
29 DefaultImage image <- arbitrary
29 let win = MockWindow (imageWidth image) (imageHeight image)
30 let win = MockWindow (imageWidth image) (imageHeight image)
30− return $ DefaultPic (picForImage image)
31+ return $ DefaultPic (picForImage image)
31− win
32+ win
32
33
33−data PicWithBGAttr = PicWithBGAttr
34+data PicWithBGAttr = PicWithBGAttr
34 { withAttrPic :: Picture
35 { withAttrPic :: Picture
35 , withAttrWin :: MockWindow
36 , withAttrWin :: MockWindow
36 , withAttrSpecifiedAttr :: Attr
37 , withAttrSpecifiedAttr :: Attr
@@ -41,12 +42,11 @@ instance Arbitrary PicWithBGAttr where
41 DefaultImage image <- arbitrary
42 DefaultImage image <- arbitrary
42 let win = MockWindow (imageWidth image) (imageHeight image)
43 let win = MockWindow (imageWidth image) (imageHeight image)
43 attr <- arbitrary
44 attr <- arbitrary
44− return $ PicWithBGAttr (picForImage image)
45+ return $ PicWithBGAttr (picForImage image)
45− win
46+ win
46 attr
47 attr
47−
48+
48instance Arbitrary Picture where
49instance Arbitrary Picture where
49 arbitrary = do
50 arbitrary = do
50 layers <- Verify.resize 20 (listOf1 arbitrary)
51 layers <- Verify.resize 20 (listOf1 arbitrary)
51 return $ picForLayers layers
52 return $ picForLayers layers
52−
test/Verify/Graphics/Vty/Prelude.hs+5−7
@@ -1,10 +1,8 @@
1−module Verify.Graphics.Vty.Prelude ( module Verify.Graphics.Vty.Prelude
1+module Verify.Graphics.Vty.Prelude
2− , module Graphics.Vty.Prelude
2+ ( module Verify.Graphics.Vty.Prelude
3− , MockWindow(..)
3+ , MockWindow(..)
4− )
4+ )
5− where
5+where
6−
7−import Graphics.Vty.Prelude
8
6
9import Graphics.Vty.Debug
7import Graphics.Vty.Debug
10
8
test/Verify/Graphics/Vty/Span.hs+7−7
@@ -1,8 +1,9 @@
1{-# LANGUAGE FlexibleInstances #-}
1{-# LANGUAGE FlexibleInstances #-}
2−module Verify.Graphics.Vty.Span ( module Verify.Graphics.Vty.Span
2+module Verify.Graphics.Vty.Span
3+ ( module Verify.Graphics.Vty.Span
3− , module Graphics.Vty.Span
4+ , module Graphics.Vty.Span
4− )
5+ )
5− where
6+where
6
7
7import Graphics.Vty.Debug
8import Graphics.Vty.Debug
8import Graphics.Vty.Span
9import Graphics.Vty.Span
@@ -19,10 +20,10 @@ isAttrSpanOp TextSpan {} = True
19isAttrSpanOp _ = False
20isAttrSpanOp _ = False
20
21
21verifyAllSpansHaveWidth i spans w
22verifyAllSpansHaveWidth i spans w
22− = let v = map (\s -> (spanOpsEffectedColumns s /= w, s)) (Vector.toList spans)
23+ = let v = map (\s -> (spanOpsAffectedColumns s /= w, s)) (Vector.toList spans)
23 in case any ((== True) . fst) v of
24 in case any ((== True) . fst) v of
24 False -> succeeded
25 False -> succeeded
25− True -> failed { reason = "Not all spans contained operations defining exactly "
26+ True -> failed { reason = "Not all spans contained operations defining exactly "
26 ++ show w
27 ++ show w
27 ++ " columns of output - \n"
28 ++ " columns of output - \n"
28 ++ (concatMap ((++ "\n") . show)) v
29 ++ (concatMap ((++ "\n") . show)) v
@@ -34,4 +35,3 @@ verifyOpsEquality i_ops i_alt_ops =
34 else failed { reason = "ops for alternate image " ++ show i_alt_ops
35 else failed { reason = "ops for alternate image " ++ show i_alt_ops
35 ++ " are not the same as " ++ show i_ops
36 ++ " are not the same as " ++ show i_ops
36 }
37 }
37−
test/VerifyAttributeOps.hsdeleted−9
@@ -1,9 +0,0 @@
1−module VerifyAttributeOps where
2−
3−import Verify.Graphics.Vty.Attributes
4−
5−import Verify
6−
7−tests :: IO [Test]
8−tests = return []
9−
test/VerifyColor240.hsnew file+269
@@ -0,0 +1,269 @@
1+module VerifyColor240 where
2+
3+import Data.Word(Word8)
4+import Verify
5+
6+import Graphics.Vty.Attributes.Color240(rgbColorToColor240)
7+
8+oldRgbColorToColor240 :: Integral i => i -> i -> i -> Word8
9+oldRgbColorToColor240 r g b
10+ | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"
11+ | r == 8 && g == 8 && b == 8 = 216
12+ | r == 18 && g == 18 && b == 18 = 217
13+ | r == 28 && g == 28 && b == 28 = 218
14+ | r == 38 && g == 38 && b == 38 = 219
15+ | r == 48 && g == 48 && b == 48 = 220
16+ | r == 58 && g == 58 && b == 58 = 221
17+ | r == 68 && g == 68 && b == 68 = 222
18+ | r == 78 && g == 78 && b == 78 = 223
19+ | r == 88 && g == 88 && b == 88 = 224
20+ | r == 98 && g == 98 && b == 98 = 225
21+ | r == 108 && g == 108 && b == 108 = 226
22+ | r == 118 && g == 118 && b == 118 = 227
23+ | r == 128 && g == 128 && b == 128 = 228
24+ | r == 138 && g == 138 && b == 138 = 229
25+ | r == 148 && g == 148 && b == 148 = 230
26+ | r == 158 && g == 158 && b == 158 = 231
27+ | r == 168 && g == 168 && b == 168 = 232
28+ | r == 178 && g == 178 && b == 178 = 233
29+ | r == 188 && g == 188 && b == 188 = 234
30+ | r == 198 && g == 198 && b == 198 = 235
31+ | r == 208 && g == 208 && b == 208 = 236
32+ | r == 218 && g == 218 && b == 218 = 237
33+ | r == 228 && g == 228 && b == 228 = 238
34+ | r == 238 && g == 238 && b == 238 = 239
35+ | r <= 0 && g <= 0 && b <= 0 = 0
36+ | r <= 0 && g <= 0 && b <= 95 = 1
37+ | r <= 0 && g <= 0 && b <= 135 = 2
38+ | r <= 0 && g <= 0 && b <= 175 = 3
39+ | r <= 0 && g <= 0 && b <= 215 = 4
40+ | r <= 0 && g <= 0 && b <= 255 = 5
41+ | r <= 0 && g <= 95 && b <= 0 = 6
42+ | r <= 0 && g <= 95 && b <= 95 = 7
43+ | r <= 0 && g <= 95 && b <= 135 = 8
44+ | r <= 0 && g <= 95 && b <= 175 = 9
45+ | r <= 0 && g <= 95 && b <= 215 = 10
46+ | r <= 0 && g <= 95 && b <= 255 = 11
47+ | r <= 0 && g <= 135 && b <= 0 = 12
48+ | r <= 0 && g <= 135 && b <= 95 = 13
49+ | r <= 0 && g <= 135 && b <= 135 = 14
50+ | r <= 0 && g <= 135 && b <= 175 = 15
51+ | r <= 0 && g <= 135 && b <= 215 = 16
52+ | r <= 0 && g <= 135 && b <= 255 = 17
53+ | r <= 0 && g <= 175 && b <= 0 = 18
54+ | r <= 0 && g <= 175 && b <= 95 = 19
55+ | r <= 0 && g <= 175 && b <= 135 = 20
56+ | r <= 0 && g <= 175 && b <= 175 = 21
57+ | r <= 0 && g <= 175 && b <= 215 = 22
58+ | r <= 0 && g <= 175 && b <= 255 = 23
59+ | r <= 0 && g <= 215 && b <= 0 = 24
60+ | r <= 0 && g <= 215 && b <= 95 = 25
61+ | r <= 0 && g <= 215 && b <= 135 = 26
62+ | r <= 0 && g <= 215 && b <= 175 = 27
63+ | r <= 0 && g <= 215 && b <= 215 = 28
64+ | r <= 0 && g <= 215 && b <= 255 = 29
65+ | r <= 0 && g <= 255 && b <= 0 = 30
66+ | r <= 0 && g <= 255 && b <= 95 = 31
67+ | r <= 0 && g <= 255 && b <= 135 = 32
68+ | r <= 0 && g <= 255 && b <= 175 = 33
69+ | r <= 0 && g <= 255 && b <= 215 = 34
70+ | r <= 0 && g <= 255 && b <= 255 = 35
71+ | r <= 95 && g <= 0 && b <= 0 = 36
72+ | r <= 95 && g <= 0 && b <= 95 = 37
73+ | r <= 95 && g <= 0 && b <= 135 = 38
74+ | r <= 95 && g <= 0 && b <= 175 = 39
75+ | r <= 95 && g <= 0 && b <= 215 = 40
76+ | r <= 95 && g <= 0 && b <= 255 = 41
77+ | r <= 95 && g <= 95 && b <= 0 = 42
78+ | r <= 95 && g <= 95 && b <= 95 = 43
79+ | r <= 95 && g <= 95 && b <= 135 = 44
80+ | r <= 95 && g <= 95 && b <= 175 = 45
81+ | r <= 95 && g <= 95 && b <= 215 = 46
82+ | r <= 95 && g <= 95 && b <= 255 = 47
83+ | r <= 95 && g <= 135 && b <= 0 = 48
84+ | r <= 95 && g <= 135 && b <= 95 = 49
85+ | r <= 95 && g <= 135 && b <= 135 = 50
86+ | r <= 95 && g <= 135 && b <= 175 = 51
87+ | r <= 95 && g <= 135 && b <= 215 = 52
88+ | r <= 95 && g <= 135 && b <= 255 = 53
89+ | r <= 95 && g <= 175 && b <= 0 = 54
90+ | r <= 95 && g <= 175 && b <= 95 = 55
91+ | r <= 95 && g <= 175 && b <= 135 = 56
92+ | r <= 95 && g <= 175 && b <= 175 = 57
93+ | r <= 95 && g <= 175 && b <= 215 = 58
94+ | r <= 95 && g <= 175 && b <= 255 = 59
95+ | r <= 95 && g <= 215 && b <= 0 = 60
96+ | r <= 95 && g <= 215 && b <= 95 = 61
97+ | r <= 95 && g <= 215 && b <= 135 = 62
98+ | r <= 95 && g <= 215 && b <= 175 = 63
99+ | r <= 95 && g <= 215 && b <= 215 = 64
100+ | r <= 95 && g <= 215 && b <= 255 = 65
101+ | r <= 95 && g <= 255 && b <= 0 = 66
102+ | r <= 95 && g <= 255 && b <= 95 = 67
103+ | r <= 95 && g <= 255 && b <= 135 = 68
104+ | r <= 95 && g <= 255 && b <= 175 = 69
105+ | r <= 95 && g <= 255 && b <= 215 = 70
106+ | r <= 95 && g <= 255 && b <= 255 = 71
107+ | r <= 135 && g <= 0 && b <= 0 = 72
108+ | r <= 135 && g <= 0 && b <= 95 = 73
109+ | r <= 135 && g <= 0 && b <= 135 = 74
110+ | r <= 135 && g <= 0 && b <= 175 = 75
111+ | r <= 135 && g <= 0 && b <= 215 = 76
112+ | r <= 135 && g <= 0 && b <= 255 = 77
113+ | r <= 135 && g <= 95 && b <= 0 = 78
114+ | r <= 135 && g <= 95 && b <= 95 = 79
115+ | r <= 135 && g <= 95 && b <= 135 = 80
116+ | r <= 135 && g <= 95 && b <= 175 = 81
117+ | r <= 135 && g <= 95 && b <= 215 = 82
118+ | r <= 135 && g <= 95 && b <= 255 = 83
119+ | r <= 135 && g <= 135 && b <= 0 = 84
120+ | r <= 135 && g <= 135 && b <= 95 = 85
121+ | r <= 135 && g <= 135 && b <= 135 = 86
122+ | r <= 135 && g <= 135 && b <= 175 = 87
123+ | r <= 135 && g <= 135 && b <= 215 = 88
124+ | r <= 135 && g <= 135 && b <= 255 = 89
125+ | r <= 135 && g <= 175 && b <= 0 = 90
126+ | r <= 135 && g <= 175 && b <= 95 = 91
127+ | r <= 135 && g <= 175 && b <= 135 = 92
128+ | r <= 135 && g <= 175 && b <= 175 = 93
129+ | r <= 135 && g <= 175 && b <= 215 = 94
130+ | r <= 135 && g <= 175 && b <= 255 = 95
131+ | r <= 135 && g <= 215 && b <= 0 = 96
132+ | r <= 135 && g <= 215 && b <= 95 = 97
133+ | r <= 135 && g <= 215 && b <= 135 = 98
134+ | r <= 135 && g <= 215 && b <= 175 = 99
135+ | r <= 135 && g <= 215 && b <= 215 = 100
136+ | r <= 135 && g <= 215 && b <= 255 = 101
137+ | r <= 135 && g <= 255 && b <= 0 = 102
138+ | r <= 135 && g <= 255 && b <= 95 = 103
139+ | r <= 135 && g <= 255 && b <= 135 = 104
140+ | r <= 135 && g <= 255 && b <= 175 = 105
141+ | r <= 135 && g <= 255 && b <= 215 = 106
142+ | r <= 135 && g <= 255 && b <= 255 = 107
143+ | r <= 175 && g <= 0 && b <= 0 = 108
144+ | r <= 175 && g <= 0 && b <= 95 = 109
145+ | r <= 175 && g <= 0 && b <= 135 = 110
146+ | r <= 175 && g <= 0 && b <= 175 = 111
147+ | r <= 175 && g <= 0 && b <= 215 = 112
148+ | r <= 175 && g <= 0 && b <= 255 = 113
149+ | r <= 175 && g <= 95 && b <= 0 = 114
150+ | r <= 175 && g <= 95 && b <= 95 = 115
151+ | r <= 175 && g <= 95 && b <= 135 = 116
152+ | r <= 175 && g <= 95 && b <= 175 = 117
153+ | r <= 175 && g <= 95 && b <= 215 = 118
154+ | r <= 175 && g <= 95 && b <= 255 = 119
155+ | r <= 175 && g <= 135 && b <= 0 = 120
156+ | r <= 175 && g <= 135 && b <= 95 = 121
157+ | r <= 175 && g <= 135 && b <= 135 = 122
158+ | r <= 175 && g <= 135 && b <= 175 = 123
159+ | r <= 175 && g <= 135 && b <= 215 = 124
160+ | r <= 175 && g <= 135 && b <= 255 = 125
161+ | r <= 175 && g <= 175 && b <= 0 = 126
162+ | r <= 175 && g <= 175 && b <= 95 = 127
163+ | r <= 175 && g <= 175 && b <= 135 = 128
164+ | r <= 175 && g <= 175 && b <= 175 = 129
165+ | r <= 175 && g <= 175 && b <= 215 = 130
166+ | r <= 175 && g <= 175 && b <= 255 = 131
167+ | r <= 175 && g <= 215 && b <= 0 = 132
168+ | r <= 175 && g <= 215 && b <= 95 = 133
169+ | r <= 175 && g <= 215 && b <= 135 = 134
170+ | r <= 175 && g <= 215 && b <= 175 = 135
171+ | r <= 175 && g <= 215 && b <= 215 = 136
172+ | r <= 175 && g <= 215 && b <= 255 = 137
173+ | r <= 175 && g <= 255 && b <= 0 = 138
174+ | r <= 175 && g <= 255 && b <= 95 = 139
175+ | r <= 175 && g <= 255 && b <= 135 = 140
176+ | r <= 175 && g <= 255 && b <= 175 = 141
177+ | r <= 175 && g <= 255 && b <= 215 = 142
178+ | r <= 175 && g <= 255 && b <= 255 = 143
179+ | r <= 215 && g <= 0 && b <= 0 = 144
180+ | r <= 215 && g <= 0 && b <= 95 = 145
181+ | r <= 215 && g <= 0 && b <= 135 = 146
182+ | r <= 215 && g <= 0 && b <= 175 = 147
183+ | r <= 215 && g <= 0 && b <= 215 = 148
184+ | r <= 215 && g <= 0 && b <= 255 = 149
185+ | r <= 215 && g <= 95 && b <= 0 = 150
186+ | r <= 215 && g <= 95 && b <= 95 = 151
187+ | r <= 215 && g <= 95 && b <= 135 = 152
188+ | r <= 215 && g <= 95 && b <= 175 = 153
189+ | r <= 215 && g <= 95 && b <= 215 = 154
190+ | r <= 215 && g <= 95 && b <= 255 = 155
191+ | r <= 215 && g <= 135 && b <= 0 = 156
192+ | r <= 215 && g <= 135 && b <= 95 = 157
193+ | r <= 215 && g <= 135 && b <= 135 = 158
194+ | r <= 215 && g <= 135 && b <= 175 = 159
195+ | r <= 215 && g <= 135 && b <= 215 = 160
196+ | r <= 215 && g <= 135 && b <= 255 = 161
197+ | r <= 215 && g <= 175 && b <= 0 = 162
198+ | r <= 215 && g <= 175 && b <= 95 = 163
199+ | r <= 215 && g <= 175 && b <= 135 = 164
200+ | r <= 215 && g <= 175 && b <= 175 = 165
201+ | r <= 215 && g <= 175 && b <= 215 = 166
202+ | r <= 215 && g <= 175 && b <= 255 = 167
203+ | r <= 215 && g <= 215 && b <= 0 = 168
204+ | r <= 215 && g <= 215 && b <= 95 = 169
205+ | r <= 215 && g <= 215 && b <= 135 = 170
206+ | r <= 215 && g <= 215 && b <= 175 = 171
207+ | r <= 215 && g <= 215 && b <= 215 = 172
208+ | r <= 215 && g <= 215 && b <= 255 = 173
209+ | r <= 215 && g <= 255 && b <= 0 = 174
210+ | r <= 215 && g <= 255 && b <= 95 = 175
211+ | r <= 215 && g <= 255 && b <= 135 = 176
212+ | r <= 215 && g <= 255 && b <= 175 = 177
213+ | r <= 215 && g <= 255 && b <= 215 = 178
214+ | r <= 215 && g <= 255 && b <= 255 = 179
215+ | r <= 255 && g <= 0 && b <= 0 = 180
216+ | r <= 255 && g <= 0 && b <= 95 = 181
217+ | r <= 255 && g <= 0 && b <= 135 = 182
218+ | r <= 255 && g <= 0 && b <= 175 = 183
219+ | r <= 255 && g <= 0 && b <= 215 = 184
220+ | r <= 255 && g <= 0 && b <= 255 = 185
221+ | r <= 255 && g <= 95 && b <= 0 = 186
222+ | r <= 255 && g <= 95 && b <= 95 = 187
223+ | r <= 255 && g <= 95 && b <= 135 = 188
224+ | r <= 255 && g <= 95 && b <= 175 = 189
225+ | r <= 255 && g <= 95 && b <= 215 = 190
226+ | r <= 255 && g <= 95 && b <= 255 = 191
227+ | r <= 255 && g <= 135 && b <= 0 = 192
228+ | r <= 255 && g <= 135 && b <= 95 = 193
229+ | r <= 255 && g <= 135 && b <= 135 = 194
230+ | r <= 255 && g <= 135 && b <= 175 = 195
231+ | r <= 255 && g <= 135 && b <= 215 = 196
232+ | r <= 255 && g <= 135 && b <= 255 = 197
233+ | r <= 255 && g <= 175 && b <= 0 = 198
234+ | r <= 255 && g <= 175 && b <= 95 = 199
235+ | r <= 255 && g <= 175 && b <= 135 = 200
236+ | r <= 255 && g <= 175 && b <= 175 = 201
237+ | r <= 255 && g <= 175 && b <= 215 = 202
238+ | r <= 255 && g <= 175 && b <= 255 = 203
239+ | r <= 255 && g <= 215 && b <= 0 = 204
240+ | r <= 255 && g <= 215 && b <= 95 = 205
241+ | r <= 255 && g <= 215 && b <= 135 = 206
242+ | r <= 255 && g <= 215 && b <= 175 = 207
243+ | r <= 255 && g <= 215 && b <= 215 = 208
244+ | r <= 255 && g <= 215 && b <= 255 = 209
245+ | r <= 255 && g <= 255 && b <= 0 = 210
246+ | r <= 255 && g <= 255 && b <= 95 = 211
247+ | r <= 255 && g <= 255 && b <= 135 = 212
248+ | r <= 255 && g <= 255 && b <= 175 = 213
249+ | r <= 255 && g <= 255 && b <= 215 = 214
250+ | r <= 255 && g <= 255 && b <= 255 = 215
251+ | otherwise = error (printf "RGB color %d %d %d does not map to 240 palette."
252+ (fromIntegral r :: Int)
253+ (fromIntegral g :: Int)
254+ (fromIntegral b :: Int))
255+
256+sameColor :: Int -> Int -> Int -> Bool
257+sameColor r g b = oldRgbColorToColor240 r g b == rgbColorToColor240 r g b
258+
259+genVal :: Gen Int
260+genVal = chooseInt (0, 255)
261+
262+sameColorCheck :: Property
263+sameColorCheck = forAll genVal (\r -> forAll genVal (\g -> forAll genVal (sameColor r g)))
264+
265+tests :: IO [Test]
266+tests = return
267+ [ verify "check rgbColorToColor240 optimization is correct" sameColorCheck
268+ ]
269+
test/VerifyConfig.hs+1−3
@@ -4,7 +4,6 @@ module Main where
4import Graphics.Vty.Config
4import Graphics.Vty.Config
5import Graphics.Vty.Input.Events
5import Graphics.Vty.Input.Events
6
6
7−import Data.Default
8import Data.String.QQ
7import Data.String.QQ
9import qualified Data.ByteString.Char8 as B
8import qualified Data.ByteString.Char8 as B
10
9
@@ -24,7 +23,7 @@ debugLog "/tmp/vty-debug.txt"
24|]
23|]
25
24
26exampleConfigConfig :: Config
25exampleConfigConfig :: Config
27−exampleConfigConfig = def
26+exampleConfigConfig = defaultConfig
28 { debugLog = Just "/tmp/vty-debug.txt"
27 { debugLog = Just "/tmp/vty-debug.txt"
29 , inputMap = [ (Nothing, "\ESC[B", EvKey KUp [])
28 , inputMap = [ (Nothing, "\ESC[B", EvKey KUp [])
30 , (Nothing, "\ESC[1;3B", EvKey KDown [MAlt])
29 , (Nothing, "\ESC[1;3B", EvKey KDown [MAlt])
@@ -43,4 +42,3 @@ main :: IO ()
43main = defaultMain
42main = defaultMain
44 [ testCase "example config parses" $ exampleConfigParses
43 [ testCase "example config parses" $ exampleConfigParses
45 ]
44 ]
46−
test/VerifyCropSpanGeneration.hs+2−3
@@ -13,7 +13,7 @@ import Graphics.Vty.PictureToSpans
13
13
14import Verify
14import Verify
15
15
16−import qualified Data.Vector as Vector
16+import qualified Data.Vector as Vector
17
17
18cropOpDisplayOps :: (Int -> Image -> Image) ->
18cropOpDisplayOps :: (Int -> Image -> Image) ->
19 Int -> Image -> (DisplayOps, Image)
19 Int -> Image -> (DisplayOps, Image)
@@ -75,7 +75,7 @@ cropTopAndBottomRejoinedEquivalence stack = imageHeight (stackImage stack) `mod`
75 in displayOpsForImage i == displayOpsForImage iAlt
75 in displayOpsForImage i == displayOpsForImage iAlt
76
76
77tests :: IO [Test]
77tests :: IO [Test]
78−tests = return
78+tests = return
79 [ verify "cropping from the bottom produces display operations covering the expected rows"
79 [ verify "cropping from the bottom produces display operations covering the expected rows"
80 cropBottomOutputRows
80 cropBottomOutputRows
81 , verify "cropping from the top produces display operations covering the expected rows"
81 , verify "cropping from the top produces display operations covering the expected rows"
@@ -90,4 +90,3 @@ tests = return
90 , verify "the output of a stack is the same as that stack cropped top & bottom and joined together"
90 , verify "the output of a stack is the same as that stack cropped top & bottom and joined together"
91 cropTopAndBottomRejoinedEquivalence
91 cropTopAndBottomRejoinedEquivalence
92 ]
92 ]
93−
test/VerifyEmptyImageProps.hs+2−1
@@ -1,9 +1,10 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
1module VerifyEmptyImageProps where
2module VerifyEmptyImageProps where
2
3
3import Verify
4import Verify
4
5
5-- should be exported by Graphics.Vty.Picture
6-- should be exported by Graphics.Vty.Picture
6−import Graphics.Vty.Picture ( Image, emptyImage )
7+import Graphics.Vty.Image ( Image, emptyImage )
7
8
8tests :: IO [Test]
9tests :: IO [Test]
9tests = do
10tests = do
test/VerifyEvalTerminfoCaps.hs+14−12
@@ -1,9 +1,10 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
3module VerifyEvalTerminfoCaps where
4module VerifyEvalTerminfoCaps where
4
5
5import Blaze.ByteString.Builder.Internal.Write (runWrite, getBound)
6import Blaze.ByteString.Builder.Internal.Write (runWrite, getBound)
6−import Data.Terminfo.Eval
7+import Data.Terminfo.Eval
7import Data.Terminfo.Parse
8import Data.Terminfo.Parse
8import Control.DeepSeq
9import Control.DeepSeq
9
10
@@ -24,8 +25,9 @@ import Foreign.Marshal.Alloc (mallocBytes)
24import Foreign.Ptr (Ptr, minusPtr)
25import Foreign.Ptr (Ptr, minusPtr)
25import Numeric
26import Numeric
26
27
27−-- If a terminal defines one of the caps then it's expected to be parsable.
28+-- If a terminal defines one of the caps then it's expected to be
29+-- parsable.
28−capsOfInterest =
30+capsOfInterest =
29 [ "cup"
31 [ "cup"
30 , "sc"
32 , "sc"
31 , "rc"
33 , "rc"
@@ -49,12 +51,13 @@ fromCapname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr na
49
51
50tests :: IO [Test]
52tests :: IO [Test]
51tests = do
53tests = do
52− evalBuffer :: Ptr Word8 <- mallocBytes (1024 * 1024) -- Should be big enough for any termcaps ;-)
54+ -- 1 MB should be big enough for any termcaps ;-)
55+ evalBuffer :: Ptr Word8 <- mallocBytes (1024 * 1024)
53 fmap concat $ forM terminalsOfInterest $ \termName -> do
56 fmap concat $ forM terminalsOfInterest $ \termName -> do
54 putStrLn $ "adding tests for terminal: " ++ termName
57 putStrLn $ "adding tests for terminal: " ++ termName
55 mti <- try $ Terminfo.setupTerm termName
58 mti <- try $ Terminfo.setupTerm termName
56 case mti of
59 case mti of
57− Left (_e :: SomeException)
60+ Left (_e :: SomeException)
58 -> return []
61 -> return []
59 Right ti -> do
62 Right ti -> do
60 fmap concat $ forM capsOfInterest $ \capName -> do
63 fmap concat $ forM capsOfInterest $ \capName -> do
@@ -71,7 +74,7 @@ tests = do
71{-# NOINLINE verifyEvalCap #-}
74{-# NOINLINE verifyEvalCap #-}
72verifyEvalCap :: Ptr Word8 -> CapExpression -> Int -> Property
75verifyEvalCap :: Ptr Word8 -> CapExpression -> Int -> Property
73verifyEvalCap evalBuffer expr !junkInt = do
76verifyEvalCap evalBuffer expr !junkInt = do
74− forAll (vector 9) $ \inputValues ->
77+ forAll (vector 9) $ \inputValues ->
75 let write = writeCapExpr expr inputValues
78 let write = writeCapExpr expr inputValues
76 !byteCount = getBound write
79 !byteCount = getBound write
77 in liftIOResult $ do
80 in liftIOResult $ do
@@ -79,12 +82,11 @@ verifyEvalCap evalBuffer expr !junkInt = do
79 forM_ [0..100] $ \i -> runWrite write startPtr
82 forM_ [0..100] $ \i -> runWrite write startPtr
80 endPtr <- runWrite write startPtr
83 endPtr <- runWrite write startPtr
81 case endPtr `minusPtr` startPtr of
84 case endPtr `minusPtr` startPtr of
82− count | count < 0 ->
85+ count | count < 0 ->
83 return $ failed { reason = "End pointer before start pointer." }
86 return $ failed { reason = "End pointer before start pointer." }
84− | toEnum count > byteCount ->
87+ | toEnum count > byteCount ->
85− return $ failed { reason = "End pointer past end of buffer by "
88+ return $ failed { reason = "End pointer past end of buffer by "
86− ++ show (toEnum count - byteCount)
89+ ++ show (toEnum count - byteCount)
87 }
90 }
88− | otherwise ->
91+ | otherwise ->
89 return succeeded
92 return succeeded
90−
test/VerifyImageOps.hs+27−27
@@ -1,3 +1,4 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
1module VerifyImageOps where
2module VerifyImageOps where
2
3
3import Graphics.Vty.Attributes
4import Graphics.Vty.Attributes
@@ -9,43 +10,43 @@ import Verify
9import Control.DeepSeq
10import Control.DeepSeq
10
11
11twoSwHorizConcat :: SingleColumnChar -> SingleColumnChar -> Bool
12twoSwHorizConcat :: SingleColumnChar -> SingleColumnChar -> Bool
12−twoSwHorizConcat (SingleColumnChar c1) (SingleColumnChar c2) =
13+twoSwHorizConcat (SingleColumnChar c1) (SingleColumnChar c2) =
13 imageWidth (char defAttr c1 <|> char defAttr c2) == 2
14 imageWidth (char defAttr c1 <|> char defAttr c2) == 2
14
15
15manySwHorizConcat :: [SingleColumnChar] -> Bool
16manySwHorizConcat :: [SingleColumnChar] -> Bool
16−manySwHorizConcat cs =
17+manySwHorizConcat cs =
17 let chars = [ char | SingleColumnChar char <- cs ]
18 let chars = [ char | SingleColumnChar char <- cs ]
18 l = fromIntegral $ length cs
19 l = fromIntegral $ length cs
19 in imageWidth ( horizCat $ map (char defAttr) chars ) == l
20 in imageWidth ( horizCat $ map (char defAttr) chars ) == l
20
21
21twoSwVertConcat :: SingleColumnChar -> SingleColumnChar -> Bool
22twoSwVertConcat :: SingleColumnChar -> SingleColumnChar -> Bool
22−twoSwVertConcat (SingleColumnChar c1) (SingleColumnChar c2) =
23+twoSwVertConcat (SingleColumnChar c1) (SingleColumnChar c2) =
23 imageHeight (char defAttr c1 <-> char defAttr c2) == 2
24 imageHeight (char defAttr c1 <-> char defAttr c2) == 2
24
25
25horizConcatSwAssoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool
26horizConcatSwAssoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool
26−horizConcatSwAssoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) =
27+horizConcatSwAssoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) =
27− (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2
28+ (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2
28− ==
29+ ==
29 char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2)
30 char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2)
30
31
31twoDwHorizConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool
32twoDwHorizConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool
32−twoDwHorizConcat (DoubleColumnChar c1) (DoubleColumnChar c2) =
33+twoDwHorizConcat (DoubleColumnChar c1) (DoubleColumnChar c2) =
33 imageWidth (char defAttr c1 <|> char defAttr c2) == 4
34 imageWidth (char defAttr c1 <|> char defAttr c2) == 4
34
35
35manyDwHorizConcat :: [DoubleColumnChar] -> Bool
36manyDwHorizConcat :: [DoubleColumnChar] -> Bool
36−manyDwHorizConcat cs =
37+manyDwHorizConcat cs =
37 let chars = [ char | DoubleColumnChar char <- cs ]
38 let chars = [ char | DoubleColumnChar char <- cs ]
38 l = fromIntegral $ length cs
39 l = fromIntegral $ length cs
39 in imageWidth ( horizCat $ map (char defAttr) chars ) == l * 2
40 in imageWidth ( horizCat $ map (char defAttr) chars ) == l * 2
40
41
41twoDwVertConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool
42twoDwVertConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool
42−twoDwVertConcat (DoubleColumnChar c1) (DoubleColumnChar c2) =
43+twoDwVertConcat (DoubleColumnChar c1) (DoubleColumnChar c2) =
43 imageHeight (char defAttr c1 <-> char defAttr c2) == 2
44 imageHeight (char defAttr c1 <-> char defAttr c2) == 2
44
45
45horizConcatDwAssoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool
46horizConcatDwAssoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool
46−horizConcatDwAssoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) =
47+horizConcatDwAssoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) =
47− (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2
48+ (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2
48− ==
49+ ==
49 char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2)
50 char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2)
50
51
51vertContatSingleRow :: NonEmptyList SingleRowSingleAttrImage -> Bool
52vertContatSingleRow :: NonEmptyList SingleRowSingleAttrImage -> Bool
@@ -54,9 +55,9 @@ vertContatSingleRow (NonEmpty stack) =
54 stackImage = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack ]
55 stackImage = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack ]
55 in imageHeight stackImage == expectedHeight
56 in imageHeight stackImage == expectedHeight
56
57
57−disjointHeightHorizJoin :: NonEmptyList SingleRowSingleAttrImage
58+disjointHeightHorizJoin :: NonEmptyList SingleRowSingleAttrImage
58− -> NonEmptyList SingleRowSingleAttrImage
59+ -> NonEmptyList SingleRowSingleAttrImage
59− -> Bool
60+ -> Bool
60disjointHeightHorizJoin (NonEmpty stack0) (NonEmpty stack1) =
61disjointHeightHorizJoin (NonEmpty stack0) (NonEmpty stack1) =
61 let expectedHeight :: Int = max (length stack0) (length stack1)
62 let expectedHeight :: Int = max (length stack0) (length stack1)
62 stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
63 stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
@@ -64,9 +65,9 @@ disjointHeightHorizJoin (NonEmpty stack0) (NonEmpty stack1) =
64 in imageHeight (stackImage0 <|> stackImage1) == expectedHeight
65 in imageHeight (stackImage0 <|> stackImage1) == expectedHeight
65
66
66
67
67−disjointHeightHorizJoinBgFill :: NonEmptyList SingleRowSingleAttrImage
68+disjointHeightHorizJoinBgFill :: NonEmptyList SingleRowSingleAttrImage
68− -> NonEmptyList SingleRowSingleAttrImage
69+ -> NonEmptyList SingleRowSingleAttrImage
69− -> Bool
70+ -> Bool
70disjointHeightHorizJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
71disjointHeightHorizJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
71 let stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
72 let stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
72 stackImage1 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
73 stackImage1 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
@@ -74,31 +75,31 @@ disjointHeightHorizJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
74 expectedHeight = imageHeight image
75 expectedHeight = imageHeight image
75 in case image of
76 in case image of
76 HorizJoin {} -> ( expectedHeight == (imageHeight $ partLeft image) )
77 HorizJoin {} -> ( expectedHeight == (imageHeight $ partLeft image) )
77− &&
78+ &&
78 ( expectedHeight == (imageHeight $ partRight image) )
79 ( expectedHeight == (imageHeight $ partRight image) )
79 _ -> True
80 _ -> True
80
81
81−disjointWidthVertJoin :: NonEmptyList SingleRowSingleAttrImage
82+disjointWidthVertJoin :: NonEmptyList SingleRowSingleAttrImage
82 -> NonEmptyList SingleRowSingleAttrImage
83 -> NonEmptyList SingleRowSingleAttrImage
83 -> Bool
84 -> Bool
84disjointWidthVertJoin (NonEmpty stack0) (NonEmpty stack1) =
85disjointWidthVertJoin (NonEmpty stack0) (NonEmpty stack1) =
85 let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)
86 let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)
86 stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
87 stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
87 stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
88 stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
88− stack0Image = vertCat stack0Images
89+ stack0Image = vertCat stack0Images
89− stack1Image = vertCat stack1Images
90+ stack1Image = vertCat stack1Images
90 image = stack0Image <-> stack1Image
91 image = stack0Image <-> stack1Image
91 in imageWidth image == expectedWidth
92 in imageWidth image == expectedWidth
92
93
93−disjointWidthVertJoinBgFill :: NonEmptyList SingleRowSingleAttrImage
94+disjointWidthVertJoinBgFill :: NonEmptyList SingleRowSingleAttrImage
94 -> NonEmptyList SingleRowSingleAttrImage
95 -> NonEmptyList SingleRowSingleAttrImage
95 -> Bool
96 -> Bool
96disjointWidthVertJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
97disjointWidthVertJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =
97 let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)
98 let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)
98 stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
99 stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]
99 stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
100 stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]
100− stack0Image = vertCat stack0Images
101+ stack0Image = vertCat stack0Images
101− stack1Image = vertCat stack1Images
102+ stack1Image = vertCat stack1Images
102 image = stack0Image <-> stack1Image
103 image = stack0Image <-> stack1Image
103 in case image of
104 in case image of
104 VertJoin {} -> ( expectedWidth == (imageWidth $ partTop image) )
105 VertJoin {} -> ( expectedWidth == (imageWidth $ partTop image) )
@@ -136,7 +137,7 @@ cropBottomLimitsHeight :: Image -> Int -> Property
136cropBottomLimitsHeight i v = v >= 0 ==>
137cropBottomLimitsHeight i v = v >= 0 ==>
137 v >= imageHeight (cropBottom v i)
138 v >= imageHeight (cropBottom v i)
138
139
139−-- rediculous tests just to satisfy my desire for nice code coverage :-P
140+-- ridiculous tests just to satisfy my desire for nice code coverage :-P
140canShowImage :: Image -> Bool
141canShowImage :: Image -> Bool
141canShowImage i = length (show i) > 0
142canShowImage i = length (show i) > 0
142
143
@@ -171,4 +172,3 @@ tests = return
171 , verify "can rnf image" canRnfImage
172 , verify "can rnf image" canRnfImage
172 , verify "can pp image" canPpImage
173 , verify "can pp image" canPpImage
173 ]
174 ]
174−
test/VerifyImageTrans.hs+1−1
@@ -22,7 +22,7 @@ verifyHorizContatWAttrChangeSimplifies :: SingleRowTwoAttrImage -> Bool
22verifyHorizContatWAttrChangeSimplifies ( SingleRowTwoAttrImage (SingleRowSingleAttrImage attr0 charCount0 _image0)
22verifyHorizContatWAttrChangeSimplifies ( SingleRowTwoAttrImage (SingleRowSingleAttrImage attr0 charCount0 _image0)
23 (SingleRowSingleAttrImage attr1 charCount1 _image1)
23 (SingleRowSingleAttrImage attr1 charCount1 _image1)
24 i
24 i
25− )
25+ )
26 | charCount0 == 0 || charCount1 == 0 || attr0 == attr1 = isHorizTextOfColumns i (charCount0 + charCount1)
26 | charCount0 == 0 || charCount1 == 0 || attr0 == attr1 = isHorizTextOfColumns i (charCount0 + charCount1)
27 | otherwise = False == isHorizTextOfColumns i (charCount0 + charCount1)
27 | otherwise = False == isHorizTextOfColumns i (charCount0 + charCount1)
28
28
test/VerifyLayersSpanGeneration.hs+9−7
@@ -4,6 +4,7 @@ module VerifyLayersSpanGeneration where
4
4
5import Verify.Graphics.Vty.Prelude
5import Verify.Graphics.Vty.Prelude
6
6
7+import Verify.Graphics.Vty.Attributes
7import Verify.Graphics.Vty.Image
8import Verify.Graphics.Vty.Image
8import Verify.Graphics.Vty.Picture
9import Verify.Graphics.Vty.Picture
9import Verify.Graphics.Vty.Span
10import Verify.Graphics.Vty.Span
@@ -13,7 +14,7 @@ import Graphics.Vty.PictureToSpans
13
14
14import Verify
15import Verify
15
16
16−import qualified Data.Vector as Vector
17+import qualified Data.Vector as Vector
17
18
18largerHorizSpanOcclusion :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
19largerHorizSpanOcclusion :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
19largerHorizSpanOcclusion row0 row1 =
20largerHorizSpanOcclusion row0 row1 =
@@ -25,8 +26,9 @@ largerHorizSpanOcclusion row0 row1 =
25 ops = displayOpsForPic p (imageWidth iLarger,imageHeight iLarger)
26 ops = displayOpsForPic p (imageWidth iLarger,imageHeight iLarger)
26 in verifyOpsEquality expectedOps ops
27 in verifyOpsEquality expectedOps ops
27
28
28−-- | Two rows stacked vertical is equivalent to the first row rendered as the top layer and the
29+-- | Two rows stacked vertical is equivalent to the first row rendered
30+-- as the top layer and the second row rendered as a bottom layer with a
29−-- second row rendered as a bottom layer with a background fill where the first row would be.
31+-- background fill where the first row would be.
30vertStackLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
32vertStackLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
31vertStackLayerEquivalence0 row0 row1 =
33vertStackLayerEquivalence0 row0 row1 =
32 let i0 = rowImage row0
34 let i0 = rowImage row0
@@ -52,8 +54,9 @@ vertStackLayerEquivalence1 row0 row1 =
52 opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
54 opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)
53 in verifyOpsEquality expectedOps opsLayered
55 in verifyOpsEquality expectedOps opsLayered
54
56
55−-- | Two rows horiz joined is equivalent to the first row rendered as the top layer and the
57+-- | Two rows horiz joined is equivalent to the first row rendered as
58+-- the top layer and the second row rendered as a bottom layer with a
56−-- second row rendered as a bottom layer with a background fill where the first row would be.
59+-- background fill where the first row would be.
57horizJoinLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
60horizJoinLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result
58horizJoinLayerEquivalence0 row0 row1 =
61horizJoinLayerEquivalence0 row0 row1 =
59 let i0 = rowImage row0
62 let i0 = rowImage row0
@@ -109,7 +112,7 @@ horizJoinAlternate1 =
109 in verifyOpsEquality expectedOps opsLayered
112 in verifyOpsEquality expectedOps opsLayered
110
113
111tests :: IO [Test]
114tests :: IO [Test]
112−tests = return
115+tests = return
113 [ verify "a larger horiz span occludes a smaller span on a lower layer"
116 [ verify "a larger horiz span occludes a smaller span on a lower layer"
114 largerHorizSpanOcclusion
117 largerHorizSpanOcclusion
115 , verify "two rows stack vertical equiv to first image layered on top of second with padding (0)"
118 , verify "two rows stack vertical equiv to first image layered on top of second with padding (0)"
@@ -125,4 +128,3 @@ tests = return
125 -- , verify "alternating images using joins is the same as alternating images using layers (1)"
128 -- , verify "alternating images using joins is the same as alternating images using layers (1)"
126 -- horizJoinAlternate1
129 -- horizJoinAlternate1
127 ]
130 ]
128−
test/VerifyOutput.hs+12−9
@@ -1,8 +1,9 @@
1−{- We setup the environment to envoke certain terminals of interest.
1+-- We setup the environment to invoke certain terminals of interest.
2− - This assumes appropriate definitions exist in the current environment for the terminals of
2+-- This assumes appropriate definitions exist in the current environment
3− - interest.
3+-- for the terminals of interest.
4− -}
4+{-# LANGUAGE ScopedTypeVariables #-}
5module VerifyOutput where
5module VerifyOutput where
6+
6import Verify
7import Verify
7
8
8import Graphics.Vty
9import Graphics.Vty
@@ -12,10 +13,7 @@ import Verify.Graphics.Vty.Output
12
13
13import Control.Monad
14import Control.Monad
14
15
15−import Data.Default
16−
17import qualified System.Console.Terminfo as Terminfo
16import qualified System.Console.Terminfo as Terminfo
18−import System.Posix.Env
19import System.Posix.IO
17import System.Posix.IO
20
18
21tests :: IO [Test]
19tests :: IO [Test]
@@ -37,11 +35,16 @@ smokeTestTermNonMac termName i = liftIOResult $ do
37smokeTestTerm :: String -> Image -> IO Result
35smokeTestTerm :: String -> Image -> IO Result
38smokeTestTerm termName i = do
36smokeTestTerm termName i = do
39 nullOut <- openFd "/dev/null" WriteOnly Nothing defaultFileFlags
37 nullOut <- openFd "/dev/null" WriteOnly Nothing defaultFileFlags
38+ t <- outputForConfig $ defaultConfig
39+ { outputFd = Just nullOut
40− t <- outputForConfig $ def { outputFd = Just nullOut, termName = Just termName }
40+ , termName = Just termName
41+ , colorMode = Just NoColor
42+ }
41 -- putStrLn $ "context color count: " ++ show (contextColorCount t)
43 -- putStrLn $ "context color count: " ++ show (contextColorCount t)
42 reserveDisplay t
44 reserveDisplay t
43 dc <- displayContext t (100,100)
45 dc <- displayContext t (100,100)
44− -- always show the cursor to produce tests for terminals with no cursor support.
46+ -- always show the cursor to produce tests for terminals with no
47+ -- cursor support.
45 let pic = (picForImage i) { picCursor = Cursor 0 0 }
48 let pic = (picForImage i) { picCursor = Cursor 0 0 }
46 outputPicture dc pic
49 outputPicture dc pic
47 setCursorPos t 0 0
50 setCursorPos t 0 0
test/VerifyParseTerminfoCaps.hs+4−4
@@ -1,4 +1,5 @@
1{-# LANGUAGE NamedFieldPuns #-}
1{-# LANGUAGE NamedFieldPuns #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
2module VerifyParseTerminfoCaps where
3module VerifyParseTerminfoCaps where
3
4
4import Prelude hiding ( catch )
5import Prelude hiding ( catch )
@@ -16,7 +17,7 @@ import Numeric
16
17
17-- If a terminal defines one of the caps then it's expected to be parsable.
18-- If a terminal defines one of the caps then it's expected to be parsable.
18-- TODO: reduce duplication with terminfo terminal implementation.
19-- TODO: reduce duplication with terminfo terminal implementation.
19−capsOfInterest =
20+capsOfInterest =
20 [ "cup"
21 [ "cup"
21 , "sc"
22 , "sc"
22 , "rc"
23 , "rc"
@@ -65,7 +66,7 @@ verifyParseCap capString onParse =
65 Right e -> onParse e
66 Right e -> onParse e
66
67
67nonParamaterizedCaps (NonParamCapString cap) = do
68nonParamaterizedCaps (NonParamCapString cap) = do
68− verifyParseCap cap $ \e ->
69+ verifyParseCap cap $ \e ->
69 let expectedBytes = map (toEnum . fromEnum) cap
70 let expectedBytes = map (toEnum . fromEnum) cap
70 outBytes = bytesForRange e 0 (length cap)
71 outBytes = bytesForRange e 0 (length cap)
71 in verifyBytesEqual outBytes expectedBytes
72 in verifyBytesEqual outBytes expectedBytes
@@ -75,7 +76,7 @@ literalPercentCaps (LiteralPercentCap capString expectedBytes) = do
75
76
76incFirstTwoCaps (IncFirstTwoCap capString expectedBytes) = do
77incFirstTwoCaps (IncFirstTwoCap capString expectedBytes) = do
77 verifyParseCap capString $ \e -> verifyBytesEqual (collectBytes e) expectedBytes
78 verifyParseCap capString $ \e -> verifyBytesEqual (collectBytes e) expectedBytes
78−
79+
79pushParamCaps (PushParamCap capString expectedParamCount expectedBytes) = do
80pushParamCaps (PushParamCap capString expectedParamCount expectedBytes) = do
80 verifyParseCap capString $ \e ->
81 verifyParseCap capString $ \e ->
81 let outBytes = collectBytes e
82 let outBytes = collectBytes e
@@ -98,4 +99,3 @@ printCap ti capName = do
98printExpression ti capName = do
99printExpression ti capName = do
99 let parseResult = parseCapExpression $ fromCapname ti capName
100 let parseResult = parseCapExpression $ fromCapname ti capName
100 putStrLn $ capName ++ ": " ++ show parseResult
101 putStrLn $ capName ++ ": " ++ show parseResult
101−
test/VerifySimpleSpanGeneration.hs+16−16
@@ -13,31 +13,31 @@ import Graphics.Vty.PictureToSpans
13
13
14import Verify
14import Verify
15
15
16−import qualified Data.Vector as Vector
16+import qualified Data.Vector as Vector
17
17
18unitImageAndZeroWindow0 :: UnitImage -> EmptyWindow -> Bool
18unitImageAndZeroWindow0 :: UnitImage -> EmptyWindow -> Bool
19−unitImageAndZeroWindow0 (UnitImage _ i) (EmptyWindow w) =
19+unitImageAndZeroWindow0 (UnitImage _ i) (EmptyWindow w) =
20 let p = picForImage i
20 let p = picForImage i
21 ops = displayOpsForPic p (regionForWindow w)
21 ops = displayOpsForPic p (regionForWindow w)
22 in displayOpsColumns ops == 0 && displayOpsRows ops == 0
22 in displayOpsColumns ops == 0 && displayOpsRows ops == 0
23
23
24unitImageAndZeroWindow1 :: UnitImage -> EmptyWindow -> Bool
24unitImageAndZeroWindow1 :: UnitImage -> EmptyWindow -> Bool
25−unitImageAndZeroWindow1 (UnitImage _ i) (EmptyWindow w) =
25+unitImageAndZeroWindow1 (UnitImage _ i) (EmptyWindow w) =
26 let p = picForImage i
26 let p = picForImage i
27 ops = displayOpsForPic p (regionForWindow w)
27 ops = displayOpsForPic p (regionForWindow w)
28− in ( spanOpsEffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )
28+ in ( spanOpsAffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )
29
29
30horizSpanImageAndZeroWindow0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
30horizSpanImageAndZeroWindow0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
31−horizSpanImageAndZeroWindow0 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) =
31+horizSpanImageAndZeroWindow0 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) =
32 let p = picForImage i
32 let p = picForImage i
33 ops = displayOpsForPic p (regionForWindow w)
33 ops = displayOpsForPic p (regionForWindow w)
34 in displayOpsColumns ops == 0 && displayOpsRows ops == 0
34 in displayOpsColumns ops == 0 && displayOpsRows ops == 0
35
35
36horizSpanImageAndZeroWindow1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
36horizSpanImageAndZeroWindow1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool
37−horizSpanImageAndZeroWindow1 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) =
37+horizSpanImageAndZeroWindow1 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) =
38 let p = picForImage i
38 let p = picForImage i
39 ops = displayOpsForPic p (regionForWindow w)
39 ops = displayOpsForPic p (regionForWindow w)
40− in ( spanOpsEffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )
40+ in ( spanOpsAffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )
41
41
42horizSpanImageAndEqualWindow0 :: SingleRowSingleAttrImage -> Result
42horizSpanImageAndEqualWindow0 :: SingleRowSingleAttrImage -> Result
43horizSpanImageAndEqualWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
43horizSpanImageAndEqualWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
@@ -51,7 +51,7 @@ horizSpanImageAndEqualWindow1 (SingleRowSingleAttrImage { rowImage = i, expected
51 let p = picForImage i
51 let p = picForImage i
52 w = MockWindow c 1
52 w = MockWindow c 1
53 ops = displayOpsForPic p (regionForWindow w)
53 ops = displayOpsForPic p (regionForWindow w)
54− in spanOpsEffectedRows ops == 1
54+ in spanOpsAffectedRows ops == 1
55
55
56horizSpanImageAndLesserWindow0 :: SingleRowSingleAttrImage -> Result
56horizSpanImageAndLesserWindow0 :: SingleRowSingleAttrImage -> Result
57horizSpanImageAndLesserWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
57horizSpanImageAndLesserWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =
@@ -74,7 +74,7 @@ singleAttrSingleSpanStackCropped1 stack =
74 expectedRowCount = stackHeight stack `div` 2
74 expectedRowCount = stackHeight stack `div` 2
75 w = MockWindow (stackWidth stack) expectedRowCount
75 w = MockWindow (stackWidth stack) expectedRowCount
76 ops = displayOpsForPic p (regionForWindow w)
76 ops = displayOpsForPic p (regionForWindow w)
77− actualRowCount = spanOpsEffectedRows ops
77+ actualRowCount = spanOpsAffectedRows ops
78 in expectedRowCount == actualRowCount
78 in expectedRowCount == actualRowCount
79
79
80singleAttrSingleSpanStackCropped2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
80singleAttrSingleSpanStackCropped2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
@@ -90,7 +90,7 @@ singleAttrSingleSpanStackCropped3 stack0 stack1 =
90 w = MockWindow (imageWidth (picImage p)) expectedRowCount
90 w = MockWindow (imageWidth (picImage p)) expectedRowCount
91 ops = displayOpsForPic p (regionForWindow w)
91 ops = displayOpsForPic p (regionForWindow w)
92 expectedRowCount = imageHeight (picImage p) `div` 2
92 expectedRowCount = imageHeight (picImage p) `div` 2
93− actualRowCount = spanOpsEffectedRows ops
93+ actualRowCount = spanOpsAffectedRows ops
94 in expectedRowCount == actualRowCount
94 in expectedRowCount == actualRowCount
95
95
96singleAttrSingleSpanStackCropped4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
96singleAttrSingleSpanStackCropped4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result
@@ -107,7 +107,7 @@ singleAttrSingleSpanStackCropped5 stack0 stack1 =
107 w = MockWindow (imageWidth (picImage p)) (stackHeight stack0)
107 w = MockWindow (imageWidth (picImage p)) (stackHeight stack0)
108 ops = displayOpsForPic p (regionForWindow w)
108 ops = displayOpsForPic p (regionForWindow w)
109 expectedRowCount = stackHeight stack0
109 expectedRowCount = stackHeight stack0
110− actualRowCount = spanOpsEffectedRows ops
110+ actualRowCount = spanOpsAffectedRows ops
111 in expectedRowCount == actualRowCount
111 in expectedRowCount == actualRowCount
112
112
113horizSpanImageAndGreaterWindow0 :: SingleRowSingleAttrImage -> Result
113horizSpanImageAndGreaterWindow0 :: SingleRowSingleAttrImage -> Result
@@ -123,13 +123,13 @@ arbImageIsCropped :: DefaultImage -> MockWindow -> Bool
123arbImageIsCropped (DefaultImage image) win@(MockWindow w h) =
123arbImageIsCropped (DefaultImage image) win@(MockWindow w h) =
124 let pic = picForImage image
124 let pic = picForImage image
125 ops = displayOpsForPic pic (regionForWindow win)
125 ops = displayOpsForPic pic (regionForWindow win)
126− in ( spanOpsEffectedRows ops == h ) && ( allSpansHaveWidth ops w )
126+ in ( spanOpsAffectedRows ops == h ) && ( allSpansHaveWidth ops w )
127
127
128spanOpsActuallyFillRows :: DefaultPic -> Bool
128spanOpsActuallyFillRows :: DefaultPic -> Bool
129spanOpsActuallyFillRows (DefaultPic pic win) =
129spanOpsActuallyFillRows (DefaultPic pic win) =
130 let ops = displayOpsForPic pic (regionForWindow win)
130 let ops = displayOpsForPic pic (regionForWindow win)
131 expectedRowCount = regionHeight (regionForWindow win)
131 expectedRowCount = regionHeight (regionForWindow win)
132− actualRowCount = spanOpsEffectedRows ops
132+ actualRowCount = spanOpsAffectedRows ops
133 in expectedRowCount == actualRowCount
133 in expectedRowCount == actualRowCount
134
134
135spanOpsActuallyFillColumns :: DefaultPic -> Bool
135spanOpsActuallyFillColumns :: DefaultPic -> Bool
@@ -139,7 +139,7 @@ spanOpsActuallyFillColumns (DefaultPic pic win) =
139 in allSpansHaveWidth ops expectedColumnCount
139 in allSpansHaveWidth ops expectedColumnCount
140
140
141firstSpanOpSetsAttr :: DefaultPic -> Bool
141firstSpanOpSetsAttr :: DefaultPic -> Bool
142−firstSpanOpSetsAttr DefaultPic { defaultPic = pic, defaultWin = win } =
142+firstSpanOpSetsAttr DefaultPic { defaultPic = pic, defaultWin = win } =
143 let ops = displayOpsForPic pic (regionForWindow win)
143 let ops = displayOpsForPic pic (regionForWindow win)
144 in all ( isAttrSpanOp . Vector.head ) ( Vector.toList ops )
144 in all ( isAttrSpanOp . Vector.head ) ( Vector.toList ops )
145
145
@@ -158,7 +158,7 @@ imageCoverageMatchesBounds i =
158 in verifyAllSpansHaveWidth i ops (imageWidth i)
158 in verifyAllSpansHaveWidth i ops (imageWidth i)
159
159
160tests :: IO [Test]
160tests :: IO [Test]
161−tests = return
161+tests = return
162 [ verify "unit image is cropped when window size == (0,0) [0]" unitImageAndZeroWindow0
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
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
164 , verify "horiz span image is cropped when window size == (0,0) [0]" horizSpanImageAndZeroWindow0
@@ -171,7 +171,7 @@ tests = return
171 , verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"
171 , verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"
172 singleAttrSingleSpanStackOpCoverage
172 singleAttrSingleSpanStackOpCoverage
173 , verify "a single attr text span is cropped when window size < size of stack image [width]"
173 , verify "a single attr text span is cropped when window size < size of stack image [width]"
174− singleAttrSingleSpanStackCropped0
174+ singleAttrSingleSpanStackCropped0
175 , verify "a single attr text span is cropped when window size < size of stack image [height]"
175 , verify "a single attr text span is cropped when window size < size of stack image [height]"
176 singleAttrSingleSpanStackCropped1
176 singleAttrSingleSpanStackCropped1
177 , verify "single attr text span <|> single attr text span display cropped. [width]"
177 , verify "single attr text span <|> single attr text span display cropped. [width]"
test/VerifyUsingMockInput.hs+26−22
@@ -1,8 +1,8 @@
1{-# LANGUAGE MultiParamTypeClasses #-}
1{-# LANGUAGE MultiParamTypeClasses #-}
2{-# LANGUAGE FlexibleInstances #-}
2{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
3−{- Generate some input bytes and delays between blocks of input bytes. Verify the events produced
4+-- Generate some input bytes and delays between blocks of input bytes.
4− - are as expected.
5+-- Verify the events produced are as expected.
5− -}
6module Main where
6module Main where
7
7
8import Verify.Graphics.Vty.Output
8import Verify.Graphics.Vty.Output
@@ -12,14 +12,12 @@ import Graphics.Vty.Input.Events
12import Graphics.Vty.Input.Loop
12import Graphics.Vty.Input.Loop
13import Graphics.Vty.Input.Terminfo
13import Graphics.Vty.Input.Terminfo
14
14
15−import Control.Applicative
16import Control.Concurrent
15import Control.Concurrent
17import Control.Concurrent.STM
16import Control.Concurrent.STM
18import Control.Exception
17import Control.Exception
19import Lens.Micro ((^.))
18import Lens.Micro ((^.))
20import Control.Monad
19import Control.Monad
21
20
22−import Data.Default
23import Data.IORef
21import Data.IORef
24import Data.List (intersperse, reverse, nubBy)
22import Data.List (intersperse, reverse, nubBy)
25
23
@@ -36,7 +34,8 @@ import Test.SmallCheck.Series
36
34
37import Text.Printf
35import Text.Printf
38
36
39−-- processing a block of 16 chars is the largest I can do without taking too long to run the test.
37+-- processing a block of 16 chars is the largest I can do without taking
38+-- too long to run the test.
40maxBlockSize :: Int
39maxBlockSize :: Int
41maxBlockSize = 16
40maxBlockSize = 16
42
41
@@ -47,8 +46,11 @@ forEachOf :: (Show a, Testable m b) => [a] -> (a -> b) -> Property m
47forEachOf l = over (generate (\n -> take n l))
46forEachOf l = over (generate (\n -> take n l))
48
47
49data InputEvent
48data InputEvent
49+ = Bytes String
50− = Bytes String -- | input sequence encoded as a string. Regardless, the input is read a byte at a time.
50+ -- ^ Input sequence encoded as a string. Regardless, the input is
51+ -- read a byte at a time.
51− | Delay Int -- | microsecond delay
52+ | Delay Int
53+ -- ^ Microsecond delay
52 deriving Show
54 deriving Show
53
55
54type InputSpec = [InputEvent]
56type InputSpec = [InputEvent]
@@ -111,11 +113,11 @@ assertEventsFromSynInput table inputSpec expectedEvents = do
111 (writeFd, readFd) <- openPseudoTerminal
113 (writeFd, readFd) <- openPseudoTerminal
112 (setTermAttr,_) <- attributeControl readFd
114 (setTermAttr,_) <- attributeControl readFd
113 setTermAttr
115 setTermAttr
114− let testConfig = def { inputFd = Just readFd
116+ let testConfig = defaultConfig { inputFd = Just readFd
115− , termName = Just "dummy"
117+ , termName = Just "dummy"
116− , vmin = Just 1
118+ , vmin = Just 1
117− , vtime = Just 100
119+ , vtime = Just 100
118− }
120+ }
119 input <- initInput testConfig table
121 input <- initInput testConfig table
120 eventsRef <- newIORef []
122 eventsRef <- newIORef []
121 let writeWaitClose = do
123 let writeWaitClose = do
@@ -130,7 +132,9 @@ assertEventsFromSynInput table inputSpec expectedEvents = do
130 readLoop 0 = return ()
132 readLoop 0 = return ()
131 readLoop n = do
133 readLoop n = do
132 e <- atomically $ readTChan $ input^.eventChannel
134 e <- atomically $ readTChan $ input^.eventChannel
135+ case e of
133− modifyIORef eventsRef ((:) e)
136+ InputEvent ev -> modifyIORef eventsRef ((:) ev)
137+ ResumeAfterSignal -> return ()
134 readLoop (n - 1)
138 readLoop (n - 1)
135 genEventsUsingIoActions maxDuration writeWaitClose readEvents
139 genEventsUsingIoActions maxDuration writeWaitClose readEvents
136 outEvents <- reverse <$> readIORef eventsRef
140 outEvents <- reverse <$> readIORef eventsRef
@@ -163,8 +167,8 @@ verifyVisibleSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) -> monadic
163
167
164verifyCapsSynInputToEvent :: Property IO
168verifyCapsSynInputToEvent :: Property IO
165verifyCapsSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
169verifyCapsSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
166− forEachOf terminalsOfInterest $ \termName -> monadic $ do
170+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
167− term <- setupTerm termName
171+ term <- setupTerm terminalName
168 let table = capsClassifyMap term keysFromCapsTable
172 let table = capsClassifyMap term keysFromCapsTable
169 inputSeq = gen table
173 inputSeq = gen table
170 events = map snd inputSeq
174 events = map snd inputSeq
@@ -183,9 +187,9 @@ verifySpecialSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) -> monadic
183
187
184verifyFullSynInputToEvent :: Property IO
188verifyFullSynInputToEvent :: Property IO
185verifyFullSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
189verifyFullSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
186− forEachOf terminalsOfInterest $ \termName -> monadic $ do
190+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
187− term <- setupTerm termName
191+ term <- setupTerm terminalName
188− let table = classifyMapForTerm termName term
192+ let table = classifyMapForTerm terminalName term
189 inputSeq = gen table
193 inputSeq = gen table
190 events = map snd inputSeq
194 events = map snd inputSeq
191 keydowns = map (Bytes . fst) inputSeq
195 keydowns = map (Bytes . fst) inputSeq
@@ -194,9 +198,9 @@ verifyFullSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->
194
198
195verifyFullSynInputToEvent_2x :: Property IO
199verifyFullSynInputToEvent_2x :: Property IO
196verifyFullSynInputToEvent_2x = forAll $ \(InputBlocksUsingTable gen) ->
200verifyFullSynInputToEvent_2x = forAll $ \(InputBlocksUsingTable gen) ->
197− forEachOf terminalsOfInterest $ \termName -> monadic $ do
201+ forEachOf terminalsOfInterest $ \terminalName -> monadic $ do
198− term <- setupTerm termName
202+ term <- setupTerm terminalName
199− let table = classifyMapForTerm termName term
203+ let table = classifyMapForTerm terminalName term
200 inputSeq = gen table
204 inputSeq = gen table
201 events = concatMap ((\s -> [s,s]) . snd) inputSeq
205 events = concatMap ((\s -> [s,s]) . snd) inputSeq
202 keydowns = map (Bytes . (\s -> s ++ s) . fst) inputSeq
206 keydowns = map (Bytes . (\s -> s ++ s) . fst) inputSeq
test/VerifyUsingMockTerminal.hs+23−15
@@ -4,9 +4,11 @@ import Verify.Graphics.Vty.Prelude
4
4
5import Verify.Graphics.Vty.Picture
5import Verify.Graphics.Vty.Picture
6import Verify.Graphics.Vty.Image
6import Verify.Graphics.Vty.Image
7+import Verify.Graphics.Vty.Attributes
7import Verify.Graphics.Vty.Span
8import Verify.Graphics.Vty.Span
8import Verify.Graphics.Vty.Output
9import Verify.Graphics.Vty.Output
9import Graphics.Vty.Output
10import Graphics.Vty.Output
11+import Graphics.Vty.Output.Interface
10import Graphics.Vty.Output.Mock
12import Graphics.Vty.Output.Mock
11
13
12import Graphics.Vty.Debug
14import Graphics.Vty.Debug
@@ -39,27 +41,30 @@ singleTRow :: MockWindow -> Property
39singleTRow (MockWindow w h) = liftIOResult $ do
41singleTRow (MockWindow w h) = liftIOResult $ do
40 (mockData,t) <- mockTerminal (w,h)
42 (mockData,t) <- mockTerminal (w,h)
41 dc <- displayBounds t >>= displayContext t
43 dc <- displayBounds t >>= displayContext t
42− -- create an image that contains just the character T repeated for a single row
44+ -- create an image that contains just the character T repeated for a
45+ -- single row
43 let i = horizCat $ replicate (fromEnum w) (char defAttr 'T')
46 let i = horizCat $ replicate (fromEnum w) (char defAttr 'T')
44 pic = (picForImage i) { picBackground = Background 'B' defAttr }
47 pic = (picForImage i) { picBackground = Background 'B' defAttr }
45 outputPicture dc pic
48 outputPicture dc pic
46− -- The mock output string that represents the output bytes a single line containing the T
49+ -- The mock output string that represents the output bytes a single
50+ -- line containing the T string: Followed by h - 1 lines of a change
47− -- string: Followed by h - 1 lines of a change to the background attribute and then the
51+ -- to the background attribute and then the background character
48− -- background character
49 let expected = "H" ++ "MDA" ++ replicate (fromEnum w) 'T'
52 let expected = "H" ++ "MDA" ++ replicate (fromEnum w) 'T'
50 ++ concat (replicate (fromEnum h - 1) $ "MDA" ++ replicate (fromEnum w) 'B')
53 ++ concat (replicate (fromEnum h - 1) $ "MDA" ++ replicate (fromEnum w) 'B')
51 compareMockOutput mockData expected
54 compareMockOutput mockData expected
52−
55+
53manyTRows :: MockWindow -> Property
56manyTRows :: MockWindow -> Property
54manyTRows (MockWindow w h) = liftIOResult $ do
57manyTRows (MockWindow w h) = liftIOResult $ do
55 (mockData, t) <- mockTerminal (w,h)
58 (mockData, t) <- mockTerminal (w,h)
56 dc <- displayBounds t >>= displayContext t
59 dc <- displayBounds t >>= displayContext t
57− -- create an image that contains the character 'T' repeated for all the rows
60+ -- create an image that contains the character 'T' repeated for all
61+ -- the rows
58 let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')
62 let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')
59 pic = (picForImage i) { picBackground = Background 'B' defAttr }
63 pic = (picForImage i) { picBackground = Background 'B' defAttr }
60 outputPicture dc pic
64 outputPicture dc pic
61− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
65+ -- The UTF8 string that represents the output bytes is h repeats of
62− -- attribute change. 'A', followed by w 'T's
66+ -- a move, 'M', followed by an attribute change. 'A', followed by w
67+ -- 'T's
63 let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
68 let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
64 compareMockOutput mockData expected
69 compareMockOutput mockData expected
65
70
@@ -67,12 +72,14 @@ manyTRowsCroppedWidth :: MockWindow -> Property
67manyTRowsCroppedWidth (MockWindow w h) = liftIOResult $ do
72manyTRowsCroppedWidth (MockWindow w h) = liftIOResult $ do
68 (mockData,t) <- mockTerminal (w,h)
73 (mockData,t) <- mockTerminal (w,h)
69 dc <- displayBounds t >>= displayContext t
74 dc <- displayBounds t >>= displayContext t
70− -- create an image that contains the character 'T' repeated for all the rows
75+ -- create an image that contains the character 'T' repeated for all
76+ -- the rows
71 let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w * 2) (char defAttr 'T')
77 let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w * 2) (char defAttr 'T')
72 pic = (picForImage i) { picBackground = Background 'B' defAttr }
78 pic = (picForImage i) { picBackground = Background 'B' defAttr }
73 outputPicture dc pic
79 outputPicture dc pic
74− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
80+ -- The UTF8 string that represents the output bytes is h repeats of
75− -- attribute change. 'A', followed by w 'T's
81+ -- a move, 'M', followed by an attribute change. 'A', followed by w
82+ -- 'T's
76 let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
83 let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
77 compareMockOutput mockData expected
84 compareMockOutput mockData expected
78
85
@@ -80,12 +87,14 @@ manyTRowsCroppedHeight :: MockWindow -> Property
80manyTRowsCroppedHeight (MockWindow w h) = liftIOResult $ do
87manyTRowsCroppedHeight (MockWindow w h) = liftIOResult $ do
81 (mockData,t) <- mockTerminal (w,h)
88 (mockData,t) <- mockTerminal (w,h)
82 dc <- displayBounds t >>= displayContext t
89 dc <- displayBounds t >>= displayContext t
83− -- create an image that contains the character 'T' repeated for all the rows
90+ -- create an image that contains the character 'T' repeated for all
91+ -- the rows
84 let i = vertCat $ replicate (fromEnum h * 2) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')
92 let i = vertCat $ replicate (fromEnum h * 2) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')
85 pic = (picForImage i) { picBackground = Background 'B' defAttr }
93 pic = (picForImage i) { picBackground = Background 'B' defAttr }
86 outputPicture dc pic
94 outputPicture dc pic
87− -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an
95+ -- The UTF8 string that represents the output bytes is h repeats of
88− -- attribute change. 'A', followed by w count 'T's
96+ -- a move, 'M', followed by an attribute change. 'A', followed by w
97+ -- count 'T's
89 let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
98 let expected = "H" ++ concat (replicate (fromEnum h) $ "MDA" ++ replicate (fromEnum w) 'T')
90 compareMockOutput mockData expected
99 compareMockOutput mockData expected
91
100
@@ -97,4 +106,3 @@ tests = return [ verify "unitImageUnitBounds" unitImageUnitBounds
97 , verify "manyTRowsCroppedWidth" manyTRowsCroppedWidth
106 , verify "manyTRowsCroppedWidth" manyTRowsCroppedWidth
98 , verify "manyTRowsCroppedHeight" manyTRowsCroppedHeight
107 , verify "manyTRowsCroppedHeight" manyTRowsCroppedHeight
99 ]
108 ]
100−
test/VerifyUtf8Width.hs+2
@@ -1,9 +1,11 @@
1module VerifyUtf8Width where
1module VerifyUtf8Width where
2+
2import Verify
3import Verify
3
4
4import Graphics.Text.Width
5import Graphics.Text.Width
5import Graphics.Vty.Attributes
6import Graphics.Vty.Attributes
6import Graphics.Vty.Picture
7import Graphics.Vty.Picture
8+import Graphics.Vty.Image
7
9
8swIs1Column :: SingleColumnChar -> Bool
10swIs1Column :: SingleColumnChar -> Bool
9swIs1Column (SingleColumnChar c) = imageWidth (char defAttr c) == 1
11swIs1Column (SingleColumnChar c) = imageWidth (char defAttr c) == 1
test/YiIssue264.hs+1
@@ -1,4 +1,5 @@
1module Main where
1module Main where
2+
2import Graphics.Vty
3import Graphics.Vty
3import Control.Exception
4import Control.Exception
4
5
test/interactive_terminal_test.hs+51−52
@@ -12,14 +12,13 @@ import Control.Concurrent (threadDelay)
12import Control.Exception
12import Control.Exception
13import Control.Monad
13import Control.Monad
14
14
15−import Data.Default (def)
16import Data.List ( lookup )
15import Data.List ( lookup )
17import Data.Maybe ( isJust, fromJust )
16import Data.Maybe ( isJust, fromJust )
18import Data.Monoid
17import Data.Monoid
19import Data.String.QQ
18import Data.String.QQ
20import Data.Word
19import Data.Word
21
20
22−import Foreign.Marshal.Array
21+import Foreign.Marshal.Array
23
22
24import qualified System.Environment as Env
23import qualified System.Environment as Env
25
24
@@ -74,12 +73,12 @@ with the test_results.list file pasted into the issue. A suitable summary is:
74|]
73|]
75 waitForReturn
74 waitForReturn
76 results <- doTestMenu 1
75 results <- doTestMenu 1
77− envAttributes <- mapM ( \envName -> Control.Exception.catch ( Env.getEnv envName >>= return . (,) envName )
76+ envAttributes <- mapM ( \envName -> Control.Exception.catch ( Env.getEnv envName >>= return . (,) envName )
78− ( \ (_ :: SomeException) -> return (envName, "") )
77+ ( \ (_ :: SomeException) -> return (envName, "") )
79− )
78+ )
80 [ "TERM", "COLORTERM", "LANG", "TERM_PROGRAM", "XTERM_VERSION" ]
79 [ "TERM", "COLORTERM", "LANG", "TERM_PROGRAM", "XTERM_VERSION" ]
81 t <- standardIOConfig >>= outputForConfig
80 t <- standardIOConfig >>= outputForConfig
82− let resultsTxt = show envAttributes ++ "\n"
81+ let resultsTxt = show envAttributes ++ "\n"
83 ++ terminalID t ++ "\n"
82 ++ terminalID t ++ "\n"
84 ++ show results ++ "\n"
83 ++ show results ++ "\n"
85 releaseTerminal t
84 releaseTerminal t
@@ -94,7 +93,7 @@ testMenu :: [(String, Test)]
94testMenu = zip (map show [1..]) allTests
93testMenu = zip (map show [1..]) allTests
95
94
96doTestMenu :: Int -> IO [(String, Bool)]
95doTestMenu :: Int -> IO [(String, Bool)]
97−doTestMenu nextID
96+doTestMenu nextID
98 | nextID > length allTests = do
97 | nextID > length allTests = do
99 putStrLn $ "Done! Please email the " ++ outputFilePath ++ " file to coreyoconnor@gmail.com"
98 putStrLn $ "Done! Please email the " ++ outputFilePath ++ " file to coreyoconnor@gmail.com"
100 return []
99 return []
@@ -108,26 +107,26 @@ doTestMenu nextID
108 s <- getLine >>= return . filter (/= '\n')
107 s <- getLine >>= return . filter (/= '\n')
109 case s of
108 case s of
110 "q" -> return mempty
109 "q" -> return mempty
111− "" -> do
110+ "" -> do
112− r <- runTest $ show nextID
111+ r <- runTest $ show nextID
113 rs <- doTestMenu ( nextID + 1 )
112 rs <- doTestMenu ( nextID + 1 )
114 return $ r : rs
113 return $ r : rs
115 i | isJust ( lookup i testMenu ) -> do
114 i | isJust ( lookup i testMenu ) -> do
116− r <- runTest i
115+ r <- runTest i
117 rs <- doTestMenu ( read i + 1 )
116 rs <- doTestMenu ( read i + 1 )
118 return $ r : rs
117 return $ r : rs
119 where
118 where
120− displayTestMenu
119+ displayTestMenu
121 = mapM_ displayTestMenu' testMenu
120 = mapM_ displayTestMenu' testMenu
122− displayTestMenu' ( i, t )
121+ displayTestMenu' ( i, t )
123− = putStrLn $ ( if i == show nextID
122+ = putStrLn $ ( if i == show nextID
124− then "> "
123+ then "> "
125 else " "
124 else " "
126 ) ++ i ++ ". " ++ testName t
125 ) ++ i ++ ". " ++ testName t
127
126
128runTest :: String -> IO (String, Bool)
127runTest :: String -> IO (String, Bool)
129runTest i = do
128runTest i = do
130− let t = fromJust $ lookup i testMenu
129+ let t = fromJust $ lookup i testMenu
131 printSummary t
130 printSummary t
132 waitForReturn
131 waitForReturn
133 testAction t
132 testAction t
@@ -153,8 +152,8 @@ data Test = Test
153 , confirmResults :: IO Bool
152 , confirmResults :: IO Bool
154 }
153 }
155
154
156−allTests
155+allTests
157− = [ reserveOutputTest
156+ = [ reserveOutputTest
158 , displayBoundsTest0
157 , displayBoundsTest0
159 , displayBoundsTest1
158 , displayBoundsTest1
160 , displayBoundsTest2
159 , displayBoundsTest2
@@ -185,7 +184,7 @@ allTests
185 , layer1
184 , layer1
186 ]
185 ]
187
186
188−reserveOutputTest = Test
187+reserveOutputTest = Test
189 { testName = "Initialize and reserve terminal output then restore previous state."
188 { testName = "Initialize and reserve terminal output then restore previous state."
190 , testID = "reserveOutputTest"
189 , testID = "reserveOutputTest"
191 , testAction = do
190 , testAction = do
@@ -203,7 +202,7 @@ reserveOutputTest = Test
203 , printSummary = do
202 , printSummary = do
204 putStr $ [s|
203 putStr $ [s|
205Once return is pressed:
204Once return is pressed:
206− 0. The screen will be cleared.
205+ 0. The screen will be cleared.
207 1. Four lines of text should be visible.
206 1. Four lines of text should be visible.
208 1. The cursor should be visible and at the start of the fifth line.
207 1. The cursor should be visible and at the start of the fifth line.
209
208
@@ -365,7 +364,7 @@ Did the test output match the description?
365|]
364|]
366 defaultSuccessConfirmResults
365 defaultSuccessConfirmResults
367
366
368−-- Explicitely definethe bytes that encode each example text.
367+-- Explicitly define the bytes that encode each example text.
369-- This avoids any issues with how the compiler represents string literals.
368-- This avoids any issues with how the compiler represents string literals.
370--
369--
371-- This document is UTF-8 encoded so the UTF-8 string is still included for
370-- This document is UTF-8 encoded so the UTF-8 string is still included for
@@ -391,7 +390,7 @@ utf8Txt0 = [ [ 0xe2 , 0x86 , 0x91 ]
391
390
392iso10646Txt0 :: String
391iso10646Txt0 :: String
393iso10646Txt0 = map toEnum
392iso10646Txt0 = map toEnum
394− [ 8593
393+ [ 8593
395 , 8593
394 , 8593
396 , 8595
395 , 8595
397 , 8595
396 , 8595
@@ -554,11 +553,11 @@ After return is pressed for the second time:
554allColors = zip [ black, red, green, yellow, blue, magenta, cyan, white ]
553allColors = zip [ black, red, green, yellow, blue, magenta, cyan, white ]
555 [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]
554 [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]
556
555
557−allBrightColors
556+allBrightColors
558 = zip [ brightBlack, brightRed, brightGreen, brightYellow, brightBlue, brightMagenta, brightCyan, brightWhite ]
557 = zip [ brightBlack, brightRed, brightGreen, brightYellow, brightBlue, brightMagenta, brightCyan, brightWhite ]
559 [ "bright black", "bright red", "bright green", "bright yellow", "bright blue", "bright magenta", "bright cyan", "bright white" ]
558 [ "bright black", "bright red", "bright green", "bright yellow", "bright blue", "bright magenta", "bright cyan", "bright white" ]
560
559
561−attributesTest0 = Test
560+attributesTest0 = Test
562 { testName = "Character attributes: foreground colors."
561 { testName = "Character attributes: foreground colors."
563 , testID = "attributesTest0"
562 , testID = "attributesTest0"
564 , testAction = do
563 , testAction = do
@@ -604,7 +603,7 @@ Did the test output match the description?
604 defaultSuccessConfirmResults
603 defaultSuccessConfirmResults
605 }
604 }
606
605
607−attributesTest1 = Test
606+attributesTest1 = Test
608 { testName = "Character attributes: background colors."
607 { testName = "Character attributes: background colors."
609 , testID = "attributesTest1"
608 , testID = "attributesTest1"
610 , testAction = do
609 , testAction = do
@@ -659,7 +658,7 @@ Did the test output match the description?
659 defaultSuccessConfirmResults
658 defaultSuccessConfirmResults
660 }
659 }
661
660
662−attributesTest2 = Test
661+attributesTest2 = Test
663 { testName = "Character attributes: Vivid foreground colors."
662 { testName = "Character attributes: Vivid foreground colors."
664 , testID = "attributesTest2"
663 , testID = "attributesTest2"
665 , testAction = do
664 , testAction = do
@@ -686,23 +685,23 @@ Once return is pressed:
686 1. The cursor will be hidden.
685 1. The cursor will be hidden.
687 2. 9 lines of text in three columns will be drawn:
686 2. 9 lines of text in three columns will be drawn:
688 a. The first column will be a name of a standard color (for an 8 color
687 a. The first column will be a name of a standard color (for an 8 color
689− terminal) rendered with that color as the foreground color.
688+ terminal) rendered with that color as the foreground color.
690 b. The next column will be also be the name of a standard color rendered
689 b. The next column will be also be the name of a standard color rendered
691 with that color as the foreground color but the shade used should be
690 with that color as the foreground color but the shade used should be
692− more vivid than the shade used in the first column.
691+ more vivid than the shade used in the first column.
693 c. The final column will be the name of a color rendered with the
692 c. The final column will be the name of a color rendered with the
694 default attributes.
693 default attributes.
695
694
696For instance, one line will be the word "magenta" and that word should be
695For instance, one line will be the word "magenta" and that word should be
697−rendered in the magenta color.
696+rendered in the magenta color.
698
697
699I'm not actually sure exactly what "vivid" means in this context. For xterm the
698I'm not actually sure exactly what "vivid" means in this context. For xterm the
700−vivid colors are brighter.
699+vivid colors are brighter.
701
700
702−Verify:
701+Verify:
703 * The first column: The foreground color matches the named color.
702 * The first column: The foreground color matches the named color.
704 * The second column: The foreground color matches the named color but is
703 * The second column: The foreground color matches the named color but is
705− more vivid than the color used in the first column.
704+ more vivid than the color used in the first column.
706 * The third column: All text is rendered with the default attributes.
705 * The third column: All text is rendered with the default attributes.
707 * The vertical bars used in each line to mark the border of a column are
706 * The vertical bars used in each line to mark the border of a column are
708 lined up.
707 lined up.
@@ -718,7 +717,7 @@ Did the test output match the description?
718 defaultSuccessConfirmResults
717 defaultSuccessConfirmResults
719 }
718 }
720
719
721−attributesTest3 = Test
720+attributesTest3 = Test
722 { testName = "Character attributes: Vivid background colors."
721 { testName = "Character attributes: Vivid background colors."
723 , testID = "attributesTest3"
722 , testID = "attributesTest3"
724 , testAction = do
723 , testAction = do
@@ -746,23 +745,23 @@ Once return is pressed:
746 2. 9 lines of text in three columns will be drawn:
745 2. 9 lines of text in three columns will be drawn:
747 a. The first column will contain be a name of a standard color for an 8
746 a. The first column will contain be a name of a standard color for an 8
748 color terminal rendered with the default foreground color with a
747 color terminal rendered with the default foreground color with a
749− background the named color.
748+ background the named color.
750 b. The first column will contain be a name of a standard color for an 8
749 b. The first column will contain be a name of a standard color for an 8
751 color terminal rendered with the default foreground color with the
750 color terminal rendered with the default foreground color with the
752− background a vivid version of the named color.
751+ background a vivid version of the named color.
753 c. The third column will be the name of a standard color rendered with
752 c. The third column will be the name of a standard color rendered with
754 the default attributes.
753 the default attributes.
755−
754+
756For instance, one line will contain be the word "magenta" and the word should
755For instance, one line will contain be the word "magenta" and the word should
757−be rendered in the default foreground color over a magenta background.
756+be rendered in the default foreground color over a magenta background.
758
757
759I'm not actually sure exactly what "vivid" means in this context. For xterm the
758I'm not actually sure exactly what "vivid" means in this context. For xterm the
760vivid colors are brighter.
759vivid colors are brighter.
761
760
762−Verify:
761+Verify:
763 * The first column: The background color matches the named color.
762 * The first column: The background color matches the named color.
764 * The second column: The background color matches the named color and is
763 * The second column: The background color matches the named color and is
765− more vivid than the color used in the first column.
764+ more vivid than the color used in the first column.
766 * The third column column: All text is rendered with the default attributes.
765 * The third column column: All text is rendered with the default attributes.
767 * The vertical bars used in each line to mark the border of a column are
766 * The vertical bars used in each line to mark the border of a column are
768 lined up.
767 lined up.
@@ -785,7 +784,7 @@ Did the test output match the description?
785 defaultSuccessConfirmResults
784 defaultSuccessConfirmResults
786 }
785 }
787
786
788−attrCombos =
787+attrCombos =
789 [ ( "default", id )
788 [ ( "default", id )
790 , ( "bold", flip withStyle bold )
789 , ( "bold", flip withStyle bold )
791 , ( "blink", flip withStyle blink )
790 , ( "blink", flip withStyle blink )
@@ -796,7 +795,7 @@ attrCombos =
796 , ( "bold + blink + underline", flip withStyle (bold + blink + underline) )
795 , ( "bold + blink + underline", flip withStyle (bold + blink + underline) )
797 ]
796 ]
798
797
799−attributesTest4 = Test
798+attributesTest4 = Test
800 { testName = "Character attributes: Bold; Blink; Underline."
799 { testName = "Character attributes: Bold; Blink; Underline."
801 , testID = "attributesTest4"
800 , testID = "attributesTest4"
802 , testAction = do
801 , testAction = do
@@ -819,10 +818,10 @@ attributesTest4 = Test
819Once return is pressed:
818Once return is pressed:
820 0. The screen will be cleared.
819 0. The screen will be cleared.
821 1. The cursor will be hidden.
820 1. The cursor will be hidden.
822− 2. 8 rows of text in two columns.
821+ 2. 8 rows of text in two columns.
823 The rows will contain the following text:
822 The rows will contain the following text:
824 default
823 default
825− bold
824+ bold
826 blink
825 blink
827 underline
826 underline
828 bold + blink
827 bold + blink
@@ -831,8 +830,8 @@ Once return is pressed:
831 bold + blink + underline
830 bold + blink + underline
832 The first column will be rendered with the described attributes. The second
831 The first column will be rendered with the described attributes. The second
833 column will be rendered with the default attributes.
832 column will be rendered with the default attributes.
834−
833+
835−Verify:
834+Verify:
836 * The vertical bars used in each line to mark the border of a column are
835 * The vertical bars used in each line to mark the border of a column are
837 lined up.
836 lined up.
838 * The text in the first column is rendered as described.
837 * The text in the first column is rendered as described.
@@ -848,7 +847,7 @@ Did the test output match the description?
848 defaultSuccessConfirmResults
847 defaultSuccessConfirmResults
849 }
848 }
850
849
851−attributesTest5 = Test
850+attributesTest5 = Test
852 { testName = "Character attributes: 240 color palette"
851 { testName = "Character attributes: 240 color palette"
853 , testID = "attributesTest5"
852 , testID = "attributesTest5"
854 , testAction = do
853 , testAction = do
@@ -873,7 +872,7 @@ Once return is pressed:
873 2. A 20 character wide and 12 row high block of color squares. This should look like a palette
872 2. A 20 character wide and 12 row high block of color squares. This should look like a palette
874 of some sort. I'm not exactly sure if all color terminals use the same palette. I doubt it...
873 of some sort. I'm not exactly sure if all color terminals use the same palette. I doubt it...
875
874
876−Verify:
875+Verify:
877
876
878After return is pressed for the second time:
877After return is pressed for the second time:
879 0. The screen containing the test summary should be restored.
878 0. The screen containing the test summary should be restored.
@@ -938,7 +937,7 @@ cursorHideTest0 = Test
938 { testName = "Verify the cursor is hid and re-shown. issue #7"
937 { testName = "Verify the cursor is hid and re-shown. issue #7"
939 , testID = "cursorHideTest0"
938 , testID = "cursorHideTest0"
940 , testAction = do
939 , testAction = do
941− vty <- mkVty def
940+ vty <- mkVty defaultConfig
942 showCursor $ outputIface vty
941 showCursor $ outputIface vty
943 setCursorPos (outputIface vty) 5 5
942 setCursorPos (outputIface vty) 5 5
944 nextEvent vty
943 nextEvent vty
@@ -971,7 +970,7 @@ outputPicAndWait pic = do
971 releaseDisplay t
970 releaseDisplay t
972 releaseTerminal t
971 releaseTerminal t
973 return ()
972 return ()
974−
973+
975vertCropTest0 :: Test
974vertCropTest0 :: Test
976vertCropTest0 = Test
975vertCropTest0 = Test
977 { testName = "Verify bottom cropping works as expected with single column chars"
976 { testName = "Verify bottom cropping works as expected with single column chars"
@@ -1046,7 +1045,7 @@ horizCropTest0 = Test
1046 , testID = "cropTest0"
1045 , testID = "cropTest0"
1047 , testAction = do
1046 , testAction = do
1048 let baseImage = vertCat $ map (string defAttr) lorumIpsum
1047 let baseImage = vertCat $ map (string defAttr) lorumIpsum
1049− croppedImage = cropRight (imageWidth baseImage `div` 2) baseImage
1048+ croppedImage = cropRight (imageWidth baseImage `div` 2) baseImage
1050 image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1049 image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1051 outputImageAndWait image
1050 outputImageAndWait image
1052 , printSummary = putStr $ [s|
1051 , printSummary = putStr $ [s|
@@ -1063,7 +1062,7 @@ horizCropTest1 = Test
1063 , testID = "cropTest0"
1062 , testID = "cropTest0"
1064 , testAction = do
1063 , testAction = do
1065 let baseImage = vertCat $ map (string defAttr) lorumIpsumChinese
1064 let baseImage = vertCat $ map (string defAttr) lorumIpsumChinese
1066− croppedImage = cropRight (imageWidth baseImage `div` 2) baseImage
1065+ croppedImage = cropRight (imageWidth baseImage `div` 2) baseImage
1067 image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1066 image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1068 outputImageAndWait image
1067 outputImageAndWait image
1069 , printSummary = putStr $ [s|
1068 , printSummary = putStr $ [s|
@@ -1080,7 +1079,7 @@ horizCropTest2 = Test
1080 , testID = "cropTest0"
1079 , testID = "cropTest0"
1081 , testAction = do
1080 , testAction = do
1082 let baseImage = vertCat $ map (string defAttr) lorumIpsum
1081 let baseImage = vertCat $ map (string defAttr) lorumIpsum
1083− croppedImage = cropLeft (imageWidth baseImage `div` 2) baseImage
1082+ croppedImage = cropLeft (imageWidth baseImage `div` 2) baseImage
1084 image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1083 image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1085 outputImageAndWait image
1084 outputImageAndWait image
1086 , printSummary = putStr $ [s|
1085 , printSummary = putStr $ [s|
@@ -1097,7 +1096,7 @@ horizCropTest3 = Test
1097 , testID = "cropTest0"
1096 , testID = "cropTest0"
1098 , testAction = do
1097 , testAction = do
1099 let baseImage = vertCat $ map (string defAttr) lorumIpsumChinese
1098 let baseImage = vertCat $ map (string defAttr) lorumIpsumChinese
1100− croppedImage = cropLeft (imageWidth baseImage `div` 2) baseImage
1099+ croppedImage = cropLeft (imageWidth baseImage `div` 2) baseImage
1101 image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1100 image = baseImage <-> backgroundFill 10 2 <-> croppedImage
1102 outputImageAndWait image
1101 outputImageAndWait image
1103 , printSummary = putStr $ [s|
1102 , printSummary = putStr $ [s|
test/vty-examples.cabal+24−14
@@ -1,28 +1,38 @@
1name: vty-examples
1name: vty-examples
2−version: 5.6
2+version: 5.7
3license: BSD3
3license: BSD3
4license-file: LICENSE
4license-file: LICENSE
5author: AUTHORS
5author: AUTHORS
6−maintainer: Corey O'Connor (coreyoconnor@gmail.com)
6+maintainer: Jonathan Daugherty (cygnus@foobox.com)
7−homepage: https://github.com/coreyoconnor/vty
7+homepage: https://github.com/jtdaugherty/vty
8category: User Interfaces
8category: User Interfaces
9synopsis: Examples programs using the vty library.
9synopsis: Examples programs using the vty library.
10description:
10description:
11− vty is terminal GUI library in the niche of ncurses. It is intended to be easy to use, have no
11+ vty is terminal GUI library in the niche of ncurses. It is intended to
12− confusing corner cases, and good support for common terminal types.
12+ be easy to use, have no confusing corner cases, and good support for
13+ common terminal types.
13 .
14 .
14− vty-interactive-terminal-test - interactive test. Useful for building a bug report for vtys author.
15+ vty-interactive-terminal-test - interactive test. Useful for building
16+ a bug report for vtys author.
17+ .
15− vty-event-echo - view a input event log for vty. Example of interacting with user.
18+ vty-event-echo - view a input event log for vty. Example of
19+ interacting with user.
20+ .
16− vty-rogue - A bad rogue-like game. Go from the entrance to exit. Example of layers
21+ vty-rogue - A bad rogue-like game. Go from the entrance to exit.
22+ Example of layers
23+ .
17− vty-benchmark - benchmarks vty. A series of tests that push random pictures to the terminal.
24+ vty-benchmark - benchmarks vty. A series of tests that push random
25+ pictures to the terminal.
18 .
26 .
19 &#169; Corey O'Connor; BSD3 license.
27 &#169; Corey O'Connor; BSD3 license.
28+ .
29+ &#169; Jonathan Daugherty; BSD3 license.
20cabal-version: >= 1.18.0
30cabal-version: >= 1.18.0
21build-type: Simple
31build-type: Simple
22
32
23source-repository head
33source-repository head
24 type: git
34 type: git
25− location: https://github.com/coreyoconnor/vty.git
35+ location: https://github.com/jtdaugherty/vty.git
26
36
27executable vty-interactive-terminal-test
37executable vty-interactive-terminal-test
28 main-is: interactive_terminal_test.hs
38 main-is: interactive_terminal_test.hs
@@ -31,7 +41,7 @@ executable vty-interactive-terminal-test
31 default-extensions: ScopedTypeVariables
41 default-extensions: ScopedTypeVariables
32 ghc-options: -threaded
42 ghc-options: -threaded
33
43
34− build-depends: vty >= 5.2.11,
44+ build-depends: vty >= 5.15,
35 base >= 4 && < 5,
45 base >= 4 && < 5,
36 bytestring,
46 bytestring,
37 containers,
47 containers,
@@ -59,7 +69,7 @@ executable vty-event-echo
59 FlexibleContexts
69 FlexibleContexts
60 ghc-options: -threaded
70 ghc-options: -threaded
61
71
62− build-depends: vty >= 5.2.11,
72+ build-depends: vty >= 5.15,
63 base >= 4 && < 5,
73 base >= 4 && < 5,
64 array,
74 array,
65 bytestring,
75 bytestring,
@@ -79,7 +89,7 @@ executable vty-rogue
79 default-extensions: ScopedTypeVariables
89 default-extensions: ScopedTypeVariables
80 ghc-options: -threaded
90 ghc-options: -threaded
81
91
82− build-depends: vty >= 5.2.11,
92+ build-depends: vty >= 5.15,
83 base >= 4 && < 5,
93 base >= 4 && < 5,
84 array,
94 array,
85 bytestring,
95 bytestring,
@@ -100,7 +110,7 @@ executable vty-benchmark
100 default-extensions: ScopedTypeVariables
110 default-extensions: ScopedTypeVariables
101 ghc-options: -threaded
111 ghc-options: -threaded
102
112
103− build-depends: vty >= 5.2.11,
113+ build-depends: vty >= 5.15,
104 base >= 4 && < 5,
114 base >= 4 && < 5,
105 bytestring,
115 bytestring,
106 Cabal,
116 Cabal,
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
view-benchmark-coredeleted−4
@@ -1,4 +0,0 @@
1−#!/bin/sh
2−ghc-core --no-asm --no-cast -- \
3− -hide-package vty-5.0.0 -i../src -XScopedTypeVariables ../dist/build/cbits/*.o \
4− benchmark
vty.cabal+101−100
@@ -1,5 +1,5 @@
1name: vty
1name: vty
2−version: 5.14
2+version: 5.35.1
3license: BSD3
3license: BSD3
4license-file: LICENSE
4license-file: LICENSE
5author: AUTHORS
5author: AUTHORS
@@ -12,27 +12,25 @@ description:
12 be easy to use, have no confusing corner cases, and good support for
12 be easy to use, have no confusing corner cases, and good support for
13 common terminal types.
13 common terminal types.
14 .
14 .
15− Included in the source distribution is a program
15+ See the @vty-examples@ package as well as the program
16− test/interactive_terminal_test.hs that demonstrates the various
16+ @test/interactive_terminal_test.hs@ included in the @vty@ package for
17− features.
17+ examples on how to use the library.
18 .
18 .
19− Developers: See the "Graphics.Vty" module.
19+ Import the "Graphics.Vty" convenience module to get access to the core
20− .
20+ parts of the library.
21− Users: See the "Graphics.Vty.Config" module.
22 .
21 .
23 &#169; 2006-2007 Stefan O'Rear; BSD3 license.
22 &#169; 2006-2007 Stefan O'Rear; BSD3 license.
24 .
23 .
25 &#169; Corey O'Connor; BSD3 license.
24 &#169; Corey O'Connor; BSD3 license.
26 .
25 .
27 &#169; Jonathan Daugherty; BSD3 license.
26 &#169; Jonathan Daugherty; BSD3 license.
28−cabal-version: >= 1.18
27+cabal-version: 1.18
29build-type: Simple
28build-type: Simple
30−data-files: README.md,
29+extra-doc-files: README.md,
31− TODO,
32 AUTHORS,
30 AUTHORS,
33− CHANGELOG,
31+ CHANGELOG.md,
34 LICENSE
32 LICENSE
35−tested-with: GHC >= 7.6.2
33+tested-with: GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3, GHC==8.6.5
36
34
37source-repository head
35source-repository head
38 type: git
36 type: git
@@ -40,18 +38,16 @@ source-repository head
40
38
41library
39library
42 default-language: Haskell2010
40 default-language: Haskell2010
43− build-depends: base >= 4.6 && < 5,
41+ build-depends: base >= 4.8 && < 5,
44 blaze-builder >= 0.3.3.2 && < 0.5,
42 blaze-builder >= 0.3.3.2 && < 0.5,
45 bytestring,
43 bytestring,
46 containers,
44 containers,
47− data-default >= 0.5.3,
48 deepseq >= 1.1 && < 1.5,
45 deepseq >= 1.1 && < 1.5,
49 directory,
46 directory,
50 filepath >= 1.0 && < 2.0,
47 filepath >= 1.0 && < 2.0,
51− microlens,
48+ microlens < 0.4.13,
52 microlens-mtl,
49 microlens-mtl,
53 microlens-th,
50 microlens-th,
54− -- required for nice installation with yi
55 hashable >= 1.2,
51 hashable >= 1.2,
56 mtl >= 1.1.1.0 && < 2.3,
52 mtl >= 1.1.1.0 && < 2.3,
57 parallel >= 2.2 && < 3.3,
53 parallel >= 2.2 && < 3.3,
@@ -62,10 +58,18 @@ library
62 text >= 0.11.3,
58 text >= 0.11.3,
63 unix,
59 unix,
64 utf8-string >= 0.3 && < 1.1,
60 utf8-string >= 0.3 && < 1.1,
65− vector >= 0.7
61+ vector >= 0.7,
62+ binary,
63+ ansi-terminal >= 0.10.3
64+
65+ if !impl(ghc >= 8.0)
66+ build-depends: semigroups >= 0.16,
67+ fail
66
68
67 exposed-modules: Graphics.Vty
69 exposed-modules: Graphics.Vty
68 Graphics.Vty.Attributes
70 Graphics.Vty.Attributes
71+ Graphics.Vty.Attributes.Color
72+ Graphics.Vty.Attributes.Color240
69 Graphics.Vty.Config
73 Graphics.Vty.Config
70 Graphics.Vty.Error
74 Graphics.Vty.Error
71 Graphics.Vty.Image
75 Graphics.Vty.Image
@@ -74,10 +78,8 @@ library
74 Graphics.Vty.Input
78 Graphics.Vty.Input
75 Graphics.Vty.Input.Events
79 Graphics.Vty.Input.Events
76 Graphics.Vty.Picture
80 Graphics.Vty.Picture
77− Graphics.Vty.Prelude
78 Graphics.Vty.Output
81 Graphics.Vty.Output
79 Graphics.Text.Width
82 Graphics.Text.Width
80− -- the modules below are only meant to be used by the tests.
81 Codec.Binary.UTF8.Debug
83 Codec.Binary.UTF8.Debug
82 Data.Terminfo.Parse
84 Data.Terminfo.Parse
83 Data.Terminfo.Eval
85 Data.Terminfo.Eval
@@ -89,6 +91,7 @@ library
89 Graphics.Vty.Input.Classify.Parse
91 Graphics.Vty.Input.Classify.Parse
90 Graphics.Vty.Input.Loop
92 Graphics.Vty.Input.Loop
91 Graphics.Vty.Input.Mouse
93 Graphics.Vty.Input.Mouse
94+ Graphics.Vty.Input.Focus
92 Graphics.Vty.Input.Paste
95 Graphics.Vty.Input.Paste
93 Graphics.Vty.Input.Terminfo
96 Graphics.Vty.Input.Terminfo
94 Graphics.Vty.PictureToSpans
97 Graphics.Vty.PictureToSpans
@@ -97,41 +100,52 @@ library
97 Graphics.Vty.Output.Interface
100 Graphics.Vty.Output.Interface
98 Graphics.Vty.Output.XTermColor
101 Graphics.Vty.Output.XTermColor
99 Graphics.Vty.Output.TerminfoBased
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
100
107
101− other-modules: Graphics.Vty.Attributes.Color
108+ other-modules: Graphics.Vty.Debug.Image
102− Graphics.Vty.Attributes.Color240
103− Graphics.Vty.Debug.Image
104 Graphics.Vty.Input.Terminfo.ANSIVT
109 Graphics.Vty.Input.Terminfo.ANSIVT
105
110
106 c-sources: cbits/gwinsz.c
111 c-sources: cbits/gwinsz.c
107 cbits/set_term_timing.c
112 cbits/set_term_timing.c
113+ cbits/get_tty_erase.c
108 cbits/mk_wcwidth.c
114 cbits/mk_wcwidth.c
109
115
110 include-dirs: cbits
116 include-dirs: cbits
111
117
112 hs-source-dirs: src
118 hs-source-dirs: src
113
119
114− default-extensions: ScopedTypeVariables
115− ForeignFunctionInterface
116−
117 ghc-options: -O2 -funbox-strict-fields -Wall -fspec-constr -fspec-constr-count=10
120 ghc-options: -O2 -funbox-strict-fields -Wall -fspec-constr -fspec-constr-count=10
118
121
119 ghc-prof-options: -O2 -funbox-strict-fields -caf-all -Wall -fspec-constr -fspec-constr-count=10
122 ghc-prof-options: -O2 -funbox-strict-fields -caf-all -Wall -fspec-constr -fspec-constr-count=10
120
123
124+executable vty-build-width-table
125+ main-is: BuildWidthTable.hs
126+ hs-source-dirs: tools
127+
128+ default-language: Haskell2010
121− cc-options: -O2
129+ ghc-options: -threaded -Wall
130+
131+ if !impl(ghc >= 8.0)
132+ build-depends: semigroups >= 0.16
133+
134+ build-depends: vty,
135+ directory,
136+ filepath,
137+ base >= 4.8 && < 5
122
138
123executable vty-mode-demo
139executable vty-mode-demo
124 main-is: ModeDemo.hs
140 main-is: ModeDemo.hs
125 hs-source-dirs: demos
141 hs-source-dirs: demos
126
142
127 default-language: Haskell2010
143 default-language: Haskell2010
128− default-extensions: ScopedTypeVariables
129 ghc-options: -threaded
144 ghc-options: -threaded
130
145
131 build-depends: vty,
146 build-depends: vty,
132− base >= 4.6 && < 5,
147+ base >= 4.8 && < 5,
133 containers,
148 containers,
134− data-default >= 0.5.3,
135 microlens,
149 microlens,
136 microlens-mtl,
150 microlens-mtl,
137 mtl >= 1.1.1.0 && < 2.3
151 mtl >= 1.1.1.0 && < 2.3
@@ -141,40 +155,17 @@ executable vty-demo
141 hs-source-dirs: demos
155 hs-source-dirs: demos
142
156
143 default-language: Haskell2010
157 default-language: Haskell2010
144− default-extensions: ScopedTypeVariables
145 ghc-options: -threaded
158 ghc-options: -threaded
146
159
147 build-depends: vty,
160 build-depends: vty,
148− base >= 4.6 && < 5,
161+ base >= 4.8 && < 5,
149 containers,
162 containers,
150− data-default >= 0.5.3,
151 microlens,
163 microlens,
152 microlens-mtl,
164 microlens-mtl,
153 mtl >= 1.1.1.0 && < 2.3
165 mtl >= 1.1.1.0 && < 2.3
154
166
155−test-suite verify-attribute-ops
156− default-language: Haskell2010
157− default-extensions: ScopedTypeVariables
158− type: detailed-0.9
159− hs-source-dirs: test
160− test-module: VerifyAttributeOps
161− build-depends: vty,
162− Cabal >= 1.20,
163− QuickCheck >= 2.7,
164− random >= 1.0 && < 1.2,
165− base >= 4.6 && < 5,
166− bytestring,
167− containers,
168− deepseq >= 1.1 && < 1.5,
169− mtl >= 1.1.1.0 && < 2.3,
170− text >= 0.11.3,
171− unix,
172− utf8-string >= 0.3 && < 1.1,
173− vector >= 0.7
174−
175test-suite verify-using-mock-terminal
167test-suite verify-using-mock-terminal
176 default-language: Haskell2010
168 default-language: Haskell2010
177− default-extensions: ScopedTypeVariables
178
169
179 type: detailed-0.9
170 type: detailed-0.9
180
171
@@ -193,8 +184,8 @@ test-suite verify-using-mock-terminal
193 build-depends: vty,
184 build-depends: vty,
194 Cabal >= 1.20,
185 Cabal >= 1.20,
195 QuickCheck >= 2.7,
186 QuickCheck >= 2.7,
196− random >= 1.0 && < 1.2,
187+ random >= 1.0 && < 1.3,
197− base >= 4.6 && < 5,
188+ base >= 4.8 && < 5,
198 bytestring,
189 bytestring,
199 containers,
190 containers,
200 deepseq >= 1.1 && < 1.5,
191 deepseq >= 1.1 && < 1.5,
@@ -207,7 +198,6 @@ test-suite verify-using-mock-terminal
207
198
208test-suite verify-terminal
199test-suite verify-terminal
209 default-language: Haskell2010
200 default-language: Haskell2010
210− default-extensions: ScopedTypeVariables
211
201
212 type: detailed-0.9
202 type: detailed-0.9
213
203
@@ -226,11 +216,10 @@ test-suite verify-terminal
226 build-depends: vty,
216 build-depends: vty,
227 Cabal >= 1.20,
217 Cabal >= 1.20,
228 QuickCheck >= 2.7,
218 QuickCheck >= 2.7,
229− random >= 1.0 && < 1.2,
219+ random >= 1.0 && < 1.3,
230− base >= 4.6 && < 5,
220+ base >= 4.8 && < 5,
231 bytestring,
221 bytestring,
232 containers,
222 containers,
233− data-default >= 0.5.3,
234 deepseq >= 1.1 && < 1.5,
223 deepseq >= 1.1 && < 1.5,
235 mtl >= 1.1.1.0 && < 2.3,
224 mtl >= 1.1.1.0 && < 2.3,
236 terminfo >= 0.3 && < 0.5,
225 terminfo >= 0.3 && < 0.5,
@@ -241,7 +230,6 @@ test-suite verify-terminal
241
230
242test-suite verify-display-attributes
231test-suite verify-display-attributes
243 default-language: Haskell2010
232 default-language: Haskell2010
244− default-extensions: ScopedTypeVariables
245
233
246 type: detailed-0.9
234 type: detailed-0.9
247
235
@@ -260,8 +248,8 @@ test-suite verify-display-attributes
260 build-depends: vty,
248 build-depends: vty,
261 Cabal >= 1.20,
249 Cabal >= 1.20,
262 QuickCheck >= 2.7,
250 QuickCheck >= 2.7,
263− random >= 1.0 && < 1.2,
251+ random >= 1.0 && < 1.3,
264− base >= 4.6 && < 5,
252+ base >= 4.8 && < 5,
265 bytestring,
253 bytestring,
266 containers,
254 containers,
267 deepseq >= 1.1 && < 1.5,
255 deepseq >= 1.1 && < 1.5,
@@ -273,7 +261,6 @@ test-suite verify-display-attributes
273
261
274test-suite verify-empty-image-props
262test-suite verify-empty-image-props
275 default-language: Haskell2010
263 default-language: Haskell2010
276− default-extensions: ScopedTypeVariables
277
264
278 type: detailed-0.9
265 type: detailed-0.9
279
266
@@ -286,8 +273,8 @@ test-suite verify-empty-image-props
286 build-depends: vty,
273 build-depends: vty,
287 Cabal >= 1.20,
274 Cabal >= 1.20,
288 QuickCheck >= 2.7,
275 QuickCheck >= 2.7,
289− random >= 1.0 && < 1.2,
276+ random >= 1.0 && < 1.3,
290− base >= 4.6 && < 5,
277+ base >= 4.8 && < 5,
291 bytestring,
278 bytestring,
292 containers,
279 containers,
293 deepseq >= 1.1 && < 1.5,
280 deepseq >= 1.1 && < 1.5,
@@ -299,7 +286,6 @@ test-suite verify-empty-image-props
299
286
300test-suite verify-eval-terminfo-caps
287test-suite verify-eval-terminfo-caps
301 default-language: Haskell2010
288 default-language: Haskell2010
302− default-extensions: ScopedTypeVariables
303
289
304 type: detailed-0.9
290 type: detailed-0.9
305
291
@@ -313,8 +299,8 @@ test-suite verify-eval-terminfo-caps
313 build-depends: vty,
299 build-depends: vty,
314 Cabal >= 1.20,
300 Cabal >= 1.20,
315 QuickCheck >= 2.7,
301 QuickCheck >= 2.7,
316− random >= 1.0 && < 1.2,
302+ random >= 1.0 && < 1.3,
317− base >= 4.6 && < 5,
303+ base >= 4.8 && < 5,
318 blaze-builder >= 0.3.3.2 && < 0.5,
304 blaze-builder >= 0.3.3.2 && < 0.5,
319 bytestring,
305 bytestring,
320 containers,
306 containers,
@@ -328,7 +314,6 @@ test-suite verify-eval-terminfo-caps
328
314
329test-suite verify-image-ops
315test-suite verify-image-ops
330 default-language: Haskell2010
316 default-language: Haskell2010
331− default-extensions: ScopedTypeVariables
332
317
333 type: detailed-0.9
318 type: detailed-0.9
334
319
@@ -343,8 +328,8 @@ test-suite verify-image-ops
343 build-depends: vty,
328 build-depends: vty,
344 Cabal >= 1.20,
329 Cabal >= 1.20,
345 QuickCheck >= 2.7,
330 QuickCheck >= 2.7,
346− random >= 1.0 && < 1.2,
331+ random >= 1.0 && < 1.3,
347− base >= 4.6 && < 5,
332+ base >= 4.8 && < 5,
348 bytestring,
333 bytestring,
349 containers,
334 containers,
350 deepseq >= 1.1 && < 1.5,
335 deepseq >= 1.1 && < 1.5,
@@ -356,7 +341,6 @@ test-suite verify-image-ops
356
341
357test-suite verify-image-trans
342test-suite verify-image-trans
358 default-language: Haskell2010
343 default-language: Haskell2010
359− default-extensions: ScopedTypeVariables
360
344
361 type: detailed-0.9
345 type: detailed-0.9
362
346
@@ -371,8 +355,8 @@ test-suite verify-image-trans
371 build-depends: vty,
355 build-depends: vty,
372 Cabal >= 1.20,
356 Cabal >= 1.20,
373 QuickCheck >= 2.7,
357 QuickCheck >= 2.7,
374− random >= 1.0 && < 1.2,
358+ random >= 1.0 && < 1.3,
375− base >= 4.6 && < 5,
359+ base >= 4.8 && < 5,
376 bytestring,
360 bytestring,
377 containers,
361 containers,
378 deepseq >= 1.1 && < 1.5,
362 deepseq >= 1.1 && < 1.5,
@@ -384,7 +368,6 @@ test-suite verify-image-trans
384
368
385test-suite verify-inline
369test-suite verify-inline
386 default-language: Haskell2010
370 default-language: Haskell2010
387− default-extensions: ScopedTypeVariables
388
371
389 type: detailed-0.9
372 type: detailed-0.9
390
373
@@ -398,8 +381,8 @@ test-suite verify-inline
398 build-depends: vty,
381 build-depends: vty,
399 Cabal >= 1.20,
382 Cabal >= 1.20,
400 QuickCheck >= 2.7,
383 QuickCheck >= 2.7,
401− random >= 1.0 && < 1.2,
384+ random >= 1.0 && < 1.3,
402− base >= 4.6 && < 5,
385+ base >= 4.8 && < 5,
403 bytestring,
386 bytestring,
404 containers,
387 containers,
405 deepseq >= 1.1 && < 1.5,
388 deepseq >= 1.1 && < 1.5,
@@ -411,7 +394,6 @@ test-suite verify-inline
411
394
412test-suite verify-parse-terminfo-caps
395test-suite verify-parse-terminfo-caps
413 default-language: Haskell2010
396 default-language: Haskell2010
414− default-extensions: ScopedTypeVariables
415
397
416 type: detailed-0.9
398 type: detailed-0.9
417
399
@@ -426,8 +408,8 @@ test-suite verify-parse-terminfo-caps
426 build-depends: vty,
408 build-depends: vty,
427 Cabal >= 1.20,
409 Cabal >= 1.20,
428 QuickCheck >= 2.7,
410 QuickCheck >= 2.7,
429− random >= 1.0 && < 1.2,
411+ random >= 1.0 && < 1.3,
430− base >= 4.6 && < 5,
412+ base >= 4.8 && < 5,
431 bytestring,
413 bytestring,
432 containers,
414 containers,
433 deepseq >= 1.1 && < 1.5,
415 deepseq >= 1.1 && < 1.5,
@@ -440,7 +422,6 @@ test-suite verify-parse-terminfo-caps
440
422
441test-suite verify-simple-span-generation
423test-suite verify-simple-span-generation
442 default-language: Haskell2010
424 default-language: Haskell2010
443− default-extensions: ScopedTypeVariables
444
425
445 type: detailed-0.9
426 type: detailed-0.9
446
427
@@ -458,8 +439,8 @@ test-suite verify-simple-span-generation
458 build-depends: vty,
439 build-depends: vty,
459 Cabal >= 1.20,
440 Cabal >= 1.20,
460 QuickCheck >= 2.7,
441 QuickCheck >= 2.7,
461− random >= 1.0 && < 1.2,
442+ random >= 1.0 && < 1.3,
462− base >= 4.6 && < 5,
443+ base >= 4.8 && < 5,
463 bytestring,
444 bytestring,
464 containers,
445 containers,
465 deepseq >= 1.1 && < 1.5,
446 deepseq >= 1.1 && < 1.5,
@@ -472,7 +453,6 @@ test-suite verify-simple-span-generation
472
453
473test-suite verify-crop-span-generation
454test-suite verify-crop-span-generation
474 default-language: Haskell2010
455 default-language: Haskell2010
475− default-extensions: ScopedTypeVariables
476
456
477 type: detailed-0.9
457 type: detailed-0.9
478
458
@@ -490,8 +470,8 @@ test-suite verify-crop-span-generation
490 build-depends: vty,
470 build-depends: vty,
491 Cabal >= 1.20,
471 Cabal >= 1.20,
492 QuickCheck >= 2.7,
472 QuickCheck >= 2.7,
493− random >= 1.0 && < 1.2,
473+ random >= 1.0 && < 1.3,
494− base >= 4.6 && < 5,
474+ base >= 4.8 && < 5,
495 bytestring,
475 bytestring,
496 containers,
476 containers,
497 deepseq >= 1.1 && < 1.5,
477 deepseq >= 1.1 && < 1.5,
@@ -504,7 +484,6 @@ test-suite verify-crop-span-generation
504
484
505test-suite verify-layers-span-generation
485test-suite verify-layers-span-generation
506 default-language: Haskell2010
486 default-language: Haskell2010
507− default-extensions: ScopedTypeVariables
508
487
509 type: detailed-0.9
488 type: detailed-0.9
510
489
@@ -522,8 +501,33 @@ test-suite verify-layers-span-generation
522 build-depends: vty,
501 build-depends: vty,
523 Cabal >= 1.20,
502 Cabal >= 1.20,
524 QuickCheck >= 2.7,
503 QuickCheck >= 2.7,
525− random >= 1.0 && < 1.2,
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,
526− base >= 4.6 && < 5,
530+ base >= 4.8 && < 5,
527 bytestring,
531 bytestring,
528 containers,
532 containers,
529 deepseq >= 1.1 && < 1.5,
533 deepseq >= 1.1 && < 1.5,
@@ -535,7 +539,6 @@ test-suite verify-layers-span-generation
535
539
536test-suite verify-utf8-width
540test-suite verify-utf8-width
537 default-language: Haskell2010
541 default-language: Haskell2010
538− default-extensions: ScopedTypeVariables
539
542
540 type: detailed-0.9
543 type: detailed-0.9
541
544
@@ -548,8 +551,8 @@ test-suite verify-utf8-width
548 build-depends: vty,
551 build-depends: vty,
549 Cabal >= 1.20,
552 Cabal >= 1.20,
550 QuickCheck >= 2.7,
553 QuickCheck >= 2.7,
551− random >= 1.0 && < 1.2,
554+ random >= 1.0 && < 1.3,
552− base >= 4.6 && < 5,
555+ base >= 4.8 && < 5,
553 bytestring,
556 bytestring,
554 containers,
557 containers,
555 deepseq >= 1.1 && < 1.5,
558 deepseq >= 1.1 && < 1.5,
@@ -561,7 +564,6 @@ test-suite verify-utf8-width
561
564
562test-suite verify-using-mock-input
565test-suite verify-using-mock-input
563 default-language: Haskell2010
566 default-language: Haskell2010
564− default-extensions: ScopedTypeVariables
565
567
566 type: exitcode-stdio-1.0
568 type: exitcode-stdio-1.0
567
569
@@ -569,16 +571,17 @@ test-suite verify-using-mock-input
569
571
570 main-is: VerifyUsingMockInput.hs
572 main-is: VerifyUsingMockInput.hs
571
573
574+ other-modules: Verify.Graphics.Vty.Output
575+
572 build-depends: vty,
576 build-depends: vty,
573 Cabal >= 1.20,
577 Cabal >= 1.20,
574− data-default >= 0.5.3,
575 QuickCheck >= 2.7,
578 QuickCheck >= 2.7,
576 smallcheck == 1.*,
579 smallcheck == 1.*,
577 quickcheck-assertions >= 0.1.1,
580 quickcheck-assertions >= 0.1.1,
578 test-framework == 0.8.*,
581 test-framework == 0.8.*,
579 test-framework-smallcheck == 0.2.*,
582 test-framework-smallcheck == 0.2.*,
580− random >= 1.0 && < 1.2,
583+ random >= 1.0 && < 1.3,
581− base >= 4.6 && < 5,
584+ base >= 4.8 && < 5,
582 bytestring,
585 bytestring,
583 containers,
586 containers,
584 deepseq >= 1.1 && < 1.5,
587 deepseq >= 1.1 && < 1.5,
@@ -596,7 +599,6 @@ test-suite verify-using-mock-input
596
599
597test-suite verify-config
600test-suite verify-config
598 default-language: Haskell2010
601 default-language: Haskell2010
599− default-extensions: ScopedTypeVariables
600
602
601 type: exitcode-stdio-1.0
603 type: exitcode-stdio-1.0
602
604
@@ -606,7 +608,6 @@ test-suite verify-config
606
608
607 build-depends: vty,
609 build-depends: vty,
608 Cabal >= 1.20,
610 Cabal >= 1.20,
609− data-default >= 0.5.3,
610 HUnit,
611 HUnit,
611 QuickCheck >= 2.7,
612 QuickCheck >= 2.7,
612 smallcheck == 1.*,
613 smallcheck == 1.*,
@@ -614,8 +615,8 @@ test-suite verify-config
614 test-framework == 0.8.*,
615 test-framework == 0.8.*,
615 test-framework-smallcheck == 0.2.*,
616 test-framework-smallcheck == 0.2.*,
616 test-framework-hunit,
617 test-framework-hunit,
617− random >= 1.0 && < 1.2,
618+ random >= 1.0 && < 1.3,
618− base >= 4.6 && < 5,
619+ base >= 4.8 && < 5,
619 bytestring,
620 bytestring,
620 containers,
621 containers,
621 deepseq >= 1.1 && < 1.5,
622 deepseq >= 1.1 && < 1.5,