vty 5.27 → 5.28
raw patch · 12 files changed
+1616/−578 lines, 12 filesdep +ansi-terminaldep +binarydep ~basedep ~filepathnew-component:exe:vty-build-width-table
Dependencies added: ansi-terminal, binary
Dependency ranges changed: base, filepath
Files
- CHANGELOG.md +574/−420
- README.md +102/−52
- cbits/mk_wcwidth.c +136/−2
- src/Graphics/Text/Width.hs +9/−8
- src/Graphics/Vty.hs +74/−30
- src/Graphics/Vty/Config.hs +209/−61
- src/Graphics/Vty/UnicodeWidthTable/IO.hs +105/−0
- src/Graphics/Vty/UnicodeWidthTable/Install.hs +135/−0
- src/Graphics/Vty/UnicodeWidthTable/Query.hs +79/−0
- src/Graphics/Vty/UnicodeWidthTable/Types.hs +29/−0
- tools/BuildWidthTable.hs +144/−0
- vty.cabal +20/−5
CHANGELOG.md view
@@ -1,555 +1,709 @@ +5.28+----++This release improves Vty's support for multi-column Unicode characters+and provides greater compatibility with a wider array of terminal+emulators. The following sections summarize the relevant changes, but an+overview of the new functionality is motivated and detailed in the new+"Multi-Column Character Support" README section. For+historical context, please also consider reading over+[#175](https://github.com/jtdaugherty/vty/issues/175).++API changes:+ * New modules were added:+ * `Graphics.Vty.UnicodeWidthTable.Types`+ * `Graphics.Vty.UnicodeWidthTable.IO`+ * `Graphics.Vty.UnicodeWidthTable.Query`+ * `Graphics.Vty.UnicodeWidthTable.Install`+ * The `Config` type got a new field, `allowCustomUnicodeWidthTables`,+ that controls whether `mkVty` will attempt to load a Unicode width+ table if specified in the configuration.++Configuration file changes:+ * A new syntax was added to support specifying Unicode width tables on+ a per-`TERM` basis. The syntax is `widthMap <TERM> <PATH>`. See the+ documentation for `Graphics.Vty.Config` for details. Since prior+ versions of this library will silently ignore any configuration file+ lines they cannot parse, this change to user configuration files is+ at least non-breaking for older versions of Vty.++Other changes:+ * The `mkVty` function now automatically attempts to load a custom+ Unicode width table if one is specified in the configuration,+ provided `allowCustomUnicodeWidthTables` is not set to `Just False`.+ See the documentation for `Graphics.Vty.mkVty` for details.+ * Vty now includes a command line tool, `vty-build-width-table`, that+ queries the terminal emulator to construct a custom Unicode width+ table and optionally update the Vty configuration file to use it.+ Programs that want to use that tool's functionality may also do so+ via the API exposed in the various modules listed above.+ 5.27- - Added Graphics.Vty.Config.getTtyEraseChar to support querying the- kernel for the current terminal's settings to obtain the character- assigned by the "stty erase" command. That can then be added to the- Vty configuration's input map to map to KBS (backspace) if desired.+---- +* Added `Graphics.Vty.Config.getTtyEraseChar` to support querying the+ kernel for the current terminal's settings to obtain the character+ assigned by the `stty erase` command. That can then be added to the+ Vty configuration's input map to map to `KBS` (backspace) if desired.+ 5.26- - Resolved various import warnings (thanks @glguy)- - Removed the MonadIO constraint from the Output type's fields and- removed MonadFail uses (PR #177, thanks @glguy)- - Clarified documentation for ANSI colors (thanks Colby Jenn)- - Graphics.Vty.Attributes no longer re-exports- Graphics.Vty.Attributes.Color- - The Graphics.Vty.Attributes.Color module is now exposed (thanks Colby- Jenn)- - Raised upper bound for microlens to 0.4.12 (thanks Artyom Kazak)- - Changed from using System.Posix.Env.getEnv to- System.Environment.lookupEnv (thanks Jonathan Osser)- - Added Graphics.Vty.Image functions for dealing with character width- computations on Text values instead of Strings:- - safeWctwidth- - safeWctlwidth- - wctwidth- - wctlwidth+---- +* Resolved various import warnings (thanks @glguy)+* Removed the `MonadIO` constraint from the Output type's fields and+ removed `MonadFail` uses (PR #177, thanks @glguy)+* Clarified documentation for ANSI colors (thanks Colby Jenn)+* `Graphics.Vty.Attributes` no longer re-exports+ `Graphics.Vty.Attributes.Color`+* The `Graphics.Vty.Attributes.Color` module is now exposed (thanks+ Colby Jenn)+* Raised upper bound for `microlens` to 0.4.12 (thanks Artyom Kazak)+* Changed from using `System.Posix.Env.getEnv` to+ `System.Environment.lookupEnv` (thanks Jonathan Osser)+* Added `Graphics.Vty.Image` functions for dealing with character width+ computations on `Text` values instead of `Strings`:+ * `safeWctwidth`+ * `safeWctlwidth`+ * `wctwidth`+ * `wctlwidth`+ 5.25.1- - Avoided a conflict with a Microlens 0.4.10 operator and added an- upper bound on Microlens of 0.4.11.+------ +* Avoided a conflict with a Microlens 0.4.10 operator and added an+ upper bound on Microlens of 0.4.11.+ 5.25- - The Vty type got a new field, isShutdown, that returns whether the- Vty handle has had its 'shutdown' function called (thanks Ian- Jeffries)- - Vty's shutdown function is now thread-safe.+---- +* The Vty type got a new field, isShutdown, that returns whether the+ Vty handle has had its 'shutdown' function called (thanks Ian+ Jeffries)+* Vty's shutdown function is now thread-safe.+ 5.24.1- - The "shutdown" method of Vty handles is now idempotent (#159)+------ +* The "shutdown" method of Vty handles is now idempotent (#159)+ 5.24- - Add Generic and NFData instances for some types- - Image: remove custom Show instance, add derived Show and Read instances- - Updated Travis build settings (thanks Eric Mertens)+---- +* Add Generic and NFData instances for some types+* Image: remove custom Show instance, add derived Show and Read+ instances+* Updated Travis build settings (thanks Eric Mertens)+ 5.23.1- - Fixed a bug where italics did not combine properly with other- display modes (#155, thanks Eric Mertens)+------ +* Fixed a bug where italics did not combine properly with other display+ modes (#155, thanks Eric Mertens)+ 5.23- - Added support for italicized output when terminfo supports it. This- takes the form of a new Style, "italic". Note that most terminfo- descriptors do not report capabilities for italics, so support for- this will be very spotty.- - Updateed text/string function documentation to indicate that escapes- are not permitted in their inputs.+---- +* Added support for italicized output when terminfo supports it. This+ takes the form of a new Style, "italic". Note that most terminfo+ descriptors do not report capabilities for italics, so support for+ this will be very spotty.+* Updateed text/string function documentation to indicate that escapes+ are not permitted in their inputs.+ 5.22- - Added Graphics.Vty.Attributes.Color240.color240CodeToRGB function- (thanks Brent Carmer)- - Added nextEventNonblocking function (field) to Vty type (#87)+---- +* Added Graphics.Vty.Attributes.Color240.color240CodeToRGB function+ (thanks Brent Carmer)+* Added nextEventNonblocking function (field) to Vty type (#87)+ 5.21- - Picture and Background now provide Eq instances (thanks Jaro Reinders)- - #145: vty builds with microlens 0.4.9 (thanks Daniel Wagner)- - #142: note requirement of threaded RTS+---- +* Picture and Background now provide Eq instances (thanks Jaro Reinders)+* #145: vty builds with microlens 0.4.9 (thanks Daniel Wagner)+* #142: note requirement of threaded RTS+ 5.20+----+ API changes:- - Split up Monoid instances into Monoid and Semigroup for newer GHCs- (thanks Ryan Scott)+* Split up Monoid instances into Monoid and Semigroup for newer GHCs+ (thanks Ryan Scott) 5.19.1+------+ API changes:- * Cursor now provides an Eq instance (thanks Jaro Reinders)+* Cursor now provides an Eq instance (thanks Jaro Reinders) 5.19+----+ API changes:- - URL hyperlinking (via 'withURL') is now optional and disabled by- default due to poor support on some common terminals. A new 'Mode'- constructor, 'Hyperlink', has been added to enable this feature. To- change the hyperlinking mode, use 'setMode' on the 'outputIface' of a- Vty handle.+* URL hyperlinking (via 'withURL') is now optional and disabled by+ default due to poor support on some common terminals. A new 'Mode'+ constructor, 'Hyperlink', has been added to enable this feature. To+ change the hyperlinking mode, use 'setMode' on the 'outputIface' of a+ Vty handle. 5.18.1+------+ Bug fixes:- - Reset the hyperlink state on line endings to avoid run-on hyperlinks+* Reset the hyperlink state on line endings to avoid run-on hyperlinks 5.18+----+ API changes:- - Added support for hyperlinking attributes (thanks Getty Ritter). This- change adds a new Attr field for containing the hyperlink to apply,- as per https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda+* Added support for hyperlinking attributes (thanks Getty Ritter). This+ change adds a new Attr field for containing the hyperlink to apply,+ as per https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda 5.17.1- - withStyle now ignores zero arguments, leaving attribute styles- untouched if the input style is the null style+------ +* withStyle now ignores zero arguments, leaving attribute styles+ untouched if the input style is the null style+ 5.17+----+ API changes:- - Add support for terminal focus events. This change adds a new mode- usable with setMode, Focus, that requests that the terminal send- events on focus lose/gain. This change also adds two new Event- constructors, EvLostFocus and EvGainedFocus.- - No longer enable UTF8 mouse event encoding. This encoding was not- working properly with Terminal.app, and using the other modes (SGR,- etc.) work.- - Graphics.Vty.Attributes: escape backticks in Haddock comment (fixes- #131)+* Add support for terminal focus events. This change adds a new mode+ usable with setMode, Focus, that requests that the terminal send+ events on focus lose/gain. This change also adds two new Event+ constructors, EvLostFocus and EvGainedFocus.+* No longer enable UTF8 mouse event encoding. This encoding was not+ working properly with Terminal.app, and using the other modes (SGR,+ etc.) work.+* Graphics.Vty.Attributes: escape backticks in Haddock comment (fixes+ #131) 5.16+----+ API changes:- - Added support for mouse wheel events while in mouse mode. The Button- type got two new constructors as a result: BScrollUp and BScrollDown.- Thanks to doublescale@tutanota.com for this contribution!+* Added support for mouse wheel events while in mouse mode. The Button+ type got two new constructors as a result: BScrollUp and BScrollDown.+ Thanks to doublescale@tutanota.com for this contribution! Bug fixes:- - charFill now clamps negative arguments to zero (thanks Eric- Mertens!)+* charFill now clamps negative arguments to zero (thanks Eric Mertens!) 5.15.1+------+ Package changes:- - Documentation files are now marked accordingly (thanks Michal- Suchánek)+* Documentation files are now marked accordingly (thanks Michal+ Suchánek) Bug fixes:- - translateX/Y: fix negative translations+* translateX/Y: fix negative translations 5.15+----+ Package changes:- - Discontinued support for GHC versions prior to 7.10.1.- - Removed instructions and configuration for Stack builds since they- are no longer supported.- - Clarified README mention of (lack of) Windows support (contributors- wanted, though!)- - Removed dependency on data-default (see below).+* Discontinued support for GHC versions prior to 7.10.1.+* Removed instructions and configuration for Stack builds since they+ are no longer supported.+* Clarified README mention of (lack of) Windows support (contributors+ wanted, though!)+* Removed dependency on data-default (see below). API changes:- - Moved color definitions from Attributes to Color module.- - In lieu of data-default (Default) instances for Attr and Config, use- 'defAttr' and the new 'defaultConfig' (or 'mempty') instead of 'def'.- - Graphics.Vty.Output no longer re-exports- Graphics.Vty.Output.Interface.- - Removed Graphics.Vty.Prelude module and moved DisplayRegion and its- accessors to Graphics.Vty.Image.- - Graphics.Vty.Image no longer re-exports Graphics.Vty.Attributes.- - Graphics.Vty.Picture no longer re-exports Graphics.Vty.Image.+* Moved color definitions from Attributes to Color module.+* In lieu of data-default (Default) instances for Attr and Config, use+ 'defAttr' and the new 'defaultConfig' (or 'mempty') instead of 'def'.+* Graphics.Vty.Output no longer re-exports+ Graphics.Vty.Output.Interface.+* Removed Graphics.Vty.Prelude module and moved DisplayRegion and its+ accessors to Graphics.Vty.Image.+* Graphics.Vty.Image no longer re-exports Graphics.Vty.Attributes.+* Graphics.Vty.Picture no longer re-exports Graphics.Vty.Image. 5.14- - addMaybeClippedJoin: instead of raising an exception when the join- is totally clipped, just reduce the clip amount and continue- - addMaybeClipped: skip blit of joins when their primary dimension is- zero- - 'string' and related text functions no longer treat an empty string- as an empty image (thanks Chris Penner). This means that now it is- possible to use 'str ""' as a non-empty image with height 1.+---- +* addMaybeClippedJoin: instead of raising an exception when the join+ is totally clipped, just reduce the clip amount and continue+* addMaybeClipped: skip blit of joins when their primary dimension is+ zero+* 'string' and related text functions no longer treat an empty string+ as an empty image (thanks Chris Penner). This means that now it is+ possible to use 'str ""' as a non-empty image with height 1.+ 5.13- - Reverted changes in 5.12 due to disagreements between terminal- emulators and utf8proc; for more details, please see the ticket- discussion at- https://github.com/coreyoconnor/vty/issues/115+---- +* Reverted changes in 5.12 due to disagreements between terminal+ emulators and utf8proc; for more details, please see the ticket+ discussion at+ https://github.com/coreyoconnor/vty/issues/115+ 5.12- - Replaced 'wcwidth' with a call to the utf8proc library's character- width function, which is much more up to date (by several Unicode- versions) and returns the right width for a much larger set of- characters.- - Added a bundled version of the utf8proc C library.+---- +* Replaced 'wcwidth' with a call to the utf8proc library's character+ width function, which is much more up to date (by several Unicode+ versions) and returns the right width for a much larger set of+ characters.+* Added a bundled version of the utf8proc C library.+ 5.11.3- - Fix mouse event offsets in mouse-up events+------ +* Fix mouse event offsets in mouse-up events+ 5.11.2- - Mouse events were modified so that the upper-left corner of the- window is (0,0) rather than (1,1).+------ +* Mouse events were modified so that the upper-left corner of the window+ is (0,0) rather than (1,1).+ 5.11.1- - Add Generic instance for Image- - nextEvent: stop trying to refresh on a resize event (fixes segfault- on refresh with normal cursor positioning mode)- - Remove redundant clause from clipForCharWidth (thanks Eric Mertens)- - Update maintainer+------ +* Add Generic instance for Image+* nextEvent: stop trying to refresh on a resize event (fixes segfault+ on refresh with normal cursor positioning mode)+* Remove redundant clause from clipForCharWidth (thanks Eric Mertens)+* Update maintainer+ 5.11- - Vty now raises a VtyConfigurationError exception when the TERM- evironment variable is missing (thanks Eric Mertens)- - Graphics.Vty.Config got an explicit export list to avoid accidentally- exporting internal types (thanks Eric Mertens)+---- +* Vty now raises a VtyConfigurationError exception when the TERM+ evironment variable is missing (thanks Eric Mertens)+* Graphics.Vty.Config got an explicit export list to avoid accidentally+ exporting internal types (thanks Eric Mertens)+ 5.10- - Add absolute cursor positioning mode AbsoluteCursor to Cursor. This- mode provides greater control over cursor positioning by bypassing- the logical positioning provided by default. Rather than positioning- the cursor by looking at the widths of characters involved, this- constructor lets you provide a physical row and column instead. This- is useful in more sophisticated programs. (thanks Eric Mertens)- - Added a new Generic-derived config parser (thanks Eric Mertens)- - Fixed the MShift case in the configuration file parser (thanks Eric- Mertens)- - Fixed wcwidth import and matched safeWcswidth to its documented- behavior. Previously vty_mk_wcwidth was being imported with- the wrong type causing the -1 return value to be mapped to the- wrong Int value. Additionally safeWcswidth was using the unsafe- character width function and only ensuring that the final result was- non-negative. (thanks Eric Mertens)+---- +* Add absolute cursor positioning mode AbsoluteCursor to Cursor. This+ mode provides greater control over cursor positioning by bypassing+ the logical positioning provided by default. Rather than positioning+ the cursor by looking at the widths of characters involved, this+ constructor lets you provide a physical row and column instead. This+ is useful in more sophisticated programs. (thanks Eric Mertens)+* Added a new Generic-derived config parser (thanks Eric Mertens)+* Fixed the MShift case in the configuration file parser (thanks Eric+ Mertens)+* Fixed wcwidth import and matched safeWcswidth to its documented+ behavior. Previously vty_mk_wcwidth was being imported with the+ wrong type causing the -1 return value to be mapped to the wrong Int+ value. Additionally safeWcswidth was using the unsafe character width+ function and only ensuring that the final result was non-negative.+ (thanks Eric Mertens)+ 5.9.1- - Vty now only emits UTF8 charset sequences in terminals without a- preexisting UTF8 declaration to avoid emitting garbage sequences- (fixes #89)+----- +* Vty now only emits UTF8 charset sequences in terminals without a+ preexisting UTF8 declaration to avoid emitting garbage sequences+ (fixes #89)+ 5.9- - Added new Output methods supportsBell and ringTerminalBell to find- out whether the output device has an audio bell and to ring it (see- #102)+--- +* Added new Output methods supportsBell and ringTerminalBell to find out+ whether the output device has an audio bell and to ring it (see #102)+ 5.8.1- - Fixed "refresh" to work as advertised (see #104)+----- +* Fixed "refresh" to work as advertised (see #104)+ 5.8- - API change: EvPaste input event now provides paste data as a raw- ByteString rather than a String to allow the application to decode- how best to decode it+--- +* API change: EvPaste input event now provides paste data as a raw+ ByteString rather than a String to allow the application to decode how+ best to decode it+ 5.7.1- - ModeDemo: added an explicit Control.Applicative import for older- GHCs+----- +* ModeDemo: added an explicit Control.Applicative import for older GHCs+ 5.7- - Mouse and paste modes are now off by default.- - The Config type got new fields: mouseMode and bracketedPasteMode.- These determine whether these modes are enabled initially (for- terminals that support them).- - Added a Mode type for modal terminal features (mouse events,- bracketed paste mode) that is used with new Output interface- functions:- * supportsMode :: Mode -> Bool tells whether the device supports a- mode- * setMode :: Mode -> Bool -> IO () turns a mode on or off- * getModeStatus :: Mode -> IO Bool tells you whether a mode is on or- off- - Added a new demo program, ModeDemo.hs, to demonstrate usage of modes+--- +* Mouse and paste modes are now off by default.+* The Config type got new fields: mouseMode and bracketedPasteMode.+ These determine whether these modes are enabled initially (for+ terminals that support them).+* Added a Mode type for modal terminal features (mouse events,+ bracketed paste mode) that is used with new Output interface+ functions:+ * supportsMode :: Mode -> Bool tells whether the device supports a+ mode+ * setMode :: Mode -> Bool -> IO () turns a mode on or off+ * getModeStatus :: Mode -> IO Bool tells you whether a mode is on or+ off+* Added a new demo program, ModeDemo.hs, to demonstrate usage of modes+ 5.6- - Added support for normal and extended mouse modes in Xterm-like- terminals via the MouseDown and MouseUp Event constructors- - Added support for bracketed paste mode in Xterm-like terminals via- the EvPaste event constructor- - Added derived Show instances for Event and Button (thanks Felix- Hirn)- - Now TERM values containing "screen" will automatically use the- XtermColor driver rather than just TerminfoBased+--- +* Added support for normal and extended mouse modes in Xterm-like+ terminals via the MouseDown and MouseUp Event constructors+* Added support for bracketed paste mode in Xterm-like terminals via+ the EvPaste event constructor+* Added derived Show instances for Event and Button (thanks Felix+ Hirn)+* Now TERM values containing "screen" will automatically use the+ XtermColor driver rather than just TerminfoBased+ 5.5.0- - Replaced lens dependency with microlens, microlens-mtl, microlens-th- dependencies. Issue #90- - Thanks Jonathan Daugherty- - Cabal corrections.- - Thanks Lennart Spitzner+----- +* Replaced lens dependency with microlens, microlens-mtl, microlens-th+ dependencies. Issue #90+ * Thanks Jonathan Daugherty+* Cabal corrections.+ * Thanks Lennart Spitzner+ 5.4.0- - Changed eventChannel of Graphics.Vty.Input from Chan to TChan.- This enables clients to query if there are no pending events. The- Graphics.Vty interface nextEvent is unchanged. Clients that use- eventChannel directly will require updating.- https://github.com/coreyoconnor/vty/issues/60+-----++* Changed eventChannel of Graphics.Vty.Input from Chan to+ TChan. This enables clients to query if there are no pending+ events. The Graphics.Vty interface nextEvent is unchanged.+ Clients that use eventChannel directly will require updating.+ https://github.com/coreyoconnor/vty/issues/60+ 5.3.1- - Reverted cabal file to depend on Cabal >= 1.18 instead of 1.20 due- to possibly breaking this on reasonable GHC versions+----- +* Reverted cabal file to depend on Cabal >= 1.18 instead of 1.20 due to+ possibly breaking this on reasonable GHC versions+ 5.3- - Upgraded QuickCheck dependency to 2.7- - The standard IO Config (standardIOConfig) was overriding any- provided application config. In addition, the inputFd and outputFd- could not be changed if mkVty was used. Fixed.- - Correct handling of display attributes at end of line. The output- attributes are set to default at the end of content for the line and- at the start of a new line. Previously the current attribute would- extend to the next start of content. This was odd to reason about- and was the cause of https://github.com/coreyoconnor/vty/issues/76- IIRC Yi requires the old behavior to display the selection region- correctly.- - shutdown of the input thread is now performed using killThread and- synchronization on an MVar. For correct handling of the terminal- read vmin and vtime the read must be a blocking read on an OS- thread. This places a threadWaitRead, which will be interrupted by- the killThread, prior to the uninterruptable read. An alternative- would be to re-import the read foreign call as interruptable.+--- +* Upgraded QuickCheck dependency to 2.7+* The standard IO Config (standardIOConfig) was overriding any provided+ application config. In addition, the inputFd and outputFd could not be+ changed if mkVty was used. Fixed.+* Correct handling of display attributes at end of line. The output+ attributes are set to default at the end of content for the line and+ at the start of a new line. Previously the current attribute would+ extend to the next start of content. This was odd to reason about and+ was the cause of https://github.com/coreyoconnor/vty/issues/76 IIRC Yi+ requires the old behavior to display the selection region correctly.+* shutdown of the input thread is now performed using killThread and+ synchronization on an MVar. For correct handling of the terminal read+ vmin and vtime the read must be a blocking read on an OS thread.+ This places a threadWaitRead, which will be interrupted by the+ killThread, prior to the uninterruptable read. An alternative would be+ to re-import the read foreign call as interruptable.+ 5.2.11- - deepseq bounds increased for tests.- - Clean up warnings when compiling on 7.10- - Thanks Eric Mertens- - Avoid discarding input bytes after multi-byte encoded codepoint- - Thanks Eric Mertens+------ +* deepseq bounds increased for tests.+* Clean up warnings when compiling on 7.10+ * Thanks Eric Mertens+* Avoid discarding input bytes after multi-byte encoded codepoint+ * Thanks Eric Mertens+ 5.2.10- - "str" now returns EmptyImage for empty strings to match behavior of- other string-like Image constructors (fixes #74)- - Thanks Jonathan Daugherty+------ +* "str" now returns EmptyImage for empty strings to match behavior of+ other string-like Image constructors (fixes #74)+ * Thanks Jonathan Daugherty+ 5.2.9- - dependency version bumps- - https://github.com/coreyoconnor/vty/pull/71- - https://github.com/coreyoconnor/vty/pull/70- - Correct/Simplify the example code- - Thanks glguy- - https://github.com/coreyoconnor/vty/pull/69+----- +* dependency version bumps+ * https://github.com/coreyoconnor/vty/pull/71+ * https://github.com/coreyoconnor/vty/pull/70+* Correct/Simplify the example code+ * Thanks glguy+ * https://github.com/coreyoconnor/vty/pull/69+ 5.2.8- - blaze-builder, lens, utf8-string version constraint bump- - Thanks glguy- - https://github.com/coreyoconnor/vty/pull/67- - Do not differentiate based on TERM_PROGRAM- - https://github.com/coreyoconnor/vty/issues/68+----- +* blaze-builder, lens, utf8-string version constraint bump+ * Thanks glguy+ * https://github.com/coreyoconnor/vty/pull/67+* Do not differentiate based on TERM_PROGRAM+ * https://github.com/coreyoconnor/vty/issues/68+ 5.2.7- - lens and deepseq constraint bump + misc- - Thanks ethercrow- - https://github.com/coreyoconnor/vty/pull/66+----- +* lens and deepseq constraint bump + misc+ * Thanks ethercrow+ * https://github.com/coreyoconnor/vty/pull/66+ 5.2.6- - lens constraint bump- - Thanks alexander-b!- - https://github.com/coreyoconnor/vty/pull/64+----- +* lens constraint bump+ * Thanks alexander-b!+ * https://github.com/coreyoconnor/vty/pull/64+ 5.2.5- - lens and random version constraint bump.- - Thanks RyanGlScott!- - https://github.com/coreyoconnor/vty/pull/62+----- +* lens and random version constraint bump.+ * Thanks RyanGlScott!+ * https://github.com/coreyoconnor/vty/pull/62+ 5.2.4- - removed -fpic from cc-options. No longer required.- - https://github.com/coreyoconnor/vty/issues/61- - https://ghc.haskell.org/trac/ghc/ticket/9657- - Thanks Fuuzetsu!+----- +* removed -fpic from cc-options. No longer required.+ * https://github.com/coreyoconnor/vty/issues/61+ * https://ghc.haskell.org/trac/ghc/ticket/9657+ * Thanks Fuuzetsu!+ 5.2.3- - evaluate/compile the input parsing table once instead of each- keystroke.- - https://github.com/coreyoconnor/vty/pull/59- - Thanks ethercrow!+----- +* evaluate/compile the input parsing table once instead of each+ keystroke.+ * https://github.com/coreyoconnor/vty/pull/59+ * Thanks ethercrow!+ 5.2.2- - When looking at input for an event, don't look too deep.- - https://github.com/coreyoconnor/vty/pull/57- - Thanks ethercrow!+----- +* When looking at input for an event, don't look too deep.+ * https://github.com/coreyoconnor/vty/pull/57+ * Thanks ethercrow!+ 5.2.1- - Bump upper version bound for lens to 4.5. Thanks markus1189!+----- +* Bump upper version bound for lens to 4.5. Thanks markus1189!+ 5.2.0- - Config structure now specifies file descriptor to use. The default- is stdInput and stdOutput file descriptors. Previously Vty used- stdInput for input and the follow code for output:- - hDuplicate stdout >>= handleToFd >>= (`hSetBuffering`- NoBuffering)- - the difference was required by Vty.Inline. Now, Vty.Inline uses- the Config structure options to acheive the same effect.- - removed: derivedVtime, derivedVmin, inputForCurrentTerminal,- inputForNameAndIO, outputForCurrentTerminal, outputForNameAndIO- - added: inputForConfig, outputForConfig- - updates to vty-rogue from jtdaugherty. Thanks!- - the oldest version of GHC tested to support vty is 7.6.2.- - the oldest version of GHC that vty compiles under is 7.4.2+----- +* Config structure now specifies file descriptor to use. The default+ is stdInput and stdOutput file descriptors. Previously Vty used+ stdInput for input and the follow code for output:+ * hDuplicate stdout >>= handleToFd >>= (`hSetBuffering`+ NoBuffering)+ * the difference was required by Vty.Inline. Now, Vty.Inline uses+ the Config structure options to acheive the same effect.+* removed: derivedVtime, derivedVmin, inputForCurrentTerminal,+ inputForNameAndIO, outputForCurrentTerminal, outputForNameAndIO+* added: inputForConfig, outputForConfig+* updates to vty-rogue from jtdaugherty. Thanks!+* the oldest version of GHC tested to support vty is 7.6.2.+* the oldest version of GHC that vty compiles under is 7.4.2+ 5.1.4- - merged https://github.com/coreyoconnor/vty/pull/51 thanks trofi!+----- +* merged https://github.com/coreyoconnor/vty/pull/51 thanks trofi!+ 5.1.1- - merged https://github.com/coreyoconnor/vty/pull/48 thanks sjmielke!- - jtdaugherty resolved a number of compiler warnings. Thanks!+----- +* merged https://github.com/coreyoconnor/vty/pull/48 thanks sjmielke!+* jtdaugherty resolved a number of compiler warnings. Thanks!+ 5.1.0- - vmin and vtime can be specified however the application requires.- See Graphics.Vty.Config.- - fixed the processing of input when vmin is set > 1.+----- +* vmin and vtime can be specified however the application requires.+ See Graphics.Vty.Config.+* fixed the processing of input when vmin is set > 1.+ 5.0.0- - The naming convention now matches:- - http://www.haskell.org/haskellwiki/Programming_guidelines#Naming_Conventions- - all projects using vty for input must be compiled with -threaded.- Please notify vty author if this is not acceptable.- - mkVtyEscDelay has been removed. Use "mkVty def". Which initialized- vty with the default configuration.- - input handling changes- - KASCII is now KChar- - KPN5 is now KCenter- - tests exist.- - Applications can add to the input tables by setting inputMap of- the Config. See Graphics.Vty.Config- - Users can define input table extensions that will apply to all vty- applications. See Graphics.Vty.Config- - terminal timing is now handled by selecting an appropriate VTIME.- Previously this was implemented within Vty itself. This reduced- complexity in vty but provides a different meta key behavior and- implies a requirement on -threaded.- - The time vty will wait to verify an ESC byte means a single ESC- key is the singleEscPeriod of the Input Config structure.- - removed the typeclass based terminal and display context interface- in favor of a data structure of properties interface.- - renamed the Terminal interface to Output- - The default picture for an image now uses the "clear" background.- This background fills background spans with spaces or just ends the- line.- - Previously the background defaulted to the space character. This- causes issues copying text from a text editor. The text would end- up with extra spaces at the end of the line.- - Layer support- - Each layer is an image.- - The layers for a picture are a list of images.- - The first image is the top-most layer. The images are ordered from- top to bottom.- - The transparent areas for a layer are the backgroundFill areas.- backgroundFill is added to pad images when images of different- sizes are joined.- - If the background is clear there is no background layer.- - If there is a background character then the bottom layer is the- background layer.- - emptyPicture is a Picture with no layers and no cursor- - addToTop and addToBottom add a layer to the top and bottom of the- given Picture.- - compatibility improvements:- - terminfo based terminals with no cursor support are silently- accepted. The cursor visibility changes in the Picture will have- no effect.- - alternate (setf/setb) color maps supported. Though colors beyond- the first 8 are just a guess.- - added "rgbColor" for easy support of RGB specified colors.- - Both applications and users can add to the mapping used to- translate from input bytes to events.- - Additional information about input and output process can be- appended to a debug log- - Set environment variable VTY_DEBUG_LOG to path of debug log- - Or use "debugLog <path>" config directive- - Or set 'debugLog' property of the Config provided to mkVty.- - examples moved to vty-examples package. See test directory for cabal- file.- - vty-interactive-terminal-test- - interactive test. Useful for building a bug report for vty's- author.- - test/interactive_terminal_test.hs- - vty-event-echo- - view a input event log for vty. Example of interacting with- user.- - test/EventEcho.hs- - vty-rogue- - The start of a rogue-like game. Example of layers and image- build operations.- - test/Rogue.hs- - vty-benchmark- - benchmarks vty. A series of tests that push random pictures to- the terminal. The random pictures are generated using- QuickCheck. The same generators used in the automated tests.- - test/benchmark.hs+----- +* The naming convention now matches:+ * http://www.haskell.org/haskellwiki/Programming_guidelines#Naming_Conventions+* all projects using vty for input must be compiled with -threaded.+ Please notify vty author if this is not acceptable.+* mkVtyEscDelay has been removed. Use "mkVty def". Which initialized+ vty with the default configuration.+* input handling changes+ * KASCII is now KChar+ * KPN5 is now KCenter+ * tests exist.+ * Applications can add to the input tables by setting inputMap of+ the Config. See Graphics.Vty.Config+ * Users can define input table extensions that will apply to all vty+ applications. See Graphics.Vty.Config+ * terminal timing is now handled by selecting an appropriate VTIME.+ Previously this was implemented within Vty itself. This reduced+ complexity in vty but provides a different meta key behavior and+ implies a requirement on -threaded.+ * The time vty will wait to verify an ESC byte means a single ESC+ key is the singleEscPeriod of the Input Config structure.+* removed the typeclass based terminal and display context interface+ in favor of a data structure of properties interface.+* renamed the Terminal interface to Output+* The default picture for an image now uses the "clear" background.+ This background fills background spans with spaces or just ends the+ line.+ * Previously the background defaulted to the space character. This+ causes issues copying text from a text editor. The text would end+ up with extra spaces at the end of the line.+* Layer support+ * Each layer is an image.+ * The layers for a picture are a list of images.+ * The first image is the top-most layer. The images are ordered from+ top to bottom.+ * The transparent areas for a layer are the backgroundFill areas.+ backgroundFill is added to pad images when images of different+ sizes are joined.+ * If the background is clear there is no background layer.+ * If there is a background character then the bottom layer is the+ background layer.+ * emptyPicture is a Picture with no layers and no cursor+ * addToTop and addToBottom add a layer to the top and bottom of the+ given Picture.+* compatibility improvements:+ * terminfo based terminals with no cursor support are silently+ accepted. The cursor visibility changes in the Picture will have+ no effect.+ * alternate (setf/setb) color maps supported. Though colors beyond+ the first 8 are just a guess.+ * added "rgbColor" for easy support of RGB specified colors.+ * Both applications and users can add to the mapping used to+ translate from input bytes to events.+* Additional information about input and output process can be+ appended to a debug log+ * Set environment variable VTY_DEBUG_LOG to path of debug log+ * Or use "debugLog <path>" config directive+ * Or set 'debugLog' property of the Config provided to mkVty.+* examples moved to vty-examples package. See test directory for cabal+ file.+ * vty-interactive-terminal-test+ * interactive test. Useful for building a bug report for vty's+ author.+ * test/interactive_terminal_test.hs+ * vty-event-echo+ * view a input event log for vty. Example of interacting with+ user.+ * test/EventEcho.hs+ * vty-rogue+ * The start of a rogue-like game. Example of layers and image+ build operations.+ * test/Rogue.hs+ * vty-benchmark+ * benchmarks vty. A series of tests that push random pictures to+ the terminal. The random pictures are generated using+ QuickCheck. The same generators used in the automated tests.+ * test/benchmark.hs+ 4.7.0.0- - API changes:- - Added Graphics.Vty.Image.crop: Ensure an image is no larger- than the specified size.- - Added Graphics.Vty.Image.pad: Ensure an image is no smaller- than the specified size.- - Added Graphics.Vty.Image.translate: Offset an image.- - Thanks Ben Boeckel <MathStuf@gmail.com> for these features.+------- -4.3.0.0+API changes:+* Added Graphics.Vty.Image.crop: Ensure an image is no larger+ than the specified size.+* Added Graphics.Vty.Image.pad: Ensure an image is no smaller+ than the specified size.+* Added Graphics.Vty.Image.translate: Offset an image.+* Thanks Ben Boeckel <MathStuf@gmail.com> for these features. 4.2.1.0- - API changes:- - Attr record accessor fore_color changed to attr_fore_color- - Attr record accessor back_color changed to attr_back_color- - Attr record accessor style changed to attr_style- - Added an "inline" display attribute changing DSL:- - put_attr_change applies a display attribute change- immediately to a terminal- - For instance, can be used to change the display attrbiutes- of text output via putStrLn and putStr. EX:- "put_attr_change $ back_color red" will set the background- color to red.- - Changes do not apply to a Picture output via output_picture.- - See Graphics.Vty.Inline- - Moved all IO actions into any monad an instance of MonadIO+------- +API changes:+* Attr record accessor fore_color changed to attr_fore_color+* Attr record accessor back_color changed to attr_back_color+* Attr record accessor style changed to attr_style+* Added an "inline" display attribute changing DSL:+ * put_attr_change applies a display attribute change+ immediately to a terminal+ * For instance, can be used to change the display attrbiutes+ of text output via putStrLn and putStr. EX:+ "put_attr_change $ back_color red" will set the background+ color to red.+ * Changes do not apply to a Picture output via output_picture.+ * See Graphics.Vty.Inline+* Moved all IO actions into any monad an instance of MonadIO+ 4.0.0.1- - binding for mk_wcswidth was incorrect. Most platforms just- magically worked due to coincidence.+------- +* binding for mk_wcswidth was incorrect. Most platforms just+ magically worked due to coincidence.+ 4.0.0- - API changes:- - "getSize" has been removed. Use "terminal vty >>= display_bounds"- where "vty" is an instance of the Vty data structure.- - added a "terminal" field to the Vty data structure. Accesses the- TerminalHandle associated with the Vty instance.- - Graphics.Vty.Types has undergone a number of changes. Summary:- - Partitioned into Graphics.Vty.Attributes for display attributes.- Graphics.Vty.Image for image combinators. Graphics.Vty.Picture- for final picture construction.- - Graphics.Vty.Attributes:- - "setFG" and "setBG" are now "with_fore_color" and- "with_back_color"- - All other "set.." equations similarly replaced.- - "attr" is now "def_attr", short for "default display attributes"- Also added a "current_attr" for "currently applied display- attributes"- - Graphics.Vty.Image:- - "horzcat" is now "horiz_cat"- - "vertcat" is now "vert_cat"- - "renderBS" is now "utf8_bytestring"- - "renderChar" is now "char"- - "renderFill" is now "char_fill"- - added a "utf8_string" and "string" (AKA "iso_10464_string") for- UTF-8 encoded Strings and ISO-10464 encoded Strings. String- literals in GHC have an ISO-10464 runtime representation.- - Graphics.Vty.Picture:- - exports Graphics.Vty.Image- - "pic" is now "pic_for_image"- - added API for setting background fill pattern.- - Completely rewritten output backend.- - Efficient, scanline style output span generator. Has not been- fully optimized, but good enough.- - The details required to display the desired picture on a- terminal are well encapsulated.- - Terminfo based display terminal implementation. With specialized- derivitives for xterm, Terminal.app, and iTerm.app.- - Attempts to robustly handle even terminals that don't- support all display attributes.- - I've tested the following terminals with success: iTerm.app,- Terminal.app, xterm, rxvt, mlterm, Eterm, gnome-terminal,- konsole, screen, linux vty. Hopefully you will be as- successfull.- - Improved unicode support. Double wide characters will display as- expected.- - 256 color support. See Graphics.Vty.Attributes.Color240. The actual- output color is adjusted according to the number of colors the- terminal supports.- - The Graphics.Vty.Image combinators no longer require matching- dimensions to arguments. Unspecified areas are filled in with a- user-customizable background pattern. See Graphics.Vty.Picture.- - output images are always cropped to display size.- - Significant code coverage by QuickCheck tests. An interactive test- for those final properties that couldn't be automatically verified.+----- +API changes:+* "getSize" has been removed. Use "terminal vty >>= display_bounds"+ where "vty" is an instance of the Vty data structure.+* added a "terminal" field to the Vty data structure. Accesses the+ TerminalHandle associated with the Vty instance.+* Graphics.Vty.Types has undergone a number of changes. Summary:+ * Partitioned into Graphics.Vty.Attributes for display attributes.+ Graphics.Vty.Image for image combinators. Graphics.Vty.Picture+ for final picture construction.+* Graphics.Vty.Attributes:+ * "setFG" and "setBG" are now "with_fore_color" and+ "with_back_color"+ * All other "set.." equations similarly replaced.+ * "attr" is now "def_attr", short for "default display attributes"+ Also added a "current_attr" for "currently applied display+ attributes"+* Graphics.Vty.Image:+ * "horzcat" is now "horiz_cat"+ * "vertcat" is now "vert_cat"+ * "renderBS" is now "utf8_bytestring"+ * "renderChar" is now "char"+ * "renderFill" is now "char_fill"+ * added a "utf8_string" and "string" (AKA "iso_10464_string") for+ UTF-8 encoded Strings and ISO-10464 encoded Strings. String+ literals in GHC have an ISO-10464 runtime representation.+* Graphics.Vty.Picture:+ * exports Graphics.Vty.Image+ * "pic" is now "pic_for_image"+ * added API for setting background fill pattern.+* Completely rewritten output backend.+ * Efficient, scanline style output span generator. Has not been+ fully optimized, but good enough.+ * The details required to display the desired picture on a+ terminal are well encapsulated.+ * Terminfo based display terminal implementation. With specialized+ derivitives for xterm, Terminal.app, and iTerm.app.+ * Attempts to robustly handle even terminals that don't+ support all display attributes.+ * I've tested the following terminals with success: iTerm.app,+ Terminal.app, xterm, rxvt, mlterm, Eterm, gnome-terminal,+ konsole, screen, linux vty. Hopefully you will be as+ successfull.+ * Improved unicode support. Double wide characters will display as+ expected.+* 256 color support. See Graphics.Vty.Attributes.Color240. The actual+ output color is adjusted according to the number of colors the+ terminal supports.+* The Graphics.Vty.Image combinators no longer require matching+ dimensions to arguments. Unspecified areas are filled in with a+ user-customizable background pattern. See Graphics.Vty.Picture.+* output images are always cropped to display size.+* Significant code coverage by QuickCheck tests. An interactive test+ for those final properties that couldn't be automatically verified.+ Issues resolved:- - "gnome terminal displays non-basic attributes as strikethrough"- - http://trac.haskell.org/vty/ticket/14- - "Multi-byte characters are not displayed correctly on update"- - http://trac.haskell.org/vty/ticket/10- - "Redraw does not handle rendering a line that extends beyond screen- width characters"- - http://trac.haskell.org/vty/ticket/13- - "The <|> and <-> combinators should be more forgiving of mismatched- dimensions"- - http://trac.haskell.org/vty/ticket/9- - "256-color support"- - http://trac.haskell.org/vty/ticket/19+* "gnome terminal displays non-basic attributes as strikethrough"+ * http://trac.haskell.org/vty/ticket/14+* "Multi-byte characters are not displayed correctly on update"+ * http://trac.haskell.org/vty/ticket/10+* "Redraw does not handle rendering a line that extends beyond screen+ width characters"+ * http://trac.haskell.org/vty/ticket/13+* "The <|> and <-> combinators should be more forgiving of mismatched+ dimensions"+ * http://trac.haskell.org/vty/ticket/9+* "256-color support"+ * http://trac.haskell.org/vty/ticket/19
README.md view
@@ -18,24 +18,21 @@ # Features -* Supports a large number of terminals. vt100, ansi, hurd, linux,- screen, etc., or anything with a sufficient terminfo entry.--* Automatic handling of window resizes.+* Supports a large number of terminals, i.e., vt100, ansi, hurd, linux,+ `screen`, etc., or anything with a sufficient terminfo entry. -* Unicode output on terminals with UTF-8 support.+* Automatically handles window resizes. -* Handles multi-column glyphs. (Requires user to properly configure- terminal.)+* Supports Unicode output on terminals with UTF-8 support. -* Efficient output. Output buffering and terminal state changes are- minimized.+* Provides an efficient output algorithm. Output buffering and terminal+ state changes are minimized. * Minimizes repaint area, which virtually eliminates the flicker problems that plague ncurses programs. -* A pure, compositional interface for efficiently constructing display- images.+* Provides a pure, compositional interface for efficiently constructing+ display images. * Automatically decodes keyboard keys into (key,[modifier]) tuples. @@ -44,7 +41,7 @@ * Supports a keypress timeout after for lone ESC. The timeout is customizable. -* The interface is designed for easy extension.+* Provides extensible input and output interfaces. * Supports ANSI graphics modes (SGR as defined in `console_codes(4)`) with a type-safe interface and graceful fallback for terminals@@ -60,6 +57,12 @@ * Supports bracketed paste mode as described at http://cirw.in/blog/bracketed-paste +* Supports multi-column Unicode characters such as emoji characters. In+ cases where Vty and your terminal emulator disagree on character+ widths, Vty provides a tool `vty-build-width-table` and library+ functionality to build a width table that will work for your terminal+ and load it on application startup.+ # Development Notes Vty uses threads internally, so programs made with Vty need to be@@ -69,64 +72,111 @@ ## Posix Terminals -Uses terminfo to determine terminal protocol. With some special rules to-handle some omissions from terminfo.+For the most part, Vty uses `terminfo` to determine terminal protocol+with some special rules to handle some omissions from `terminfo`. ## Windows -Unsupported (but contributions and testing here are very welcome!)+Windows is not supported. -# Contributing+# Multi-Column Character Support -If you decide to contribute, that's great! Here are some guidelines you-should consider to make submitting patches easier for all concerned:+Vty supports rendering of multi-column characters such as two-column+Asian characters and Emoji characters. This section details how to+take advantage of this feature, since its behavior will depend on the+terminal emulator in use. - - If you want to take on big things, talk to me first; let's have a- design/vision discussion before you start coding. Create a GitHub- issue and we can use that as the place to hash things out.- - If you make changes, make them consistent with the syntactic- conventions already used in the codebase.- - Please provide Haddock documentation for any changes you make.+Terminal emulators support Unicode to varying degrees, and each terminal+emulator relies on a table of column widths for each supported Unicode+character. Vty also needs to rely on such a table to compute the width+of Vty images to do image layout. Since those tables can disagree if+Vty and the terminal emulator support different versions of Unicode,+and since different terminal emulators will support different versions+of Unicode, it's likely that for some wide characters, Vty applications+will exhibit rendering problems. Those rendering problems arise from Vty+and the terminal emulator coming to different conclusions about how wide+some characters are. -# Development Notes+To address this, Vty supports loading custom character width tables+that are based on the terminal's behavior in order to eliminate these+disagreements. By default, though, Vty will use its built-in Unicode+character width table. Since the built-in table is likely to eventually+disagree with your terminal, Vty provides an API and a command-line tool+to generate and install custom tables. -## Under NixOS+Custom Unicode width tables based on your terminal emulator can be+built by running Vty's built-in tool, `vty-build-width-table`. The tool+works by querying the current terminal emulator to obtain its width+measurements for the entire supported Unicode range. The+results are then saved to a disk file. These custom tables+can also be generated programmatically by using the API in+`Graphics.Vty.UnicodeWidthTable.Query`. -### Using cabal+Saved width tables can then be loaded in one of two ways: -After installing `ncurses` to the user environment:+* Via the library API in `Graphics.Vty.UnicodeWidthTable.IO`+* By adding a `widthMap` directive to your Vty configuration file and+ then invoking `mkVty` to initialize Vty -~~~-LIBRARY_PATH=$HOME/.nix-profile/lib/ cabal configure --enable-tests --extra-lib-dirs=$HOME/.nix-profile/lib-LIBRARY_PATH=$HOME/.nix-profile/lib/ cabal build-LIBRARY_PATH=$HOME/.nix-profile/lib/ cabal test-~~~+The Vty configuration file supports the `widthMap` directive to allow+users to specify which custom width table should be loaded for a given+terminal type. This is done by specifying, e.g., -## Code Coverage+```+widthMap "xterm" "/path/to/map.dat"+``` -As of last testing, profiling causes issues with coverage when enabled.-To evaluate coverage, configure as follows:+where the first argument is the value that `TERM` must have in order for+the table to be loaded, and the second argument is the path to the table+file itself as generated by the two alternatives listed above. If the+Vty configuration file contains multiple matching `widthMap` directives+for the current value of `TERM`, the last one listed in the file is+used. -~~~-rm -rf dist ; cabal configure --enable-tests --enable-library-coverage \- --disable-library-profiling \- --disable-executable-profiling-~~~+The tables declared in the configuration file are only ever+automatically loaded when applications set up Vty by calling+`Graphics.Vty.mkVty`. -## Profiling+Before a custom table has been loaded, calls to the library's character+width functions (e.g. `wcwidth`) will use the default built-in table.+Once a custom table has been loaded, the functions will use the new+custom table. Only one custom table load can be performed in a Vty+program. Once a custom table has been loaded, it cannot be replaced or+removed. -~~~-rm -rf dist ; cabal configure --enable-tests --disable-library-coverage \- --enable-library-profiling \- --enable-executable-profiling-~~~+Without using a custom width table, users of Vty-based applications+are likely to eventually experience rendering problems with with wide+characters. We recommend that developers of Vty-based applications either: +* Provide the `vty-build-width-table` tool and documentation for running+ it and updating the Vty configuration, or+* Have the application invoke the Vty library's table-building+ functionality and load the table at startup without using the Vty+ configuration.++The best option will depend on a number of factors: the user audience,+the amount of risk posed by wide character rendering, the terminal+emulators in use, etc.++# Contributing++If you decide to contribute, that's great! Here are some guidelines you+should consider to make submitting patches easier for all concerned:++ - If you want to take on big things, talk to me first; let's have a+ design/vision discussion before you start coding. Create a GitHub+ issue and we can use that as the place to hash things out.+ - If you make changes, make them consistent with the syntactic+ conventions already used in the codebase.+ - Please provide Haddock documentation for any changes you make.+ # Known Issues -* Terminals have numerous quirks and bugs. Vty picks what works best for- the author in ambiguous or buggy situations.+* Terminals have numerous quirks and bugs, so mileage may vary. Please+ report issues as you encounter them and provide details on your+ terminal emulator, operating system, etc. -* Signal handling of STOP, TERM and INT are nonexistent.+* STOP, TERM and INT signals are not handled. * The character encoding of the terminal is assumed to be UTF-8 if unicode is used.@@ -136,10 +186,10 @@ (shifted F10 etc). See `Config` for customizing vty's behavior for a particular terminal. -* Uses the `TIOCGWINSZ` ioctl to find the current window size, which+* Vty uses the `TIOCGWINSZ` ioctl to find the current window size, which appears to be limited to Linux and BSD. -# Sources+# Further Reading Good sources of documentation for terminal programming are:
cbits/mk_wcwidth.c view
@@ -60,7 +60,29 @@ */ #include <HsFFI.h>+#include <stdlib.h>+#include <string.h> +// The maximum size for a custom character width table is the total+// number of possible characters as dictated by the compiler.+#define MAX_CUSTOM_TABLE_SIZE (HS_CHAR_MAX + 1)++// The pointer to the start of the custom character width table, if+// any. If this is NULL or this is set but the ready flag is false, the+// built-in tree search logic is used.+static uint8_t* custom_table = NULL;++// The size of the custom table, in entries. This should only be set+// if custom_table is not NULL. Its value should be the size of the+// custom_table array.+static uint32_t custom_table_size = 0;++// A flag indicating whether the custom table is ready for+// use. This should only be set once the table has been+// allocated with vty_init_custom_table and initialized with+// vty_set_custom_table_range.+static uint8_t custom_table_ready = 0;+ struct interval { int first; int last;@@ -119,7 +141,7 @@ * in ISO 10646. */ -HsInt vty_mk_wcwidth(HsChar ucs)+static HsInt builtin_wcwidth(HsChar ucs) { /* sorted list of non-overlapping intervals of non-spacing characters */ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */@@ -182,7 +204,7 @@ /* binary search in table of non-spacing characters */ if (vty_bisearch(ucs, combining,- sizeof(combining) / sizeof(struct interval) - 1))+ sizeof(combining) / sizeof(struct interval) - 1)) return 0; /* if we arrive here, ucs is not a combining or C0/C1 control character */@@ -201,4 +223,116 @@ (ucs >= 0xffe0 && ucs <= 0xffe6) || (ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)));+}++// Return the width, in terminal cells, of the specified character.+//+// If the global custom width table is present, that table will be+// consulted for the character's width. If the character is not in+// the table, zero will be returned. If the custom width table is not+// present, the built-in width table will be used.+HsInt vty_mk_wcwidth(HsChar ch)+{+ if (custom_table_ready) {+ if ((ch >= 0) && (ch < custom_table_size)) {+ return custom_table[ch];+ } else {+ return -1;+ }+ } else {+ return builtin_wcwidth(ch);+ }+}++// Initialize a custom character width table.+//+// This allocates a new character width table of the specified size+// (in characters). If a custom table has already been allocated, this+// returns 1. Otherwise it allocates a new table, initializes all of its+// entries to 1, and returns zero.+//+// Note that this does *not* mark the table as ready for use. Until the+// table is marked ready, it will not be used by vty_mk_wcwidth. To mark+// the table as ready, call vty_activate_custom_table() after the table+// has been set up with calls to vty_set_custom_table_range.+int vty_init_custom_table(int size)+{+ if (custom_table == NULL) {+ if (size > 0 && size <= MAX_CUSTOM_TABLE_SIZE) {+ custom_table_ready = 0;+ custom_table = malloc(size);+ memset(custom_table, 1, size);+ custom_table_size = size;+ return 0;+ } else {+ return 1;+ }+ } else {+ return 1;+ }+}++// Set the specified character range in the custom width table to the+// specified width.+//+// This function sets 'width' as the character width for all entries+// in the custom character table starting at the 'start' entry and+// including all entries up to and including 'start + size - 1'.+//+// If this succeeds, it returns zero. If it fails, it returns 1. It+// fails if the table is not allocated, marked as ready (i.e. it is in+// use and has already been populated), or if the start or size values+// are not in bounds for the table.+int vty_set_custom_table_range(uint32_t start, uint32_t size, uint8_t width)+{+ if ((custom_table == NULL) ||+ (size >= custom_table_size) ||+ (start >= custom_table_size) ||+ ((start + 1) >= (custom_table_size - size)) ||+ custom_table_ready) {+ return 1;+ } else {+ memset(custom_table + start, width, size);+ return 0;+ }+}++// Mark the allocated custom character width table as ready for use.+//+// After this call, further calls to vty_set_custom_table_range will+// fail.+//+// This function returns 0 if it succeeds. If it fails, it returns 1.+// It fails if the custom table is already ready or if it has not been+// allocated.+int vty_activate_custom_table()+{+ if (custom_table_ready || (custom_table == NULL)) {+ return 1;+ } else {+ custom_table_ready = 1;+ return 0;+ }+}++// Returns whether a custom character width table has been marked ready.+int vty_custom_table_ready()+{+ return custom_table_ready;+}++// Deallocate the custom width table.+//+// This does nothing if there is no allocated custom width table, or if+// there is one but it is in use (marked ready). This is only useful if+// an initial allocation succeeds, but range population fails, after+// which point the application may want to deallocate the table to avoid+// leaving it in an intermediate state.+void vty_deallocate_custom_table()+{+ if ((custom_table != NULL) && (!custom_table_ready)) {+ free(custom_table);+ custom_table = NULL;+ custom_table_size = 0;+ } }
src/Graphics/Text/Width.hs view
@@ -1,6 +1,15 @@ -- Copyright 2009 Corey O'Connor {-# OPTIONS_GHC -D_XOPEN_SOURCE #-} {-# LANGUAGE ForeignFunctionInterface #-}+-- | This module provides functions to measure the terminal column width+-- of characters and strings.+--+-- The functions provided in this module all ultimately make calls to+-- the C implementation in @cbits/mk_wcwidth.c@. That code manages some+-- global state that carries a table of Unicode character widths. For+-- more details, see 'Graphics.Vty.UnicodeWidthTable.Install', the C+-- code, and the "Multi-Column Character Support" section of the project+-- @README@. module Graphics.Text.Width ( wcwidth , wcswidth@@ -33,14 +42,6 @@ "wcswidth/unpack" forall x. wcswidth (T.unpack x) = wctwidth x "wcswidth/lazy-unpack" forall x. wcswidth (TL.unpack x) = wctlwidth x #-}---- XXX: Characters with unknown widths occupy 1 column?------ Not sure if this is actually correct. I presume there is a--- replacement character that is output by the terminal instead of the--- character and this replacement character is 1 column wide. If this is--- not true for all terminals then a per-terminal replacement character--- width needs to be implemented. -- | Returns the display width of a character. Assumes all characters -- with unknown widths are 0 width.
src/Graphics/Vty.hs view
@@ -13,7 +13,7 @@ -- values in the "Graphics.Vty.Attributes" module. -- -- Vty uses threads internally, so programs made with Vty need to be--- compiled with the threaded runtime using the GHC `-threaded` option.+-- compiled with the threaded runtime using the GHC @-threaded@ option. -- -- @ -- import "Graphics.Vty"@@ -51,7 +51,10 @@ import Graphics.Vty.Picture import Graphics.Vty.Image import Graphics.Vty.Attributes+import Graphics.Vty.UnicodeWidthTable.IO+import Graphics.Vty.UnicodeWidthTable.Install +import qualified Control.Exception as E import Control.Monad (when) import Control.Concurrent.STM @@ -65,7 +68,7 @@ -- -- The use of Vty typically follows this process: ----- 1. Initialize vty+-- 1. Initialize vty with 'mkVty' (this takes control of the terminal). -- -- 2. Use 'update' to display a picture. --@@ -73,31 +76,33 @@ -- -- 4. Depending on the event, go to 2 or 5. ----- 5. Shutdown vty.+-- 5. Shutdown vty and restore the terminal state with 'shutdown'. At+-- this point the 'Vty' handle cannot be used again. -- -- Operations on Vty handles are not thread-safe.-data Vty = Vty- { -- | Outputs the given 'Picture'.- update :: Picture -> IO ()- -- | Return the next 'Event' or block until one becomes available.- , nextEvent :: IO Event- -- | Non-blocking version of 'nextEvent'.- , nextEventNonblocking :: IO (Maybe Event)- -- | The input interface. See 'Input'.- , inputIface :: Input- -- | The output interface. See 'Output'.- , outputIface :: Output- -- | Refresh the display. If other programs output to the terminal- -- and mess up the display then the application might want to- -- force a refresh using this function.- , refresh :: IO ()- -- | Clean up after vty. A call to this function is necessary to- -- cleanly restore the terminal state before application exit. The- -- above methods will throw an exception if executed after this is- -- executed. Idempotent.- , shutdown :: IO ()- , isShutdown :: IO Bool- }+data Vty =+ Vty { update :: Picture -> IO ()+ -- ^ Outputs the given 'Picture'.+ , nextEvent :: IO Event+ -- ^ Return the next 'Event' or block until one becomes+ -- available.+ , nextEventNonblocking :: IO (Maybe Event)+ -- ^ Non-blocking version of 'nextEvent'.+ , inputIface :: Input+ -- ^ The input interface. See 'Input'.+ , outputIface :: Output+ -- ^ The output interface. See 'Output'.+ , refresh :: IO ()+ -- ^ Refresh the display. If other programs output to the+ -- terminal and mess up the display then the application might+ -- want to force a refresh using this function.+ , shutdown :: IO ()+ -- ^ Clean up after vty. A call to this function is necessary to+ -- cleanly restore the terminal state before application exit.+ -- The above methods will throw an exception if executed after+ -- this is executed. Idempotent.+ , isShutdown :: IO Bool+ } -- | Create a Vty handle. At most one handle should be created at a time -- for a given terminal device.@@ -110,12 +115,51 @@ mkVty :: Config -> IO Vty mkVty appConfig = do config <- (<> appConfig) <$> userConfig- input <- inputForConfig config- out <- outputForConfig config- intMkVty input out -intMkVty :: Input -> Output -> IO Vty-intMkVty input out = do+ when (allowCustomUnicodeWidthTables config /= Just False) $+ installCustomWidthTable config++ input <- inputForConfig config+ out <- outputForConfig config+ internalMkVty input out++installCustomWidthTable :: Config -> IO ()+installCustomWidthTable c = do+ let doLog s = case debugLog c of+ Nothing -> return ()+ Just path -> appendFile path $ "installWidthTable: " <> s <> "\n"++ customInstalled <- isCustomTableReady+ when (not customInstalled) $ do+ mTerm <- currentTerminalName+ case mTerm of+ Nothing ->+ doLog "No current terminal name available"+ Just currentTerm ->+ case lookup currentTerm (termWidthMaps c) of+ Nothing ->+ doLog "Current terminal not found in custom character width mapping list"+ Just path -> do+ tableResult <- E.try $ readUnicodeWidthTable path+ case tableResult of+ Left (e::E.SomeException) ->+ doLog $ "Error reading custom character width table " <>+ "at " <> show path <> ": " <> show e+ Right (Left msg) ->+ doLog $ "Error reading custom character width table " <>+ "at " <> show path <> ": " <> msg+ Right (Right table) -> do+ installResult <- E.try $ installUnicodeWidthTable table+ case installResult of+ Left (e::E.SomeException) ->+ doLog $ "Error installing unicode table (" <>+ show path <> ": " <> show e+ Right () ->+ doLog $ "Successfully installed Unicode width table " <>+ " from " <> show path++internalMkVty :: Input -> Output -> IO Vty+internalMkVty input out = do reserveDisplay out shutdownVar <- atomically $ newTVar False
src/Graphics/Vty/Config.hs view
@@ -55,6 +55,32 @@ -- table to the log in the above format. A workflow for using this is -- to set @VTY_DEBUG_LOG@. Run the application. Check the debug log for -- incorrect mappings. Add corrected mappings to @$HOME/.vty/config@.+--+-- = Unicode Character Width Maps+--+-- == @widthMap@+--+-- Format:+--+-- @+-- \"widthMap\" string string+-- @+--+-- E.g.,+--+-- @+-- widthMap \"xterm\" \"\/home\/user\/.vty\/xterm\_map.dat\"+-- @+--+-- This directive specifies the path to a Unicode character width+-- map (the second argument) that should be loaded and used when+-- the value of TERM matches the first argument. Unicode character+-- width maps can be produced either by running the provided binary+-- @vty-build-width-table@ or by calling the library routine+-- 'Graphics.Vty.UnicodeWidthTable.Query.buildUnicodeWidthTable'. The+-- 'Graphics.Vty.mkVty' function will use these configuration settings+-- to attempt to load and install the specified width map. See the+-- documentation for 'Graphics.Vty.mkVty' for details. module Graphics.Vty.Config ( InputMap , Config(..)@@ -66,6 +92,16 @@ , parseConfigFile , defaultConfig , getTtyEraseChar+ , currentTerminalName++ , vtyConfigPath+ , widthTableFilename+ , vtyDataDirectory+ , terminalWidthTablePath+ , vtyConfigFileEnvName++ , ConfigUpdateResult(..)+ , addConfigWidthMap ) where @@ -89,8 +125,11 @@ import GHC.Generics -import System.Directory (getAppUserDataDirectory)+import System.Directory ( getAppUserDataDirectory, doesFileExist+ , createDirectoryIfMissing+ ) import System.Environment (lookupEnv)+import System.FilePath ((</>), takeDirectory) import System.Posix.IO (stdInput, stdOutput) import System.Posix.Types (Fd(..)) import Foreign.C.Types (CInt(..), CChar(..))@@ -100,12 +139,13 @@ import qualified Text.Parsec.Token as P -- | Type of errors that can be thrown when configuring VTY-data VtyConfigurationError- = VtyMissingTermEnvVar -- ^ TERM environment variable not set- deriving (Show, Eq, Typeable)+data VtyConfigurationError =+ VtyMissingTermEnvVar+ -- ^ TERM environment variable not set+ deriving (Show, Eq, Typeable) instance Exception VtyConfigurationError where- displayException VtyMissingTermEnvVar = "TERM environment variable not set"+ displayException VtyMissingTermEnvVar = "TERM environment variable not set" -- | Mappings from input bytes to event in the order specified. Later -- entries take precedence over earlier in the case multiple entries@@ -113,77 +153,125 @@ type InputMap = [(Maybe String, String, Event)] -- | A Vty configuration.-data Config = Config- {- -- | The default is 1 character.- vmin :: Maybe Int- -- | The default is 100 milliseconds, 0.1 seconds.- , vtime :: Maybe Int- -- | The default is False.- , mouseMode :: Maybe Bool- -- | The default is False.- , bracketedPasteMode :: Maybe Bool- -- | Debug information is appended to this file if not Nothing.- , debugLog :: Maybe FilePath- -- | The (input byte, output event) pairs extend the internal input- -- table of VTY and the table from terminfo.- --- -- See "Graphics.Vty.Config" module documentation for documentation- -- of the @map@ directive.- , inputMap :: InputMap- -- | The input file descriptor to use. The default is- -- 'System.Posix.IO.stdInput'- , inputFd :: Maybe Fd- -- | The output file descriptor to use. The default is- -- 'System.Posix.IO.stdOutput'- , outputFd :: Maybe Fd- -- | The terminal name used to look up terminfo capabilities.- -- The default is the value of the TERM environment variable.- , termName :: Maybe String- } deriving (Show, Eq)+data Config =+ Config { vmin :: Maybe Int+ -- ^ The default is 1 character.+ , vtime :: Maybe Int+ -- ^ The default is 100 milliseconds, 0.1 seconds.+ , mouseMode :: Maybe Bool+ -- ^ The default is False.+ , bracketedPasteMode :: Maybe Bool+ -- ^ The default is False.+ , debugLog :: Maybe FilePath+ -- ^ Debug information is appended to this file if not+ -- Nothing.+ , inputMap :: InputMap+ -- ^ The (input byte, output event) pairs extend the internal+ -- input table of VTY and the table from terminfo.+ --+ -- See "Graphics.Vty.Config" module documentation for+ -- documentation of the @map@ directive.+ , inputFd :: Maybe Fd+ -- ^ The input file descriptor to use. The default is+ -- 'System.Posix.IO.stdInput'+ , outputFd :: Maybe Fd+ -- ^ The output file descriptor to use. The default is+ -- 'System.Posix.IO.stdOutput'+ , termName :: Maybe String+ -- ^ The terminal name used to look up terminfo capabilities.+ -- The default is the value of the TERM environment variable.+ , termWidthMaps :: [(String, FilePath)]+ -- ^ Terminal width map files.+ , allowCustomUnicodeWidthTables :: Maybe Bool+ -- ^ Whether to permit custom Unicode width table loading by+ -- 'Graphics.Vty.mkVty'. @'Just' 'False'@ indicates that+ -- table loading should not be performed. Other values permit+ -- table loading.+ --+ -- If a table load is attempted and fails, information+ -- about the failure will be logged to the debug log if the+ -- configuration specifies one. If no custom table is loaded+ -- (or if a load fails), the built-in character width table+ -- will be used.+ }+ deriving (Show, Eq) defaultConfig :: Config defaultConfig = mempty instance Semigroup Config where- c0 <> c1 = Config+ c0 <> c1 = -- latter config takes priority for everything but inputMap- { vmin = vmin c1 <|> vmin c0- , vtime = vtime c1 <|> vtime c0- , mouseMode = mouseMode c1- , bracketedPasteMode = bracketedPasteMode c1- , debugLog = debugLog c1 <|> debugLog c0- , inputMap = inputMap c0 <> inputMap c1- , inputFd = inputFd c1 <|> inputFd c0- , outputFd = outputFd c1 <|> outputFd c0- , termName = termName c1 <|> termName c0- }+ Config { vmin = vmin c1 <|> vmin c0+ , vtime = vtime c1 <|> vtime c0+ , mouseMode = mouseMode c1+ , bracketedPasteMode = bracketedPasteMode c1+ , debugLog = debugLog c1 <|> debugLog c0+ , inputMap = inputMap c0 <> inputMap c1+ , inputFd = inputFd c1 <|> inputFd c0+ , outputFd = outputFd c1 <|> outputFd c0+ , termName = termName c1 <|> termName c0+ , termWidthMaps = termWidthMaps c1 <|> termWidthMaps c0+ , allowCustomUnicodeWidthTables =+ allowCustomUnicodeWidthTables c1 <|> allowCustomUnicodeWidthTables c0+ } instance Monoid Config where- mempty = Config- { vmin = Nothing- , vtime = Nothing- , mouseMode = Nothing- , bracketedPasteMode = Nothing- , debugLog = mempty- , inputMap = mempty- , inputFd = Nothing- , outputFd = Nothing- , termName = Nothing- }+ mempty =+ Config { vmin = Nothing+ , vtime = Nothing+ , mouseMode = Nothing+ , bracketedPasteMode = Nothing+ , debugLog = mempty+ , inputMap = mempty+ , inputFd = Nothing+ , outputFd = Nothing+ , termName = Nothing+ , termWidthMaps = []+ , allowCustomUnicodeWidthTables = Nothing+ } #if !(MIN_VERSION_base(4,11,0)) mappend = (<>) #endif --- | Load a configuration from @'getAppUserDataDirectory'/config@ and--- @$VTY_CONFIG_FILE@.+vtyDataDirectory :: IO FilePath+vtyDataDirectory = getAppUserDataDirectory "vty"++vtyConfigPath :: IO FilePath+vtyConfigPath = do+ dir <- vtyDataDirectory+ return $ dir </> "config"++vtyConfigFileEnvName :: String+vtyConfigFileEnvName = "VTY_CONFIG_FILE"++-- | Load a configuration from 'vtyConfigPath' and @$VTY_CONFIG_FILE@. userConfig :: IO Config userConfig = do- configFile <- (mappend <$> getAppUserDataDirectory "vty" <*> pure "/config") >>= parseConfigFile- overrideConfig <- maybe (return defaultConfig) parseConfigFile =<< lookupEnv "VTY_CONFIG_FILE"+ configFile <- vtyConfigPath >>= parseConfigFile+ overrideConfig <- maybe (return defaultConfig) parseConfigFile =<<+ lookupEnv vtyConfigFileEnvName let base = configFile <> overrideConfig mappend base <$> overrideEnvConfig +widthTableFilename :: String -> String+widthTableFilename term = "width_table_" <> term <> ".dat"++termVariable :: String+termVariable = "TERM"++currentTerminalName :: IO (Maybe String)+currentTerminalName = lookupEnv termVariable++terminalWidthTablePath :: IO (Maybe FilePath)+terminalWidthTablePath = do+ dataDir <- vtyDataDirectory+ result <- lookupEnv termVariable+ case result of+ Nothing -> return Nothing+ Just term -> do+ return $ Just $ dataDir </> widthTableFilename term+ overrideEnvConfig :: IO Config overrideEnvConfig = do d <- lookupEnv "VTY_DEBUG_LOG"@@ -193,7 +281,7 @@ -- can raise 'VtyConfigurationError'. standardIOConfig :: IO Config standardIOConfig = do- mb <- lookupEnv "TERM"+ mb <- lookupEnv termVariable case mb of Nothing -> throwIO VtyMissingTermEnvVar Just t ->@@ -256,13 +344,20 @@ path <- P.stringLiteral configLexer return defaultConfig { debugLog = Just path } +widthMapDecl :: Parser Config+widthMapDecl = do+ "widthMap" <- P.identifier configLexer+ tName <- P.stringLiteral configLexer+ path <- P.stringLiteral configLexer+ return defaultConfig { termWidthMaps = [(tName, path)] }+ ignoreLine :: Parser () ignoreLine = void $ manyTill anyChar newline parseConfig :: Parser Config parseConfig = liftM mconcat $ many $ do P.whiteSpace configLexer- let directives = [try mapDecl, try debugLogDecl]+ let directives = [try mapDecl, try debugLogDecl, try widthMapDecl] choice directives <|> (ignoreLine >> return defaultConfig) class Parse a where parseValue :: Parser a@@ -329,3 +424,56 @@ if c /= 0 then return $ Just $ toEnum $ fromEnum c else return Nothing++data ConfigUpdateResult =+ ConfigurationCreated+ | ConfigurationModified+ | ConfigurationConflict String+ | ConfigurationRedundant+ deriving (Eq, Show)++-- | Add a @widthMap@ directive to the Vty configuration file at the+-- specified path.+--+-- If the configuration path refers to a configuration that already+-- contains the directive for the specified map and terminal type, the+-- configuration file will not be modified. If the file does not contain+-- the directive, it will be appended to the file.+--+-- If the configuration path does not exist, a new configuration file+-- will be created and any directories in the path will also be created.+--+-- This returns @True@ if the configuration was created or modified and+-- @False@ otherwise. This does not handle exceptions raised by file or+-- directory permissions issues.+addConfigWidthMap :: FilePath+ -- ^ The configuration file path of the configuration+ -- to modify or create.+ -> String+ -- ^ The @TERM@ value for the @widthMap@ directive.+ -> FilePath+ -- ^ The width table file path for the directive.+ -> IO ConfigUpdateResult+addConfigWidthMap configPath term tablePath = do+ configEx <- doesFileExist configPath+ if configEx+ then updateConfig+ else createConfig >> return ConfigurationCreated++ where+ directive = "widthMap " <> show term <> " " <> show tablePath <> "\n"++ createConfig = do+ let dir = takeDirectory configPath+ createDirectoryIfMissing True dir+ writeFile configPath directive++ updateConfig = do+ config <- parseConfigFile configPath+ if (term, tablePath) `elem` termWidthMaps config+ then return ConfigurationRedundant+ else case lookup term (termWidthMaps config) of+ Just other -> return $ ConfigurationConflict other+ Nothing -> do+ appendFile configPath directive+ return ConfigurationModified
+ src/Graphics/Vty/UnicodeWidthTable/IO.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE CPP #-}+module Graphics.Vty.UnicodeWidthTable.IO+ ( readUnicodeWidthTable+ , parseUnicodeWidthTable+ , writeUnicodeWidthTable+ )+where++import Control.Monad (when, forM)+import Data.Binary+import Data.Binary.Get+import Data.Binary.Put+import qualified Data.ByteString.Lazy as BSL+#if !(MIN_VERSION_base(4,11,0))+import Data.Semigroup ((<>))+#endif++import Graphics.Vty.UnicodeWidthTable.Types++-- | Load a binary unicode width table from the specified file.+--+-- This either returns a successfully parsed table or a table parsing+-- error message. This does not handle I/O exceptions.+readUnicodeWidthTable :: FilePath -> IO (Either String UnicodeWidthTable)+readUnicodeWidthTable path = parseUnicodeWidthTable <$> BSL.readFile path++-- | Parse a binary unicode width table.+parseUnicodeWidthTable :: BSL.ByteString -> Either String UnicodeWidthTable+parseUnicodeWidthTable bs =+ case runGetOrFail tableParser bs of+ Left (_, _, msg) ->+ Left msg++ -- Even if we parsed a table, leftover bytes indicate something+ -- could be wrong.+ Right (remainingBytes, _, _) | not (BSL.null remainingBytes) ->+ Left $ "Error: " <> show (BSL.length remainingBytes) <>+ " byte(s) left unconsumed"++ Right (_, _, table) ->+ Right table++-- | Write the unicode width table to the specified path.+--+-- This does not handle I/O exceptions.+writeUnicodeWidthTable :: FilePath -> UnicodeWidthTable -> IO ()+writeUnicodeWidthTable path table = do+ let body = runPut (tableV1Writer table)+ BSL.writeFile path body++-- | Width table magic bytes for use in the binary format.+widthTableMagic :: Word32+widthTableMagic = 0xc1a9f7e0++tableParser :: Get UnicodeWidthTable+tableParser = do+ magic <- getWord32le++ when (magic /= widthTableMagic) $+ fail "Table magic number invalid"++ version <- getWord8++ case version of+ 1 -> tableV1Parser+ _ -> fail "Table version invalid"++tableV1Parser :: Get UnicodeWidthTable+tableV1Parser = do+ numRanges <- getWord32le++ let parseRange = do+ start <- getWord32le+ size <- getWord32le+ cols <- getWord8+ return WidthTableRange { rangeStart = start+ , rangeSize = size+ , rangeColumns = cols+ }++ ranges <- forM [1..numRanges] $ const parseRange++ return UnicodeWidthTable { unicodeWidthTableRanges = ranges+ }++tableV1Writer :: UnicodeWidthTable -> Put+tableV1Writer table = do+ -- Magic bytes+ putWord32le widthTableMagic++ -- Version+ putWord8 1++ -- Number of ranges+ let ranges = unicodeWidthTableRanges table+ let numRanges = length ranges+ putWord32le (fromIntegral numRanges)++ -- Ranges+ let putRange r = do+ putWord32le $ rangeStart r+ putWord32le $ rangeSize r+ putWord8 $ rangeColumns r++ mapM_ putRange ranges
+ src/Graphics/Vty/UnicodeWidthTable/Install.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ForeignFunctionInterface #-}+module Graphics.Vty.UnicodeWidthTable.Install+ ( TableInstallException(..)+ , installUnicodeWidthTable+ , isCustomTableReady+ )+where++import Control.Monad (when, forM_)+import qualified Control.Exception as E+import Control.Concurrent.MVar (MVar, newMVar, takeMVar, putMVar)+import Data.Word (Word8, Word32)+#if !(MIN_VERSION_base(4,11,0))+import Data.Semigroup ((<>))+#endif+import System.IO.Unsafe (unsafePerformIO)++import Graphics.Vty.UnicodeWidthTable.Types++-- | The lock used to make functions in this module thread-safe.+installLock :: MVar ()+{-# NOINLINE installLock #-}+installLock = unsafePerformIO $ newMVar ()++-- | Returns True if and only if a custom table has been allocated and+-- marked as ready for use.+--+-- This function is thread-safe.+isCustomTableReady :: IO Bool+isCustomTableReady = withInstallLock $ (== 1) <$> c_isCustomTableReady++withInstallLock :: IO a -> IO a+withInstallLock act =+ E.bracket takeInstallLock (const releaseInstallLock) $ const act++takeInstallLock :: IO ()+takeInstallLock = takeMVar installLock++releaseInstallLock :: IO ()+releaseInstallLock = putMVar installLock ()++-- This is the size of the allocated custom character width table, in+-- character slots. It's important that this be large enough to hold all+-- possible Unicode character values. At the time of this writing, the+-- valid Unicode range is 0 - 0x10ffff, hence this value.+tableSize :: Int+tableSize = 0x110000++-- | Exception type raised by 'installUnicodeWidthTable'.+data TableInstallException =+ TableInitFailure Int Int+ -- ^ The width table could not be initialized. Args: failure status,+ -- requested table size.+ | TableRangeFailure Int WidthTableRange+ -- ^ A code point range could not be configured. Args: failure+ -- status, offending range.+ | TableActivationFailure Int+ -- ^ The table could not be activated. Args: failure status.+ deriving (Eq, Show)++instance E.Exception TableInstallException++-- | Install a custom unicode character width+-- table. Such tables are obtained with+-- 'Graphics.Vty.UnicodeWidthTable.Query.buildUnicodeWidthTable' and+-- 'Graphics.Vty.UnicodeWidthTable.IO.readUnicodeWidthTable'.+--+-- ALERT! This function is probably not what you want to use because+-- it is automatically called by 'Graphics.Vty.mkVty'. You will only+-- ever need to call this function if you want to use functions+-- in 'Graphics.Text.Width' without controlling the terminal with+-- 'Graphics.Vty.mkVty'.+--+-- This affects the behavior of the 'Graphics.Vty.Image.wcwidth'+-- function and functions that call it. It does so by+-- changing global state available to the C implementation+-- of 'Graphics.Vty.Image.wcwidth'. To ensure that your+-- program gets consistent results from evaluating calls to+-- 'Graphics.Vty.Image.wcwidth', the installation of a custom table+-- should be performed before you call 'Graphics.Vty.Image.wcwidth' in+-- your program.+--+-- This is best done at Vty startup, and if you use+-- 'Graphics.Vty.mkVty', that function calls this automatically based on+-- the Vty configuration's declared width tables. It is exposed as part+-- of the public API so that applications can call this as needed if+-- they don't want to control the terminal with 'mkVty' but do want to+-- make calls to 'Graphics.Vty.Image.wcwidth'.+--+-- It's also important to note that once a custom table has been+-- installed, it is permanent for the life of the process. No new table+-- can be installed, and the new custom table cannot be removed.+--+-- If this function fails for any reason -- if the table cannot be+-- installed or is invalid, or if a custom table already exists -- this+-- will raise a 'TableInstallException' exception.+--+-- This function is thread-safe.+installUnicodeWidthTable :: UnicodeWidthTable -> IO ()+installUnicodeWidthTable table = withInstallLock $ do+ initResult <- initCustomTable tableSize+ when (initResult /= 0) $+ E.throw $ TableInitFailure initResult tableSize++ forM_ (unicodeWidthTableRanges table) $ \r -> do+ result <- setCustomTableRange (rangeStart r)+ (rangeSize r)+ (rangeColumns r)++ when (result /= 0) $ do+ deallocateCustomTable+ E.throw $ TableRangeFailure result r++ actResult <- activateCustomTable+ when (actResult /= 0) $+ E.throw $ TableActivationFailure actResult++------------------------------------------------------------------------+-- C imports++foreign import ccall unsafe "vty_init_custom_table"+ initCustomTable :: Int -> IO Int++foreign import ccall unsafe "vty_set_custom_table_range"+ setCustomTableRange :: Word32 -> Word32 -> Word8 -> IO Int++foreign import ccall unsafe "vty_activate_custom_table"+ activateCustomTable :: IO Int++foreign import ccall unsafe "vty_custom_table_ready"+ c_isCustomTableReady :: IO Int++foreign import ccall unsafe "vty_deallocate_custom_table"+ deallocateCustomTable :: IO ()
+ src/Graphics/Vty/UnicodeWidthTable/Query.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE TupleSections #-}+module Graphics.Vty.UnicodeWidthTable.Query+ ( buildUnicodeWidthTable+ , defaultUnicodeTableUpperBound+ )+where++import Control.Monad (forM)+import Data.Char (generalCategory, GeneralCategory(..))+import System.Console.ANSI (getCursorPosition)+import Text.Printf (printf)++import Graphics.Vty.UnicodeWidthTable.Types++shouldConsider :: Char -> Bool+shouldConsider c =+ case generalCategory c of+ Control -> False+ NotAssigned -> False+ Surrogate -> False+ _ -> True++charWidth :: Char -> IO Int+charWidth c = do+ printf "\r"+ putChar c+ Just (_, col) <- getCursorPosition+ return col++-- | Convert a sequence of character/width pairs into a list of+-- run-length encoded ranges. This function assumes the pairs come+-- sorted by character ordinal value. It does not require that the+-- character range is fully covered by the sequence.+--+-- The result of this function is a list of ranges in reverse order+-- relative to the input sequence.+mkRanges :: [(Char, Int)] -> [WidthTableRange]+mkRanges pairs =+ let convertedPairs = convert <$> pairs+ convert (c, i) = (fromIntegral $ fromEnum c, fromIntegral i)++ go Nothing finishedRanges [] = finishedRanges+ go (Just r) finishedRanges [] = r:finishedRanges++ go Nothing finishedRanges ((c, width):rest) =+ go (Just $ WidthTableRange c 1 width) finishedRanges rest++ go (Just r@(WidthTableRange prevCh sz prevWidth)) finishedRanges ((c, width):rest) =+ if c == prevCh + sz && prevWidth == width+ then go (Just (WidthTableRange prevCh (sz + 1) prevWidth)) finishedRanges rest+ else go (Just (WidthTableRange c 1 width)) (r:finishedRanges) rest++ in go Nothing [] convertedPairs++-- The uppermost code point to consider when building Unicode width+-- tables.+defaultUnicodeTableUpperBound :: Char+defaultUnicodeTableUpperBound = '\xe0000'++-- | Construct a unicode character width table by querying the terminal+-- connected to stdout. This works by emitting characters to stdout+-- and then querying the terminal to determine the resulting cursor+-- position in order to measure character widths. Consequently this will+-- generate a lot of output and may take a while, depending on your+-- system performance. This should not be run in a terminal while it is+-- controlled by Vty.+--+-- The argument specifies the upper bound code point to test when+-- building the table. This allows callers to decide how much of the+-- Unicode code point space to scan when building the table.+--+-- This does not handle exceptions.+buildUnicodeWidthTable :: Char -> IO UnicodeWidthTable+buildUnicodeWidthTable tableUpperBound = do+ pairs <- forM (filter shouldConsider ['\0'..tableUpperBound]) $ \i ->+ (i,) <$> charWidth i++ return UnicodeWidthTable { unicodeWidthTableRanges = reverse $ mkRanges pairs+ }
+ src/Graphics/Vty/UnicodeWidthTable/Types.hs view
@@ -0,0 +1,29 @@+module Graphics.Vty.UnicodeWidthTable.Types+ ( UnicodeWidthTable(..)+ , WidthTableRange(..)+ )+where++import Data.Word (Word8, Word32)++-- | A range of code points in a width table.+data WidthTableRange =+ WidthTableRange { rangeStart :: Word32+ -- ^ The range's starting code point.+ , rangeSize :: Word32+ -- ^ The number of code points in the contiguous+ -- range, beginning with the starting code point+ -- ('rangeStart').+ , rangeColumns :: Word8+ -- ^ The terminal width, in columns, of all of the+ -- characters corresponding to the code points in+ -- this range.+ }+ deriving (Eq, Show)++-- | A run-length-encoded table of Unicode character widths.+data UnicodeWidthTable =+ UnicodeWidthTable { unicodeWidthTableRanges :: [WidthTableRange]+ -- ^ The ranges in the table.+ }+ deriving (Show)
+ tools/BuildWidthTable.hs view
@@ -0,0 +1,144 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE CPP #-}+module Main where++import qualified Control.Exception as E+import Control.Monad (when)+import Data.Maybe (fromMaybe)+#if !(MIN_VERSION_base(4,11,0))+import Data.Semigroup ((<>))+#endif+import System.Directory (createDirectoryIfMissing)+import System.Environment (getArgs, getProgName)+import System.FilePath (takeDirectory)+import System.Exit (exitFailure)+import System.Console.GetOpt+import Text.Read (readMaybe)++import Graphics.Vty.Config ( terminalWidthTablePath, currentTerminalName+ , vtyConfigPath, addConfigWidthMap+ , ConfigUpdateResult(..)+ )+import Graphics.Vty.UnicodeWidthTable.IO+import Graphics.Vty.UnicodeWidthTable.Query++data Arg = Help+ | OutputPath String+ | TableUpperBound String+ | UpdateConfig+ | VtyConfigPath String+ deriving (Eq, Show)++options :: Config -> [OptDescr Arg]+options config =+ [ Option "h" ["help"] (NoArg Help)+ "This help output"+ , Option "b" ["bound"] (ReqArg TableUpperBound "MAX_CHAR")+ ("The maximum Unicode code point to test when building the table " <>+ "(default: " <> (show $ fromEnum $ configBound config) <> ")")+ , Option "p" ["path"] (ReqArg OutputPath "PATH")+ ("The output path to write to (default: " <>+ fromMaybe "<none>" (configOutputPath config) <> ")")+ , Option "u" ["update-config"] (NoArg UpdateConfig)+ "Create or update the Vty configuration file to use the new table (default: no)"+ , Option "c" ["config-path"] (ReqArg VtyConfigPath "PATH")+ ("Update the specified Vty configuration file path when -u is set (default: " <>+ configPath config <> ")")+ ]++data Config =+ Config { configOutputPath :: Maybe FilePath+ , configBound :: Char+ , configUpdate :: Bool+ , configPath :: FilePath+ }+ deriving (Show)++mkDefaultConfig :: IO Config+mkDefaultConfig = do+ Config <$> terminalWidthTablePath+ <*> pure defaultUnicodeTableUpperBound+ <*> pure False+ <*> vtyConfigPath++usage :: IO ()+usage = do+ config <- mkDefaultConfig+ pn <- getProgName+ putStrLn $ "Usage: " <> pn <> " [options]"+ putStrLn ""+ putStrLn "This tool queries the terminal on stdout to determine the widths"+ putStrLn "of Unicode characters rendered to the terminal. The resulting data"+ putStrLn "is written to a table at the specified output path for later"+ putStrLn "loading by Vty-based applications."+ putStrLn ""++ putStrLn $ usageInfo pn (options config)++updateConfigFromArg :: Arg -> Config -> Config+updateConfigFromArg Help c =+ c+updateConfigFromArg UpdateConfig c =+ c { configUpdate = True }+updateConfigFromArg (VtyConfigPath p) c =+ c { configPath = p }+updateConfigFromArg (TableUpperBound s) c =+ case readMaybe s of+ Nothing -> error $ "Invalid table upper bound: " <> show s+ Just v -> c { configBound = toEnum v }+updateConfigFromArg (OutputPath p) c =+ c { configOutputPath = Just p }++main :: IO ()+main = do+ defConfig <- mkDefaultConfig+ strArgs <- getArgs+ let (args, unused, errors) = getOpt Permute (options defConfig) strArgs++ when (not $ null errors) $ do+ mapM_ putStrLn errors+ exitFailure++ when ((not $ null unused) || (Help `elem` args)) $ do+ usage+ exitFailure++ let config = foldr updateConfigFromArg defConfig args++ outputPath <- case configOutputPath config of+ Nothing -> do+ putStrLn "Error: could not obtain terminal width table path"+ exitFailure+ Just path -> return path++ putStrLn "Querying terminal:"+ builtTable <- buildUnicodeWidthTable $ configBound config++ let dir = takeDirectory outputPath+ createDirectoryIfMissing True dir+ writeUnicodeWidthTable outputPath builtTable++ putStrLn $ "\nOutput table written to " <> outputPath++ when (configUpdate config) $ do+ let cPath = configPath config+ Just tName <- currentTerminalName++ result <- E.try $ addConfigWidthMap cPath tName outputPath++ case result of+ Left (e::E.SomeException) -> do+ putStrLn $ "Error updating Vty configuration at " <> cPath <> ": " <>+ show e+ exitFailure+ Right ConfigurationCreated -> do+ putStrLn $ "Configuration file created: " <> cPath+ Right ConfigurationModified -> do+ putStrLn $ "Configuration file updated: " <> cPath+ Right (ConfigurationConflict other) -> do+ putStrLn $ "Configuration file not updated: uses a different table " <>+ "for TERM=" <> tName <> ": " <> other+ Right ConfigurationRedundant -> do+ putStrLn $ "Configuration file not updated: configuration " <>+ cPath <> " already uses table " <> outputPath <>+ " for TERM=" <> tName
vty.cabal view
@@ -1,5 +1,5 @@ name: vty-version: 5.27+version: 5.28 license: BSD3 license-file: LICENSE author: AUTHORS@@ -48,7 +48,6 @@ microlens < 0.4.12, microlens-mtl, microlens-th,- -- required for nice installation with yi hashable >= 1.2, mtl >= 1.1.1.0 && < 2.3, parallel >= 2.2 && < 3.3,@@ -59,7 +58,9 @@ text >= 0.11.3, unix, utf8-string >= 0.3 && < 1.1,- vector >= 0.7+ vector >= 0.7,+ binary,+ ansi-terminal >= 0.10.3 if !impl(ghc >= 8.0) build-depends: semigroups >= 0.16,@@ -78,7 +79,6 @@ Graphics.Vty.Picture Graphics.Vty.Output Graphics.Text.Width- -- the modules below are only meant to be used by the tests. Codec.Binary.UTF8.Debug Data.Terminfo.Parse Data.Terminfo.Eval@@ -99,6 +99,10 @@ Graphics.Vty.Output.Interface Graphics.Vty.Output.XTermColor Graphics.Vty.Output.TerminfoBased+ Graphics.Vty.UnicodeWidthTable.Types+ Graphics.Vty.UnicodeWidthTable.IO+ Graphics.Vty.UnicodeWidthTable.Query+ Graphics.Vty.UnicodeWidthTable.Install other-modules: Graphics.Vty.Attributes.Color240 Graphics.Vty.Debug.Image@@ -114,11 +118,22 @@ hs-source-dirs: src default-extensions: ScopedTypeVariables- ForeignFunctionInterface ghc-options: -O2 -funbox-strict-fields -Wall -fspec-constr -fspec-constr-count=10 ghc-prof-options: -O2 -funbox-strict-fields -caf-all -Wall -fspec-constr -fspec-constr-count=10++executable vty-build-width-table+ main-is: BuildWidthTable.hs+ hs-source-dirs: tools++ default-language: Haskell2010+ ghc-options: -threaded -Wall++ build-depends: vty,+ directory,+ filepath,+ base >= 4.8 && < 5 executable vty-mode-demo main-is: ModeDemo.hs