···11-## 0.0.2 (2024-03-13)
22-33-### Changed
44-55-- Upgrade `eio` to `1.0` (no change required).
66-- Uses `expect-test-helpers` (reduce core dependencies)
77-- Upgrade `eio` to `0.15`.
88-- Run `ppx_js_style` as a linter & make it a `dev` dependency.
99-- Upgrade GitHub workflows `actions/checkout` to v4.
1010-- In CI, specify build target `@all`, and add `@lint`.
1111-- List ppxs instead of `ppx_jane`.
1212-1313-## 0.0.1 (2024-02-25)
1414-1515-### Added
1616-1717-- Add an initial API.
-21
eio-process/LICENSE
···11-MIT License
22-33-Copyright (c) 2023 Mathieu Barbin
44-55-Permission is hereby granted, free of charge, to any person obtaining a copy
66-of this software and associated documentation files (the "Software"), to deal
77-in the Software without restriction, including without limitation the rights
88-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99-copies of the Software, and to permit persons to whom the Software is
1010-furnished to do so, subject to the following conditions:
1111-1212-The above copyright notice and this permission notice shall be included in all
1313-copies or substantial portions of the Software.
1414-1515-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121-SOFTWARE.
-18
eio-process/LICENSE.eio
···11-This project extends the module `Eio.Process` from the
22-[eio](https://github.com/ocaml-multicore/eio) project, which has the
33-following license:
44-55-Copyright (C) 2021 Anil Madhavapeddy
66-Copyright (C) 2022 Thomas Leonard
77-88-Permission to use, copy, modify, and distribute this software for any
99-purpose with or without fee is hereby granted, provided that the above
1010-copyright notice and this permission notice appear in all copies.
1111-1212-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1313-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1414-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1515-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1616-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1717-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1818-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-25
eio-process/LICENSE.janestreet
···11-This project took inspiration from the `Async_unix.Process` module,
22-from the [async_unix](https://github.com/janestreet/async_unix)
33-project, which has the following license:
44-55-The MIT License
66-77-Copyright (c) 2008--2023 Jane Street Group, LLC <opensource-contacts@janestreet.com>
88-99-Permission is hereby granted, free of charge, to any person obtaining a copy
1010-of this software and associated documentation files (the "Software"), to deal
1111-in the Software without restriction, including without limitation the rights
1212-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1313-copies of the Software, and to permit persons to whom the Software is
1414-furnished to do so, subject to the following conditions:
1515-1616-The above copyright notice and this permission notice shall be included in all
1717-copies or substantial portions of the Software.
1818-1919-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2020-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2121-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2222-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2323-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2424-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2525-SOFTWARE.
···11-# eio-process
22-33-[](https://github.com/mbarbin/eio-process/actions/workflows/ci.yml)
44-[](https://coveralls.io/github/mbarbin/eio-process?branch=main)
55-66-This is an experimental library to spawn external processes in
77-[Eio](https://github.com/ocaml-multicore/eio) with an api that resembles
88-[Async.Process](https://github.com/janestreet/async_unix).
99-1010-This project re-uses some function and type names from the `Async_unix.Process`
1111-interface. The implementation however is quite different, since the original
1212-runs in the `Async` monad, whereas this lib targets `Eio`.
1313-1414-## Motivation
1515-1616-We find that this API offers convenient wrappers that we believe are a good fit
1717-on top of the core functionality offered by `Eio.Process`.
1818-1919-## Usage
2020-2121-`Eio_process` is meant to be used directly as a top-level module, alongside
2222-other `Eio` modules. We do not recommend shadowing `Eio.Process` with
2323-`Eio_process` in user code at this time.
2424-2525-## Acknowledgements
2626-2727-We would like to express our gratitude to the `Eio` developers for their work on
2828-the [eio](https://github.com/ocaml-multicore/eio) project, and for the original
2929-module `Eio.Process` that this project extends. `Eio` is released under the
3030-terms of an ISC License. Its copyright and permission notice are included at the
3131-root of this project, in the file `LICENSE.eio`.
3232-3333-We also appreciate the work done by the async team at Jane Street and their
3434-contribution to the open source community. We're thankful for the api exposed by
3535-the `Async_unix.Process` module which we took inspiration from in this project.
3636-`Async_unix` is released under the terms of an `MIT` License. Its copyright and
3737-permission notice are included at the root of this project, in the file
3838-`LICENSE.janestreet`.
3939-4040-## Code documentation
4141-4242-The code documentation of the latest release is built with `odoc` and published
4343-to `GitHub` pages [here](https://mbarbin.github.io/eio-process).
···11-## v0.2.3 (2022-09-02)
22-33-* Moved to Dune.
44-* Renders faster, uses less memory.
55-* Nested uses of `I.pp_attr` within `I.strf` now stack, instead of replacing.
66-* Removed dependency on Uucp. Uses internal data instead (Unicode 13).
77-* Support OCaml 4.08 - 4.14. Thanks to @kit-ty-kate for the 4.14 fixes.
88-99-## v0.2.2 (2019-02-19)
1010-1111-* Fix a long-standing terminal cleanup bug. Reported by @ttamttam, fix by @cfcs.
1212-1313-## v0.2.1 (2017-11-06)
1414-1515-* OCaml 4.06 compatible.
1616-* Cache the internal representation of Unicode strings.
1717-* Remove `I.ichar`. **breaking**
1818-1919-## v0.2.0 (2017-10-31)
2020-2121-* All-around speed and memory improvements.
2222-* Draw over lines cell-by-cell instead of using erase-and-skip.
2323- Slower, but flicker-free drawing.
2424-* `Term.create`: optionally inhibit synthetic TTY signals.
2525-* Cursor origin moved from `(1, 1)` to `(0, 0)`. **breaking**
2626-* `#key` renamed to `#special`. **breaking**
2727-* Added `Term.fds` to get connected file descriptors.
2828-* Added `A.equal` and `I.equal`.
2929-* Switched over to `Uchar.t`. **breaking**
3030-* Separated ASCII from the rest of Unicode input. **breaking**
3131-* Added image pretty-printer `I.pp`.
3232-* Added `notty.top` for use in the toplevel.
3333-* Removed `I.tile`. **breaking**
3434-* Added `I.tabulate`, generalizing `I.tile`.
3535-* Added support for 24-bit color.
3636-* Added `Notty_*.show_cursor` and `Notty_*.move_cursor` for manual cursor
3737- positioning in inline mode.
3838-* Removed `output_image_endline`. Can be replaced by `eol`. **breaking**
3939-* `Notty_*.output_image` lost the `~clear` parameter. Can be replaced in various
4040- ways by cursor positioning.
4141-* `Notty_unix.output_image ~chan` renamed to `~fd`. **breaking**
4242-* Added support for bracketed paste.
4343-* More example programs.
4444-4545-## v0.1.1 (2016-02-09)
4646-* `Term.input` -> `Term.event`
4747-* Option to redraw the line
4848-4949-## v0.1.0 (2016-02-09)
5050-* Initial release
-13
notty/LICENSE.md
···11-Copyright (c) 2016-2017 David Kaloper Meršinjak
22-33-Permission to use, copy, modify, and/or distribute this software for any
44-purpose with or without fee is hereby granted, provided that the above
55-copyright notice and this permission notice appear in all copies.
66-77-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
88-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
99-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1010-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1111-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1212-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1313-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-70
notty/README.md
···11-# Notty — Declaring terminals
22-33-<a href="https://asciinema.org/a/ZIXzn2ZmIxK39qoT3eJla5OyO" alt="dumper"><img src="https://asciinema.org/a/ZIXzn2ZmIxK39qoT3eJla5OyO.png" width="400"/></a>
44-<a href="https://asciinema.org/a/TsIhDJv5S00AB2biVmhHRzZ8I" alt="input"><img src="https://asciinema.org/a/TsIhDJv5S00AB2biVmhHRzZ8I.png" width="400"/></a>
55-<a href="https://asciinema.org/a/z1Pc0Mppg2JFzteZzdeigLwYc" alt="microdots"><img src="https://asciinema.org/a/z1Pc0Mppg2JFzteZzdeigLwYc.png" width="400"/></a>
66-<a href="https://asciinema.org/a/NgpF9Im8qfUICC39GDDAe9Ede" alt="rain"><img src="https://asciinema.org/a/R94gnHTQhCFJAsWpRfVlZWcUB.png" width="400"/></a>
77-88-Notty is a declarative terminal library for OCaml structured around a notion
99-of composable images. It tries to abstract away the basic terminal programming
1010-model, providing something simpler and more expressive.
1111-1212-The core layout engine and IO codecs are pure platform-independent OCaml.
1313-Distribution includes modules with input and output facilities for Unix, and Lwt
1414-on Unix.
1515-1616-As an attempt to redefine terminal programming, Notty has to be
1717-_opinionated_. It assumes Unicode throughout, does not have universal support
1818-for various terminals out there, and has a peculiar programming and rendering
1919-model.
2020-2121-Notty's core API was heavily influenced by Haskell's [Vty][vty].
2222-2323-## Where to start
2424-2525-Check out the [documentation], [examples], or peek directly into the [interface]
2626-file.
2727-2828-Building with `dune build @ex` will produce several little example programs that
2929-also double as tests.
3030-3131-```OCaml
3232-(* Game of Life with ZX Spectrum kitsch. *)
3333-3434-let dot : image = I.uchar A.(fg lightred) (Uchar.of_int 0x25cf) 1 1
3535-3636-let background step (n, m) =
3737- let k = 24. *. sin (float (step + m + n) /. 10.) |> truncate in
3838- if k > 0 then I.char A.(fg (gray k)) '.' 1 1 else I.void 1 1
3939-4040-let render (w, h) step life : image =
4141- I.tabulate w (h - 1) @@ fun x y ->
4242- let pt = (x, y) in
4343- if CSet.mem pt life then dot else background step pt
4444-```
4545-4646-[documentation]: https://pqwy.github.io/notty/doc
4747-[examples]: http://pqwy.github.io/notty/doc/Notty.html#examples
4848-[interface]: https://github.com/pqwy/notty/blob/master/src/notty.mli
4949-[vty]: https://hackage.haskell.org/package/vty
5050-5151-## What?
5252-5353-- _Notty?_
5454-5555- Terminals are tedious to program for. Notty tries to abstract the tedium away,
5656- leaving you with a more pleasant programming surface that's quite unlike a TTY.
5757- Hence, _No-TTY_.
5858-5959-- A new kind of Rust terminal?
6060-6161- This Notty has no connection to any other body of code named Notty.
6262-6363-- Why make yet another terminal output library?
6464-6565- Because:
6666- * It allows one to *describe* what should be seen, as opposed to *commanding*
6767- a terminal.
6868- * It's pretty compact. Both bells and whistles can be implemented separately.
6969- * Core is easy to glue onto various IO backends.
7070- * Pure platform-independent OCaml.
···11-open Notty
22-open Common
33-44-let iter = 200
55-66-let member x y =
77- let rec go cx cy x y n =
88- let xx = x *. x and yy = y *. y in
99- if n = 0 || xx +. yy > 4. then n else
1010- go cx cy (xx -. yy +. cx) (2. *. x *. y +. cy) (n - 1) in
1111- float (iter - go x y 0. 0. iter) /. float iter
1212-1313-let pi2 = 2. *. 3.14159
1414-let pi2_3 = pi2 /. 3.
1515-1616-let mandelbrot x y =
1717- (* let esc = 1. -. member x y in *)
1818- (* 23. *. esc *. esc |> truncate |> A.gray *)
1919- match member x y with
2020- | 1. -> A.gray 0
2121- | esc ->
2222- let t = esc *. pi2 in
2323- let f d = (sin (t +. d) *. 128. +. 128.) |> truncate in
2424- A.rgb_888 ~b:(f (-.pi2_3)) ~g:(f 0.) ~r:(f pi2_3)
2525-2626-let xlate dx dy f x y = f (x -. dx) (y -. dy)
2727-let scale k f = let k1 = 1./.k in fun x y -> f (x *. k1) (y *. k1)
2828-let rot a f =
2929- let sina = sin a and cosa = cos a in fun x y ->
3030- f (x *. cosa -. y *. sina) (x *. sina +. cosa *. y)
3131-3232-let render_unit f (w, h) =
3333- let sw = 1. /. float w
3434- and sh = 1. /. float (2 * h) in
3535- pxmatrix w h (fun x y -> f (float x *. sw) (float y *. sh))
3636-3737-let () =
3838- let pix =
3939- render_unit @@
4040- rot (-1.570795) @@ xlate (1.6) (-0.5) @@
4141- mandelbrot in
4242- Notty_unix.(output_image_size @@ fun (w, h) -> pix (w, h - 1) |> eol)
-60
notty/examples/colors.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * Demonstrates text attributes.
66- *)
77-open Notty
88-open Common
99-1010-let colors = A.[
1111- "black" , black
1212-; "red" , red
1313-; "green" , green
1414-; "yellow" , yellow
1515-; "blue" , blue
1616-; "magenta" , magenta
1717-; "cyan" , cyan
1818-; "white" , white
1919-; "lightblack" , lightblack
2020-; "lightred" , lightred
2121-; "lightgreen" , lightgreen
2222-; "lightyellow" , lightyellow
2323-; "lightblue" , lightblue
2424-; "lightmagenta" , lightmagenta
2525-; "lightcyan" , lightcyan
2626-; "lightwhite" , lightwhite
2727-]
2828-2929-let styles = A.[
3030- "empty" , empty
3131-; "bold" , st bold
3232-; "italic" , st italic
3333-; "underline" , st underline
3434-; "blink" , st blink
3535-; "reverse" , st reverse
3636-; "bold/italic", st bold ++ st italic
3737-; "rev/underln", st underline ++ st reverse
3838-; "bold/rev" , st reverse ++ st bold
3939-]
4040-4141-let image w =
4242- let open List in
4343- let core16 =
4444- let c1 = map (fun (n, c) -> I.string A.(fg c) n) colors
4545- and c2 = map (fun (n, c) -> I.string A.(fg black ++ bg c) n) colors
4646- in I.(vcat c1 <|> void 1 0 <|> vcat c2)
4747- and attr =
4848- I.( styles |> map (fun (n, s) -> hpad 0 1 (string s n)) |> hcat) in
4949- let combine imgs =
5050- List.map I.(fun (n, i) -> string A.empty n <-> i <-> void 0 1) imgs
5151- |> I.vcat |> I.pad ~l:1 ~t:1 in
5252- combine [
5353- "System colors:", core16;
5454- "Color cube, 6x6x6:", Images.c_cube_ix;
5555- "Grayscale ramp:", Images.c_gray_ramp;
5656- "24bit:", Images.c_rainbow (w - 2) 1;
5757- "Text styles:", attr
5858- ]
5959-6060-let () = Notty_unix.output_image_size @@ fun (w, _) -> image w
-151
notty/examples/common.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-open Notty
55-open Notty.Infix
66-77-let pow n e = int_of_float (float n ** float e)
88-99-module List = struct
1010-1111- include List
1212-1313- let rec replicate n a = if n < 1 then [] else a :: replicate (n - 1) a
1414-1515- let rec range a b = if a > b then [] else a :: range (a + 1) b
1616-1717- let rec intersperse a = function
1818- | [] | [_] as t -> t
1919- | x::xs -> x :: a :: intersperse a xs
2020-2121- let rec take n = function
2222- | x::xs when n > 0 -> x :: take (pred n) xs
2323- | _ -> []
2424-2525- let rec splitat n = function
2626- | x::xs when n > 0 ->
2727- let (a, b) = splitat (pred n) xs in (x::a, b)
2828- | xs -> ([], xs)
2929-3030- let rec chunks n xs =
3131- match splitat n xs with
3232- | (a, []) -> [a]
3333- | (a, b) -> a :: chunks n b
3434-3535- let rec zip xs ys = match (xs, ys) with
3636- | ([], _) | (_, []) -> []
3737- | (x::xs, y::ys) -> (x, y) :: zip xs ys
3838-3939-end
4040-4141-module String = struct
4242-4343- include String
4444-4545- let repeat n str =
4646- let b = Buffer.create 16 in
4747- for _ = 1 to n do Buffer.add_string b str done;
4848- Buffer.contents b
4949-end
5050-5151-let tile w h i = I.tabulate w h (fun _ _ -> i)
5252-5353-(** A few images used in several places. *)
5454-module Images = struct
5555-5656- let i1 =
5757- I.(string A.(fg lightblack) "omgbbq" <->
5858- string A.(fg white ++ bg red) "@")
5959- <|> I.(pad ~t:2 @@ string A.(fg green) "xo")
6060-6161- let i2 = I.(hpad 1 1 (hcrop 1 1 @@ tile 3 3 i1) <|> i1)
6262-6363- let i3 = tile 5 5 i2
6464-6565- let i4 =
6666- let i = I.(i3 <|> crop ~t:1 i3 <|> i3) in
6767- I.(crop ~l:1 i <-> crop ~r:1 i <-> crop ~b:2 i)
6868-6969- let i5 =
7070- tile 5 1 List.(
7171- range 0 15 |> map (fun i -> I.pad ~t:i ~l:(i*2) i2) |> I.zcat
7272- )
7373-7474- let c_gray_ramp =
7575- I.tabulate 24 1 (fun g _ -> I.string A.(bg (gray g)) " ")
7676-7777- let c_cube_ix =
7878- I.tabulate 6 1 @@ fun r _ ->
7979- I.hpad 0 1 @@ I.tabulate 6 6 @@ fun b g ->
8080- I.string A.(bg (rgb ~r ~g ~b)) " "
8181-8282- let c_cube_rgb =
8383- let f x = [| 0x00; 0x5f; 0x87; 0xaf; 0xd7; 0xff |].(x) in
8484- I.tabulate 6 1 @@ fun r _ ->
8585- I.hpad 0 1 @@ I.tabulate 6 6 @@ fun b g ->
8686- I.string A.(bg (rgb_888 ~r:(f r) ~g:(f g) ~b:(f b))) " "
8787-8888- let c_rainbow w h =
8989- let pi2 = 2. *. 3.14159 in
9090- let pi2_3 = pi2 /. 3.
9191- and f t off = sin (t +. off) *. 128. +. 128. |> truncate in
9292- let color t = A.rgb_888 ~r:(f t (-.pi2_3)) ~g:(f t 0.) ~b:(f t pi2_3) in
9393- I.tabulate (w - 1) 1 @@ fun x _ ->
9494- let t = (pi2 *. float x /. float w) +. 3.7 in
9595- I.char A.(bg (color t)) ' ' 1 h
9696-9797- (* U+25CF BLACK CIRCLE *)
9898- let dot color = I.string (A.fg color) "●"
9999- (* U+25AA BLACK SMALL SQUARE *)
100100- let square color = I.string (A.fg color) "▪"
101101-102102- let rec cantor = function
103103- | 0 -> square A.lightblue
104104- | n ->
105105- let sub = cantor (pred n) in
106106- I.hcat (List.replicate (pow 3 n) (square A.lightblue)) <->
107107- (sub <|> I.void (pow 3 (n - 1)) 0 <|> sub)
108108-109109- let checker n m i =
110110- let w = I.width i in
111111- I.(tile (n/2) (m/2) (hpad 0 w i <-> hpad w 0 i))
112112-113113- let checker1 = checker 20 20 I.(char A.(bg magenta) ' ' 2 1)
114114-115115- let rec sierp c n = I.(
116116- if n > 1 then
117117- let ss = sierp c (pred n) in ss <-> (ss <|> ss)
118118- else hpad 1 0 (square c)
119119- )
120120-121121- let grid xxs = xxs |> List.map I.hcat |> I.vcat
122122-123123- let outline attr i =
124124- let (w, h) = I.(width i, height i) in
125125- let chr x = I.uchar attr (Uchar.of_int x) 1 1
126126- and hbar = I.uchar attr (Uchar.of_int 0x2500) w 1
127127- and vbar = I.uchar attr (Uchar.of_int 0x2502) 1 h in
128128- let (a, b, c, d) = (chr 0x256d, chr 0x256e, chr 0x256f, chr 0x2570) in
129129- grid [ [a; hbar; b]; [vbar; i; vbar]; [d; hbar; c] ]
130130-end
131131-132132-let halfblock = "▄"
133133-134134-let pxmatrix w h f = I.tabulate w h @@ fun x y ->
135135- let y = y * 2 in
136136- I.string A.(bg (f x y) ++ fg (f x (y + 1))) halfblock
137137-138138-module Term = Notty_unix.Term
139139-140140-let simpleterm ~imgf ~f ~s =
141141- let term = Term.create () in
142142- let imgf (w, h) s =
143143- I.(string A.(fg lightblack) "[ESC quits.]" <-> imgf (w, h - 1) s) in
144144- let rec go s =
145145- Term.image term (imgf (Term.size term) s);
146146- match Term.event term with
147147- | `End | `Key (`Escape, []) | `Key (`ASCII 'C', [`Ctrl]) -> ()
148148- | `Resize _ -> go s
149149- | #Unescape.event as e ->
150150- match f s e with Some s -> go s | _ -> ()
151151- in go s
-52
notty/examples/common_lwt.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-open Notty
55-open Lwt.Infix
66-77-include Common
88-99-module T = Notty_lwt.Term
1010-1111-let simpleterm_lwt ~imgf ~f ~s =
1212- let term = T.create () in
1313- let imgf (w, h) s =
1414- I.(string A.(fg lightblack) "[ESC quits.]" <-> imgf (w, h - 1) s) in
1515- let step e s = match e with
1616- | `Key (`Escape, []) | `Key (`ASCII 'C', [`Ctrl]) ->
1717- T.release term >|= fun () -> s
1818- | `Resize dim -> T.image term (imgf dim s) >|= fun () -> s
1919- | #Unescape.event as e ->
2020- match f s e with
2121- | Some s -> T.image term (imgf (T.size term) s) >|= fun () -> s
2222- | None -> T.release term >|= fun () -> s
2323- in
2424- ( T.image term (imgf (T.size term) s)
2525- >>= fun () -> Lwt_stream.fold_s step (T.events term) s )
2626- |> Lwt_main.run |> ignore
2727-2828-2929-let timer = function
3030- | None -> Lwt.wait () |> fst
3131- | Some t -> Lwt_unix.sleep t >|= fun _ -> `Timer
3232-3333-let event e = Lwt_stream.get (T.events e) >|= function
3434- | Some (`Resize _ | #Unescape.event as x) -> x
3535- | None -> `End
3636-3737-let simpleterm_lwt_timed ?delay ~f s0 =
3838- let term = T.create () in
3939- let rec loop (e, t) dim s =
4040- (e <?> t) >>= function
4141- | `End | `Key (`Escape, []) | `Key (`ASCII 'C', [`Ctrl]) ->
4242- Lwt.return_unit
4343- | `Resize dim as evt -> invoke (event term, t) dim s evt
4444- | #Unescape.event as evt -> invoke (event term, t) dim s evt
4545- | `Timer as evt -> invoke (e, timer delay) dim s evt
4646- and invoke es dim s e =
4747- match f dim s e with
4848- | `Continue s -> loop es dim s
4949- | `Redraw (s, i) -> T.image term i >>= fun () -> loop es dim s
5050- | `Stop -> Lwt.return_unit in
5151- let size = T.size term in
5252- loop (event term, timer delay) size s0
-36
notty/examples/crops.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * Demonstrates edge-case behavior of functions that produce rectangle-like
66- * things.
77- *)
88-open Notty
99-open Common
1010-1111-let hdistribute ?align w imgs =
1212- let n = List.length imgs in
1313- I.(List.map (hsnap ?align (w / n)) imgs |> hcat)
1414-1515-let take w h i = I.(vsnap h i |> hsnap w)
1616-1717-let () =
1818- simpleterm ~s:(2, 2)
1919- ~f:(fun (w, h as s) -> function
2020- `Key (`Arrow `Left, _) -> Some (w - 1, h)
2121- | `Key (`Arrow `Right, _) -> Some (w + 1, h)
2222- | `Key (`Arrow `Up, _) -> Some (w, h - 1)
2323- | `Key (`Arrow `Down, _) -> Some (w, h + 1)
2424- | `Key (`ASCII '0', _) -> Some (0, 0)
2525- | _ -> Some s)
2626- ~imgf:I.(fun (ow, oh) (w, h) ->
2727- let (a1, a2, a3) = A.(fg lightmagenta, fg lightred, fg lightblue) in
2828- strf "Sizing edge behavior. Dim: (%d, %d)" w h <->
2929- ( hdistribute ow Images.[
3030- outline a1 (uchar a1 (Uchar.of_int 0x2022) w h)
3131- ; outline a2 (uchar a2 (Uchar.of_int 0x2022) 300 300 |> take w h)
3232- ; outline a3 (void w h)
3333- ] |> vsnap (oh - 4) )
3434- <->
3535- hdistribute ow [string a1 "char"; string a2 "crop"; string a3 "void"]
3636- )
-26
notty/examples/cursor.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-open Notty
55-open Common
66-77-let rec main t (x, y as pos) =
88- let img =
99- let dot = I.string A.(bg lightred ++ fg black) "✓" |> I.pad ~l:x ~t:y
1010- and txt = I.strf ~attr:A.(fg lightblack) "@(%d, %d)" x y in
1111- I.(txt </> dot) in
1212- Term.image t img;
1313- Term.cursor t (Some pos);
1414- match Term.event t with
1515- | `End | `Key (`Escape, []) | `Key (`ASCII 'C', [`Ctrl]) -> ()
1616- | `Resize _ -> main t pos
1717- | `Mouse ((`Press _ | `Drag), pos, _) -> main t pos
1818- | `Key (`Arrow d, _) ->
1919- ( main t @@ match d with
2020- | `Up -> (x, y - 1)
2121- | `Down -> (x, y + 1)
2222- | `Left -> (x - 1, y)
2323- | `Right -> (x + 1, y) )
2424- | _ -> main t pos
2525-2626-let () = main (Term.create ()) (0, 1)
-58
notty/examples/cuts.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * Demonstrates text cropping, particularly of grapheme clusters and wide
66- * characters.
77- *)
88-open Notty
99-open Notty_unix
1010-open Common
1111-1212-let hpadwith attr c a b i =
1313- I.(char attr c a 1 <|> i <|> char attr c b 1)
1414-1515-let cuts i =
1616- let w = I.width i in
1717- List.(
1818- range 0 w |> map (fun a ->
1919- range 0 (w - a) |> map (fun b ->
2020- i |> I.hcrop a b |> hpadwith A.(fg lightblack) '.' a b
2121- ) |> I.vcat |> I.hpad 1 1
2222- ) |> I.hcat |> I.vpad 1 1
2323- )
2424-2525-let colors = A.[red; green; yellow; blue; magenta; cyan]
2626-2727-let patterns = [
2828- "desu"
2929-; ".▪e\204\129●."
3030-; "(茶‸茶‶)"
3131-; "(⌐■_■)"
3232-(* ; "¯\\(ツ)/¯" *)
3333-(* ; "ಠ_ಠ" *)
3434-(* ; "ಡ_ಡ" *)
3535-(* ; "\xe0\xb2\xa0\x5f\xe0\xb1\x83" *)
3636-(* ; "ತಎತ" *)
3737-(* ; "ಥ_ಥ" *)
3838-; "ᕕ( ᐛ )ᕗ"
3939-(* ; "ᕙ(⇀‸↼‶)ᕗ" *)
4040-(* ; "ᕦ(ò_óˇ)ᕤ" *)
4141-(* ; "(╯ ︵╰ )" *)
4242-(* ; "\x28\x20\xcd\xa1\xc2\xb0\x20\xcd\x9c\xca\x96\x20\xcd\xa1\xc2\xb0\x29" *)
4343-]
4444-4545-4646-let () =
4747- let open I in
4848-4949- patterns |> List.map (fun s ->
5050- cuts (string A.(fg lightmagenta ++ bg lightblack) s)
5151- ) |> I.vcat |> eol |> output_image ;
5252-5353- colors |> List.mapi (fun i c ->
5454- pad ~l:i ~t:i (
5555- string A.(fg black ++ bg c ++ st blink) "茶" <|>
5656- pad ~l:2 ~t:1
5757- (string A.(fg c ++ st blink) "PARTY!"))
5858- ) |> zcat |> pad ~l:2 ~t:2 ~b:2 |> output_image
···11-open Notty
22-open Common
33-44-let es = [
55- [0x2e; 0x2e; 0x2e; 0x2e];
66- [0x25aa; 0x25fe; 0x25fc; 0x2b1b];
77- [0x1f346; 0x1f351; 0x1f605; 0x1f4a6];
88- [0x1f62d; 0x1f52a; 0x1f52a; 0x1f47c];
99-]
1010-1111-let image =
1212- es |> List.(map (map @@ fun x ->
1313- let i = I.uchar A.(fg lightwhite) (Uchar.of_int x) 1 1 in
1414- I.(pad ~r:(3 - width i) i)
1515- )) |> Images.grid |> I.pad ~l:1 |> Images.outline A.(fg lightblack)
1616-1717-let () = Notty_unix.output_image_size @@ fun (w, _) ->
1818- I.(pad ~l:((w - width image) / 2) ~b:1 image)
-65
notty/examples/inline.ml
···11-(* Copyright (c) 2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(** Demonstrates manual cursor positioning. *)
55-66-open Notty
77-open Notty.Infix
88-open Notty_unix
99-1010-let sleep n = flush stdout; Unix.select [] [] [] n |> ignore
1111-1212-let pp_str attr = I.pp_attr attr Format.pp_print_string
1313-1414-let rewind n = move_cursor `Home; move_cursor (`By (0, - (max n 0)))
1515-1616-let output_subst ~prev i =
1717- let h = I.height prev in
1818- let d = h - I.height i in
1919- if d > 0 then ( rewind (d - 1); output_image (I.void 0 d) );
2020- rewind (h - 1); output_image i
2121-2222-let cmyk = function
2323- | 0 -> A.rgb ~r:0 ~g:5 ~b:5
2424- | 1 -> A.rgb ~r:5 ~g:0 ~b:5
2525- | 2 -> A.rgb ~r:5 ~g:5 ~b:0
2626- | 3 -> A.rgb ~r:0 ~g:0 ~b:0
2727- | _ -> A.rgb ~r:5 ~g:5 ~b:5
2828-2929-let () =
3030-3131- let (w, h) = match winsize Unix.stdout with
3232- Some dim -> dim | _ -> assert false
3333- and attr = A.(fg lightwhite ++ bg blue) in
3434- let img1 =
3535- I.(string attr "THE BLUE STRIPE ABOVE" <->
3636- tabulate 1 h (fun _ _ -> I.strf "HIDDEN"))
3737- and img2 =
3838- I.(strf "Top line. There's a %a above. ^^^"
3939- (pp_str attr) "blue stripe" |> vpad 0 2) in
4040-4141- output_image img1; output_subst ~prev:img1 img2;
4242-4343- output_image I.(string A.(fg white) "[..]" |> eol);
4444- for i = 0 to 5 do
4545- let a = A.(bg (rgb ~r:i ~b:(5 - i) ~g:0)) in
4646- let bg = I.tabulate 1 i (fun _ -> I.strf "HIDDEN [%d]") |> eol
4747- and fg = I.char a ' ' 19 (5 - i) <|> I.char a '-' 1 (5 - i) |> eol in
4848- output_image bg; output_subst ~prev:bg fg;
4949- done;
5050- output_image I.(string A.(fg white) "[..]" |> vpad 0 2);
5151-5252- let rec go prev n =
5353- if n <= w then
5454- let h = log (float n) |> truncate in
5555- let i = prev <|> I.tabulate 1 h (fun _ y -> I.char A.(bg (cmyk y)) ' ' 1 1) in
5656- output_subst ~prev i; sleep 0.01; go i (n + 1)
5757- else output_subst ~prev I.empty in
5858- show_cursor false;
5959- go I.empty 1;
6060- show_cursor true;
6161-6262- output_image
6363- I.(strf "It doesn't say %a anywhere on screen, either."
6464- (pp_attr A.(fg white) Format.pp_print_string) "hidden" |> eol)
6565-
-73
notty/examples/keys.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * Demonstrates input parsing.
66- *)
77-open Notty
88-open Common
99-1010-let pps = Format.pp_print_string
1111-let ppi = Format.pp_print_int
1212-1313-let pp_special fmt = function
1414- | `Escape -> pps fmt "ESCAPE"
1515- | `Enter -> pps fmt "ENTER"
1616- | `Tab -> pps fmt "TAB"
1717- | `Backspace -> pps fmt "BACKSPACE"
1818- | `Arrow `Up -> pps fmt "UP"
1919- | `Arrow `Down -> pps fmt "DOWN"
2020- | `Arrow `Left -> pps fmt "LEFT"
2121- | `Arrow `Right -> pps fmt "RIGHT"
2222- | `Page `Up -> pps fmt "PAGE UP"
2323- | `Page `Down -> pps fmt "PAGE DOWN"
2424- | `Home -> pps fmt "HOME"
2525- | `End -> pps fmt "END"
2626- | `Insert -> pps fmt "INSERT"
2727- | `Delete -> pps fmt "DELETE"
2828- | `Function n -> pps fmt "FN"; ppi fmt n
2929-3030-let pp_mods fmt = function
3131- | [] -> ()
3232- | ms -> ms |> List.iter (fun m ->
3333- pps fmt @@ match m with `Meta -> "M" | `Ctrl -> "C" | `Shift -> "S"
3434- )
3535-3636-let pp_mouse fmt = function
3737- | `Release -> pps fmt "Release"
3838- | `Drag -> pps fmt "Drag"
3939- | `Move -> pps fmt "Move"
4040- | `Press k ->
4141- pps fmt "Press ";
4242- pps fmt @@ match k with
4343- | `Left -> "Left"
4444- | `Middle -> "Middle"
4545- | `Right -> "Right"
4646- | `Scroll `Up -> "Scroll Up"
4747- | `Scroll `Down -> "Scroll Down"
4848-4949-let pp_u ppf u = Format.fprintf ppf "U+%04X" (Uchar.to_int u)
5050-5151-let () =
5252- let magenta = A.(fg lightmagenta ++ bg black)
5353- and green = A.(fg lightgreen ++ bg black)
5454- and blue = A.(fg lightblue ++ bg black) in
5555- let pp_mods = I.pp_attr green pp_mods
5656- and pp_mouse = I.pp_attr blue pp_mouse in
5757- simpleterm ~s:[]
5858- ~f:(fun xs x -> Some (List.take 100 (x::xs)))
5959- ~imgf:(fun (_, h) xs ->
6060- let attr = magenta in
6161- let msg = I.string A.empty "Push keys."
6262- and ks = List.map (function
6363- | `Key ((`ASCII _ | `Uchar _) as c, mods) ->
6464- let u = Unescape.uchar c in
6565- I.(uchar blue u 1 1 <|> strf ~attr " %a %a" pp_u u pp_mods mods)
6666- | `Key (#Unescape.special as k, mods) ->
6767- I.strf ~attr "%a %a" pp_special k pp_mods mods
6868- | `Mouse (e, (x, y), mods) ->
6969- I.strf ~attr "MOUSE %a (%d, %d) %a" pp_mouse e x y pp_mods mods
7070- | `Paste e ->
7171- I.strf ~attr "PASTE %s" (if e = `Start then "START" else "END")
7272- ) xs |> I.vcat in
7373- I.(vsnap ~align:`Top (h - 3) ks <-> void 0 1 <-> msg |> pad ~l:1 ~t:1))
-31
notty/examples/letters.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * Dancing letters.
66- *)
77-open Notty
88-open Common
99-1010-let nw = 6
1111-and nh = 5
1212-1313-let () =
1414- simpleterm ~s:[]
1515- ~f:(fun us -> function
1616- | `Key ((`Delete|`Backspace), _) ->
1717- Some (match us with _::xs -> xs | _ -> us)
1818- | `Key ((`ASCII _|`Uchar _ as u), _) ->
1919- Some (List.take (nw * nh) (Unescape.uchar u :: us))
2020- | _ -> Some us)
2121- ~imgf:(fun _ us ->
2222- let open List in
2323- let uus = chunks nw (rev us) in
2424- mapi (fun i us ->
2525- mapi (fun j u ->
2626- I.uchar A.(fg white ++ bg (rgb ~r:0 ~g:i ~b:j)) u 1 1
2727- ) us |> I.hcat
2828- ) uus |> I.vcat
2929- |> I.pad ~t:1 ~l:1
3030- |> I.hsnap ~align:`Left (nw + 1)
3131- |> tile nw 1)
-108
notty/examples/life.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(*
55- * Game of Life with some ZX spectrum kitsch.
66- *)
77-88-let flip f a b = f b a
99-1010-(** Live, **)
1111-1212-module Coord = struct
1313- type t = int * int
1414- let compare ((a, b) : t) (c, d) =
1515- match compare a c with 0 -> compare b d | r -> r
1616- let equal ((a, b) : t) (c, d) = a = c && b = d
1717-end
1818-1919-module CSet = struct
2020- include Set.Make (Coord)
2121- let of_list = List.fold_left (flip add) empty
2222- let map f s = fold (fun x s -> add (f x) s) s empty
2323-end
2424-2525-module CMap = struct
2626- include Map.Make (Coord)
2727- let preimg p m =
2828- fold (fun k v s -> if p v then CSet.add k s else s) m CSet.empty
2929-end
3030-3131-let erem x y = (x mod y + y) mod y
3232-let square (w, h) (a, b as ab) =
3333- if a < 0 || a >= w || b < 0 || b >= h then (-1, -1) else ab
3434-let torus (w, h) (a, b) = (erem a w, erem b h)
3535-let moebius (w, h) (a, b as ab) =
3636- if a < 0 || a >= w then (erem a w, h - b - 1) else ab
3737-3838-let neigh topo (a, b) = [
3939- (a-1, b); (a+1, b); (a-1, b-1); (a-1, b+1)
4040-; (a, b-1); (a, b+1); (a+1, b-1); (a+1, b+1)
4141-] |> List.map topo
4242-4343-let step topo life =
4444- let nlive pt =
4545- List.(neigh topo pt |> filter (flip CSet.mem life) |> length) in
4646- let f1 pt acc =
4747- pt :: neigh topo pt |> List.fold_left (fun acc -> function
4848- | (-1, -1) -> acc
4949- | pt when CMap.mem pt acc -> acc
5050- | pt ->
5151- let n = nlive pt in
5252- acc |> CMap.add pt
5353- (if n = 3 || (n = 2 && CSet.mem pt life) then 0 else 1)
5454- ) acc in
5555- CSet.fold f1 life CMap.empty |> CMap.preimg ((=) 0)
5656-5757-let glider = CSet.of_list [(2,1); (3,2); (1,3); (2,3); (3,3)]
5858-5959-(** ...render, **)
6060-6161-open Notty
6262-open Notty.Infix
6363-6464-let dot = I.string A.(fg lightred) "●"
6565-6666-let background step (n, m) =
6767- let k = 24. *. sin (float (step + m + n) /. 10.) |> truncate in
6868- if k > 0 then I.string A.(fg (gray k)) "." else I.void 1 1
6969-7070-let render (w, h) step life =
7171- I.tabulate w (h - 1) (fun x y ->
7272- let pt = (x, y) in if CSet.mem pt life then dot else background step pt
7373- ) <->
7474- I.(strf ~attr:A.(fg lightblack) "[generation %04d]" step |>
7575- hsnap ~align:`Right w)
7676-7777-(** ...and interact. **)
7878-7979-open Lwt.Infix
8080-open Notty_lwt
8181-8282-let timer () = Lwt_unix.sleep 0.1 >|= fun () -> `Timer
8383-let event term = Lwt_stream.get (Term.events term) >|= function
8484- | Some (`Resize _ | #Unescape.event as x) -> x
8585- | None -> `End
8686-8787-let rec loop term (e, t) (dim, n, life as st) =
8888- (e <?> t) >>= function
8989- | `End | `Key (`Escape, []) | `Key (`ASCII 'C', [`Ctrl]) ->
9090- Lwt.return_unit
9191- | `Timer ->
9292- Term.image term (render dim n life) >>= fun () ->
9393- loop term (e, timer ())
9494- (dim, n + 1, step (torus dim) life)
9595- | `Mouse ((`Press `Left|`Drag), (x, y), _) ->
9696- loop term (event term, t)
9797- (dim, n, CSet.add (torus dim (x, y)) life)
9898- | `Resize dim ->
9999- let life = CSet.map (torus dim) life in
100100- Term.image term (render dim n life) >>= fun () ->
101101- loop term (event term, t) (dim, n, life)
102102- | _ -> loop term (event term, t) st
103103-104104-let main () =
105105- let t = Term.create () in
106106- loop t (event t, timer ()) (Term.size t, 0, glider)
107107-108108-let () = Lwt_main.run @@ main ()
-64
notty/examples/linear.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(*
55- * Elementary Cellular Automata
66- *)
77-open Notty
88-open Notty.Infix
99-open Common_lwt
1010-1111-let flip f a b = f b a
1212-let rec take n = function
1313- | x::xs when n > 0 -> x :: take (pred n) xs
1414- | _ -> []
1515-1616-let getd arr d i =
1717- if i < 0 || i >= Array.length arr then d else arr.(i)
1818-1919-let f ~rule a b c =
2020- if rule land (1 lsl (a lsl 2 + b lsl 1 + c)) > 0 then 1 else 0
2121-2222-let step ~rule w arr =
2323- let get = getd arr 0 in
2424- Array.init w @@ fun i ->
2525- f ~rule (get (i - 1)) (get i) (get (i + 1))
2626-2727-let dot = I.char A.(bg lightwhite) ' ' 1 1
2828-let void = I.void 1 1
2929-3030-let render ~rule ~h xss =
3131- let cons k = function
3232- | 0 -> I.void k 1
3333- | _ -> I.char A.(bg lightwhite) ' ' k 1 in
3434- let rec rline s k i arr =
3535- if i >= Array.length arr then
3636- cons k s
3737- else if arr.(i) = s then
3838- rline s (k + 1) (i + 1) arr
3939- else cons k s <|> rline (1 - s) 1 (i + 1) arr in
4040- ( xss |> List.rev |> List.map (rline 0 0 0) |> I.vcat
4141- |> I.vsnap ~align:`Top (h - 2) ) <->
4242- ( I.strf ~attr:A.(fg lightgreen ++ bg black) " RULE %d " rule
4343- |> I.vpad 1 0 )
4444-4545-let rule = 124 (* 110 mirrored *)
4646-4747-let main () =
4848- simpleterm_lwt_timed ~delay:0.1 ([], rule)
4949- ~f:(fun (w, h) (lines, rule) -> function
5050- | `Timer ->
5151- let prev = match lines with [] -> [|1|] | h::_ -> h in
5252- let lines = step ~rule w prev :: lines |> take (h - 2) in
5353- `Redraw ((lines, rule), render ~rule ~h lines)
5454- | `Resize _ ->
5555- let lines = lines |> take h in
5656- `Redraw ((lines, rule), render ~rule ~h lines)
5757- | `Key (`Arrow `Left, []) ->
5858- `Redraw (([], rule - 1), render ~rule ~h lines)
5959- | `Key (`Arrow `Right, []) ->
6060- `Redraw (([], rule + 1), render ~rule ~h lines)
6161- | _ -> `Continue (lines, rule)
6262- )
6363-6464-let () = Lwt_main.run @@ main ()
-47
notty/examples/mouse.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * Demonstrates mouse input.
66- *)
77-open Notty
88-open Common
99-1010-let lnv = Uchar.of_int 0x2502
1111-and lnh = Uchar.of_int 0x2500
1212-and crs = Uchar.of_int 0x253c
1313-1414-let clip a b x = min b (max a x)
1515-1616-let () =
1717- simpleterm ~s:(`Down, (0, 0), [], 11)
1818- ~f:(fun (st, pos, mods, scr as s) -> function
1919- | `Mouse ((`Press `Left|`Drag), pos, mods) -> Some (`Drag, pos, mods, scr)
2020- | `Mouse (`Press (`Scroll s), _, _) ->
2121- Some (st, pos, mods, clip 0 23 (scr + match s with `Up -> 1 | _ -> -1))
2222- | `Mouse (`Release, pos, _) -> Some (`Down, pos, [], scr)
2323- | _ -> Some s)
2424- ~imgf:I.(fun (w, h) (st, (x, y), mods, scr) ->
2525- let cross =
2626- let a = match st with `Drag -> A.(fg lightgreen) | `Down -> A.(fg green) in
2727- (uchar a lnh x 1 |> vpad y 0) <|>
2828- (uchar a lnv 1 y <-> uchar a crs 1 1 <-> uchar a lnv 1 (h - y)) <|>
2929- (uchar a lnh (w - x - 1) 1 |> vpad y 0)
3030- |> crop ~t:1 ~l:1 ~r:3
3131- |> hpad 1 1
3232- |> vsnap ~align:`Top (h - 1)
3333- and scroll =
3434- List.(range 0 scr |> rev |> map @@ fun level ->
3535- Images.dot A.(gray level)
3636- ) |> vcat |> vsnap ~align:`Bottom (h - 1)
3737- and status =
3838- let a = A.(fg lightblack ++ bg black) in
3939- let fa m = if List.mem m mods then A.(fg lightgreen ++ bg black) else a in
4040- string A.empty "Use the mouse." </>
4141- (hcat [ string a "["
4242- ; string (fa `Ctrl) "C"
4343- ; string (fa `Meta) "M"
4444- ; strf ~attr:a "] @(%03d, %03d)" x y ]
4545- |> hsnap ~align:`Right w)
4646- in (cross <|> scroll) <-> status
4747- )
-95
notty/examples/rain.ml
···11-22-let () = Random.self_init ()
33-44-let rec (--) a b = if a > b then [] else a :: succ a -- b
55-66-let utf8_of_code_point =
77- let buf = Buffer.create 7 in fun cp ->
88- Buffer.clear buf;
99- Uutf.Buffer.add_utf_8 buf (Uchar.of_int cp);
1010- Buffer.contents buf
1111-1212-let nsym = 4096
1313-let glitch = nsym / 20
1414-let symbols = Array.(concat [
1515- init 58 (fun x -> utf8_of_code_point (0xff66 + x));
1616- init 10 (fun x -> utf8_of_code_point (0x30 + x));
1717- (* init 26 (fun x -> utf8_of_code_point (0x61 + x)); *)
1818- (* init 14 (fun x -> utf8_of_code_point (0x21 + x)); *)
1919-])
2020-let sym () = symbols.(Random.int (Array.length symbols))
2121-let syms = Array.init nsym (fun _ -> sym ())
2222-2323-let gen_wait h = `Wait Random.(int (h / 2))
2424-and gen_line h =
2525- `Line Random.(0, int (nsym - h), int (h + h / 2) + 1, int 2 + 1)
2626-let gen (w, h as dim) =
2727- let lines = 1 -- w |> List.map @@ fun _ ->
2828- if Random.float 1. < 0.1 then gen_line h else gen_wait h in
2929- (dim, lines)
3030-3131-let step ((_, h as dim), xs) =
3232- let xs = xs |> List.map @@ function
3333- `Wait 0 -> gen_line h
3434- | `Wait n -> `Wait (n - 1)
3535- | `Line (i, _, win, k) when i - win + k >= h -> gen_wait h
3636- | `Line (i, s, win, k) -> `Line (i + k, s, win, k) in
3737- Random.(for _ = 0 to int glitch do syms.(int nsym) <- sym () done);
3838- (dim, xs)
3939-4040-open Notty
4141-open Notty.Infix
4242-4343-let bgc = A.(bg @@ rgb ~r:0 ~g:0 ~b:0)
4444-4545-let color i n =
4646- let chan x = x *. 255. |> truncate
4747- and t = float i /. float n in
4848- let t1 = exp (-. t /. 0.02) |> chan
4949- and t2 = exp (-. t /. 0.45) |> chan in
5050- A.rgb_888 ~r:t1 ~b:t1 ~g:t2
5151-5252-let show ((w, h), xs) =
5353- let f = function
5454- `Wait _ -> I.void 1 0
5555- | `Line (i, sym, win, _) ->
5656- let last = i - win
5757- and off = max 0 (i - h + 1) in
5858- let rec chars w =
5959- let ix = w + last in
6060- if 0 <= min ix w then syms.(sym + ix) :: chars (w - 1) else [] in
6161- let rec images acc i = function
6262- | [] -> acc
6363- | x::xs -> let img = I.string A.(fg (color i win) ++ bgc) x in
6464- images (img :: acc) (i + 1) xs in
6565- chars (win - off) |> images [] off
6666- |> I.vcat |> I.vpad (max 0 (i - win)) 0 in
6767- (List.map f xs |> I.hcat) </> I.char bgc ' ' w h
6868-6969-open Notty_unix
7070-7171-type r = [ Unescape.event | `Resize of int * int | `End | `Timer ]
7272-7373-let event ~delay t =
7474- if Term.pending t then (Term.event t :> r) else
7575- let open Unix in
7676- match select [Term.fds t |> fst] [] [] delay with
7777- | ([], _, _) -> `Timer
7878- | (_::_, _, _) -> (Term.event t :> r)
7979- | exception Unix_error (EINTR, _, _) -> (Term.event t :> r)
8080-8181-let loop t ~frame st =
8282- let rec go st deadline =
8383- let now = Unix.gettimeofday () in
8484- if deadline <= now then
8585- ( Term.image t (show st); go (step st) (frame +. deadline) )
8686- else match event ~delay:(deadline -. now) t with
8787- | `End | `Key (`Escape, _) | `Key (`ASCII 'C', [`Ctrl]) -> ()
8888- | `Resize _ | `Key (`ASCII ' ', _) -> go (gen (Term.size t)) deadline
8989- | _ -> go st deadline in
9090- go st (Unix.gettimeofday ())
9191-9292-let () =
9393- let t = Term.create () in
9494- loop t ~frame:0.075 (gen (Term.size t));
9595- Term.release t
-92
notty/examples/runes.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * Demonstrates geometry computation with various scripts. A few of those will
66- * usually break.
77- *)
88-open Notty
99-open Notty.Infix
1010-open Common
1111-1212-let hpad_sp attr l r i =
1313- let h = I.height i in
1414- I.(char attr ' ' l h <|> i <|> char attr ' ' r h)
1515-1616-let vpad_sp attr t b i =
1717- let w = I.width i in
1818- I.(char attr ' ' w t <-> i <-> char attr ' ' w b)
1919-2020-let grid xxs = xxs |> List.map I.hcat |> I.vcat
2121-2222-let centered attr xs =
2323- let lns = List.map I.(string attr) xs in
2424- let w = List.fold_left (fun a i -> max a I.(width i)) 0 lns in
2525- lns |> List.map I.(fun ln ->
2626- let d = w - I.width ln in
2727- char attr ' ' (d / 2) 1 <|> ln <|> char attr ' ' (d - d / 2) 1
2828- ) |> I.vcat
2929-3030-let note xs = I.(
3131- string A.(st bold) "Note:" <|>
3232- (xs |> List.map (string A.empty) |> vcat |> hpad 1 0)
3333-)
3434-3535-let text = [
3636- "\225\154\160\225\155\135\225\154\187\225\155\171\225\155\146\225\155\166\225\154\166\225\155\171\225\154\160\225\154\177\225\154\169\225\154\160\225\154\162\225\154\177\225\155\171\225\154\160\225\155\129\225\154\177\225\154\170\225\155\171\225\154\183\225\155\150\225\154\187\225\154\185\225\155\166\225\155\154\225\154\179\225\154\162\225\155\151"
3737- ; "\225\155\139\225\154\179\225\155\150\225\154\170\225\155\154\225\155\171\225\154\166\225\155\150\225\154\170\225\154\187\225\155\171\225\155\151\225\154\170\225\154\190\225\154\190\225\154\170\225\155\171\225\154\183\225\155\150\225\154\187\225\154\185\225\155\166\225\155\154\225\154\179\225\155\171\225\155\151\225\155\129\225\154\179\225\155\154\225\154\162\225\154\190\225\155\171\225\154\187\225\155\166\225\155\143\225\155\171\225\155\158\225\154\171\225\155\154\225\154\170\225\154\190"
3838- ; "\225\154\183\225\155\129\225\154\160\225\155\171\225\154\187\225\155\150\225\155\171\225\154\185\225\155\129\225\155\154\225\155\150\225\155\171\225\154\160\225\154\169\225\154\177\225\155\171\225\155\158\225\154\177\225\155\129\225\154\187\225\155\143\225\154\190\225\155\150\225\155\171\225\155\158\225\154\169\225\155\151\225\155\150\225\155\139\225\155\171\225\154\187\225\155\154\225\155\135\225\155\143\225\154\170\225\154\190\225\155\172"
3939- ; ""
4040- ; "\227\129\132\227\130\141\227\129\175\227\129\171\227\129\187\227\129\184\227\129\168\227\129\161\227\130\138\227\129\172\227\130\139\227\130\146"
4141- ; "\227\130\143\227\129\139\227\130\136\227\129\159\227\130\140\227\129\157\227\129\164\227\129\173\227\129\170\227\130\137\227\130\128"
4242- ; "\227\129\134\227\130\144\227\129\174\227\129\138\227\129\143\227\130\132\227\129\190\227\129\145\227\129\181\227\129\147\227\129\136\227\129\166"
4343- ; "\227\129\130\227\129\149\227\129\141\227\130\134\227\130\129\227\129\191\227\129\151\227\130\145\227\129\178\227\130\130\227\129\155\227\129\153"
4444- ; ""
4545- ; "\227\130\164\227\131\173\227\131\143\227\131\139\227\131\155\227\131\152\227\131\136 \227\131\129\227\131\170\227\131\140\227\131\171\227\131\178 \227\131\175\227\130\171\227\131\168\227\130\191\227\131\172\227\130\189 \227\131\132\227\131\141\227\131\138\227\131\169\227\131\160"
4646- ; "\227\130\166\227\131\176\227\131\142\227\130\170\227\130\175\227\131\164\227\131\158 \227\130\177\227\131\149\227\130\179\227\130\168\227\131\134 \227\130\162\227\130\181\227\130\173\227\131\166\227\131\161\227\131\159\227\130\183 \227\131\177\227\131\146\227\131\162\227\130\187\227\130\185\227\131\179"
4747- ; ""
4848- ; "\237\130\164\236\138\164\236\157\152 \234\179\160\236\156\160\236\161\176\234\177\180\236\157\128 \236\158\133\236\136\160\235\129\188\235\166\172 \235\167\140\235\130\152\236\149\188"
4949- ; "\237\149\152\234\179\160 \237\138\185\235\179\132\237\149\156 \234\184\176\236\136\160\236\157\128 \237\149\132\236\154\148\236\185\152 \236\149\138\235\139\164"
5050- ; ""
5151- ; "\206\158\206\181\207\131\206\186\206\181\207\128\206\172\206\182\207\137 \207\132\225\189\180\206\189 \207\136\207\133\207\135\206\191\207\134\206\184\207\140\207\129\206\177 \206\178\206\180\206\181\206\187\207\133\206\179\206\188\206\175\206\177"
5252- ; ""
5353- ; "\208\167\208\181\209\136\209\155\208\181 \209\134e\209\146\208\181\209\154\208\181 \208\188\209\128e\208\182\208\176\209\129\209\130\208\184\208\188 \209\159\208\176\208\186\208\190\208\188 \208\191\208\190\208\177\208\190\209\153\209\136\208\176\208\178\208\176"
5454- ; "\209\132\208\181\209\128\209\130\208\184\208\187\208\184\208\183\208\176\209\134\208\184\209\152\209\131 \208\179\208\181\208\189\209\129\208\186\208\184\209\133 \209\133\208\184\208\177\209\128\208\184\208\180\208\176!"
5555- ; ""
5656- ; "Heiz\195\182lr\195\188cksto\195\159abd\195\164mpfung."
5757- ; ""
5858- ; "\208\146 \209\135\208\176\209\137\208\176\209\133 \209\142\208\179\208\176 \208\182\208\184\208\187 \208\177\209\139 \209\134\208\184\209\130\209\128\209\131\209\129? \208\148\208\176, \208\189\208\190 \209\132\208\176\208\187\209\140\209\136\208\184\208\178\209\139\208\185 \209\141\208\186\208\183\208\181\208\188\208\191\208\187\209\143\209\128!"
5959- ; ""
6060- ; "\225\131\149\225\131\148\225\131\158\225\131\174\225\131\152\225\131\161 \225\131\162\225\131\167\225\131\144\225\131\157\225\131\161\225\131\144\225\131\156\225\131\152 \225\131\168\225\131\157\225\131\151\225\131\144 \225\131\160\225\131\163\225\131\161\225\131\151\225\131\144\225\131\149\225\131\148\225\131\154\225\131\152"
6161- ; ""
6262- ; "Lu\195\173s arg\195\188ia \195\160 J\195\186lia que \194\171bra\195\167\195\181es, f\195\169, ch\195\161,"
6363- ; "\195\179xido, p\195\180r, z\195\162ng\195\163o\194\187 eram palavras do portugu\195\170s."
6464- ; ""
6565- ; "ding ↹ ∀ ⌘ ▓ ◭ ☃ ♠ ♋ ♕ ⚅ ♩ ☭ ✎ 🂡 bats"
6666- ; ""
6767- ; "\216\181\217\144\217\129 \216\174\217\142\217\132\217\130\217\142 \216\174\217\142\217\136\216\175\217\144 \217\131\217\142\217\133\217\144\216\171\217\132\217\144 \216\167\217\132\216\180\217\142\217\133\216\179\217\144 \216\165\217\144\216\176 \216\168\217\142\216\178\217\142\216\186\217\142\216\170 \226\128\148 \217\138\217\142\216\173\216\184\217\137 \216\167\217\132\216\182\217\142\216\172\217\138\216\185\217\143 \216\168\217\144\217\135\216\167 \217\134\217\142\216\172\217\132\216\167\216\161\217\142 \217\133\217\144\216\185\216\183\216\167\216\177\217\144"
6868- ; ""
6969- ; "\215\147\215\146 \215\161\215\167\215\168\215\159 \215\169\215\152 \215\145\215\153\215\157 \215\158\215\144\215\149\215\155\215\150\215\145 \215\149\215\156\215\164\215\170\215\162 \215\158\215\166\215\144 \215\156\215\149 \215\151\215\145\215\168\215\148 \215\144\215\153\215\154 \215\148\215\167\215\156\215\153\215\152\215\148"
7070- ; ""
7171- ; "\224\174\175\224\174\190\224\174\174\224\174\177\224\174\191\224\174\168\224\175\141\224\174\164 \224\174\174\224\175\138\224\174\180\224\174\191\224\174\149\224\174\179\224\174\191\224\174\178\224\175\135 \224\174\164\224\174\174\224\174\191\224\174\180\224\175\141\224\174\174\224\175\138\224\174\180\224\174\191 \224\174\170\224\175\139\224\174\178\224\175\141 \224\174\135\224\174\169\224\174\191\224\174\164\224\174\190\224\174\181\224\174\164\224\175\129 \224\174\142\224\174\153\224\175\141\224\174\149\224\175\129\224\174\174\224\175\141 \224\174\149\224\174\190\224\174\163\224\175\139\224\174\174\224\175\141,"
7272- ; "\224\174\170\224\174\190\224\174\174\224\174\176\224\174\176\224\174\190\224\174\175\224\175\141 \224\174\181\224\174\191\224\174\178\224\174\153\224\175\141\224\174\149\224\175\129\224\174\149\224\174\179\224\174\190\224\174\175\224\175\141, \224\174\137\224\174\178\224\174\149\224\174\169\224\175\136\224\174\164\224\175\141\224\174\164\224\175\129\224\174\174\224\175\141 \224\174\135\224\174\149\224\174\180\224\175\141\224\174\154\224\175\141\224\174\154\224\174\191\224\174\154\224\175\138\224\174\178\224\174\170\224\175\141 \224\174\170\224\174\190\224\174\169\224\175\141\224\174\174\224\175\136 \224\174\149\224\175\134\224\174\159\224\175\141\224\174\159\224\175\129"
7373- ; ""
7474- ; "\224\178\172\224\178\190 \224\178\135\224\178\178\224\179\141\224\178\178\224\178\191 \224\178\184\224\178\130\224\178\173\224\178\181\224\178\191\224\178\184\224\179\129 \224\178\135\224\178\130\224\178\166\224\179\134\224\178\168\224\179\141\224\178\168 \224\178\185\224\179\131\224\178\166\224\178\175\224\178\166\224\178\178\224\178\191"
7575- ; "\224\178\168\224\178\191\224\178\164\224\179\141\224\178\175\224\178\181\224\179\130 \224\178\133\224\178\181\224\178\164\224\178\176\224\178\191\224\178\170 \224\178\184\224\178\164\224\179\141\224\178\175\224\178\190\224\178\181\224\178\164\224\178\190\224\178\176"
7676- ; ""
7777- ; "\224\164\139\224\164\183\224\164\191\224\164\175\224\165\139\224\164\130 \224\164\149\224\165\139 \224\164\184\224\164\164\224\164\190\224\164\168\224\165\135 \224\164\181\224\164\190\224\164\178\224\165\135 \224\164\166\224\165\129\224\164\183\224\165\141\224\164\159 \224\164\176\224\164\190\224\164\149\224\165\141\224\164\183\224\164\184\224\165\139\224\164\130 \224\164\149\224\165\135 \224\164\176\224\164\190\224\164\156\224\164\190 \224\164\176\224\164\190\224\164\181\224\164\163 \224\164\149\224\164\190 \224\164\184\224\164\176\224\165\141\224\164\181\224\164\168\224\164\190\224\164\182 \224\164\149\224\164\176\224\164\168\224\165\135 \224\164\181\224\164\190\224\164\178\224\165\135"
7878- ; "\224\164\181\224\164\191\224\164\183\224\165\141\224\164\163\224\165\129\224\164\181\224\164\164\224\164\190\224\164\176 \224\164\173\224\164\151\224\164\181\224\164\190\224\164\168 \224\164\182\224\165\141\224\164\176\224\165\128\224\164\176\224\164\190\224\164\174, \224\164\133\224\164\175\224\165\139\224\164\167\224\165\141\224\164\175\224\164\190 \224\164\149\224\165\135 \224\164\174\224\164\185\224\164\190\224\164\176\224\164\190\224\164\156 \224\164\166\224\164\182\224\164\176\224\164\165 \224\164\149\224\165\135 \224\164\172\224\164\161\224\164\188\224\165\135 \224\164\184\224\164\170\224\165\129\224\164\164\224\165\141\224\164\176 \224\164\165\224\165\135\224\165\164"
7979-]
8080-8181-let () =
8282- let attr = A.(fg lightmagenta) in
8383- let img = I.(
8484- centered attr text
8585- |> vpad_sp attr 1 1 |> hpad_sp attr 2 2
8686- |> Images.outline attr
8787- |> pad ~t:1 ~b:1 ~l:2 ~r:2
8888- ) <->
8989- note [ "Alignment will usually break on the last few scripts."
9090- ; "This is at the limit of what terminals can do."
9191- ; ":(" ]
9292- in Notty_unix.(eol img |> output_image)
-24
notty/examples/sierpinski.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * A classic example in combinatory graphics.
66- *
77- * Demonstrates interaction.
88- *)
99-open Notty
1010-open Common
1111-1212-let () =
1313- simpleterm ~s:1
1414- ~f:(fun s -> function
1515- | `Key (`ASCII 'q', _) -> None
1616- | `Key (`Arrow a, _) ->
1717- ( match a with
1818- | `Up | `Left -> Some (max 1 (s - 1))
1919- | `Down | `Right -> Some (min 10 (s + 1)) )
2020- | _ -> Some s)
2121- ~imgf:I.(fun _ s ->
2222- string A.empty (string_of_int s) <->
2323- pad ~l:2 ~t:1 (Images.sierp A.magenta s)
2424- )
-26
notty/examples/sierpinski_lwt.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * Demonstrates Lwt interaction.
66- *)
77-open Notty
88-open Common_lwt
99-1010-let img s = I.(
1111- string A.empty (string_of_int s) <-> hpad 2 0 (Images.sierp A.magenta s)
1212-)
1313-1414-let () =
1515- simpleterm_lwt ~s:1
1616- ~f:(fun s -> function
1717- | `Key (`ASCII 'q', _) -> None
1818- | `Key (`Arrow a, _) ->
1919- ( match a with
2020- | `Up | `Left -> Some (max 1 (s - 1))
2121- | `Down | `Right -> Some (min 10 (s + 1)) )
2222- | _ -> Some s)
2323- ~imgf:I.(fun _ s ->
2424- string A.empty (string_of_int s) <->
2525- pad ~l:2 ~t:1 (Images.sierp A.magenta s)
2626- )
-11
notty/examples/testpatterns.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(**
55- * A few images that exercise image composition, cropping, and padding. This
66- * test is a good canary.
77- *)
88-open Common
99-open Notty_unix
1010-1111-let () = Images.[i3; i5; checker1] |> List.map eol |> List.iter output_image
-12
notty/examples/thisbig.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-open Notty
55-open Common
66-77-let () =
88- Notty_unix.output_image_size @@ fun (w, h) ->
99- Images.outline A.(fg lightblue)
1010- I.(hsnap (w - 2) @@
1111- vsnap (h - 3) @@ (* +1 for the prompt *)
1212- Images.sierp A.lightblue 5)
-24
notty/notty.opam
···11-opam-version: "2.0"
22-homepage: "https://github.com/pqwy/notty"
33-dev-repo: "git+https://github.com/pqwy/notty.git"
44-bug-reports: "https://github.com/pqwy/notty/issues"
55-doc: "https://pqwy.github.io/notty/doc"
66-author: "David Kaloper <dk505@cam.ac.uk>"
77-maintainer: "David Kaloper <dk505@cam.ac.uk>"
88-license: "ISC"
99-synopsis: "Declaring terminals"
1010-description:
1111- "Notty is a declarative terminal library for OCaml structured around a notion
1212- of composable images. It tries to abstract away the basic terminal programming
1313- model, providing something simpler and more expressive."
1414-1515-build: [ [ "dune" "subst" ] {dev}
1616- [ "dune" "build" "-p" name "-j" jobs ] ]
1717-depends: [
1818- "ocaml" {>= "4.08.0"}
1919- "dune" {>= "1.7"}
2020- "cppo" {build & >= "1.1.0"}
2121- "uutf" {>= "1.0.0"}
2222-]
2323-depopts: [ "lwt" ]
2424-conflicts: [ "lwt" {<"2.5.2"} ]
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-open Lwt.Infix
55-66-open Notty
77-open Notty_unix
88-open Private
99-1010-1111-type ('a, 'b) either = Left of 'a | Right of 'b
1212-let left x = Left x
1313-let right y = Right y
1414-1515-let (</>) a b = Lwt.pick [(a >|= left); (b >|= right)]
1616-let (<??>) a b = (a >|= left) <?> (b >|= right)
1717-1818-let whenopt f = function Some x -> f x | None -> ()
1919-2020-let rec write fd buf off = function
2121- | 0 -> Lwt.return_unit
2222- | n -> Lwt_unix.write fd buf off n >>= fun w -> write fd buf (off + w) (n - w)
2323-2424-module Lwt_condition = struct
2525-2626- include Lwt_condition
2727-2828- let map f c =
2929- let d = create () in
3030- let rec go () = wait c >>= fun x -> broadcast d (f x); go ()
3131- in (Lwt.async go; d)
3232-3333- let unburst ~t c =
3434- let d = create () in
3535- let rec delay x = Lwt_unix.sleep t </> wait c >>= function
3636- | Left () -> broadcast d x; start ()
3737- | Right x -> delay x
3838- and start () = wait c >>= delay in
3939- Lwt.async start; d
4040-end
4141-4242-module Term = struct
4343-4444- let winches = lazy (
4545- let c = Lwt_condition.create () in
4646- let `Revert _ = set_winch_handler (Lwt_condition.broadcast c) in
4747- c
4848- )
4949-5050- let winch () = Lazy.force winches |> Lwt_condition.wait
5151-5252- let bsize = 1024
5353-5454- let input_stream ~nosig fd stop =
5555- let `Revert f = setup_tcattr ~nosig (Lwt_unix.unix_file_descr fd) in
5656- let stream =
5757- let flt = Unescape.create ()
5858- and ibuf = Bytes.create bsize in
5959- let rec next () =
6060- match Unescape.next flt with
6161- | #Unescape.event as r -> Lwt.return_some r
6262- | `End -> Lwt.return_none
6363- | `Await ->
6464- (Lwt_unix.read fd ibuf 0 bsize <??> stop) >>= function
6565- | Left n -> Unescape.input flt ibuf 0 n; next ()
6666- | Right _ -> Lwt.return_none
6767- in Lwt_stream.from next in
6868- Lwt.async (fun () -> Lwt_stream.closed stream >|= f);
6969- stream
7070-7171- type t = {
7272- ochan : Lwt_io.output_channel
7373- ; trm : Tmachine.t
7474- ; buf : Buffer.t
7575- ; fds : Lwt_unix.file_descr * Lwt_unix.file_descr
7676- ; events : [ Unescape.event | `Resize of (int * int) ] Lwt_stream.t
7777- ; stop : (unit -> unit)
7878- }
7979-8080- let write t =
8181- Tmachine.output t.trm t.buf;
8282- let out = Buffer.contents t.buf in (* XXX There goes 0copy. :/ *)
8383- Buffer.clear t.buf; Lwt_io.write t.ochan out
8484-8585- let refresh t = Tmachine.refresh t.trm; write t
8686- let image t image = Tmachine.image t.trm image; write t
8787- let cursor t curs = Tmachine.cursor t.trm curs; write t
8888- let set_size t dim = Tmachine.set_size t.trm dim
8989- let size t = Tmachine.size t.trm
9090-9191- let release t =
9292- if Tmachine.release t.trm then
9393- ( t.stop (); write t >>= fun () -> Lwt_io.flush t.ochan )
9494- else Lwt.return_unit
9595-9696- let resizef fd stop on_resize =
9797- if Unix.isatty fd then
9898- let rcond = Lwt_condition.(
9999- Lazy.force winches |> unburst ~t:0.1 |> map (fun () -> winsize fd)) in
100100- let rec monitor () =
101101- (Lwt_condition.wait rcond <?> stop) >>= function
102102- | Some dim -> on_resize dim; monitor ()
103103- | None -> Lwt.return_unit in
104104- Lwt.async monitor;
105105- Lwt_stream.from (fun () -> Lwt_condition.wait rcond <?> stop)
106106- |> Lwt_stream.map (fun dim -> `Resize dim)
107107- else Lwt_stream.of_list []
108108-109109- let create ?(dispose=true) ?(nosig=true) ?(mouse=true) ?(bpaste=true)
110110- ?(input=Lwt_unix.stdin) ?(output=Lwt_unix.stdout) () =
111111- let fd = Lwt_unix.unix_file_descr output in
112112- let (stop, stopw) = Lwt.wait () in
113113- let rec t = lazy {
114114- trm = Tmachine.create ~mouse ~bpaste (cap_for_fd fd)
115115- ; ochan = Lwt_io.(of_fd ~mode:output) output
116116- ; buf = Buffer.create 4096
117117- ; fds = (input, output)
118118- ; stop = (fun () -> Lwt.wakeup stopw None)
119119- ; events = Lwt_stream.choose
120120- [ input_stream ~nosig input stop
121121- ; resizef fd stop @@ fun dim ->
122122- let t = Lazy.force t in Buffer.reset t.buf; set_size t dim ]
123123- } in
124124- let t = Lazy.force t in
125125- winsize fd |> whenopt (set_size t);
126126- Lwt.async (fun () -> write t); (* XXX async? *)
127127- if dispose then Lwt_main.at_exit (fun () -> release t);
128128- t
129129-130130- let events t = t.events
131131- let fds t = t.fds
132132-end
133133-134134-let winsize fd = winsize (Lwt_unix.unix_file_descr fd)
135135-136136-include Gen_output (struct
137137- type fd = Lwt_unix.file_descr and k = unit Lwt.t
138138- let (def, to_fd) = Lwt_unix.(stdout, unix_file_descr)
139139- and write fd buf = Buffer.(write fd (to_bytes buf) 0 (length buf))
140140-end)
-100
notty/src-lwt/notty_lwt.mli
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(** [Notty] IO [Lwt] on [Unix].
55-66- This is an IO module for {!Notty}.
77-88- It mirrors {!Notty_unix} and the corresponding operations behave
99- analogously. Consult its documentation for more info.
1010-1111- {e %%VERSION%% — {{:%%PKG_HOMEPAGE%% }homepage}} *)
1212-1313-open Notty
1414-1515-(** {1:fullscreen Fullscreen input and output}. *)
1616-1717-(** Terminal IO with concurrency.
1818-1919- For more info, see {!Notty_unix.Term}. *)
2020-module Term : sig
2121-2222- type t
2323-2424- (** {1 Construction and destruction} *)
2525-2626- val create : ?dispose:bool ->
2727- ?nosig:bool ->
2828- ?mouse:bool ->
2929- ?bpaste:bool ->
3030- ?input:Lwt_unix.file_descr ->
3131- ?output:Lwt_unix.file_descr ->
3232- unit -> t
3333- (** [create ~dispose ~nosig ~mouse ~input ~output ()] creates a new
3434- {{!t}terminal}.
3535-3636- {b Note} [~dispose] arranges for the terminal to be disposed of at the end
3737- of the [Lwt] main loop, and not at process exit.
3838-3939- See {!Notty_unix.Term.create}. *)
4040-4141- val release : t -> unit Lwt.t
4242-4343- (** {1 Commands} *)
4444-4545- val image : t -> image -> unit Lwt.t
4646- val refresh : t -> unit Lwt.t
4747- val cursor : t -> (int * int) option -> unit Lwt.t
4848-4949- (** {1 Events} *)
5050-5151- val events : t -> [ Unescape.event | `Resize of (int * int) ] Lwt_stream.t
5252- (** [events t] is the stream of incoming events.
5353-5454- Invoking {{!release}release} will terminate this stream.
5555-5656- Events are:
5757- {ul
5858- {- [#Unescape.event], an {{!Notty.Unescape.event}event} from the input
5959- fd; or}
6060- {- [`Resize (cols, rows)] whenever the terminal size changes.}}
6161-6262- {b Note} This stream is unique; for the same [t], [events t] always
6363- returns the same stream. *)
6464-6565- (** {1 Properties} *)
6666-6767- val size : t -> int * int
6868-6969- val fds : t -> Lwt_unix.file_descr * Lwt_unix.file_descr
7070-7171- (** {1 Window size change notifications}
7272-7373- {{!create}Creating} a terminal will install a [SIGWINCH] handler.
7474- The handler should not be replaced directly. This API allows the user to
7575- monitor deliveries of the signal.
7676-7777- See {!Notty_unix.Term.Winch}. *)
7878-7979- val winch : unit -> unit Lwt.t
8080- (** [winch ()] is a thread completing after the next [SIGWINCH]. A single
8181- signal delivery will cause the completion of all waiting [winch] threads. *)
8282-end
8383-8484-(** {1:inline Inline output} *)
8585-8686-val winsize : Lwt_unix.file_descr -> (int * int) option
8787-8888-val eol : image -> image
8989-9090-val output_image :
9191- ?cap:Cap.t -> ?fd:Lwt_unix.file_descr -> image -> unit Lwt.t
9292-9393-val output_image_size :
9494- ?cap:Cap.t -> ?fd:Lwt_unix.file_descr -> (int * int -> image) -> unit Lwt.t
9595-9696-val show_cursor : ?cap:Cap.t -> ?fd:Lwt_unix.file_descr -> bool -> unit Lwt.t
9797-9898-val move_cursor :
9999- ?cap:Cap.t -> ?fd:Lwt_unix.file_descr ->
100100- [ `Home | `By of int * int | `To of int * int ] -> unit Lwt.t
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-open Notty
55-66-external c_winsize : Unix.file_descr -> int = "caml_notty_winsize" [@@noalloc]
77-external winch_number : unit -> int = "caml_notty_winch_number" [@@noalloc]
88-99-let iter f = function Some x -> f x | _ -> ()
1010-let value x = function Some a -> a | _ -> x
1111-1212-let winsize fd = match c_winsize fd with
1313- | 0 -> None
1414- | wh -> Some (wh lsr 16, wh lsr 1 land 0x7fff)
1515-1616-module Private = struct
1717-1818- let once f = let v = lazy (f ()) in fun () -> Lazy.force v
1919-2020- let cap_for_fd =
2121- let open Cap in
2222- match Sys.getenv "TERM" with
2323- | exception Not_found -> fun _ -> dumb
2424- | (""|"dumb") -> fun _ -> dumb
2525- | _ -> fun fd -> if Unix.isatty fd then ansi else dumb
2626-2727- let setup_tcattr ~nosig fd =
2828- let open Unix in try
2929- let tc = tcgetattr fd in
3030- let tc1 = { tc with c_icanon = false; c_echo = false } in
3131- tcsetattr fd TCSANOW
3232- ( if nosig then { tc1 with c_isig = false; c_ixon = false } else tc1 );
3333- `Revert (once @@ fun _ -> tcsetattr fd TCSANOW tc)
3434- with Unix_error (ENOTTY, _, _) -> `Revert ignore
3535-3636- let set_winch_handler f =
3737- let signum = winch_number () in
3838- let old_hdl = Sys.(signal signum (Signal_handle (fun _ -> f ()))) in
3939- `Revert (once @@ fun () -> Sys.set_signal signum old_hdl)
4040-4141- module Gen_output (O : sig
4242- type fd
4343- type k
4444- val def : fd
4545- val to_fd : fd -> Unix.file_descr
4646- val write : fd -> Buffer.t -> k
4747- end) = struct
4848-4949- let scratch = lazy (Buffer.create 4096)
5050-5151- let output ?cap ?(fd = O.def) f =
5252- let cap = cap |> value (cap_for_fd (O.to_fd fd)) in
5353- let buf = Lazy.force scratch in
5454- Buffer.reset buf; f buf cap fd; O.write fd buf
5555-5656- let output_image_size ?cap ?fd f =
5757- output ?cap ?fd @@ fun buf cap fd ->
5858- let size = winsize (O.to_fd fd) in
5959- let i = f (value (80, 24) size) in
6060- let dim = match size with
6161- | Some (w, _) -> I.(w, height i)
6262- | None -> I.(width i, height i) in
6363- Render.to_buffer buf cap (0, 0) dim i
6464-6565- let show_cursor ?cap ?fd x =
6666- output ?cap ?fd @@ fun buf cap _ -> Direct.show_cursor buf cap x
6767-6868- let move_cursor ?cap ?fd x =
6969- output ?cap ?fd @@ fun buf cap _ -> Direct.move_cursor buf cap x
7070-7171- let output_image ?cap ?fd i = output_image_size ?cap ?fd (fun _ -> i)
7272-7373- let eol i = I.(i <-> void 0 1)
7474- end
7575-end
7676-7777-open Private
7878-7979-module Term = struct
8080-8181- module Winch = struct
8282-8383- let h = Hashtbl.create 3
8484- and id = ref 0
8585-8686- let add fd f =
8787- let n = !id in
8888- set_winch_handler (fun () -> Hashtbl.iter (fun _ f -> f ()) h) |> ignore;
8989- Hashtbl.add h n (fun () -> winsize fd |> iter f); incr id;
9090- `Revert (fun () -> Hashtbl.remove h n)
9191- end
9292-9393- module Input = struct
9494-9595- type t = {
9696- fd : Unix.file_descr
9797- ; flt : Unescape.t
9898- ; ibuf : bytes
9999- ; cleanup : unit -> unit
100100- }
101101-102102- let bsize = 1024
103103-104104- let create ~nosig fd =
105105- let flt = Unescape.create ()
106106- and ibuf = Bytes.create bsize
107107- and `Revert cleanup = setup_tcattr ~nosig fd in
108108- { fd; flt; ibuf; cleanup }
109109-110110- let rec event t =
111111- match Unescape.next t.flt with
112112- | #Unescape.event | `End as r -> r
113113- | `Await ->
114114- let n = Unix.read t.fd t.ibuf 0 bsize in
115115- Unescape.input t.flt t.ibuf 0 n; event t
116116- end
117117-118118- type t = {
119119- output : out_channel
120120- ; trm : Tmachine.t
121121- ; buf : Buffer.t
122122- ; input : Input.t
123123- ; fds : Unix.file_descr * Unix.file_descr
124124- ; unwinch : (unit -> unit) Lazy.t
125125- ; mutable winched : bool
126126- }
127127-128128- let write t =
129129- Buffer.clear t.buf;
130130- Tmachine.output t.trm t.buf;
131131- Buffer.output_buffer t.output t.buf; flush t.output
132132-133133- let set_size t dim = Tmachine.set_size t.trm dim
134134- let refresh t = Tmachine.refresh t.trm; write t
135135- let image t image = Tmachine.image t.trm image; write t
136136- let cursor t curs = Tmachine.cursor t.trm curs; write t
137137- let size t = Tmachine.size t.trm
138138-139139- let release t =
140140- if Tmachine.release t.trm then
141141- ( Lazy.force t.unwinch ();
142142- t.input.Input.cleanup ();
143143- write t )
144144-145145- let create ?(dispose=true) ?(nosig=true) ?(mouse=true) ?(bpaste=true)
146146- ?(input=Unix.stdin) ?(output=Unix.stdout) () =
147147- let rec t = {
148148- output = Unix.out_channel_of_descr output
149149- ; trm = Tmachine.create ~mouse ~bpaste (cap_for_fd input)
150150- ; buf = Buffer.create 4096
151151- ; input = Input.create ~nosig input
152152- ; fds = (input, output)
153153- ; winched = false
154154- ; unwinch = lazy (
155155- let `Revert f = Winch.add output @@ fun dim ->
156156- Buffer.reset t.buf; t.winched <- true; set_size t dim in f)
157157- } in
158158- winsize output |> iter (set_size t);
159159- (Lazy.force t.unwinch |> ignore) [@ocaml.warning "-5"];
160160- if dispose then at_exit (fun () -> release t);
161161- write t;
162162- t
163163-164164- let rec event = function
165165- | t when Tmachine.dead t.trm -> `End
166166- | t when t.winched -> t.winched <- false; `Resize (size t)
167167- | t -> Unix.(try Input.event t.input with Unix_error (EINTR, _, _) -> event t)
168168-169169- let pending t =
170170- not (Tmachine.dead t.trm) &&
171171- (t.winched || Unescape.pending t.input.Input.flt)
172172-173173- let fds t = t.fds
174174-end
175175-176176-include Gen_output (struct
177177- type fd = out_channel and k = unit
178178- let def = stdout
179179- and to_fd = Unix.descr_of_out_channel
180180- and write = Buffer.output_buffer
181181-end)
-222
notty/src-unix/notty_unix.mli
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(** [Notty] IO for pure [Unix].
55-66- This is an IO module for {!Notty}.
77-88- {e %%VERSION%% — {{:%%PKG_HOMEPAGE%% }homepage}} *)
99-1010-open Notty
1111-1212-(** {1:fullscreen Fullscreen input and output}. *)
1313-1414-(** Terminal IO abstraction for fullscreen, interactive applications.
1515-1616- This module provides both input and output. It assumes exclusive ownership of
1717- the IO streams between {{!create}initialization} and {{!release}shutdown}. *)
1818-module Term : sig
1919-2020- type t
2121- (** Representation of the terminal, giving structured access to IO. *)
2222-2323- (** {1 Construction and destruction} *)
2424-2525- val create : ?dispose:bool ->
2626- ?nosig:bool ->
2727- ?mouse:bool ->
2828- ?bpaste:bool ->
2929- ?input:Unix.file_descr ->
3030- ?output:Unix.file_descr ->
3131- unit -> t
3232- (** [create ~dispose ~nosig ~mouse ~input ~output ()] creates a fresh
3333- {{!t}terminal}. It has the following side effects:
3434- {ul
3535- {- [Unix.tcsetattr] is applied to [input] to disable {e echo} and
3636- {e canonical mode}.}
3737- {- [output] is set to {e alternate screen mode}, and the cursor is
3838- hidden. Mouse and {e bracketed paste} reporting are (optionally)
3939- enabled.}
4040- {- [SIGWINCH] signal, normally ignored, is handled.}}
4141-4242- [~dispose] arranges for automatic {{!release}cleanup} of the terminal
4343- before the process terminates. The downside is that a reference to this
4444- terminal is retained until the program exits. Defaults to [true].
4545-4646- [~nosig] additionally turns off signal delivery and flow control
4747- ({e isig} and {e ixon}) on input. Inhibits automatic handling of
4848- {e CTRL-\{C,Z,\,S,Q\}}. Defaults to [true].
4949-5050- [~mouse] activates mouse reporting. Defaults to [true].
5151-5252- [~bpaste] activates bracketed paste reporting. Defaults to [true].
5353-5454- [~input] is the input file descriptor. Defaults to [stdin].
5555-5656- [~output] is the output file descriptor. Defaults to [stdout]. *)
5757-5858- val release : t -> unit
5959- (** Dispose of this terminal. Original behavior of input fd is reinstated,
6060- cursor is restored, mouse reporting disabled, and alternate mode is
6161- terminated.
6262-6363- It is an error to use the {{!cmds}commands} on a released terminal, and
6464- will raise [Invalid_argument], while [release] itself is idempotent. *)
6565-6666- (** {1:cmds Commands} *)
6767-6868- val image : t -> image -> unit
6969- (** [image t i] sets [i] as [t]'s current image and redraws the terminal. *)
7070-7171- val refresh : t -> unit
7272- (** [refresh t] redraws the terminal using the current image.
7373-7474- Useful if the output might have become garbled. *)
7575-7676- val cursor : t -> (int * int) option -> unit
7777- (** [cursor t pos] sets and redraws the cursor.
7878-7979- [None] hides it. [Some (x, y)] places it at column [x] and row [y], with
8080- the origin at [(0, 0)], mapping to the upper-left corner. *)
8181-8282- (** {1 Events} *)
8383-8484- val event : t -> [ Unescape.event | `Resize of (int * int) | `End ]
8585- (** Wait for a new event. [event t] can be:
8686- {ul
8787- {- [#Unescape.event], an {{!Notty.Unescape.event}[event]} from the input fd;}
8888- {- [`End] if the input fd is closed, or the terminal was released; or}
8989- {- [`Resize (cols, rows)] giving the current size of the output tty, if a
9090- [SIGWINCH] was delivered before or during this call to [event].}}
9191-9292- {b Note} [event] is buffered. Calls can either block or immediately
9393- return. Use {{!pending}[pending]} to detect when the next call would not
9494- block. *)
9595-9696- val pending : t -> bool
9797- (** [pending t] is [true] if the next call to {{!event}[event]} would not
9898- block and the terminal has not yet been released. *)
9999-100100- (** {1 Properties} *)
101101-102102- val size : t -> int * int
103103- (** [size t] is the current size of the terminal's output tty. *)
104104-105105- val fds : t -> Unix.file_descr * Unix.file_descr
106106- (** [fds t] are [t]'s input and output file descriptors. *)
107107-108108- (** {1 Window size change notifications} *)
109109-110110- (** Manual [SIGWINCH] handling.
111111-112112- Unix delivers notifications about tty size changes through the [SIGWINCH]
113113- signal. A handler for this signal is installed as soon as a new terminal
114114- is {{!create}created}. Replacing the global [SIGWINCH] handler using
115115- the [Sys] module will cause this module to malfunction, as the size change
116116- notifications will no longer be delivered.
117117-118118- You might still want to ignore resizes reported by {{!event}[event]} and
119119- directly listen to [SIGWINCH]. This module allows installing such
120120- listeners without conflicting with the rest of the machinery. *)
121121- module Winch : sig
122122-123123- val add : Unix.file_descr -> ((int * int) -> unit) -> [`Revert of unit -> unit]
124124- (** [add fd f] registers a [SIGWINCH] handler. Every time the signal is
125125- delivered, [f] is called with the current size of the tty backing [fd].
126126- If [fd] is not a tty, [f] is never called.
127127-128128- Return value is a function that removes the handler [f].
129129-130130- Handlers are called in an unspecified order. *)
131131-132132- end
133133-end
134134-135135-(** {1:inline Inline output}
136136-137137- These operations do not assume exclusive access to the output. This means
138138- that they can be combined with other means of producing output. At the same
139139- time, it means that they are affected by the current terminal state, and
140140- that this state is not tracked. *)
141141-142142-val winsize : Unix.file_descr -> (int * int) option
143143-(** [winsize fd] is [Some (columns, rows)], the current dimensions of [fd]'s
144144- backing tty, or [None], when [fd] is not backed by a tty. *)
145145-146146-val eol : image -> image
147147-(** [eol image] is [image], producing an extra newline when printed. *)
148148-149149-val output_image :
150150- ?cap:Cap.t -> ?fd:out_channel -> image -> unit
151151-(** [output_image ?cap ?fd image] writes [image] to [fd].
152152-153153- The image is displayed in its full height. If the output is a tty, image
154154- width is clipped to the output width. Otherwise, full width is used.
155155-156156- [~cap] is the {{!caps}optional} terminal capability set.
157157-158158- [~fd] defaults to [stdout]. *)
159159-160160-val output_image_size : ?cap:Cap.t -> ?fd:out_channel -> (int * int -> image) -> unit
161161-(** [output_image_size ?cap ?fd f] is
162162- [output_image ?cap ?fd (f size)] where [size] are [fd]'s current
163163- {{!winsize}output dimensions}.
164164-165165- If [fd] is not backed by a tty, as a matter of convenience, [f] is applied
166166- to [(80, 24)]. Use {!Unix.isatty} or {{!winsize}[winsize]} to detect whether
167167- the output has a well-defined size. *)
168168-169169-val show_cursor : ?cap:Cap.t -> ?fd:out_channel -> bool -> unit
170170-(** [show_cursor ?cap ?fd visible] toggles the cursor visibility on [fd]. *)
171171-172172-val move_cursor :
173173- ?cap:Cap.t -> ?fd:out_channel ->
174174- [ `Home | `By of int * int | `To of int * int ] -> unit
175175-(** [move_cursor ?cap ?fd motion] moves the cursor on [fd].
176176-177177- [motion] is one of:
178178- {ul
179179- {- [`To (column, line)], positioning the cursor to [(column, line)]. Origin
180180- is [(0, 0)], the upper-left corner of the screen.}
181181- {- [`Home], moving the cursor the beginning of line.}
182182- {- [`By (columns, lines)], moving the cursor [columns] to the right (left if
183183- negative) and [lines] down (up if negative).
184184-185185- {b Note} Behavior is terminal dependent if the movement overshoots the
186186- output size.}} *)
187187-188188-(** {1:caps Capability detection}
189189-190190- All [image] output requires {{!Notty.Cap.t}terminal capabilities}.
191191-192192- When not provided, capabilities are auto-detected, by checking that the
193193- output is a tty, that the environment variable [$TERM] is set, and that it
194194- is not set to either [""] or ["dumb"]. If these conditions hold,
195195- {{!Notty.Cap.ansi}ANSI} escapes are used. Otherwise, {{!Notty.Cap.dumb}no}
196196- escapes are used. *)
197197-198198-(**/**)
199199-(** {1 Private}
200200-201201- These are private interfaces, prone to breakage. Don't use them. *)
202202-module Private : sig
203203-204204- val cap_for_fd : Unix.file_descr -> Cap.t
205205- val setup_tcattr : nosig:bool -> Unix.file_descr -> [ `Revert of (unit -> unit) ]
206206- val set_winch_handler : (unit -> unit) -> [ `Revert of (unit -> unit) ]
207207-208208- module Gen_output (O : sig
209209- type fd
210210- type k
211211- val def : fd
212212- val to_fd : fd -> Unix.file_descr
213213- val write : fd -> Buffer.t -> k
214214- end ) : sig
215215- val output_image : ?cap:Cap.t -> ?fd:O.fd -> image -> O.k
216216- val output_image_size : ?cap:Cap.t -> ?fd:O.fd -> (int * int -> image) -> O.k
217217- val show_cursor : ?cap:Cap.t -> ?fd:O.fd -> bool -> O.k
218218- val move_cursor : ?cap:Cap.t -> ?fd:O.fd -> [ `Home | `By of int * int | `To of int * int ] -> O.k
219219- val eol : image -> image
220220- end
221221-end
222222-(**/**)
···11-Cannibalized bits of Uucp:
22-33-- `Notty_uucp_data` is generated from an actual Uucp installation.
44-- `Notty_uucp` uses it to provide the few Unicode properties that Notty needs.
55-- `Notty_grapheme_cluster` is `Grapheme_cluster` from Uuseg, adapted to use the
66- above.
77-88-Compiled size of these is on the order of 70K. Uucp is presently a monolithic 10M.
99-1010-The idea is to remove these in favor of the actual Uucp/Uuseg, as soon as it
1111-becomes possible to depend only on the necessary parts of Uucp.
1212-1313-Uucp and Uuseg are Copyright (c) 2014 Daniel C. Bünzli.
-133
notty/src/no-uucp/notty_grapheme_cluster.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2014 Daniel C. Bünzli. All rights reserved.
33- Distributed under the ISC license, see terms at the end of the file.
44- %%NAME%% %%VERSION%%
55- ---------------------------------------------------------------------------*)
66-77-(* These are the rules as found in [1], with property values aliases [2]
88- substituted.
99-1010- GB1. sot ÷ Any
1111- GB2. Any ÷ eot
1212- GB3. CR × LF
1313- GB4. (CN|CR|LF) ÷
1414- GB5. ÷ (CN|CR|LF)
1515- GB6. L × (L|V|LV|LVT)
1616- GB7. (LV|V) × (V|T)
1717- GB8. (LVT|T) × T
1818- GB9. × (EX|ZWJ)
1919- GB9a. × SM
2020- GB9b. PP ×
2121- GB10. (v10.0.0) (EB|EBG) EX* × EM
2222- GB11. (v10.0.0) ZWJ × (GAZ|EBG)
2323- GB12. sot (RI RI)* RI × RI
2424- GB13. [^RI] (RI RI)* × RI
2525- GB999. Any ÷ Any
2626-2727- [1]: http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
2828- [2]: http://www.unicode.org/Public/7.0.0/ucd/PropertyValueAliases.txt
2929- [3]: http://www.unicode.org/Public/7.0.0/ucd/auxiliary/GraphemeBreakTest.html
3030-3131- By the structure of the rules we see that grapheme clusters
3232- boundaries can *mostly* be determined by simply looking at the
3333- grapheme cluster break property value of the character on the left
3434- and on the right of a boundary. The exceptions are GB10 and GB12-13
3535- which are handled specially by enriching the segmenter state in
3636- a horribly ad-hoc fashion. *)
3737-3838-type ret = [ `Await | `Boundary | `End | `Uchar of Uchar.t ]
3939-4040-type gcb =
4141- | CN | CR | EX | EB | EBG | EM | GAZ | L | LF | LV | LVT | PP | RI
4242- | SM | T | V | XX | ZWJ | Sot
4343-4444-(* WARNING. The indexes used here need to be synchronized with those
4545- assigned by uucp for Uucp.Break.Low.grapheme_cluster. *)
4646-4747-let byte_to_gcb =
4848- [| CN; CR; EX; EB; EBG; EM; GAZ; L; LF; LV; LVT; PP; RI;
4949- SM; T; V; XX; ZWJ; |]
5050-5151-let gcb u = byte_to_gcb.(Notty_uucp.grapheme_cluster_boundary u)
5252-5353-type state =
5454-| Fill (* get next uchar to decide boundary. *)
5555-| Flush (* an uchar is buffered, client needs to get it out with `Await. *)
5656-| End (* `End was added. *)
5757-5858-type t =
5959- { mutable state : state; (* current state. *)
6060- mutable left : gcb; (* break property value left of boundary. *)
6161- mutable odd_ri : bool; (* odd number of RI on the left. *)
6262- mutable emoji_seq : bool; (* (EB|EBG) Extend* on the left. *)
6363- mutable buf : [ `Uchar of Uchar.t ] } (* bufferized add. *)
6464-6565-let nul_buf = `Uchar (Uchar.unsafe_of_int 0x0000)
6666-6767-let create () =
6868- { state = Fill; left = Sot;
6969- odd_ri = false; emoji_seq = false;
7070- buf = nul_buf (* overwritten *); }
7171-7272-let break s right = match s.left, right with
7373-| (* GB1 *) Sot, _ -> true
7474- (* GB2 is handled by `End *)
7575-| (* GB3 *) CR, LF -> false
7676-| (* GB4 *) (CN|CR|LF), _ -> true
7777-| (* GB5 *) _, (CN|CR|LF) -> true
7878-| (* GB6 *) L, (L|V|LV|LVT) -> false
7979-| (* GB7 *) (LV|V), (V|T) -> false
8080-| (* GB8 *) (LVT|T), T -> false
8181-| (* GB9+a *) _, (EX|ZWJ|SM) -> false
8282-| (* GB9b *) PP, _ -> false
8383-| (* GB10 *) _, EM when s.emoji_seq -> false
8484-| (* GB11 *) ZWJ, (GAZ|EBG) -> false
8585-| (* GB12+13 *) RI, RI when s.odd_ri -> false
8686-| (* GB999 *) _, _ -> true
8787-8888-let update_left s right =
8989- s.left <- right;
9090- match s.left with
9191- | EX -> (* keep s.emoji_seq as is *) s.odd_ri <- false
9292- | EB | EBG -> s.emoji_seq <- true; s.odd_ri <- false
9393- | RI -> s.emoji_seq <- false; s.odd_ri <- not s.odd_ri
9494- | _ -> s.emoji_seq <- false; s.odd_ri <- false
9595-9696-let add s = function
9797-| `Uchar u as add ->
9898- begin match s.state with
9999- | Fill ->
100100- let right = gcb u in
101101- let break = break s right in
102102- update_left s right;
103103- if not break then add else
104104- (s.state <- Flush; s.buf <- add; `Boundary)
105105- | Flush | End -> assert false
106106- end
107107-| `Await ->
108108- begin match s.state with
109109- | Flush -> s.state <- Fill; (s.buf :> ret)
110110- | End -> `End
111111- | Fill -> `Await
112112- end
113113-| `End ->
114114- begin match s.state with
115115- | Fill -> s.state <- End; if s.left = Sot then `End else `Boundary
116116- | Flush | End -> assert false
117117- end
118118-119119-(*---------------------------------------------------------------------------
120120- Copyright (c) 2014 Daniel C. Bünzli
121121-122122- Permission to use, copy, modify, and/or distribute this software for any
123123- purpose with or without fee is hereby granted, provided that the above
124124- copyright notice and this permission notice appear in all copies.
125125-126126- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
127127- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
128128- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
129129- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
130130- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
131131- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
132132- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
133133- ---------------------------------------------------------------------------*)
-27
notty/src/no-uucp/notty_grapheme_cluster.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2014 Daniel C. Bünzli. All rights reserved.
33- Distributed under the ISC license, see terms at the end of the file.
44- %%NAME%% %%VERSION%%
55- ---------------------------------------------------------------------------*)
66-77-type ret = [ `Await | `Boundary | `End | `Uchar of Uchar.t ]
88-99-type t
1010-val create : unit -> t
1111-val add : t -> [ `Await | `End | `Uchar of Uchar.t ] -> ret
1212-1313-(*---------------------------------------------------------------------------
1414- Copyright (c) 2014 Daniel C. Bünzli
1515-1616- Permission to use, copy, modify, and/or distribute this software for any
1717- purpose with or without fee is hereby granted, provided that the above
1818- copyright notice and this permission notice appear in all copies.
1919-2020- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
2121- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
2222- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
2323- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
2424- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
2525- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
2626- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2727- ---------------------------------------------------------------------------*)
-48
notty/src/no-uucp/notty_uucp.ml
···11-(* Copyright (c) 2020 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(* Unpacked interval lookup table. *)
55-let find_i ~def k (xs, _, _ as tab) =
66- let rec go i j (los, his, vs as tab) (k: int) def =
77- if i > j then def else
88- let x = (i + j) / 2 in
99- if k < Array.unsafe_get los x then go i (x - 1) tab k def else
1010- if k > Array.unsafe_get his x then go (x + 1) j tab k def else
1111- Array.unsafe_get vs x in
1212- go 0 (Array.length xs - 1) tab k def
1313-1414-(* 12-6-6-bit (0xfff-0x3f-0x3f) trie, 3 levels, array-array-string.
1515- Root is variable; lower levels are either empty or complete. *)
1616-let find_t ~def k tab =
1717- let k = if k > 0xd7ff then k - 0x800 else k in (* Pack to continuous range. *)
1818- let b0 = (k lsr 12) land 0xfff in
1919- if Array.length tab <= b0 then def else
2020- match Array.unsafe_get tab b0 with
2121- | [||] -> def
2222- | arr -> match Array.unsafe_get arr ((k lsr 6) land 0x3f) with
2323- | "" -> def
2424- | str -> String.unsafe_get str (k land 0x3f) |> Char.code
2525-2626-(* We catch w = -1 and default to w = 1 to minimize the table. *)
2727-let tty_width_hint u = match Uchar.to_int u with
2828-| 0 -> 0
2929-| u when u <= 0x001F || 0x007F <= u && u <= 0x009F -> -1
3030-| u when u <= 0x02ff -> 1
3131-| u -> find_i ~def:1 u Notty_uucp_data.tty_width_hint
3232-3333-let grapheme_cluster_boundary u =
3434- find_t ~def:16 (Uchar.to_int u) Notty_uucp_data.grapheme_cluster_boundary
3535-3636-(* let check () = *)
3737-(* let pp_u ppf u = Format.fprintf ppf "u+%04x" (Uchar.to_int u) in *)
3838-(* let rec go i u = *)
3939-(* let w1 = tty_width_hint u *)
4040-(* and w2 = Uucp.Break.tty_width_hint u in *)
4141-(* if w1 <> w2 then Format.printf "w: %a here: %d there: %d@." pp_u u w1 w2; *)
4242-(* let gc1 = grapheme_cluster_boundary u *)
4343-(* and gc2 = Uucp.Break.Low.grapheme_cluster u in *)
4444-(* if gc1 <> gc2 then Format.printf "gc: %a here: %d there: %d@." pp_u u gc1 gc2; *)
4545-(* if u = Uchar.max then i else go (i + 1) (Uchar.succ u) in *)
4646-(* let n = go 1 Uchar.min in *)
4747-(* Format.printf "Checked equality for %d code points.@." n *)
4848-
-13
notty/src/no-uucp/notty_uucp.mli
···11-(* Copyright (c) 2020 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(* This is a local copy of the (very few) relevant [uucp] properties. *)
55-66-val tty_width_hint : Uchar.t -> int
77-(* [Uucp.Break.tty_width_hint]. *)
88-99-val grapheme_cluster_boundary : Uchar.t -> int
1010-(* [Uucp.Break.Low.grapheme_cluster]. *)
1111-1212-(* val check : unit -> unit *)
1313-
···11-(* Do not edit.
22- *
33- * This module contains select unicode properties extracted from Uucp,
44- * using `./support/gen_unicode_props.ml`.
55- *
66- * Unicode version 13.0.0.
77- *)
88-99-(* Uucp.Break.tty_width_hint *)
1010-val tty_width_hint: int array * int array * int array
1111-1212-(* Uucp.Break.Low.grapheme_cluster. *)
1313-val grapheme_cluster_boundary: string array array
1414-
-920
notty/src/notty.ml
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-let invalid_arg fmt = Format.kasprintf invalid_arg fmt
55-66-let (&.) f g x = f (g x)
77-88-let btw (x : int) a b = a <= x && x <= b
99-let bit n b = b land (1 lsl n) > 0
1010-1111-let max (a : int) b = if a > b then a else b
1212-let min (a : int) b = if a < b then a else b
1313-1414-let is_C0 x = (x < 0x20 || x = 0x7f) && x!= 0x09
1515-and is_C1 x = 0x80 <= x && x < 0xa0
1616-let is_ctrl x = is_C0 x || is_C1 x
1717-and is_ascii x = x < 0x80
1818-1919-let rec concatm z (@) xs =
2020- let rec accum (@) = function
2121- | []|[_] as xs -> xs
2222- | a::b::xs -> (a @ b) :: accum (@) xs in
2323- match xs with [] -> z | [x] -> x | xs -> concatm z (@) (accum (@) xs)
2424-2525-let rec linspcm z (@) x n f = match n with
2626- | 0 -> z
2727- | 1 -> f x
2828- | _ -> let m = n / 2 in linspcm z (@) x m f @ linspcm z (@) (x + m) (n - m) f
2929-3030-let memo (type a) ?(hash=Hashtbl.hash) ?(eq=(=)) ~size f =
3131- let module H = Ephemeron.K1.Make
3232- (struct type t = a let (hash, equal) = (hash, eq) end) in
3333- let t = H.create size in fun x ->
3434- try H.find t x with Not_found -> let y = f x in H.add t x y; y
3535-3636-module Buffer = struct
3737- include Buffer
3838- let buf = Buffer.create 1024
3939- let mkstring f = f buf; let res = contents buf in reset buf; res
4040- let add_decimal b = function
4141- | x when btw x 0 999 ->
4242- let d1 = x / 100 and d2 = (x mod 100) / 10 and d3 = x mod 10 in
4343- if d1 > 0 then 0x30 + d1 |> Char.unsafe_chr |> add_char b;
4444- if (d1 + d2) > 0 then 0x30 + d2 |> Char.unsafe_chr |> add_char b;
4545- 0x30 + d3 |> Char.unsafe_chr |> add_char b
4646- | x -> string_of_int x |> add_string b
4747- let add_chars b c n = for _ = 1 to n do add_char b c done
4848-end
4949-5050-module String = struct
5151- include String
5252- let sub0cp s i len = if i > 0 || len < length s then sub s i len else s
5353- let of_chars_rev = function
5454- | [] -> ""
5555- | [c] -> String.make 1 c
5656- | cs ->
5757- let n = List.length cs in
5858- let rec go bs i = Bytes.(function
5959- | [] -> unsafe_to_string bs
6060- | x::xs -> unsafe_set bs i x; go bs (pred i) xs
6161- ) in go (Bytes.create n) (n - 1) cs
6262-end
6363-6464-module Option = struct
6565-6666- let map f = function Some x -> Some (f x) | _ -> None
6767- let get def = function Some x -> x | _ -> def
6868- let to_list = function Some x -> [x] | _ -> []
6969- let (>>|) a f = map f a
7070- let (>>=) a f = match a with Some x -> f x | _ -> None
7171-end
7272-7373-module Text = struct
7474-7575- let err_ctrl u = invalid_arg "Notty: control char: U+%02X, %S" (Char.code u)
7676- let err_malformed = invalid_arg "Notty: malformed UTF-8: %s, %S"
7777-7878- type t =
7979- | Ascii of string * int * int
8080- | Utf8 of string * int array * int * int
8181-8282- let equal t1 t2 = match (t1, t2) with
8383- | (Utf8 (s1, _, i1, n1), Utf8 (s2, _, i2, n2))
8484- | (Ascii (s1, i1, n1), Ascii (s2, i2, n2)) -> i1 = i2 && n1 = n2 && s1 = s2
8585- | _ -> false
8686-8787- let width = function Utf8 (_, _, _, w) -> w | Ascii (_, _, w) -> w
8888-8989- let empty = Ascii ("", 0, 0)
9090-9191- let is_empty t = width t = 0
9292-9393- let graphemes ?(should_throw=false) str =
9494- let module Uuseg = Notty_grapheme_cluster in
9595- let seg = Uuseg.create () in
9696- let rec f (is, w as acc) i evt =
9797- match Uuseg.add seg evt with
9898- | `Await | `End -> acc
9999- | `Uchar u -> f (is, w + Notty_uucp.tty_width_hint u) i `Await
100100- | `Boundary ->
101101- let is = match w with 0 -> is | 1 -> i::is | _ -> i::(-1)::is in
102102- f (is, 0) i `Await in
103103- let acc = Uutf.String.fold_utf_8(fun acc i -> function
104104- | `Malformed err ->
105105- if should_throw then
106106- err_malformed err str
107107- else
108108- f acc i (`Uchar (Uchar.of_int 0xffd ))
109109- | `Uchar _ as u -> f acc i u
110110- ) ([0], 0) str in
111111- f acc (String.length str) `End |> fst |> List.rev |> Array.of_list (*XXX*)
112112-113113- let dead = ' '
114114-115115- let to_buffer buf = function
116116- | Ascii (s, off, w) -> Buffer.add_substring buf s off w
117117- | Utf8 (s, ix, off, w) ->
118118- let x1 = match ix.(off) with
119119- | -1 -> Buffer.add_char buf dead; ix.(off + 1) | x -> x
120120- and x2 = ix.(off + w) in
121121- Buffer.add_substring buf s x1 @@
122122- (if x2 = -1 then ix.(off + w - 1) else x2) - x1;
123123- if x2 = -1 then Buffer.add_char buf dead
124124-125125- let sub t x w =
126126- let w1 = width t in
127127- if w = 0 || x >= w1 then empty else
128128- let w = min w (w1 - x) in
129129- if w = w1 then t else match t with
130130- Ascii (s, off, _) -> Ascii (s, off + x, w)
131131- | Utf8 (s, ix, off, _) -> Utf8 (s, ix, off + x, w)
132132-133133- let is_ascii_or_raise_ctrl s =
134134- let (@!) s i = String.unsafe_get s i |> Char.code in
135135- let rec go s acc i n =
136136- if n = 0 then acc else
137137- let x = s @! i in
138138- if is_C0 x then
139139- err_ctrl s.[i] s
140140- else if x = 0xc2 && n > 1 && is_C1 (s @! (i + 1)) then
141141- err_ctrl s.[i + 1] s
142142- else go s (acc && is_ascii x) (i + 1) (n - 1) in
143143- go s true 0 (String.length s)
144144-145145- let of_ascii s = Ascii (s, 0, String.length s)
146146- and of_unicode s = let x = graphemes s in Utf8 (s, x, 0, Array.length x - 1)
147147- let of_unicode = memo ~eq:String.equal ~size:128 of_unicode
148148-149149- let of_string = function
150150- | "" -> empty
151151- | s -> if is_ascii_or_raise_ctrl s then of_ascii s else of_unicode s
152152-153153- let of_uchars ucs = of_string @@ Buffer.mkstring @@ fun buf ->
154154- Array.iter (Buffer.add_utf_8_uchar buf) ucs
155155-156156- let replicateu w u =
157157- if is_ctrl (Uchar.to_int u) then
158158- err_ctrl (Uchar.unsafe_to_char u) "<repeated character>"
159159- else if w < 1 then empty
160160- else if is_ascii (Uchar.to_int u) then
161161- of_ascii (String.make w (Uchar.unsafe_to_char u))
162162- else of_unicode @@ Buffer.mkstring @@ fun buf ->
163163- for _ = 1 to w do Buffer.add_utf_8_uchar buf u done
164164-165165- let replicatec w c = replicateu w (Uchar.of_char c)
166166-end
167167-168168-module A = struct
169169-170170- type color = int
171171- type style = int
172172- type t = { fg : color; bg : color; st : style }
173173-174174- let equal t1 t2 = t1.fg = t2.fg && t1.bg = t2.bg && t1.st = t2.st
175175- let unsafe_color_of_int int= int
176176- let unsafe_style_of_int int= int
177177-178178- let black = 0x01000000
179179- and red = 0x01000001
180180- and green = 0x01000002
181181- and yellow = 0x01000003
182182- and blue = 0x01000004
183183- and magenta = 0x01000005
184184- and cyan = 0x01000006
185185- and white = 0x01000007
186186- and lightblack = 0x01000008
187187- and lightred = 0x01000009
188188- and lightgreen = 0x0100000a
189189- and lightyellow = 0x0100000b
190190- and lightblue = 0x0100000c
191191- and lightmagenta = 0x0100000d
192192- and lightcyan = 0x0100000e
193193- and lightwhite = 0x0100000f
194194-195195- let tag c = (c land 0x03000000) lsr 24
196196-197197- let rgb ~r ~g ~b =
198198- if r < 0 || g < 0 || b < 0 || r > 5 || g > 5 || b > 5 then
199199- invalid_arg "Notty.A.rgb %d %d %d: channel out of range" r g b
200200- else 0x01000000 lor (r * 36 + g * 6 + b + 16)
201201-202202- let gray level =
203203- if level < 0 || level > 23 then
204204- invalid_arg "Notty.A.gray %d: level out of range" level
205205- else 0x01000000 lor (level + 232)
206206-207207- let rgb_888 ~r ~g ~b =
208208- if r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255 then
209209- invalid_arg "Notty.A.rgb_888 %d %d %d: channel out of range" r g b
210210- else 0x02000000 lor ((r lsl 16) lor (g lsl 8) lor b)
211211-212212- let i x = x land 0xff
213213- and r x = x lsr 16 land 0xff
214214- and g x = x lsr 8 land 0xff
215215- and b x = x land 0xff
216216-217217- let bold = 1
218218- and italic = 2
219219- and underline = 4
220220- and blink = 8
221221- and reverse = 16
222222-223223- let empty = { fg = 0; bg = 0; st = 0 }
224224-225225- let (++) a1 a2 =
226226- if a1 == empty then a2 else if a2 == empty then a1 else
227227- { fg = (match a2.fg with 0 -> a1.fg | x -> x)
228228- ; bg = (match a2.bg with 0 -> a1.bg | x -> x)
229229- ; st = a1.st lor a2.st }
230230-231231- let fg fg = { empty with fg }
232232- let bg bg = { empty with bg }
233233- let st st = { empty with st }
234234-end
235235-236236-module I = struct
237237-238238- type dim = int * int
239239-240240- type t =
241241- | Empty
242242- | Segment of A.t * Text.t
243243- | Hcompose of (t * t) * dim
244244- | Vcompose of (t * t) * dim
245245- | Zcompose of (t * t) * dim
246246- | Hcrop of (t * int * int) * dim
247247- | Vcrop of (t * int * int) * dim
248248- | Void of dim
249249-250250- let width = function
251251- | Empty -> 0
252252- | Segment (_, text) -> Text.width text
253253- | Hcompose (_, (w, _)) -> w
254254- | Vcompose (_, (w, _)) -> w
255255- | Zcompose (_, (w, _)) -> w
256256- | Hcrop (_, (w, _)) -> w
257257- | Vcrop (_, (w, _)) -> w
258258- | Void (w, _) -> w [@@inline]
259259-260260- let height = function
261261- | Empty -> 0
262262- | Segment _ -> 1
263263- | Hcompose (_, (_, h)) -> h
264264- | Vcompose (_, (_, h)) -> h
265265- | Zcompose (_, (_, h)) -> h
266266- | Hcrop (_, (_, h)) -> h
267267- | Vcrop (_, (_, h)) -> h
268268- | Void (_, h) -> h [@@inline]
269269-270270- let equal t1 t2 =
271271- let rec eq t1 t2 = match (t1, t2) with
272272- | (Empty, Empty) -> true
273273- | (Segment (a1, t1), Segment (a2, t2)) ->
274274- A.equal a1 a2 && Text.equal t1 t2
275275- | (Hcompose ((a, b), _), Hcompose ((c, d), _))
276276- | (Vcompose ((a, b), _), Vcompose ((c, d), _))
277277- | (Zcompose ((a, b), _), Zcompose ((c, d), _)) -> eq a c && eq b d
278278- | (Hcrop ((a, i1, n1), _), Hcrop ((b, i2, n2), _))
279279- | (Vcrop ((a, i1, n1), _), Vcrop ((b, i2, n2), _)) ->
280280- i1 = i2 && n1 = n2 && eq a b
281281- | (Void (a, b), Void (c, d)) -> a = c && b = d
282282- | _ -> false in
283283- width t1 = width t2 && height t1 = height t2 && eq t1 t2
284284-285285- let empty = Empty
286286-287287- let (<|>) t1 t2 = match (t1, t2) with
288288- | (_, Empty) -> t1
289289- | (Empty, _) -> t2
290290- | _ ->
291291- let w = width t1 + width t2
292292- and h = max (height t1) (height t2) in
293293- Hcompose ((t1, t2), (w, h))
294294-295295- let (<->) t1 t2 = match (t1, t2) with
296296- | (_, Empty) -> t1
297297- | (Empty, _) -> t2
298298- | _ ->
299299- let w = max (width t1) (width t2)
300300- and h = height t1 + height t2 in
301301- Vcompose ((t1, t2), (w, h))
302302-303303- let (</>) t1 t2 = match (t1, t2) with
304304- | (_, Empty) -> t1
305305- | (Empty, _) -> t2
306306- | _ ->
307307- let w = max (width t1) (width t2)
308308- and h = max (height t1) (height t2) in
309309- Zcompose ((t1, t2), (w, h))
310310-311311- let void w h =
312312- if w < 1 && h < 1 then Empty else Void (max 0 w, max 0 h)
313313-314314- let lincropinv crop void (++) init fini img =
315315- match (init >= 0, fini >= 0) with
316316- | (true, true) -> crop init fini img
317317- | (true, _ ) -> crop init 0 img ++ void (-fini)
318318- | (_ , true) -> void (-init) ++ crop 0 fini img
319319- | _ -> void (-init) ++ img ++ void (-fini)
320320-321321- let hcrop =
322322- let ctor left right img =
323323- let h = height img and w = width img - left - right in
324324- if w > 0 then Hcrop ((img, left, right), (w, h)) else void w h
325325- in lincropinv ctor (fun w -> void w 0) (<|>)
326326-327327- let vcrop =
328328- let ctor top bottom img =
329329- let w = width img and h = height img - top - bottom in
330330- if h > 0 then Vcrop ((img, top, bottom), (w, h)) else void w h
331331- in lincropinv ctor (void 0) (<->)
332332-333333- let crop ?(l=0) ?(r=0) ?(t=0) ?(b=0) img =
334334- let img = if l <> 0 || r <> 0 then hcrop l r img else img in
335335- if t <> 0 || b <> 0 then vcrop t b img else img
336336-337337- let hpad left right img = hcrop (-left) (-right) img
338338-339339- let vpad top bottom img = vcrop (-top) (-bottom) img
340340-341341- let pad ?(l=0) ?(r=0) ?(t=0) ?(b=0) img =
342342- crop ~l:(-l) ~r:(-r) ~t:(-t) ~b:(-b) img
343343-344344- let hcat = concatm empty (<|>)
345345-346346- let vcat = concatm empty (<->)
347347-348348- let zcat xs = List.fold_right (</>) xs empty
349349-350350- let text attr tx =
351351- if Text.is_empty tx then void 0 1 else Segment (attr, tx)
352352-353353- let string attr s = text attr (Text.of_string s)
354354-355355- let uchars attr a = text attr (Text.of_uchars a)
356356-357357- let tabulate m n f =
358358- let m = max m 0 and n = max n 0 in
359359- linspcm empty (<->) 0 n (fun y -> linspcm empty (<|>) 0 m (fun x -> f x y))
360360-361361- let chars ctor attr c w h =
362362- if w < 1 || h < 1 then void w h else
363363- let line = text attr (ctor w c) in tabulate 1 h (fun _ _ -> line)
364364-365365- let char = chars Text.replicatec
366366- let uchar = chars Text.replicateu
367367-368368- let hsnap ?(align=`Middle) w img =
369369- let off = width img - w in match align with
370370- | `Left -> hcrop 0 off img
371371- | `Right -> hcrop off 0 img
372372- | `Middle -> let w1 = off / 2 in hcrop w1 (off - w1) img
373373-374374- let vsnap ?(align=`Middle) h img =
375375- let off = height img - h in match align with
376376- | `Top -> vcrop 0 off img
377377- | `Bottom -> vcrop off 0 img
378378- | `Middle -> let h1 = off / 2 in vcrop h1 (off - h1) img
379379-380380- module Fmt = struct
381381-382382- open Format
383383-384384- type stag += Attr of A.t
385385-386386- let push r x = r := x :: !r
387387- let pop r = r := (match !r with _::xs -> xs | _ -> [])
388388- let top_a r = match !r with a::_ -> a | _ -> A.empty
389389-390390- let create () =
391391- let img, line, attr = ref empty, ref empty, ref [] in
392392- let fmt = formatter_of_out_functions {
393393- out_flush = (fun () ->
394394- img := !img <-> !line; line := empty; attr := [])
395395- ; out_newline = (fun () ->
396396- img := !img <-> !line; line := void 0 1)
397397- ; out_string = (fun s i n ->
398398- line := !line <|> string (top_a attr) String.(sub0cp s i n))
399399- (* Not entirely clear; either or both could be void: *)
400400- ; out_spaces = (fun w -> line := !line <|> char (top_a attr) ' ' w 1)
401401- ; out_indent = (fun w -> line := !line <|> char (top_a attr) ' ' w 1)
402402- } in
403403- pp_set_formatter_stag_functions fmt {
404404- (pp_get_formatter_stag_functions fmt ()) with
405405- mark_open_stag =
406406- (function Attr a -> push attr A.(top_a attr ++ a); "" | _ -> "")
407407- ; mark_close_stag = (fun _ -> pop attr; "") };
408408- pp_set_mark_tags fmt true;
409409- fmt, fun () -> let i = !img in img := empty; line := empty; attr := []; i
410410-411411- let ppf, reset = create ()
412412-413413- let kstrf ?(attr = A.empty) ?(w = 1000000) k format =
414414- let m = ref 0 in
415415- let f1 _ () =
416416- m := pp_get_margin ppf ();
417417- pp_set_margin ppf w;
418418- pp_open_stag ppf (Attr attr)
419419- and k _ =
420420- pp_print_flush ppf ();
421421- pp_set_margin ppf !m;
422422- reset () |> k
423423- in kfprintf k ppf ("%a" ^^ format) f1 ()
424424-425425- let strf ?attr ?w format = kstrf ?attr ?w (fun i -> i) format
426426-427427- let attr attr f fmt x =
428428- pp_open_stag fmt (Attr attr); f fmt x; pp_close_stag fmt ()
429429- end
430430-431431- let kstrf, strf, pp_attr = Fmt.(kstrf, strf, attr)
432432-end
433433-434434-module Operation = struct
435435-436436- type t =
437437- End
438438- | Skip of int * t
439439- | Text of A.t * Text.t * t
440440-441441- let skip n k = if n = 0 then k else match k with
442442- End -> End
443443- | Skip (m, k) -> Skip (m + n, k)
444444- | _ -> Skip (n, k) [@@inline]
445445-446446- let rec scan x w row i k =
447447- let open I in match i with
448448-449449- | Empty | Void _ -> skip w k
450450-451451- | Segment _ when row > 0 -> skip w k
452452- | Segment (attr, text) ->
453453- let t = Text.sub text x w in
454454- let w1 = Text.width t in
455455- let p = if w > w1 then skip (w - w1) k else k in
456456- if w1 > 0 then Text (attr, t, p) else p
457457-458458- | Hcompose ((i1, i2), _) ->
459459- let w1 = width i1
460460- and w2 = width i2 in
461461- if x >= w1 + w2 then skip w k else
462462- if x >= w1 then scan (x - w1) w row i2 k else
463463- if x + w <= w1 then scan x w row i1 k else
464464- scan x (w1 - x) row i1 @@ scan 0 (w - w1 + x) row i2 @@ k
465465-466466- | Vcompose ((i1, i2), _) ->
467467- let h1 = height i1
468468- and h2 = height i2 in
469469- if row >= h1 + h2 then skip w k else
470470- if row >= h1 then scan x w (row - h1) i2 k else scan x w row i1 k
471471-472472- | Zcompose ((i1, i2), _) ->
473473- let rec stitch x w row i = function
474474- | End -> scan x w row i End
475475- | Text (a, t, ops) as opss ->
476476- let w1 = Text.width t in
477477- if w1 >= w then opss else
478478- Text (a, t, stitch (x + w1) (w - w1) row i ops)
479479- | Skip (w1, ops) ->
480480- scan x w1 row i @@
481481- if w1 >= w then ops else stitch (x + w1) (w - w1) row i ops
482482- in stitch x w row i2 @@ scan x w row i1 @@ k
483483-484484- | Hcrop ((i, left, _), (w1, _)) ->
485485- if x >= w1 then skip w k else
486486- if x + w <= w1 then scan (x + left) w row i k else
487487- scan (x + left) (w1 - x) row i @@ skip (w - w1 + x) k
488488-489489- | Vcrop ((i, top, _), (_, h1)) ->
490490- if row < h1 then scan x w (top + row) i k else skip w k
491491-492492- let of_image (x, y) (w, h) i =
493493- List.init h (fun off -> scan x (x + w) (y + off) i End)
494494-end
495495-496496-module Cap = struct
497497-498498- type op = Buffer.t -> unit
499499-500500- let (&) op1 op2 buf = op1 buf; op2 buf
501501-502502- type t = {
503503- skip : int -> op
504504- ; sgr : A.t -> op
505505- ; newline : op
506506- ; clreol : op
507507- ; cursvis : bool -> op
508508- ; cursat : int -> int -> op
509509- ; cubcuf : int -> op
510510- ; cuucud : int -> op
511511- ; cr : op
512512- ; altscr : bool -> op
513513- ; mouse : bool -> op
514514- ; bpaste : bool -> op
515515- }
516516-517517- let ((<|), (<.), (<!)) = Buffer.(add_string, add_char, add_decimal)
518518-519519- let sts = [ ";1"; ";3"; ";4"; ";5"; ";7" ]
520520-521521- let sgr { A.fg; bg; st } buf =
522522- buf <| "\x1b[0";
523523- let rgb888 buf x =
524524- buf <! A.r x; buf <. ';'; buf <! A.g x; buf <. ';'; buf <! A.b x in
525525- ( match A.tag fg with
526526- 0 -> ()
527527- | 1 -> let c = A.i fg in
528528- if c < 8 then ( buf <. ';'; buf <! (c + 30) )
529529- else if c < 16 then ( buf <. ';'; buf <! (c + 82) )
530530- else ( buf <| ";38;5;"; buf <! c )
531531- | _ -> buf <| ";38;2;"; rgb888 buf fg );
532532- ( match A.tag bg with
533533- 0 -> ()
534534- | 1 -> let c = A.i bg in
535535- if c < 8 then ( buf <. ';'; buf <! (c + 40) )
536536- else if c < 16 then ( buf <. ';'; buf <! (c + 92) )
537537- else ( buf <| ";48;5;"; buf <! c )
538538- | _ -> buf <| ";48;2;"; rgb888 buf bg );
539539- if st <> 0 then
540540- ( let rec go f xs = match (f, xs) with
541541- | (0, _) | (_, []) -> ()
542542- | (_, x::xs) -> if f land 1 > 0 then buf <| x; go (f lsr 1) xs in
543543- go st sts );
544544- buf <. 'm'
545545-546546- let ansi = {
547547- skip = (fun n b -> b <| "\x1b[0m"; Buffer.add_chars b ' ' n)
548548- ; newline = (fun b -> b <| "\x1bE")
549549- ; altscr = (fun x b -> b <| if x then "\x1b[?1049h" else "\x1b[?1049l")
550550- ; cursat = (fun w h b -> b <| "\x1b["; b <! h; b <. ';'; b <! w; b <. 'H')
551551- ; cubcuf = (fun x b -> b <| "\x1b["; b <! abs x; b <. if x < 0 then 'D' else 'C')
552552- ; cuucud = (fun y b -> b <| "\x1b["; b <! abs y; b <. if y < 0 then 'A' else 'B')
553553- ; cr = (fun b -> b <| "\x1b[1G")
554554- ; clreol = (fun b -> b <| "\x1b[K")
555555- ; cursvis = (fun x b -> b <| if x then "\x1b[34h\x1b[?25h" else "\x1b[?25l")
556556- ; mouse = (fun x b -> b <| if x then "\x1b[?1000;1002;1005;1015;1006h"
557557- else "\x1b[?1000;1002;1005;1015;1006l")
558558- ; bpaste = (fun x b -> b <| if x then "\x1b[?2004h" else "\x1b[?2004l")
559559- ; sgr }
560560-561561- let no0 _ = ()
562562- and no1 _ _ = ()
563563- and no2 _ _ _ = ()
564564-565565- let dumb = {
566566- skip = (fun n b -> Buffer.add_chars b ' ' n)
567567- ; newline = (fun b -> b <| "\n")
568568- ; altscr = no1
569569- ; cursat = no2
570570- ; cubcuf = no1
571571- ; cuucud = no1
572572- ; cr = no0
573573- ; clreol = no0
574574- ; cursvis = no1
575575- ; sgr = no1
576576- ; mouse = no1
577577- ; bpaste = no1
578578- }
579579-580580- let erase cap buf = cap.sgr A.empty buf; cap.clreol buf (* KEEP ETA-LONG. *)
581581- let cursat0 cap w h = cap.cursat (max w 0 + 1) (max h 0 + 1)
582582-end
583583-584584-module Render = struct
585585-586586- open Cap
587587- open Operation
588588-589589- let skip_op cap buf n = cap.skip n buf
590590- let text_op cap buf a x = cap.sgr a buf; Text.to_buffer buf x
591591-592592- let rec line cap buf = function
593593- End -> erase cap buf
594594- | Skip (n, End) -> erase cap buf; skip_op cap buf n
595595- | Text (a, x, End) -> erase cap buf; text_op cap buf a x
596596- | Skip (n, ops) -> skip_op cap buf n; line cap buf ops
597597- | Text (a, x, ops) -> text_op cap buf a x; line cap buf ops
598598-599599- let rec lines cap buf = function
600600- [] -> ()
601601- | [ln] -> line cap buf ln; cap.sgr A.empty buf
602602- | ln::lns -> line cap buf ln; cap.newline buf; lines cap buf lns
603603-604604- let to_buffer buf cap off dim img =
605605- Operation.of_image off dim img |> lines cap buf
606606-607607- let pp cap ppf img =
608608- let open Format in
609609- let buf = Buffer.create (I.width img * 2) in
610610- let h, w = I.(height img, width img |> min (pp_get_margin ppf ())) in
611611- let img = I.(img </> vpad (h - 1) 0 (char A.empty ' ' w 1)) in
612612- pp_open_vbox ppf 0;
613613- for y = 0 to h - 1 do
614614- Buffer.clear buf; to_buffer buf cap (0, y) (w, 1) img;
615615- pp_print_as ppf w (Buffer.contents buf);
616616- if y < h - 1 then pp_print_cut ppf ()
617617- done;
618618- pp_close_box ppf ()
619619-620620- let pp_image = pp Cap.ansi
621621- let pp_attr ppf a =
622622- let string_ = I.string A.empty in
623623- pp_image ppf I.(string_ "<" <|> string a "ATTR" <|> string_ ">")
624624-end
625625-626626-module Unescape = struct
627627-628628- type special = [
629629- `Escape
630630- | `Enter
631631- | `Tab
632632- | `Backspace
633633- | `Insert
634634- | `Delete
635635- | `Home | `End
636636- | `Arrow of [ `Up | `Down | `Left | `Right ]
637637- | `Page of [ `Up | `Down ]
638638- | `Function of int
639639- ]
640640-641641- type button = [ `Left | `Middle | `Right | `Scroll of [ `Up | `Down ] ]
642642-643643- type mods = [ `Meta | `Ctrl | `Shift ] list
644644-645645- type key = [ special | `Uchar of Uchar.t | `ASCII of char ] * mods
646646-647647- type mouse = [ `Press of button | `Drag | `Release ] * (int * int) * mods
648648-649649- type paste = [ `Start | `End ]
650650-651651- type event = [ `Key of key | `Mouse of mouse | `Paste of paste ]
652652-653653- type esc =
654654- C0 of char
655655- | C1 of char
656656- | SS2 of char
657657- | CSI of string * int list * char
658658- | Esc_M of int * int * int
659659- | Uchar of Uchar.t
660660-661661- let uchar = function `Uchar u -> u | `ASCII c -> Uchar.of_char c
662662-663663- let csi =
664664- let open Option in
665665- let rec priv acc = function
666666- | x::xs when btw x 0x3c 0x3f -> priv (Char.unsafe_chr x::acc) xs
667667- | xs -> param (String.of_chars_rev acc) None [] xs
668668- and param prv p ps = function
669669- | x::xs when btw x 0x30 0x39 -> param prv (Some (get 0 p * 10 + x - 0x30)) ps xs
670670- | 0x3b::xs -> param prv None (get 0 p :: ps) xs
671671- | xs -> code prv (List.rev (to_list p @ ps)) xs
672672- and code prv ps = function (* Conflate two classes because urxvt... *)
673673- | x::xs when btw x 0x20 0x2f || btw x 0x40 0x7e ->
674674- Some (CSI (prv, ps, (Char.chr x)), xs)
675675- | _ -> None in
676676- priv []
677677-678678- let rec demux =
679679- let chr = Char.chr in function
680680- | 0x1b::0x5b::0x4d::a::b::c::xs -> Esc_M (a, b, c) :: demux xs
681681- | 0x1b::0x5b::xs | 0x9b::xs ->
682682- let (r, xs) = csi xs |> Option.get (C1 '\x5b', xs) in r :: demux xs
683683- | 0x1b::0x4f::x::xs | 0x8f::x::xs
684684- when is_ascii x -> SS2 (chr x) :: demux xs
685685- | 0x1b::x::xs when is_C1 (x + 0x40) -> C1 (chr x) :: demux xs
686686- | x::xs when is_C1 x -> C1 (chr (x - 0x40)) :: demux xs
687687- | x::xs when is_C0 x -> C0 (chr x) :: demux xs
688688- | x::xs -> Uchar (Uchar.unsafe_of_int x) :: demux xs
689689- | [] -> []
690690-691691- let xtrm_mod_flags = function
692692- | 2 -> Some [`Shift]
693693- | 3 -> Some [`Meta]
694694- | 4 -> Some [`Shift; `Meta]
695695- | 5 -> Some [`Ctrl]
696696- | 6 -> Some [`Shift; `Ctrl]
697697- | 7 -> Some [`Meta; `Ctrl]
698698- | 8 -> Some [`Shift; `Meta; `Ctrl]
699699- | _ -> None
700700-701701- let mods_xtrm = function
702702- | [1;p] -> xtrm_mod_flags p
703703- | [] -> Some []
704704- | _ -> None
705705-706706- let mods_rxvt = function
707707- | '~' -> Some []
708708- | '$' -> Some [`Shift]
709709- | '^' -> Some [`Ctrl]
710710- | '@' -> Some [`Ctrl; `Shift]
711711- | _ -> None
712712-713713- let mods_common ps code = match (ps, code) with
714714- | ([], '~') -> Some []
715715- | ([], c) -> mods_rxvt c
716716- | ([p], '~') -> xtrm_mod_flags p
717717- | _ -> None
718718-719719- let mouse_p p =
720720- let btn = match p land 3 with
721721- | 0 when bit 6 p -> `Scroll `Up
722722- | 0 -> `Left
723723- | 1 when bit 6 p -> `Scroll `Down
724724- | 1 -> `Middle
725725- | 2 when bit 6 p -> `ALL (* `Scroll `Left *)
726726- | 2 -> `Right
727727- | 3 when bit 6 p -> `ALL (* `Scroll `Right *)
728728- | _ -> `ALL
729729- and drag = bit 5 p
730730- and mods =
731731- (if bit 3 p then [`Meta] else []) @
732732- (if bit 4 p then [`Ctrl] else [])
733733- in (btn, drag, mods)
734734-735735- let key k mods = Some (`Key (k, mods))
736736-737737- let event_of_control_code =
738738- let open Option in function
739739- | Uchar u when Uchar.to_int u |> is_ascii ->
740740- Some (`Key (`ASCII (Uchar.unsafe_to_char u), []))
741741- | Uchar u -> Some (`Key (`Uchar u, []))
742742-743743- | C0 '\x1b' -> key `Escape []
744744- | C0 ('\b'|'\x7f') -> key `Backspace []
745745- | C0 '\n' -> key `Enter []
746746- | C0 '\t' -> key `Tab []
747747-748748- | C0 x -> key (`ASCII Char.(code x + 0x40 |> unsafe_chr)) [`Ctrl]
749749- | C1 x -> key (`ASCII x) [`Meta]
750750-751751- | CSI ("",[],'Z') -> key `Tab [`Shift]
752752-753753- | CSI ("",p,'A') -> mods_xtrm p >>= key (`Arrow `Up)
754754- | CSI ("",p,'B') -> mods_xtrm p >>= key (`Arrow `Down)
755755- | CSI ("",p,'C') -> mods_xtrm p >>= key (`Arrow `Right)
756756- | CSI ("",p,'D') -> mods_xtrm p >>= key (`Arrow `Left)
757757-758758- | CSI ("",[],'a') -> key (`Arrow `Up) [`Shift]
759759- | CSI ("",[],'b') -> key (`Arrow `Down) [`Shift]
760760- | CSI ("",[],'c') -> key (`Arrow `Right) [`Shift]
761761- | CSI ("",[],'d') -> key (`Arrow `Left) [`Shift]
762762- | SS2 ('A'|'a') -> key (`Arrow `Up) [`Ctrl]
763763- | SS2 ('B'|'b') -> key (`Arrow `Down) [`Ctrl]
764764- | SS2 ('C'|'c') -> key (`Arrow `Right) [`Ctrl]
765765- | SS2 ('D'|'d') -> key (`Arrow `Left) [`Ctrl]
766766-767767- | CSI ("",5::p,c) -> mods_common p c >>= key (`Page `Up)
768768- | CSI ("",6::p,c) -> mods_common p c >>= key (`Page `Down)
769769-770770- | CSI ("",2::p,c) -> mods_common p c >>= key `Insert
771771- | CSI ("",3::p,c) -> mods_common p c >>= key `Delete
772772-773773- | CSI ("",[4],'h') -> key `Insert []
774774- | CSI ("",[],'L') -> key `Insert [`Ctrl]
775775- | CSI ("",[],'P') -> key `Delete []
776776- | CSI ("",[],'M') -> key `Delete [`Ctrl]
777777-778778- | CSI ("",p,'H') -> mods_xtrm p >>= key `Home
779779- | CSI ("",[7|1],c) -> mods_rxvt c >>= key `Home
780780-781781- | CSI ("",p,'F') -> mods_xtrm p >>= key `End
782782- | CSI ("",[8|4],c) -> mods_rxvt c >>= key `End
783783- | CSI ("",[],'J') -> key `End [`Ctrl]
784784-785785- | SS2 ('P'..'S' as c) -> key (`Function (Char.code c - 0x4f)) []
786786-787787- | CSI ("",p,('P'..'S' as c)) ->
788788- mods_xtrm p >>= key (`Function (Char.code c - 0x4f))
789789-790790- | CSI ("",k::p,c) when btw k 11 15 || btw k 17 21 || btw k 23 26 ->
791791- mods_common p c >>= key (`Function ((k - 10) - (k - 10) / 6))
792792-793793- | CSI ("<",[p;x;y],('M'|'m' as c)) ->
794794- let (btn, drag, mods) = mouse_p p in
795795- ( match (c, btn, drag) with
796796- | ('M', (#button as b), false) -> Some (`Press b)
797797- | ('M', #button, true) -> Some `Drag
798798- | ('m', #button, false) -> Some `Release
799799- (* | ('M', `ALL , true) -> Some `Move *)
800800- | _ -> None
801801- ) >>| fun e -> `Mouse (e, (x - 1, y - 1), mods)
802802-803803- | CSI ("",[p;x;y],'M') | Esc_M (p,x,y) as evt ->
804804- let (x, y) = match evt with Esc_M _ -> x - 32, y - 32 | _ -> x, y
805805- and (btn, drag, mods) = mouse_p (p - 32) in
806806- ( match (btn, drag) with
807807- | (#button as b, false) -> Some (`Press b)
808808- | (#button , true ) -> Some `Drag
809809- | (`ALL , false) -> Some `Release
810810- (* | (`ALL , true) -> Some `Move *)
811811- | _ -> None
812812- ) >>| fun e -> `Mouse (e, (x - 1, y - 1), mods)
813813-814814- | CSI ("",[200],'~') -> Some (`Paste `Start)
815815- | CSI ("",[201],'~') -> Some (`Paste `End)
816816-817817- | CSI _ | SS2 _ -> None
818818-819819- let rec events = function
820820- | C0 '\x1b' :: cc :: ccs ->
821821- ( match event_of_control_code cc with
822822- | Some (`Key (k, mods)) -> `Key (k, `Meta :: mods) :: events ccs
823823- | Some _ -> `Key (`Escape, []) :: events (cc::ccs)
824824- | None -> events ccs )
825825- | cc::ccs -> (event_of_control_code cc |> Option.to_list) @ events ccs
826826- | [] -> []
827827-828828- let decode = events &. demux &. List.map Uchar.to_int
829829-830830- type t = (event list * bool) ref
831831-832832- let create () = ref ([], false)
833833-834834- let next t = match !t with
835835- | (#event as e::es, eof) -> t := (es, eof) ; e
836836- | ([], false) -> `Await
837837- | _ -> `End
838838-839839- let list_of_utf8 buf i l =
840840- let f cs _ = function `Uchar c -> c::cs | _ -> cs in
841841- String.sub0cp (Bytes.unsafe_to_string buf) i l
842842- |> Uutf.String.fold_utf_8 f [] |> List.rev
843843-844844- let input t buf i l = t := match !t with
845845- | (es, false) when l > 0 -> (es @ (list_of_utf8 buf i l |> decode), false)
846846- | (es, _) -> (es, true)
847847-848848- let pending t = match !t with ([], false) -> false | _ -> true
849849-end
850850-851851-module Tmachine = struct
852852-853853- open Cap
854854- (* XXX This is sad. This should be a composable, stateless transducer. *)
855855-856856- type t = {
857857- cap : Cap.t
858858- ; mutable write : Buffer.t -> unit
859859- ; mutable curs : (int * int) option
860860- ; mutable dim : (int * int)
861861- ; mutable image : I.t
862862- ; mutable dead : bool
863863- }
864864-865865- let emit t op =
866866- if t.dead then
867867- invalid_arg "Notty: use of released terminal"
868868- else t.write <- t.write & op
869869-870870- let cursor cap = function
871871- | None -> cap.cursvis false
872872- | Some (w, h) -> cap.cursvis true & cursat0 cap w h
873873-874874- let create ~mouse ~bpaste cap = {
875875- cap
876876- ; curs = None
877877- ; dim = (0, 0)
878878- ; image = I.empty
879879- ; dead = false
880880- ; write =
881881- cap.altscr true & cursor cap None & cap.mouse mouse & cap.bpaste bpaste
882882- }
883883-884884- let release t =
885885- if t.dead then false else
886886- ( emit t ( t.cap.altscr false & t.cap.cursvis true &
887887- t.cap.mouse false & t.cap.bpaste false );
888888- t.dead <- true; true )
889889-890890- let output t buf = t.write buf; t.write <- ignore
891891-892892- let refresh ({ dim; image; _ } as t) =
893893- emit t ( cursor t.cap None & cursat0 t.cap 0 0 &
894894- (fun buf -> Render.to_buffer buf t.cap (0, 0) dim image) &
895895- cursor t.cap t.curs )
896896-897897- let set_size t dim = t.dim <- dim
898898- let image t image = t.image <- image; refresh t
899899- let cursor t curs = t.curs <- curs; emit t (cursor t.cap curs)
900900-901901- let size t = t.dim
902902- let dead t = t.dead
903903-end
904904-905905-module Direct = struct
906906- let show_cursor buf cap x = cap.Cap.cursvis x buf
907907- and move_cursor buf cap cmd = match cmd with
908908- | `To (w, h) -> Cap.cursat0 cap w h buf
909909- | `Home -> cap.Cap.cr buf
910910- | `By (x, y) ->
911911- Cap.(if x <> 0 then cap.cubcuf x buf; if y <> 0 then cap.cuucud y buf)
912912-end
913913-914914-type attr = A.t
915915-type image = I.t
916916-917917-module Infix = struct
918918- let ((<->), (<|>), (</>)) = I.((<->), (<|>), (</>))
919919- let (++) = A.(++)
920920-end
-967
notty/src/notty.mli
···11-(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(** Declaring terminals.
55-66- Notty is a terminal library that revolves around construction and
77- composition of displayable images.
88-99- This module provides the core {{!I}[image]} abstraction, standalone
1010- {{!Render}rendering}, and escape sequence {{!Unescape}parsing}. It does not
1111- depend on any platform code, and does not interact with the environment.
1212- Input and output are provided by {!Notty_unix} and {!Notty_lwt}.
1313-1414- Consult the {{!basics}basics}, {{!examples}examples} and
1515- {{!limitations}limitations}.
1616-1717- {e %%VERSION%% — {{:%%PKG_HOMEPAGE%% }homepage}} *)
1818-1919-(** {1 Interface} *)
2020-2121-type attr
2222-(** Visual characteristics of displayed text. *)
2323-2424-type image
2525-(** Rectangles of styled characters. *)
2626-2727-(** [A] is for attribute.
2828-2929- Construction and composition of styling characteristics of text.
3030-3131- Consult the {{!basics}basics} for an overview. *)
3232-module A : sig
3333-3434- (** {1 Colors} *)
3535-3636- type color
3737- (** An ineffable quality of light.
3838-3939- There are three kinds of colors:
4040- {ul
4141- {- {e Core 16 colors.}
4242-4343- ANSI defines 8 color {e names}, with the actual display colors
4444- considered an implementation detail. Historically, this palette was
4545- extended with their light (sometimes {e bright} or {e high-intensity})
4646- counterparts. Their presentation is undefined too, but typically
4747- produces a brighter shade. These colors - often called the {e ANSI
4848- colors} - tend to be unpredictable, but ubiquitously supported.
4949-5050- }
5151- {- {e Extended 256-color palette.}
5252-5353- This common feature extends the palette by further 240 colors. They
5454- come in two groups:
5555-5656- {ul
5757- {- The {e color cube}, a 6*6*6 approximation to the usual 24-bit RGB
5858- color cube; and}
5959- {- the {e grayscale ramp}, containing (merely) 24 shades of gray.}}
6060-6161- XTerm was the first to support this extension. Many terminals have
6262- since cloned it, so the support is wide, but not universal.
6363-6464- As the extended colors are still palette-driven they do not have a
6565- fixed presentation, and the presentation can be changed in some
6666- terminals. Default palette tends to match {{:
6767- https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg}
6868- XTerm's}.
6969-7070- }
7171- {- {e True color}
7272-7373- A recently established convention allows directly sending 24-bit colors
7474- to the terminal. This has been adopted by a growing minority of
7575- terminals. A reasonably up-to-date status document maintained by the
7676- community can be found {{:https://gist.github.com/XVilka/8346728}here}.}}
7777-7878- Some of the technical and historical background can be found in {{:
7979- http://invisible-island.net/xterm/xterm.faq.html#problems_colors}
8080- XTerm's FAQ}.
8181-8282- {b Note} No attempt is made to remap colors depending on the terminal.
8383- Terminals might ignore, remap, or completely misinterpret unsupported
8484- colors. *)
8585-8686- (** {2:corecolors Core 16 colors}
8787-8888- The first 8 have their standard ANSI names. *)
8989- val unsafe_color_of_int:int->color
9090-9191- val black : color
9292- val red : color
9393- val green : color
9494- val yellow : color
9595- val blue : color
9696- val magenta : color
9797- val cyan : color
9898- val white : color
9999- val lightblack : color
100100- val lightred : color
101101- val lightgreen : color
102102- val lightyellow : color
103103- val lightblue : color
104104- val lightmagenta : color
105105- val lightcyan : color
106106- val lightwhite : color
107107-108108- (** {2 Extended 256-color palette} *)
109109-110110- val rgb : r:int -> g:int -> b:int -> color
111111- (** [rgb ~r:red ~g:green ~b:blue] is an extended-palette color from the color cube.
112112-113113- All three channels must be in the range [0 - 5]. XTerm default palette maps
114114- this to [0x00], [0x5f], [0x87], [0xaf], [0xd7], and [0xff] independently
115115- per channel.
116116-117117- @raise Invalid_argument if a channel is outside the range. *)
118118-119119- val gray : int -> color
120120- (** [gray level] is an extended-palette color from the grayscale ramp.
121121-122122- [level] must be in the range [0 - 23]. XTerm default palette maps this to
123123- [8 + level * 10] on all three channels.
124124-125125- @raise Invalid_argument if the [level] is outside the range. *)
126126-127127- (** {2 True Color} *)
128128-129129- val rgb_888 : r:int -> g:int -> b:int -> color
130130- (** [rgb_888 ~r:red ~g:green ~b:blue] is a 24-bit color.
131131-132132- All three channels must be in the range [0 - 255].
133133-134134- @raise Invalid_argument if a channel is outside the range. *)
135135-136136- (** {1 Text styles} *)
137137-138138- type style
139139- (** Additional text properties. *)
140140-141141- val unsafe_style_of_int: int-> style
142142-143143- val bold : style
144144- val italic : style
145145- val underline : style
146146- val blink : style
147147- val reverse : style
148148-149149- (** {1 Attribute construction and composition} *)
150150-151151- type t = attr
152152-153153- val equal : t -> t -> bool
154154-155155- val empty : attr
156156- (** [empty] is the attribute with the default foreground and background color
157157- and empty style set. *)
158158-159159- val (++) : attr -> attr -> attr
160160- (** [a1 ++ a2] is the concatenation of [a1] and [a2], the attribute that has
161161- [a2]'s foreground (resp. background), unless {e unset}, in which case it
162162- is [a1]'s, and the union of both style sets.
163163-164164- [++] is left-associative, and forms a monoid with [empty]. *)
165165-166166- val fg : color -> attr
167167- (** [fg c] is [empty] with foreground [c]. *)
168168-169169- val bg : color -> attr
170170- (** [bg c] is [empty] with background [c]. *)
171171-172172- val st : style -> attr
173173- (** [st s] is [empty] with style [s]. *)
174174-end
175175-176176-(** [I] is for image.
177177-178178- Construction and composition of images.
179179-180180- Consult the {{!basics}basics} for an overview. *)
181181-module I : sig
182182-183183- type t = image
184184-185185- val height : image -> int
186186- val width : image -> int
187187-188188- val equal : t -> t -> bool
189189- (** [equal t1 t2] is [true] iff [t1] and [t2] are constructed by the same term.
190190-191191- {b Note} This is a weak form of equality. Images that are not [equal]
192192- could still render the same. *)
193193-194194- (** {1:imgprims Primitives} *)
195195-196196- val empty : image
197197- (** [empty] is a zero-sized image. *)
198198-199199- val string : attr -> string -> image
200200- (** [string attr s] is an image containing text [s], styled with [attr].
201201-202202- @raise Invalid_argument if [string] is not a valid UTF-8 sequence, or
203203- contains {{!ctrls}control characters}. *)
204204-205205- val uchars : attr -> Uchar.t array -> image
206206- (** [uchars attr us] is an image containing text [us], styled with [attr].
207207-208208- @raise Invalid_argument if [us] contains {{!ctrls}control characters}. *)
209209-210210- val char : attr -> char -> int -> int -> image
211211- (** [char attr c w h] is a [w * h] grid of [c].
212212-213213- @raise Invalid_argument if [c] is a {{!ctrls}control character}. *)
214214-215215- val uchar : attr -> Uchar.t -> int -> int -> image
216216- (** [uchar attr u w h] is a [w * h] grid of [u].
217217-218218- @raise Invalid_argument if [u] is a {{!ctrls}control character}. *)
219219-220220- val void : int -> int -> image
221221- (** [void w h] is a [w * h] rectangle of transparent cells.
222222-223223- [void] is magical: it has geometry, but no displayable content. This is
224224- different, for example, from the space character [U+0020], which renders
225225- as a cell filled with the background color. This means that [void]
226226- interacts specially with {{!(</>)}overlays}.
227227-228228- [void 0 0 = empty].
229229- [void] with only one dimension [0] acts as a spacing element in the other
230230- dimension. Negative size is treated as [0]. *)
231231-232232- (** {1:imgcomp Image composition}
233233-234234- Three basic composition modes allow construction of more complex images
235235- from simpler ones.
236236-237237- Composition operators are left-associative and form a monoid with [void].
238238- *)
239239-240240- val (<|>) : image -> image -> image
241241- (** [i1 <|> i2] is the horizontal combination of [i1] and [i2].
242242-243243- [width (i1 <|> i2) = width i1 + width i2]
244244- [height (i1 <|> i2) = max (height i1) (height i2)]
245245-246246- Images are top-aligned. The missing region is implicitly filled with
247247- {{!void}[void]}.
248248-249249-{v
250250-[x] <|> [y] = [xy]
251251- [y] [.y]
252252-v}
253253-254254- where [.] denotes {{!void}[void]}. *)
255255-256256- val (<->) : image -> image -> image
257257- (** [i1 <-> i2] is the vertical combination of [i1] and [i2].
258258-259259- [width (i1 <-> i2) = max (width i1) (width i2)]
260260- [height (i1 <-> i2) = height i1 + height i2]
261261-262262- Images are left-aligned. The missing region is implicitly filled with
263263- {{!void}[void]}.
264264-265265-{v
266266-[xx] <-> [y] = [xx]
267267- [y.]
268268-v}
269269- *)
270270-271271- val (</>) : image -> image -> image
272272- (** [i1 </> i2] is [i1] overlaid over [i2].
273273-274274- [width (i1 </> i2) = max (width i1) (width i2)]
275275- [height (i1 </> i2) = max (height i1) (height i2)]
276276-277277- Images are top-left-aligned. In the region of their overlap, only the
278278- {{!void}[void]} cells of [i1] show fragments of [i2].
279279-280280-{v
281281-[x.x] </> [yyyy] = [xyxy]
282282-v}
283283- *)
284284-285285- (** {1:imgcrop Cropping and padding} *)
286286-287287- val hcrop : int -> int -> image -> image
288288- (** [hcrop left right i] is [i] with [left] leftmost, and [right]
289289- rightmost columns missing. If [left + right >= width i] the result is
290290- [empty].
291291-292292- If either [left] or [right] is negative, instead of being cropped, the
293293- image is padded on that side.
294294-295295- For example:
296296- {ul
297297- {- [hcrop 0 1 [abc]] = [[ab]]}
298298- {- [hcrop 1 1 [abc]] = [[b]]}
299299- {- [hcrop (-1) 1 [abc]] = [void 1 1 <|> hcrop 0 1 [abc]] = [[.ab]]}
300300- {- [hcrop 2 2 [abc]] = [empty]}} *)
301301-302302- val vcrop : int -> int -> image -> image
303303- (** [vcrop top bottom i] is the vertical analogue to {{!hcrop}[hcrop]}. *)
304304-305305- val crop : ?l:int -> ?r:int -> ?t:int -> ?b:int -> image -> image
306306- (** [crop ~l:left ~r:right ~t:top ~b:bottom i] is
307307- [vcrop left right (hcrop top bottom) i].
308308-309309- Missing arguments default to [0]. *)
310310-311311- val hpad : int -> int -> image -> image
312312- (** {{!hcrop}[hcrop]} with margins negated. *)
313313-314314- val vpad : int -> int -> image -> image
315315- (** {{!vcrop}[vcrop]} with margins negated. *)
316316-317317- val pad : ?l:int -> ?r:int -> ?t:int -> ?b:int -> image -> image
318318- (** {{!crop}[crop]} with margins negated. *)
319319-320320-321321- (** {1 Additional combinators} *)
322322-323323- val hcat : image list -> image
324324- (** [hcat xs] horizontally concatenates [xs]. See {{!(<|>)}beside}. *)
325325-326326- val vcat : image list -> image
327327- (** [vcat xs] vertically concatenates [xs]. See {{!(<->)}above}. *)
328328-329329- val zcat : image list -> image
330330- (** [zcat xs] overlays [xs]. See {{!(</>)}over}. *)
331331-332332- val tabulate : int -> int -> (int -> int -> image) -> image
333333- (** [tabulate m n f] is the grid of values [f x y] with [x = 0..m-1]
334334- and [y = 0..n-1], where [x] grows to the right, and [y] growns down.
335335-336336- [f a y] is to the left of [f b y] if [a < b], and [f x a] is above [f x b]
337337- if [a < b], but the exact alignment is unspecified if the various [f x y]
338338- have different dimensions. *)
339339-340340- val hsnap : ?align:[ `Left | `Middle | `Right ] -> int -> image -> image
341341- (** [hsnap ~align w i] is an image of width strictly [w] obtained by either
342342- horizontally padding or cropping [i] and positioning it according to
343343- [~align].
344344-345345- [~align] defaults to [`Middle]. *)
346346-347347- val vsnap : ?align:[ `Top | `Middle | `Bottom ] -> int -> image -> image
348348- (** [vsnap ~align h i] is an image of height strictly [h] obtained by either
349349- vertically padding or cropping [i] and positioning it according to
350350- [~align].
351351-352352- [~align] defaults to [`Middle]. *)
353353-354354- (** {1 [Format] interoperability} *)
355355-356356- val strf : ?attr:attr -> ?w:int -> ('a, Format.formatter, unit, image) format4 -> 'a
357357- (** [strf ?attr ?w:width format ...] pretty-prints like
358358- [Format.asprintf format ...], but returns an [image].
359359-360360- [attr] is the (outermost) attribute. Defaults to {!A.empty}.
361361-362362- [width] is used to set the margin on the formatter. This is only a hint,
363363- and does not guarantee the width of the result. Consult
364364- {{: http://caml.inria.fr/pub/docs/manual-ocaml/libref/Format.html#VALset_margin}
365365- [Format.set_margin]} for details. Defaults to an unspecified, large
366366- number.
367367-368368- @raise Invalid_argument if the printing process attempts to directly
369369- output {{!ctrls}control characters}, by embedding them in [format] or a
370370- string printed with the [%s] conversion, for example.
371371- {{: http://caml.inria.fr/pub/docs/manual-ocaml/libref/Format.html#fpp}
372372- Formatted printing} is allowed. *)
373373-374374- val kstrf : ?attr:attr -> ?w:int -> (image -> 'a) -> ('b, Format.formatter, unit, 'a) format4 -> 'b
375375- (** [kstrf ?attr ?w k format ...] is continuation-based [strf ?attr ?w format ...]. *)
376376-377377- val pp_attr : attr -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit
378378- (** [pp_attr a f] is a pretty-printer like [f], except its output is styled
379379- with [a]. This applies only outside of any styling [f] itself might embed. *)
380380-end
381381-382382-(** Operators, repeated. *)
383383-module Infix : sig
384384-385385- (** {2 [I]}
386386-387387- See {{!I}[I]}. *)
388388-389389- val (<->) : image -> image -> image
390390- val (<|>) : image -> image -> image
391391- val (</>) : image -> image -> image
392392-393393- (** {2 [A]}
394394-395395- See {{!A}[A]}. *)
396396-397397- val (++) : attr -> attr -> attr
398398-end
399399-400400-(** {1 Low-level interface}
401401-402402- You can ignore it, unless you are porting [Notty] to a new platform not
403403- supported by the existing IO backends. *)
404404-405405-(** Terminal capabilities.
406406-407407- This module describes how to output things so that a terminal understands
408408- them. *)
409409-module Cap : sig
410410-411411- type t
412412- (** A set of capabilities that distinguish terminals from one another.
413413-414414- A bundle of magic strings, really. *)
415415-416416- val ansi : t
417417- (** The usual ANSI terminal, with colors, text styles and cursor
418418- positioning. *)
419419-420420- val dumb : t
421421- (** Pure text output. Text attributes are stripped and positioning is done
422422- with the character [U+0020], SPACE. *)
423423-end
424424-425425-(** Dump images to string buffers. *)
426426-module Render : sig
427427-428428- val to_buffer : Buffer.t -> Cap.t -> int * int -> int * int -> image -> unit
429429- (** [to_buffer buf cap (x, y) (w, h) i] writes the string representation of
430430- [i] to [buf], as interpreted by [cap].
431431-432432- It renders the [w * h] rectangle of [i], offset by [(x, y)] from the top
433433- left. *)
434434-435435- val pp : Cap.t -> Format.formatter -> image -> unit
436436- (** [pp cap ppf i] renders [i] to the pretty-printer [ppf].
437437-438438- {b Note} [pp] is generally meant for development and debugging. It tries
439439- to be reasonable, but dedicated IO modules handle the actual output
440440- better. *)
441441-442442- (**/**)
443443- (* Toplevel. *)
444444- val pp_image : Format.formatter -> image -> unit
445445- val pp_attr : Format.formatter -> attr -> unit
446446- (**/**)
447447-end
448448-449449-(** Parse and decode escape sequences in character streams. *)
450450-module Unescape : sig
451451-452452- (** {1 Input events} *)
453453-454454- type special = [
455455- `Escape
456456- | `Enter
457457- | `Tab
458458- | `Backspace
459459- | `Insert
460460- | `Delete
461461- | `Home | `End
462462- | `Arrow of [ `Up | `Down | `Left | `Right ]
463463- | `Page of [ `Up | `Down ]
464464- | `Function of int
465465- ]
466466- (** A selection of extra keys on the keyboard. *)
467467-468468- type button = [ `Left | `Middle | `Right | `Scroll of [ `Up | `Down ] ]
469469- (** Mouse buttons. *)
470470-471471- type mods = [ `Meta | `Ctrl | `Shift ] list
472472- (** Modifier state. *)
473473-474474- type key = [ special | `Uchar of Uchar.t | `ASCII of char ] * mods
475475- (** Keypress event. *)
476476-477477- type mouse = [ `Press of button | `Drag | `Release ] * (int * int) * mods
478478- (** Mouse event. *)
479479-480480- type paste = [ `Start | `End ]
481481- (** Paste event. *)
482482-483483- type event = [ `Key of key | `Mouse of mouse | `Paste of paste ]
484484- (** Things that terminals say to applications.
485485-486486- {ul
487487- {- [`Key (k, mods)] is keyboard input.
488488-489489- [k] is a {{!key}key}, one of:
490490- {ul
491491- {- [`ASCII c] where [c] is a [char] in the
492492- {{: https://tools.ietf.org/html/rfc20}ASCII} range;}
493493- {- [`Uchar u] where [u] is any other {{!Uchar.t}unicode character}; or}
494494- {- a {{!special}special key}.}}
495495-496496- [`ASCII] and [`Uchar] together represent the textual part of the input.
497497- These characters are guaranteed not to be {{!ctrls}control
498498- characters}, and are safe to use when constructing images. ASCII is
499499- separated from the rest of Unicode for convenient pattern-matching.
500500-501501- [mods] are the extra {{!mods}modifier keys}.
502502-503503- }
504504- {- [`Mouse (event, (x, y), mods)] is mouse input.
505505-506506- [event] is the actual mouse event: {{!button}[button]} press, release,
507507- or motion of the mouse with buttons depressed.
508508-509509- [(x, y)] are column and row position of the mouse. The origin is
510510- [(0,0)], the upper-left corner.
511511-512512- {b Note} Every [`Press (`Left|`Middle|`Right)] generates a corresponding
513513- [`Release], but there is no portable way to detect which button was
514514- released. [`Scroll (`Up|`Down)] presses are not followed by releases.
515515-516516- }
517517- {- [`Paste (`Start|`End)] are {e bracketed paste} events, signalling the
518518- beginning and end of a sequence of events pasted into the terminal.
519519-520520- {b Note} This mechanism is useful, but not reliable. The pasted text
521521- could contain spurious start-of-paste or end-of-paste markers, or they
522522- could be entered by hand. }}
523523-524524- Terminal input protocols are historical cruft, and heavily overload the
525525- ASCII range. For instance:
526526- {ul
527527- {- It is impossible to distinguish lower- and upper-case ASCII characters
528528- if {b Ctrl} is pressed;}
529529- {- several combinations of key-presses are aliased as special keys; and}
530530- {- in a UTF-8 encoded stream, there is no representation for non-ASCII
531531- characters with modifier keys.}}
532532-533533- This means that many values that inhabit the [event] type are impossible,
534534- while some reflect multiple different user actions. Limitations include:
535535-536536- {ul
537537- {- [`Shift] is reported only with special keys, and not all of them.}
538538- {- [`Meta] and [`Control] are reported with mouse events, key events with
539539- special keys, and key events with values in the ranges [0x40-0x5f]
540540- ([@] to [_]) and [0x60-0x7e] ([`] to [~]). If {b Ctrl} is pressed, the higher
541541- range is mapped into the lower range.}
542542- {- Terminals will variously under-report modifier key state.}}
543543-544544- Perform own experiments before relying on elaborate key combinations. *)
545545-546546- val uchar : [< `Uchar of Uchar.t | `ASCII of char ] -> Uchar.t
547547- (** [uchar x] is the {!Uchar.t} corresponding to [x]. This operations merges
548548- the ASCII and Unicode variants of {{!key}key}. *)
549549-550550- (** {1 Decoding filter}
551551-552552- Simple IO-less terminal input processor. It can be used for building
553553- custom terminal input abstractions. *)
554554-555555- type t
556556- (** Input decoding filter.
557557-558558- The filter should be {{!input}fed} strings, which it first decodes from
559559- UTF-8, and then extracts the input events.
560560-561561- Malformed UTF-8 input bytes and unrecognized escape sequences are silently
562562- discarded. *)
563563-564564- val create : unit -> t
565565- (** [create ()] is a new, empty filter. *)
566566-567567- val input : t -> bytes -> int -> int -> unit
568568- (** [input t buffer i len] feeds [len] bytes of [string] into [t], starting
569569- from position [len].
570570-571571- [len = 0] signals the end of input.
572572-573573- [buffer] is immediately processed and can be reused after the call
574574- returns. *)
575575-576576- val next : t -> [ event | `Await | `End ]
577577- (** [next t] is the next event in the filter's input stream:
578578-579579- {ul
580580- {- [#event], an input {{!event}[event]}.}
581581- {- [`Await] if the filter needs more {{!input}input}.}
582582- {- [`End] if the input had ended.}} *)
583583-584584- val pending : t -> bool
585585- (** [pending t] is [true] if a call to [next], without any intervening input,
586586- would {e not} return [`Await]. *)
587587-588588- (** {1 Low-level parsing}
589589-590590- {b Warning} The parsing interface is subject to change.
591591-592592- Implementation of small parts of
593593- {{: http://www.ecma-international.org/publications/standards/Ecma-035.htm}ECMA-35}
594594- and
595595- {{: http://www.ecma-international.org/publications/standards/Ecma-048.htm}ECMA-48},
596596- as needed by terminal emulators in common use. *)
597597-598598- val decode : Uchar.t list -> event list
599599- (** [decode us] are the events encoded by [us].
600600-601601- [us] are assumed to have been generated in a burst, and the end of the
602602- list is taken to mean a pause.
603603- Therefore, [decode us1 @ decode us2 <> decode (us1 @ us2)] if [us1] ends
604604- with a partial escape sequence, including a lone [\x1b].
605605-606606- Unsupported escape sequences are silently discarded. *)
607607-end
608608-609609-(**/**)
610610-(** {1 Private}
611611-612612- These are private interfaces, prone to breakage. Don't use them. *)
613613-614614-module Operation : sig
615615- type t
616616- val of_image : (int * int) -> int * int -> image -> t list
617617-end
618618-619619-module Tmachine : sig
620620-621621- type t
622622-623623- val create : mouse:bool -> bpaste:bool -> Cap.t -> t
624624- val release : t -> bool
625625- val output : t -> Buffer.t -> unit
626626-627627- val refresh : t -> unit
628628- val cursor : t -> (int * int) option -> unit
629629- val image : t -> image -> unit
630630-631631- val set_size : t -> int * int -> unit
632632-633633- val size : t -> int * int
634634- val dead : t -> bool
635635-end
636636-637637-module Direct : sig
638638- val move_cursor : Buffer.t -> Cap.t -> [ `Home | `By of int * int | `To of int * int ] -> unit
639639- val show_cursor : Buffer.t -> Cap.t -> bool -> unit
640640-end
641641-(**/**)
642642-643643-(** {1:basics Basics}
644644-645645- Print a red-on-black ["Wow!"] above its right-shifted copy:
646646-{[
647647-let wow = I.string A.(fg red ++ bg black) "Wow!" in
648648-I.(wow <-> (void 2 0 <|> wow)) |> Notty_unix.output_image
649649-]}
650650-651651- {2:meaning The meaning of images}
652652-653653- An {{!image}[image]} value is a rectangle of styled character cells. It has a
654654- width and height, but is not anchored to an origin. A single character with
655655- associated display attributes, or a short fragment of text, are simple
656656- examples of images.
657657-658658- Images are created by combining text fragments with {{!attributes}display
659659- attributes}, and composed by placing them {{!I.(<|>)}beside} each other,
660660- {{!I.(<->)}above} each other, and {{!I.(</>)}over} each other.
661661-662662- Once constructed, an image can be rendered, and only at that point it obtains
663663- absolute placement.
664664-665665- Consult {{!I}[I]} for more details.
666666-667667- {2:attributes Display attributes}
668668-669669- {{!attr}[attr]} values describe the styling characteristics of fragments of
670670- text.
671671-672672- They combine a foreground and a background {{!A.color}[color]} with a
673673- set of {{!A.style}[styles]}. Either color can be {e unset}, which corresponds to
674674- the terminal's default foreground (resp. background) color.
675675-676676- Attributes are used to construct primitive images.
677677-678678- Consult {{!A}[A]} for more details.
679679-680680- {2:ctrls Control characters}
681681-682682- These are taken to be characters in the ranges [0x00-0x1f] ({b C0}), [0x7f]
683683- (BACKSPACE), [0x80-0x9f] ({b C1}). This is the
684684- {{: http://unicode.org/reports/tr44/#General_Category_Values}Unicode
685685- general category} {b Cc}.
686686-687687- As control characters directly influence the cursor positioning, they
688688- cannot be used to create images.
689689-690690- This, in particular, means that images cannot contain [U+000a] (NEWLINE).
691691-692692- {1:limitations Limitations}
693693-694694- [Notty] does not use Terminfo. If your terminal is particularly
695695- idiosyncratic, things might fail to work. Get in touch with the author to
696696- expand support.
697697-698698- [Notty] assumes that the terminal is using UTF-8 for input and output.
699699- Things might break arbitrarily if this is not the case.
700700-701701- For performance considerations, consult the {{!perf}performance model}.
702702-703703- {2:cwidth Unicode vs. Text geometry}
704704-705705- [Notty] uses [Uucp.Break.tty_width_hint] to guess the width of text
706706- fragments when computing geometry, and it suffers from the same
707707- shortcomings:
708708-709709- {ul
710710- {- Geometry in general works for alphabets and east Asian scripts, mostly
711711- works for abjad scripts, and is a matter of luck for abugidas.}
712712- {- East Asian scripts work better when in
713713- {{:http://unicode.org/glossary/#normalization_form_c}NFC}.}
714714- {- For proper emoji display, [Uucp] and the terminal have to agree on the
715715- Unicode version.}}
716716-717717- When in doubt, see
718718- {{: http://erratique.ch/software/uucp/doc/Uucp.Break.html#VALtty_width_hint}
719719- [Uucp.Break.tty_width_hint]}.
720720-721721- Unicode has special interaction with {{!I.hcrop}horizontal cropping}:
722722- {ul
723723- {- Strings within images are cropped at {{:
724724- http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries}grapheme
725725- cluster} boundaries. This means that scalar value sequences that are
726726- rendered combined, or overlaid, stay unbroken.}
727727- {- When a crop splits a wide character in two, the remaining half is
728728- replaced by [U+0020] (SPACE). Hence, character-cell-accurate cropping is
729729- possible even in the presence of characters that horizontally occupy
730730- more than one cell.}}
731731-732732- {1:examples Examples}
733733-734734- We assume a toplevel with [Notty] support ([#require "notty.top"]).
735735-736736- {2 Hello}
737737-738738- ["Rad!"] with default foreground and background:
739739-740740- {[I.string A.empty "Rad!"]}
741741-742742- Everything has to start somewhere.
743743-744744- {2 Colors}
745745-746746- ["Rad!"] in rad letters:
747747-748748- {[I.string A.(fg lightred) "Rad!"]}
749749-750750- {2 Padding and spacing}
751751-752752-{[
753753-let a1 = A.(fg lightwhite ++ bg red)
754754-and a2 = A.(fg red)
755755-]}
756756-757757- ["Rad"] and [" stuff!"] in different colors:
758758-759759- {[I.(string a1 "Rad" <|> string a2 " stuff!")]}
760760-761761- The second word hanging on a line below:
762762-763763- {[I.(string a1 "Rad" <|> (string a2 "stuff!" |> vpad 1 0))]}
764764-765765- {2 More geometry}
766766-767767- Sierpinski triangle:
768768-769769-{[
770770-let square = "\xe2\x96\xaa"
771771-772772-let rec sierp n =
773773- if n > 1 then
774774- let ss = sierp (pred n) in I.(ss <-> (ss <|> ss))
775775- else I.(string A.(fg magenta) square |> hpad 1 0)
776776-]}
777777-778778- {[sierp 8]}
779779-780780- A triangle overlaid over its shifted copy:
781781-782782- {[let s = sierp 6 in I.(s </> vpad 1 0 s)]}
783783-784784- Blinkenlights:
785785-786786-{[
787787-let rad n color =
788788- let a1 = A.fg color in
789789- let a2 = A.(st blink ++ a1) in
790790- I.((string a2 "Rad" |> hpad n 0) <->
791791- (string a1 "(⌐■_■)" |> hpad (n + 7) 0))
792792-793793-let colors = A.[red; green; yellow; blue; magenta; cyan]
794794-]}
795795-796796-{[
797797-colors |> List.mapi I.(fun i c -> rad i c |> pad ~t:i ~l:(2 * i))
798798- |> I.zcat
799799-]}
800800-801801- {b Note} Usage of {{!A.blink}[blink]} might be regulated by law in some
802802- jurisdictions.
803803-804804- {2 Pretty-printing}
805805-806806- Images can be pretty-printed into:
807807-808808- {[I.strf "(%d)" 42]}
809809-810810- Attributes can be applied to the entire format string, or by decorating
811811- {e user-defined printers} that are supplied with [%a] conversions:
812812-813813- {[let pp = Format.pp_print_int]}
814814-815815- {[I.strf ~attr:A.(fg lightwhite) "(%a)" (I.pp_attr A.(fg green) pp) 42]}
816816-817817- {2 Now with output}
818818-819819- The core module has no real IO. Examples above are simple [image]-valued
820820- expressions, displayed by the pretty-printer that is installed by the
821821- toplevel support. Self-contained programs need a separate IO module:
822822-823823- {[#require "notty.unix"]}
824824-825825- {[sierp 8 |> Notty_unix.output_image]}
826826-827827- (Note the difference in cropping behavior.)
828828-829829- Computations can be adapted to the current terminal size. A line can stretch
830830- end-to-end:
831831-832832-{[
833833-Notty_unix.output_image_size @@ fun (w, _) ->
834834- let i1 = I.string A.(fg green) "very"
835835- and i2 = I.string A.(fg yellow) "melon" in
836836- I.(i1 <|> void (w - width i1 - width i2) 1 <|> i2)
837837-]}
838838-839839- The largest triangle that horizontally fits into the terminal:
840840-841841-{[
842842-Notty_unix.output_image_size @@ fun (w, _) ->
843843- let steps = int_of_float ((log (float w)) /. log 2.) in
844844- sierp steps |> I.vpad 0 1
845845-]}
846846-847847- {2 Simple interaction}
848848-849849- Interactive Sierpinski:
850850-851851- {[open Notty_unix]}
852852-853853-{[
854854-let img (double, n) =
855855- let s = sierp n in
856856- if double then I.(s </> vpad 1 0 s) else s
857857-in
858858-let rec update t state = Term.image t (img state); loop t state
859859-and loop t (double, n as state) =
860860- match Term.event t with
861861- | `Key (`Enter,_) -> ()
862862- | `Key (`Arrow `Left,_) -> update t (double, max 1 (n - 1))
863863- | `Key (`Arrow `Right,_) -> update t (double, min 8 (n + 1))
864864- | `Key (`ASCII ' ', _) -> update t (not double, n)
865865- | `Resize _ -> update t state
866866- | _ -> loop t state
867867-in
868868-let t = Term.create ()
869869-in
870870-update t (false, 1); Term.release t
871871-]}
872872-873873- The program uses a fullscreen {{!Notty_unix.Term}terminal} and loops reading
874874- the {{!Notty_unix.Term.event}input}. LEFT and RIGHT control the iteration
875875- count, and SPACE toggles double-drawing. Resizing the window causes a
876876- redraw. When the loop exits on ENTER, the terminal is
877877- {{!Notty_unix.Term.release}cleaned up}.
878878-879879- {1:perf Performance model}
880880-881881- This section is only relevant if using [Notty] becomes your bottleneck.
882882-883883- {b TL;DR} Shared sub-expressions do not share work, so operators stick with
884884- you.
885885-886886- The main performance parameter is {e image complexity}. This roughly
887887- corresponds to the number of image {{!I.imgcomp}composition} and
888888- {{!I.imgcrop}cropping} operators in the fully expanded [image] term,
889889- {b ignoring all sharing}.
890890-891891- Outline numbers:
892892-893893- {ul
894894- {- Highly complex images can be rendered and pushed out to a full-screen
895895- terminal more than 1000 times per second.}
896896- {- With more realistic images, this number is closer to 30,000.}
897897- {- Input processing is somewhere around 50MB/s.}}
898898-899899-900900- Image complexity [cplx] of an image [i] is:
901901- {ul
902902- {- For a {{!I.imgprims}primitive} [i], [cplx i = 1].}
903903- {- For a {{!I.imgcomp}composition} operator [op],
904904- [cplx (op i1 i2) = 1 + cplx i1 + cplx i2].}
905905- {- For a {{!I.imgcomp}crop} [cr],
906906- [cplx (cr i1) = 1 + cplx i1 - k], where [k] is the combined complexity of
907907- all the {e maximal} sub-terms that do not contribute to the output.}}
908908-909909- For example (assuming an image [i]):
910910-911911-{[
912912- let img1 = I.((i <|> i) <-> (i <|> i))
913913- let img2 = I.(let x = i <|> i in x <-> x)
914914- let img3 = I.(((i <|> i) <|> i) <|> i)
915915-]}
916916-917917- Complexity of each of these is [4 * cplx i + 3]. This might be surprising
918918- for [img2].
919919-920920- If [width i = 1], [cplx (hcrop 1 0 img1) = 3 + 2 * cplx i], and
921921- [cplx (hcrop 2 0 img3) = 2 + 2 * cplx i].
922922-923923- While [Notty] strives to be accommodating to all usage scenarios, these are
924924- the things to keep in mind if the rendering becomes slow:
925925-926926- {ol
927927- {- Image composition is cheap.
928928-929929- Combining images performs a negligible amount of computation.
930930-931931- Constructing primitive images that contain scalar values outside of the
932932- ASCII range does a little more work upfront and is worth holding onto.
933933-934934- }
935935- {- {{!Render}Rendering} depends on image complexity.
936936-937937- As a consequence, this real-world example of wrapping renders in time
938938- O(n{^ 2}) in the number of lines:
939939-940940-{[
941941-let wrap1 width img =
942942- let rec go img = img ::
943943- if I.width img > width then go (I.hcrop width 0 img) else []
944944- in go img |> I.vcat |> I.hsnap ~align:`Left width
945945-]}
946946-947947- Although [crop] is applied only [lines] times, the image complexity of
948948- each line depends on the number of preceding lines.
949949-950950- An O(n) version does not iterate [crop]:
951951-952952-{[
953953-let wrap2 width img =
954954- let rec go off = I.hcrop off 0 img ::
955955- if I.width img - off > width then go (off + width) else []
956956- in go 0 |> I.vcat |> I.hsnap ~align:`Left width
957957-]}
958958- }
959959- {- Rendering depends on the {e output} dimensions, but not on the {e image}
960960- dimensions.
961961-962962- Rendering an image to [w * h] implicitly crops it to its leftmost [w]
963963- columns and topmost [h] rows. While [w] and [h] will have an impact on
964964- the rendering performance, the complexity of the (cropped) image tends to
965965- be more important.}}
966966-967967-*)
-11
notty/src/notty_top.ml
···11-(* Copyright (c) 2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-(* Force linking with Notty for e.g. Omod. *)
55-let _ = Sys.opaque_identity Notty.I.empty
66-77-#if OCAML_VERSION >= (4,14,0)
88-let _ = Toploop.use_silently Format.err_formatter (Toploop.File "notty_top_init.ml")
99-#else
1010-let _ = Toploop.use_silently Format.err_formatter "notty_top_init.ml"
1111-#endif
-7
notty/src/notty_top_init.ml
···11-(* Copyright (c) 2017 David Kaloper Meršinjak. All rights reserved.
22- See LICENSE.md. *)
33-44-open Notty;;
55-66-#install_printer Notty.Render.pp_image;;
77-#install_printer Notty.Render.pp_attr;;
-146
notty/support/gen_unicode_props.ml
···11-#!/usr/bin/env ocaml
22-(* Copyright (c) 2020 David Kaloper Meršinjak. All rights reserved.
33- See LICENSE.md. *)
44-55-#use "topfind"
66-#require "uucp"
77-88-let filter p seq i = seq (fun x -> if p x then i x)
99-let map f seq i = seq (fun x -> i (f x))
1010-let uchars it =
1111- let rec go it u = it u; go it (Uchar.succ u) in
1212- try go it Uchar.min with Invalid_argument _ -> ()
1313-let to_list seq =
1414- let xs = ref [] in
1515- seq (fun x -> xs := x :: !xs);
1616- List.rev !xs
1717-1818-let intervals_kv seq i =
1919- let s = ref None in
2020- let f (x, v) = match !s with
2121- | None -> s := Some (x, x, v)
2222- | Some (a, b, v0) when v = v0 && x = Uchar.succ b -> s := Some (a, x, v0)
2323- | Some e -> i e; s := Some (x, x, v) in
2424- seq f;
2525- match !s with Some e -> i e | _ -> ()
2626-2727-let intervals_p seq =
2828- map (fun x -> x, ()) seq |> intervals_kv |> map (fun (a, b, _) -> a, b)
2929-3030-(* Condenses code points into continuous range. *)
3131-let pack_u u = let i = Uchar.to_int u in if i > 0xd7ff then i - 0x800 else i
3232-let unpack_u i = Uchar.of_int (if i < 0xd800 then i else i + 0x800)
3333-3434-(* 12-6-6-bit (0xfff-0x3f-0x3f) trie, 3 levels, array-array-string.
3535- Root is variable; lower levels are either empty or complete.
3636-3737- At the moment, packed Uchar.max is 0x10f7ff; this can map up to 0xffffff
3838- distinct code points. *)
3939-let trie ~default f =
4040- let xs = List.init ((pack_u Uchar.max lsr 12) + 1) @@ fun b0 ->
4141- let mask = b0 lsl 12 in
4242- let arr = Array.init 0x40 @@ fun b1 ->
4343- let mask = mask lor (b1 lsl 6) in
4444- let v b2 = match unpack_u (mask lor b2) with
4545- | x -> f x
4646- | exception Invalid_argument _ -> default in
4747- match (for b2 = 0 to 0x3f do if v b2 <> default then raise Exit done) with
4848- | exception Exit -> String.init 0x40 (fun b2 -> Char.chr (v b2))
4949- | () -> ""
5050- in
5151- if Array.for_all ((=) "") arr then [||] else arr
5252- in
5353- let rec trim = function [||]::xs -> trim xs | xs -> xs in
5454- List.rev (trim (List.rev xs)) |> Array.of_list
5555-5656-let pf = Format.fprintf
5757-let strf = Format.sprintf
5858-let pp_iter ?(sep = fun _ _ -> ()) iter pp ppf x =
5959- let fst = ref true in
6060- let f x = (if !fst then fst := false else sep ppf ()); pp ppf x in
6161- iter f x
6262-let pp_u ppf u = pf ppf "0x%04x" (Uchar.to_int u)
6363-let pp_as_array iter pp ppf x =
6464- let sep ppf () = pf ppf ";@ " in
6565- pf ppf "@[<2>[|%a|]@]" (pp_iter ~sep iter pp) x
6666-6767-let intern ppf_ml iter =
6868- let t = Hashtbl.create 16 in
6969- let n = ref 0 in
7070- iter (fun s -> if not (Hashtbl.mem t s) then begin
7171- let name = strf "s%03d" !n in
7272- Hashtbl.add t s name; incr n;
7373- pf ppf_ml "let %s = %S@." name s
7474- end);
7575- pf ppf_ml "@.";
7676- (fun ppf s -> match Hashtbl.find_opt t s with
7777- | Some name -> pf ppf "%s" name
7878- | None -> pf ppf "%S" s)
7979-8080-let dump_interval_map (ppf_mli, ppf_ml) ~name ~desc seq =
8181- pf ppf_mli "(* %s *)@.val %s: int array * int array * int array@.@." desc name;
8282- let xs = to_list (intervals_kv seq) in
8383- let aa = List.map (fun (a, _, _) -> a) xs
8484- and bb = List.map (fun (_, b, _) -> b) xs
8585- and cc = List.map (fun (_, _, c) -> c) xs in
8686- let pp_arr pp = pp_as_array List.iter pp in
8787- let pp_arr_u = pp_arr pp_u and pp_arr_i = pp_arr Format.pp_print_int in
8888- pf ppf_ml "@[<2>let %s =@ @[<1>(%a,@ %a,@ %a)@]@]@.@."
8989- name pp_arr_u aa pp_arr_u bb pp_arr_i cc
9090-9191-let dump_trie_map (ppf_mli, ppf_ml) ~name ~desc ~default f =
9292- pf ppf_mli "(* %s *)@.val %s: string array array@.@." desc name;
9393- let xs = trie ~default f in
9494- let pp_s = intern ppf_ml Array.(fun i -> i ""; iter (iter i) xs) in
9595- pf ppf_ml "@[<2>let %s =@ %a@]" name
9696- Array.(pp_as_array iter (pp_as_array iter pp_s)) xs
9797-9898-let pp_header ppf = Format.fprintf ppf
9999-"(* Do not edit.
100100- *
101101- * This module contains select unicode properties extracted from Uucp,
102102- * using `%s`.
103103- *
104104- * Unicode version %s.
105105- *)
106106-107107-" Sys.argv.(0) Uucp.unicode_version
108108-109109-let extract (ppmli, ppml as ppfs) =
110110-111111- pp_header ppmli; pp_header ppml;
112112-113113- dump_interval_map ppfs
114114- ~name:"tty_width_hint"
115115- ~desc:"Uucp.Break.tty_width_hint"
116116- (* w = -1 is easy to detect.
117117- w = 1 covers the most intervals, so we default it. *)
118118- (uchars |> map (fun u -> u, Uucp.Break.tty_width_hint u)
119119- |> filter (fun (_, w) -> w <> -1 && w <> 1));
120120-121121- (* dump_interval_map ppfs *)
122122- (* ~name:"grapheme_cluster_boundary" *)
123123- (* ~desc:"Uucp.Break.Low.grapheme_cluster." *)
124124- (* (1* No single value dominates the histogram. *1) *)
125125- (* (uchars |> map (fun u -> u, Uucp.Break.Low.grapheme_cluster u)); *)
126126-127127- dump_trie_map ppfs
128128- ~name:"grapheme_cluster_boundary"
129129- ~desc:"Uucp.Break.Low.grapheme_cluster."
130130- ~default:16 (* 16 - `XX - is by far the most prevalent value *)
131131- Uucp.Break.Low.grapheme_cluster;
132132-133133- ()
134134-135135-let file = "src/no-uucp/notty_uucp_data"
136136-137137-let with_new name f =
138138- let o = open_out_gen [Open_trunc; Open_creat; Open_wronly] 0o664 name in
139139- let ppf = Format.formatter_of_out_channel o in
140140- f ppf; Format.pp_print_flush ppf (); close_out o
141141-142142-let () =
143143- Format.printf "Dumping Unicode v%s data to %s.@." Uucp.unicode_version file;
144144- with_new (file ^ ".mli") @@ fun ppmli ->
145145- with_new (file ^ ".ml") @@ fun ppml ->
146146- extract (ppmli, ppml)