1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-28 13:48:51 +08:00

Merge pull request #355 from mum4k/release-v0.18.0

Release Termdash v0.18.0.

## v0.18.0 - 08-Feb-2023

### Added

- The `gauge` widget now supports drawing of a vertical threshold bar.
- The `TextInput` widget now supports an OnChange handler that allows user code
  to be notified when the content of the text input changes.

### Changed

- Bump github.com/gdamore/tcell/v2 from 2.5.1 to 2.5.4.
- Bump github.com/mattn/go-runewidth from 0.0.13 to 0.0.14.
- Bump github.com/gdamore/tcell/v2 from 2.5.1 to 2.5.3.
- Bump github.com/gdamore/tcell/v2 from 2.5.1 to 2.5.2
- Change the Go version in `go.mod` to 1.20.
- Executed `go mod tidy`.
- CI now executes tests with Golang v1.20 only.

### Removed

- Removed the `Sourcegraph` badge from the main page.

### Fixed

- Formatted all Go files with `gofmt` from Golang v1.20.
- Fixed line coverage reporting.
This commit is contained in:
Jakub Sobon 2023-02-08 17:03:09 -05:00 committed by GitHub
commit e7f235a952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 737 additions and 342 deletions

View File

@ -1,7 +1,6 @@
language: go language: go
go: go:
- 1.16.x - 1.20.x
- 1.17.x
- stable - stable
before_install: before_install:
- go install golang.org/x/tools/cmd/cover@latest - go install golang.org/x/tools/cmd/cover@latest
@ -9,11 +8,8 @@ before_install:
- go install golang.org/x/lint/golint@latest - go install golang.org/x/lint/golint@latest
script: script:
- go get -t ./... - go get -t ./...
# Temporarily set -mod=mod to allow modification of go.mod and go.sum. - go test -v -covermode=count -coverprofile=coverage.out ./...
# This seems to be caused by a sum missing in the tcell dependency and - CGO_ENABLED=1 go test -race ./...
# should be removed when no longer needed.
- go test -v -covermode=count -coverprofile=coverage.out -mod=mod ./...
- CGO_ENABLED=1 go test -mod=mod -race ./...
- go vet ./... - go vet ./...
- diff -u <(echo -n) <(gofmt -d -s .) - diff -u <(echo -n) <(gofmt -d -s .)
- diff -u <(echo -n) <(./private/scripts/autogen_licences.sh .) - diff -u <(echo -n) <(./private/scripts/autogen_licences.sh .)
@ -22,4 +18,4 @@ script:
env: env:
global: global:
- CGO_ENABLED=0 - CGO_ENABLED=0
- secure: VOOh/w2YNAn+psiWYjIOQ5ZhhMb6Wz7zzmhIlj0dc5mGQztFAX5TuNWOU5JokvOigFy18JhPeDJRmp661xqM4gy1Znx1odSXES3YdCwt42pmpjYIkI9lI09xTRH6WYIRmYfCHe4J3A9/CWLeDRaAU1e+YqmNyraaGzE82ouUPH/I4A9gur4j4j6t1X/t0iovyd/4qNDsetUPevQsJS224Pv6Xhg3LGnSAXMPM+tu0t3UeEfRu/l9OgP6/bnet9BUx0BryFCVJp6fAtq7x61+WRIJesugrhHVgl/dz8CgFsVjRkqWQSNnZvt07dHNOX0mZj2U22OAkH+9ZN93wScs3bDZFXozrta7eOWhrJLcJTMrAxdHYMNKmoXqQQ0TGFV/L9blOtT8uj9US3wxeD11s4TyZePWIC5hnpUsNFoGPsBB45uwW2TSwvTTEL9bxWWzjYzSkLG5P6Kk4/JkeMh3OMFCM/LutX8QDch1n/s0CfXdy7qgh5G4I9ZhGTU+huJlumeuM4U+my0EPnA3uclJ97cw0n6K7MKwKCTTA8La2ifATunKC/U4Hjo1rf9DxofIrRIvwV5zEUIn1r6ut5fO+o+MWDupkvsMqIA7QJyCLhRp+pAlPWGDZLdrFEicN/kpULH4IGUIPn532gXzEOAG+Aq0WYDVPXGLSifSyxafiDk= - secure: FEFt9o7MvSvK2lnXJTydml/DCmNqPKIc+YJjPlKhfeGGJyruFvP55qkB0rHN7wb2r4L44qMqtM12c41YzA0sFhXXNowhCB0wd7mfyEt3cCLr4akqRedxOpz+qFBGv2oCBObP6e6irtsN0071KtDz9wsITchme3gf0A8l9PhjPeEu9UVbmN8lipouvQFqmdxqLNyxJ+ykbACkSTwIx2rkvU79aOPDoSnDO56Wqaq7V75CmZ13EtHupgmJIz2GECyIL6Ll6824AQiK1O5XXpnc8973Vw1s6kZB5Tyzvb2KaW5U9KhovgX1mTvnj1XJ//SQvh9yMjC5L51DLNbLSshVo7L4CiJZkq8QQLFILPOMaC1emrmhXxwizjbTmNOo96t2MYk2WASq636Uoez0ZkkZPjuwuKoexFi7r04vHEOjnF2N9liN+M8gwvJ5N/MakbS3ZPbZ+57w4xgwuxJRSUYQoT1VSU8E2rc+DQYa4HzY4wrcrJQG7HBjd4KBHnmHjEDAmawBb6iEI9d5d40j0LThxAOweh05YoWVmnTzMScuO27V5embStXab31jUrD4Qnj5/lliklGdYD5aWLKRZhSeg5mLikUITbhriABuUxMaR1FuQW4r1nSg3n8XA6MbwdvngO2DJyzp5iUQX3yfnUgrGzfp9okrA4CdxUHrY/jj8cw=

View File

@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.18.0] - 08-Feb-2023
### Added
- The `gauge` widget now supports drawing of a vertical threshold bar.
- The `TextInput` widget now supports an OnChange handler that allows user code
to be notified when the content of the text input changes.
### Changed
- Bump github.com/gdamore/tcell/v2 from 2.5.1 to 2.5.4.
- Bump github.com/mattn/go-runewidth from 0.0.13 to 0.0.14.
- Bump github.com/gdamore/tcell/v2 from 2.5.1 to 2.5.3.
- Bump github.com/gdamore/tcell/v2 from 2.5.1 to 2.5.2
- Change the Go version in `go.mod` to 1.20.
- Executed `go mod tidy`.
- CI now executes tests with Golang v1.20 only.
### Removed
- Removed the `Sourcegraph` badge from the main page.
### Fixed
- Formatted all Go files with `gofmt` from Golang v1.20.
- Fixed line coverage reporting.
## [0.17.0] - 07-Jul-2022 ## [0.17.0] - 07-Jul-2022
### Added ### Added
@ -490,6 +517,7 @@ identifiers shouldn't be used externally.
- The Text widget. - The Text widget.
[unreleased]: https://github.com/mum4k/termdash/compare/v0.17.0...devel [unreleased]: https://github.com/mum4k/termdash/compare/v0.17.0...devel
[0.18.0]: https://github.com/mum4k/termdash/compare/v0.17.0...v0.18.0
[0.17.0]: https://github.com/mum4k/termdash/compare/v0.16.1...v0.17.0 [0.17.0]: https://github.com/mum4k/termdash/compare/v0.16.1...v0.17.0
[0.16.1]: https://github.com/mum4k/termdash/compare/v0.16.0...v0.16.1 [0.16.1]: https://github.com/mum4k/termdash/compare/v0.16.0...v0.16.1
[0.16.0]: https://github.com/mum4k/termdash/compare/v0.15.0...v0.16.0 [0.16.0]: https://github.com/mum4k/termdash/compare/v0.15.0...v0.16.0

View File

@ -1,6 +1,5 @@
[![Doc Status](https://godoc.org/github.com/mum4k/termdash?status.png)](https://godoc.org/github.com/mum4k/termdash) [![Doc Status](https://godoc.org/github.com/mum4k/termdash?status.png)](https://godoc.org/github.com/mum4k/termdash)
[![Build Status](https://travis-ci.com/mum4k/termdash.svg?branch=master)](https://travis-ci.com/mum4k/termdash) [![Build Status](https://travis-ci.com/mum4k/termdash.svg?branch=master)](https://travis-ci.com/mum4k/termdash)
[![Sourcegraph](https://sourcegraph.com/github.com/mum4k/termdash/-/badge.svg)](https://sourcegraph.com/github.com/mum4k/termdash?badge)
[![Coverage Status](https://coveralls.io/repos/github/mum4k/termdash/badge.svg?branch=master)](https://coveralls.io/github/mum4k/termdash?branch=master) [![Coverage Status](https://coveralls.io/repos/github/mum4k/termdash/badge.svg?branch=master)](https://coveralls.io/github/mum4k/termdash?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/mum4k/termdash)](https://goreportcard.com/report/github.com/mum4k/termdash) [![Go Report Card](https://goreportcard.com/badge/github.com/mum4k/termdash)](https://goreportcard.com/report/github.com/mum4k/termdash)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/mum4k/termdash/blob/master/LICENSE) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/mum4k/termdash/blob/master/LICENSE)

View File

@ -53,11 +53,13 @@ func (b *Builder) Build() ([]container.Option, error) {
// validate recursively validates the elements that were added to the builder. // validate recursively validates the elements that were added to the builder.
// Validates the following per each level of Rows or Columns.: // Validates the following per each level of Rows or Columns.:
//
// The subElements are either exactly one Widget or any number of Rows and // The subElements are either exactly one Widget or any number of Rows and
// Columns. // Columns.
// Each individual width or height is in the range 0 < v < 100. // Each individual width or height is in the range 0 < v < 100.
// The sum of all widths is <= 100. // The sum of all widths is <= 100.
// The sum of all heights is <= 100. // The sum of all heights is <= 100.
//
// Argument fixedSizeParent indicates if any of the parent elements uses fixed // Argument fixedSizeParent indicates if any of the parent elements uses fixed
// size splitType. // size splitType.
func validate(elems []Element, fixedSizeParent bool) error { func validate(elems []Element, fixedSizeParent bool) error {
@ -182,14 +184,23 @@ func build(elems []Element, parentHeightPerc, parentWidthPerc int) []container.O
// E.g. multiple rows would specify that they want the outer split percentage // E.g. multiple rows would specify that they want the outer split percentage
// of 25% each, but we are representing them in a tree of containers so the // of 25% each, but we are representing them in a tree of containers so the
// inner splits vary: // inner splits vary:
//
// ╭─────────╮ // ╭─────────╮
//
// 25% │ 25% │ // 25% │ 25% │
//
// │╭───────╮│ --- // │╭───────╮│ ---
//
// 25% ││ 33% ││ // 25% ││ 33% ││
//
// ││╭─────╮││ // ││╭─────╮││
//
// 25% │││ 50% │││ // 25% │││ 50% │││
//
// ││├─────┤││ 75% // ││├─────┤││ 75%
//
// 25% │││ 50% │││ // 25% │││ 50% │││
//
// ││╰─────╯││ // ││╰─────╯││
// │╰───────╯│ // │╰───────╯│
// ╰─────────╯ --- // ╰─────────╯ ---

10
go.mod
View File

@ -1,11 +1,11 @@
module github.com/mum4k/termdash module github.com/mum4k/termdash
go 1.17 go 1.20
require ( require (
github.com/gdamore/tcell/v2 v2.5.1 github.com/gdamore/tcell/v2 v2.5.4
github.com/kylelemons/godebug v1.1.0 github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-runewidth v0.0.13 github.com/mattn/go-runewidth v0.0.14
github.com/nsf/termbox-go v1.1.1 github.com/nsf/termbox-go v1.1.1
) )
@ -13,7 +13,7 @@ require (
github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.5.0 // indirect
) )

34
go.sum
View File

@ -1,27 +1,43 @@
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I= github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo= github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -75,6 +75,7 @@ func VerticalTextOverrunMode(om OverrunMode) VerticalTextOption {
// VerticalText prints the provided text on the canvas starting at the provided point. // VerticalText prints the provided text on the canvas starting at the provided point.
// The text is printed in a vertical orientation, i.e: // The text is printed in a vertical orientation, i.e:
//
// H // H
// e // e
// l // l

View File

@ -128,6 +128,7 @@ func (ar *angleRange) contains(angle int) bool {
// normalizeRange normalizes the start and end angles in degrees into ranges of // normalizeRange normalizes the start and end angles in degrees into ranges of
// angles. Useful for cases where the 0/360 point falls within the range. // angles. Useful for cases where the 0/360 point falls within the range.
// E.g: // E.g:
//
// 0,25 => angleRange{0, 26} // 0,25 => angleRange{0, 26}
// 0,360 => angleRange{0, 361} // 0,360 => angleRange{0, 361}
// 359,20 => angleRange{359, 361}, angleRange{0, 21} // 359,20 => angleRange{359, 361}, angleRange{0, 21}
@ -159,6 +160,7 @@ func normalizeRange(start, end int) ([]*angleRange, error) {
// RangeSize returns the size of the degree range. // RangeSize returns the size of the degree range.
// E.g: // E.g:
//
// 0,25 => 25 // 0,25 => 25
// 359,1 => 2 // 359,1 => 2
func RangeSize(start, end int) (int, error) { func RangeSize(start, end int) (int, error) {
@ -174,6 +176,7 @@ func RangeSize(start, end int) (int, error) {
// RangeMid returns an angle that lies in the middle between start and end. // RangeMid returns an angle that lies in the middle between start and end.
// E.g: // E.g:
//
// 0,10 => 5 // 0,10 => 5
// 350,10 => 0 // 350,10 => 0
func RangeMid(start, end int) (int, error) { func RangeMid(start, end int) (int, error) {

View File

@ -95,14 +95,15 @@ func SkipSlopesLTE(v int) Option {
// This only has a visible effect when the horizontal segment has height of two // This only has a visible effect when the horizontal segment has height of two
// or the vertical segment has width of two. // or the vertical segment has width of two.
// Without this option segments with height / width of two look like this: // Without this option segments with height / width of two look like this:
// - | // x - |
// --- || // x --- ||
// | // x |
// // x
// With this option: // x With this option:
// --- | // x
// - || // x --- |
// | // x - ||
// x |
func ReverseSlopes() Option { func ReverseSlopes() Option {
return option(func(opts *options) { return option(func(opts *options) {
opts.reverseSlopes = true opts.reverseSlopes = true

View File

@ -976,8 +976,10 @@ func newLayoutButtons(c *container.Container, w *widgets) (*layoutButtons, error
// rotateFloats returns a new slice with inputs rotated by step. // rotateFloats returns a new slice with inputs rotated by step.
// I.e. for a step of one: // I.e. for a step of one:
//
// inputs[0] -> inputs[len(inputs)-1] // inputs[0] -> inputs[len(inputs)-1]
// inputs[1] -> inputs[0] // inputs[1] -> inputs[0]
//
// And so on. // And so on.
func rotateFloats(inputs []float64, step int) []float64 { func rotateFloats(inputs []float64, step int) []float64 {
return append(inputs[step:], inputs[:step]...) return append(inputs[step:], inputs[:step]...)
@ -985,8 +987,10 @@ func rotateFloats(inputs []float64, step int) []float64 {
// rotateRunes returns a new slice with inputs rotated by step. // rotateRunes returns a new slice with inputs rotated by step.
// I.e. for a step of one: // I.e. for a step of one:
//
// inputs[0] -> inputs[len(inputs)-1] // inputs[0] -> inputs[len(inputs)-1]
// inputs[1] -> inputs[0] // inputs[1] -> inputs[0]
//
// And so on. // And so on.
func rotateRunes(inputs []rune, step int) []rune { func rotateRunes(inputs []rune, step int) []rune {
return append(inputs[step:], inputs[:step]...) return append(inputs[step:], inputs[:step]...)

View File

@ -136,10 +136,12 @@ func (g *Gauge) Percent(p int, opts ...Option) error {
return nil return nil
} }
// width determines the required width of the gauge drawn on the provided area // width determines the X coordinate that represents point w in rectangle ar.
// in order to represent the current progress. // This is used to calculate the width of the gauge drawn on the provided area
func (g *Gauge) width(ar image.Rectangle) int { // in order to represent the current progress or to figure out the coordinate
mult := float32(g.current) / float32(g.total) // for the threshold line.
func (g *Gauge) width(ar image.Rectangle, w int) int {
mult := float32(w) / float32(g.total)
width := float32(ar.Dx()) * mult width := float32(ar.Dx()) * mult
return int(width) return int(width)
} }
@ -157,6 +159,11 @@ func (g *Gauge) usable(cvs *canvas.Canvas) image.Rectangle {
return cvs.Area() return cvs.Area()
} }
// thresholdVisible determines if the threshold line should be drawn.
func (g *Gauge) thresholdVisible() bool {
return g.opts.threshold > 0 && g.opts.threshold < g.total
}
// progressText returns the textual representation of the current progress. // progressText returns the textual representation of the current progress.
func (g *Gauge) progressText() string { func (g *Gauge) progressText() string {
if g.opts.hideTextProgress { if g.opts.hideTextProgress {
@ -244,6 +251,26 @@ func (g *Gauge) drawText(cvs *canvas.Canvas, progress image.Rectangle) error {
return nil return nil
} }
// drawThreshold draws the threshold line.
func (g *Gauge) drawThreshold(cvs *canvas.Canvas) error {
ar := g.usable(cvs)
line := draw.HVLine{
Start: image.Point{
X: ar.Min.X + g.width(ar, g.opts.threshold),
Y: cvs.Area().Min.Y,
},
End: image.Point{
X: ar.Min.X + g.width(ar, g.opts.threshold),
Y: cvs.Area().Max.Y - 1,
},
}
return draw.HVLines(cvs, []draw.HVLine{line},
draw.HVLineStyle(g.opts.thresholdLineStyle),
draw.HVLineCellOpts(g.opts.thresholdCellOpts...),
)
}
// Draw draws the Gauge widget onto the canvas. // Draw draws the Gauge widget onto the canvas.
// Implements widgetapi.Widget.Draw. // Implements widgetapi.Widget.Draw.
func (g *Gauge) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error { func (g *Gauge) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error {
@ -273,7 +300,7 @@ func (g *Gauge) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error {
progress := image.Rect( progress := image.Rect(
usable.Min.X, usable.Min.X,
usable.Min.Y, usable.Min.Y,
usable.Min.X+g.width(usable), usable.Min.X+g.width(usable, g.current),
usable.Max.Y, usable.Max.Y,
) )
if progress.Dx() > 0 { if progress.Dx() > 0 {
@ -284,6 +311,12 @@ func (g *Gauge) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error {
return err return err
} }
} }
if g.thresholdVisible() {
if err := g.drawThreshold(cvs); err != nil {
return err
}
}
return g.drawText(cvs, progress) return g.drawText(cvs, progress)
} }

View File

@ -70,9 +70,9 @@ func TestGauge(t *testing.T) {
wantErr: true, wantErr: true,
}, },
{ {
desc: "fails on negative height", desc: "fails on negative threshold",
opts: []Option{ opts: []Option{
Height(-1), Threshold(-1, linestyle.Light),
}, },
canvas: image.Rect(0, 0, 10, 3), canvas: image.Rect(0, 0, 10, 3),
want: func(size image.Point) *faketerm.Terminal { want: func(size image.Point) *faketerm.Terminal {
@ -841,6 +841,157 @@ func TestGauge(t *testing.T) {
return ft return ft
}, },
}, },
{
desc: "threshold with border percentage",
opts: []Option{
Char('o'),
Threshold(20, linestyle.Double),
},
percent: &percentCall{p: 35},
canvas: image.Rect(0, 0, 10, 3),
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
testdraw.MustRectangle(c, image.Rect(0, 0, 3, 3),
draw.RectChar('o'),
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
)
testdraw.MustText(c, "35%", image.Point{3, 1})
testdraw.MustHVLines(c, []draw.HVLine{{
Start: image.Point{X: 2, Y: 0},
End: image.Point{X: 2, Y: 2},
}}, draw.HVLineStyle(linestyle.Double))
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "threshold without border absolute",
opts: []Option{
Char('o'),
Threshold(3, linestyle.Light, cell.BgColor(cell.ColorRed)),
Border(linestyle.None),
HideTextProgress(),
},
absolute: &absoluteCall{done: 4, total: 10},
canvas: image.Rect(0, 0, 10, 3),
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
testdraw.MustRectangle(c, image.Rect(0, 0, 3, 3),
draw.RectChar('o'),
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
)
testdraw.MustHVLines(c, []draw.HVLine{{
Start: image.Point{X: 3, Y: 0},
End: image.Point{X: 3, Y: 2},
}}, draw.HVLineStyle(linestyle.Light),
draw.HVLineCellOpts(cell.BgColor(cell.ColorRed)))
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "threshold outside of bounds (>=max)",
opts: []Option{
Char('o'),
HideTextProgress(),
Threshold(100, linestyle.Light), // ignored
},
percent: &percentCall{p: 35},
canvas: image.Rect(0, 0, 10, 3),
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
testdraw.MustRectangle(c, image.Rect(0, 0, 3, 3),
draw.RectChar('o'),
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
)
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "progress below threshold",
opts: []Option{
Char('o'),
Threshold(5, linestyle.Light, cell.BgColor(cell.ColorRed)),
HideTextProgress(),
},
absolute: &absoluteCall{done: 4, total: 10},
canvas: image.Rect(0, 0, 10, 3),
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
testdraw.MustRectangle(c, image.Rect(0, 0, 4, 3),
draw.RectChar('o'),
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
)
testdraw.MustHVLines(c, []draw.HVLine{{
Start: image.Point{X: 5, Y: 0},
End: image.Point{X: 5, Y: 2},
}}, draw.HVLineStyle(linestyle.Light),
draw.HVLineCellOpts(cell.BgColor(cell.ColorRed)))
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "progress exactly at threshold",
opts: []Option{
Char('o'),
Threshold(5, linestyle.Light, cell.BgColor(cell.ColorRed)),
HideTextProgress(),
},
absolute: &absoluteCall{done: 5, total: 10},
canvas: image.Rect(0, 0, 10, 3),
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
testdraw.MustRectangle(c, image.Rect(0, 0, 5, 3),
draw.RectChar('o'),
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
)
testdraw.MustHVLines(c, []draw.HVLine{{
Start: image.Point{X: 5, Y: 0},
End: image.Point{X: 5, Y: 2},
}}, draw.HVLineStyle(linestyle.Light),
draw.HVLineCellOpts(cell.BgColor(cell.ColorRed)))
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "progress after threshold",
opts: []Option{
Char('o'),
Threshold(5, linestyle.Light, cell.BgColor(cell.ColorRed)),
HideTextProgress(),
},
absolute: &absoluteCall{done: 6, total: 10},
canvas: image.Rect(0, 0, 10, 3),
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
testdraw.MustRectangle(c, image.Rect(0, 0, 6, 3),
draw.RectChar('o'),
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
)
testdraw.MustHVLines(c, []draw.HVLine{{
Start: image.Point{X: 5, Y: 0},
End: image.Point{X: 5, Y: 2},
}}, draw.HVLineStyle(linestyle.Light),
draw.HVLineCellOpts(cell.BgColor(cell.ColorRed)))
testcanvas.MustApply(c, ft)
return ft
},
},
} }
for _, tc := range tests { for _, tc := range tests {

View File

@ -101,6 +101,7 @@ func main() {
gauge.Color(cell.ColorNumber(33)), gauge.Color(cell.ColorNumber(33)),
gauge.Border(linestyle.Light), gauge.Border(linestyle.Light),
gauge.BorderTitle("Absolute progress"), gauge.BorderTitle("Absolute progress"),
gauge.Threshold(43, linestyle.Light, cell.FgColor(cell.ColorRed)),
) )
if err != nil { if err != nil {
panic(err) panic(err)
@ -124,6 +125,7 @@ func main() {
gauge.Color(cell.ColorRed), gauge.Color(cell.ColorRed),
gauge.FilledTextColor(cell.ColorBlack), gauge.FilledTextColor(cell.ColorBlack),
gauge.EmptyTextColor(cell.ColorYellow), gauge.EmptyTextColor(cell.ColorYellow),
gauge.Threshold(20, linestyle.Double),
) )
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -47,6 +47,10 @@ type options struct {
borderCellOpts []cell.Option borderCellOpts []cell.Option
borderTitle string borderTitle string
borderTitleHAlign align.Horizontal borderTitleHAlign align.Horizontal
// If set draws a vertical line representing the threshold.
threshold int
thresholdCellOpts []cell.Option
thresholdLineStyle linestyle.LineStyle
} }
// newOptions returns options with the default values set. // newOptions returns options with the default values set.
@ -66,6 +70,9 @@ func (o *options) validate() error {
if got, min := o.height, 0; got < min { if got, min := o.height, 0; got < min {
return fmt.Errorf("invalid Height %d, must be %d <= Height", got, min) return fmt.Errorf("invalid Height %d, must be %d <= Height", got, min)
} }
if got, min := o.threshold, 0; got < min {
return fmt.Errorf("invalid Threshold %d, must be %d <= Threshold", got, min)
}
return nil return nil
} }
@ -201,3 +208,17 @@ func BorderTitleAlign(h align.Horizontal) Option {
opts.borderTitleHAlign = h opts.borderTitleHAlign = h
}) })
} }
// Threshold configures the Gauge to display a vertical threshold line at value
// t. If the progress is set by a call to Percent(), t represents a percentage,
// e.g. "40" means line is displayed at 40%. If the progress is set by a call to
// Absolute(), the threshold is considered an absolute number.
// Threshold must be positive to be displayed. If the threshold is zero or
// greater than total, it won't be displayed. Defaults to zero.
func Threshold(t int, ls linestyle.LineStyle, cOpts ...cell.Option) Option {
return option(func(opts *options) {
opts.threshold = t
opts.thresholdLineStyle = ls
opts.thresholdCellOpts = cOpts
})
}

View File

@ -328,6 +328,7 @@ func (xs *XScale) CellLabel(x int) (*Value, error) {
// the position. Positions grow up, coordinates grow down. // the position. Positions grow up, coordinates grow down.
// //
// Positions Y Coordinates // Positions Y Coordinates
//
// 2 | 0 // 2 | 0
// 1 | 1 // 1 | 1
// 0 | 2 // 0 | 2

View File

@ -62,8 +62,10 @@ func clock(ctx context.Context, sd *segmentdisplay.SegmentDisplay) {
// rotate returns a new slice with inputs rotated by step. // rotate returns a new slice with inputs rotated by step.
// I.e. for a step of one: // I.e. for a step of one:
//
// inputs[0] -> inputs[len(inputs)-1] // inputs[0] -> inputs[len(inputs)-1]
// inputs[1] -> inputs[0] // inputs[1] -> inputs[0]
//
// And so on. // And so on.
func rotate(inputs []rune, step int) []rune { func rotate(inputs []rune, step int) []rune {
return append(inputs[step:], inputs[:step]...) return append(inputs[step:], inputs[:step]...)

View File

@ -105,7 +105,9 @@ func (t *Text) contentCells() int {
// Write writes text for the widget to display. Multiple calls append // Write writes text for the widget to display. Multiple calls append
// additional text. The text contain cannot control characters // additional text. The text contain cannot control characters
// (unicode.IsControl) or space character (unicode.IsSpace) other than: // (unicode.IsControl) or space character (unicode.IsSpace) other than:
//
// ' ', '\n' // ' ', '\n'
//
// Any newline ('\n') characters are interpreted as newlines when displaying // Any newline ('\n') characters are interpreted as newlines when displaying
// the text. // the text.
func (t *Text) Write(text string, wOpts ...WriteOption) error { func (t *Text) Write(text string, wOpts ...WriteOption) error {

View File

@ -274,11 +274,14 @@ type fieldEditor struct {
// width is the width of the text input field last time viewFor was called. // width is the width of the text input field last time viewFor was called.
width int width int
// onChange if provided is the handler called when fieldData changes
onChange ChangeFn
} }
// newFieldEditor returns a new fieldEditor instance. // newFieldEditor returns a new fieldEditor instance.
func newFieldEditor() *fieldEditor { func newFieldEditor(onChange ChangeFn) *fieldEditor {
return &fieldEditor{} return &fieldEditor{onChange: onChange}
} }
// minFieldWidth is the minimum supported width of the text input field. // minFieldWidth is the minimum supported width of the text input field.
@ -326,7 +329,7 @@ func (fe *fieldEditor) content() string {
// reset resets the content back to zero. // reset resets the content back to zero.
func (fe *fieldEditor) reset() { func (fe *fieldEditor) reset() {
*fe = *newFieldEditor() *fe = *newFieldEditor(fe.onChange)
} }
// insert inserts the rune at the current position of the cursor. // insert inserts the rune at the current position of the cursor.
@ -338,6 +341,9 @@ func (fe *fieldEditor) insert(r rune) {
} }
fe.data.insertAt(fe.curDataPos, r) fe.data.insertAt(fe.curDataPos, r)
fe.curDataPos++ fe.curDataPos++
if fe.onChange != nil {
fe.onChange(string(fe.data))
}
} }
// delete deletes the rune at the current position of the cursor. // delete deletes the rune at the current position of the cursor.
@ -347,6 +353,9 @@ func (fe *fieldEditor) delete() {
return return
} }
fe.data.deleteAt(fe.curDataPos) fe.data.deleteAt(fe.curDataPos)
if fe.onChange != nil {
fe.onChange(string(fe.data))
}
} }
// deleteBefore deletes the rune that is immediately to the left of the cursor. // deleteBefore deletes the rune that is immediately to the left of the cursor.

View File

@ -309,7 +309,7 @@ func TestCurCell(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
fe := newFieldEditor() fe := newFieldEditor(nil)
fe.data = tc.data fe.data = tc.data
fe.firstRune = tc.firstRune fe.firstRune = tc.firstRune
fe.curDataPos = tc.curDataPos fe.curDataPos = tc.curDataPos
@ -330,6 +330,7 @@ func TestFieldEditor(t *testing.T) {
wantContent string wantContent string
wantCurIdx int wantCurIdx int
wantErr bool wantErr bool
wantOnChangeCalls int
}{ }{
{ {
desc: "fails for width too small", desc: "fails for width too small",
@ -355,6 +356,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 3,
}, },
{ {
desc: "longer data than the width, cursor at the end", desc: "longer data than the width, cursor at the end",
@ -369,6 +371,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 4,
}, },
{ {
desc: "longer data than the width, cursor at the end, has full-width runes", desc: "longer data than the width, cursor at the end, has full-width runes",
@ -383,6 +386,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦世", wantView: "⇦世",
wantContent: "abc世", wantContent: "abc世",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 4,
}, },
{ {
desc: "width decreased, adjusts cursor and shifts data", desc: "width decreased, adjusts cursor and shifts data",
@ -400,6 +404,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 4,
}, },
{ {
desc: "cursor won't go right beyond the end of the data", desc: "cursor won't go right beyond the end of the data",
@ -417,6 +422,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 4,
}, },
{ {
desc: "moves cursor to the left", desc: "moves cursor to the left",
@ -435,6 +441,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls content to the left, start becomes visible", desc: "scrolls content to the left, start becomes visible",
@ -455,6 +462,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls content to the left, both ends invisible", desc: "scrolls content to the left, both ends invisible",
@ -476,6 +484,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 5,
}, },
{ {
desc: "scrolls left, then back right to make end visible again", desc: "scrolls left, then back right to make end visible again",
@ -503,6 +512,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 5,
}, },
{ {
desc: "scrolls left, won't go beyond the start of data", desc: "scrolls left, won't go beyond the start of data",
@ -527,6 +537,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 0, wantCurIdx: 0,
wantOnChangeCalls: 5,
}, },
{ {
desc: "scrolls left, then back right won't go beyond the end of data", desc: "scrolls left, then back right won't go beyond the end of data",
@ -555,6 +566,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 5,
}, },
{ {
desc: "have less data than width, all fits", desc: "have less data than width, all fits",
@ -571,6 +583,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 3,
}, },
{ {
desc: "moves cursor to the start", desc: "moves cursor to the start",
@ -590,6 +603,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 0, wantCurIdx: 0,
wantOnChangeCalls: 5,
}, },
{ {
desc: "moves cursor to the end", desc: "moves cursor to the end",
@ -613,6 +627,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 5,
}, },
{ {
desc: "deletesBefore when cursor after the data", desc: "deletesBefore when cursor after the data",
@ -632,6 +647,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 6,
}, },
{ {
desc: "deletesBefore when cursor after the data, text has full-width rune", desc: "deletesBefore when cursor after the data, text has full-width rune",
@ -651,6 +667,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦世", wantView: "⇦世",
wantContent: "abc世", wantContent: "abc世",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 6,
}, },
{ {
desc: "deletesBefore when cursor in the middle", desc: "deletesBefore when cursor in the middle",
@ -676,6 +693,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "acd⇨", wantView: "acd⇨",
wantContent: "acde", wantContent: "acde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 6,
}, },
{ {
desc: "deletesBefore when cursor in the middle, full-width runes", desc: "deletesBefore when cursor in the middle, full-width runes",
@ -701,6 +719,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "世c⇨", wantView: "世c⇨",
wantContent: "世cde", wantContent: "世cde",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 6,
}, },
{ {
desc: "deletesBefore does nothing when cursor at the start", desc: "deletesBefore does nothing when cursor at the start",
@ -724,6 +743,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 0, wantCurIdx: 0,
wantOnChangeCalls: 5,
}, },
{ {
desc: "delete does nothing when cursor at the end", desc: "delete does nothing when cursor at the end",
@ -743,6 +763,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 5,
}, },
{ {
desc: "delete in the middle, last rune remains hidden", desc: "delete in the middle, last rune remains hidden",
@ -767,6 +788,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "acd⇨", wantView: "acd⇨",
wantContent: "acde", wantContent: "acde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 6,
}, },
{ {
desc: "delete in the middle, last rune becomes visible", desc: "delete in the middle, last rune becomes visible",
@ -792,6 +814,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "ade", wantView: "ade",
wantContent: "ade", wantContent: "ade",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 7,
}, },
{ {
desc: "delete in the middle, last full-width rune would be invisible, shifts to keep cursor in window", desc: "delete in the middle, last full-width rune would be invisible, shifts to keep cursor in window",
@ -818,6 +841,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦世", wantView: "⇦世",
wantContent: "ab世", wantContent: "ab世",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 7,
}, },
{ {
desc: "delete in the middle, last rune was and is visible", desc: "delete in the middle, last rune was and is visible",
@ -840,6 +864,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "ac", wantView: "ac",
wantContent: "ac", wantContent: "ac",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 4,
}, },
{ {
desc: "delete in the middle, last full-width rune was and is visible", desc: "delete in the middle, last full-width rune was and is visible",
@ -862,6 +887,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "a世", wantView: "a世",
wantContent: "a世", wantContent: "a世",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 4,
}, },
{ {
desc: "delete last rune, contains full-width runes", desc: "delete last rune, contains full-width runes",
@ -885,6 +911,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "", wantView: "",
wantContent: "", wantContent: "",
wantCurIdx: 0, wantCurIdx: 0,
wantOnChangeCalls: 6,
}, },
{ {
desc: "half-width runes only, exact fit", desc: "half-width runes only, exact fit",
@ -901,6 +928,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 3,
}, },
{ {
desc: "full-width runes only, exact fit", desc: "full-width runes only, exact fit",
@ -917,6 +945,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "你好世", wantView: "你好世",
wantContent: "你好世", wantContent: "你好世",
wantCurIdx: 6, wantCurIdx: 6,
wantOnChangeCalls: 3,
}, },
{ {
desc: "half-width runes only, both ends hidden", desc: "half-width runes only, both ends hidden",
@ -938,6 +967,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 5,
}, },
{ {
desc: "half-width runes only, both ends invisible, scrolls to make start visible", desc: "half-width runes only, both ends invisible, scrolls to make start visible",
@ -960,6 +990,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 5,
}, },
{ {
desc: "half-width runes only, both ends invisible, deletes to make start visible", desc: "half-width runes only, both ends invisible, deletes to make start visible",
@ -982,6 +1013,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "acd⇨", wantView: "acd⇨",
wantContent: "acde", wantContent: "acde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 6,
}, },
{ {
desc: "half-width runes only, deletion on second page refills the field", desc: "half-width runes only, deletion on second page refills the field",
@ -1004,6 +1036,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦df", wantView: "⇦df",
wantContent: "abcdf", wantContent: "abcdf",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 7,
}, },
{ {
desc: "half-width runes only, both ends invisible, scrolls to make end visible", desc: "half-width runes only, both ends invisible, scrolls to make end visible",
@ -1030,6 +1063,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 5,
}, },
{ {
desc: "half-width runes only, both ends invisible, deletes to make end visible", desc: "half-width runes only, both ends invisible, deletes to make end visible",
@ -1055,6 +1089,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦de", wantView: "⇦de",
wantContent: "abde", wantContent: "abde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 6,
}, },
{ {
desc: "full-width runes only, both ends invisible", desc: "full-width runes only, both ends invisible",
@ -1074,6 +1109,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦世⇨", wantView: "⇦⇦世⇨",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 4,
}, },
{ {
desc: "full-width runes only, both ends invisible, scrolls to make start visible", desc: "full-width runes only, both ends invisible, scrolls to make start visible",
@ -1097,6 +1133,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "你好⇨", wantView: "你好⇨",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 4,
}, },
{ {
desc: "full-width runes only, both ends invisible, deletes to make start visible", desc: "full-width runes only, both ends invisible, deletes to make start visible",
@ -1120,6 +1157,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "你世⇨", wantView: "你世⇨",
wantContent: "你世界", wantContent: "你世界",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 5,
}, },
{ {
desc: "full-width runes only, both ends invisible, scrolls to make end visible", desc: "full-width runes only, both ends invisible, scrolls to make end visible",
@ -1143,6 +1181,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦界", wantView: "⇦⇦界",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 4,
}, },
{ {
desc: "full-width runes only, both ends invisible, deletes to make end visible", desc: "full-width runes only, both ends invisible, deletes to make end visible",
@ -1166,6 +1205,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦界", wantView: "⇦⇦界",
wantContent: "你好界", wantContent: "你好界",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 5,
}, },
{ {
desc: "scrolls to make full-width rune appear at the beginning", desc: "scrolls to make full-width rune appear at the beginning",
@ -1186,6 +1226,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "你b⇨", wantView: "你b⇨",
wantContent: "你bcd", wantContent: "你bcd",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls to make full-width rune appear at the end", desc: "scrolls to make full-width rune appear at the end",
@ -1207,6 +1248,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦你", wantView: "⇦你",
wantContent: "abc你", wantContent: "abc你",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 4,
}, },
{ {
desc: "inserts after last full width rune, first is half-width", desc: "inserts after last full width rune, first is half-width",
@ -1225,6 +1267,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦c你e", wantView: "⇦c你e",
wantContent: "abc你e", wantContent: "abc你e",
wantCurIdx: 5, wantCurIdx: 5,
wantOnChangeCalls: 5,
}, },
{ {
desc: "inserts after last full width rune, first is half-width", desc: "inserts after last full width rune, first is half-width",
@ -1242,6 +1285,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦你d", wantView: "⇦你d",
wantContent: "世b你d", wantContent: "世b你d",
wantCurIdx: 4, wantCurIdx: 4,
wantOnChangeCalls: 4,
}, },
{ {
desc: "inserts after last full width rune, hidden rune is full-width", desc: "inserts after last full width rune, hidden rune is full-width",
@ -1259,6 +1303,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦cd", wantView: "⇦⇦cd",
wantContent: "世你cd", wantContent: "世你cd",
wantCurIdx: 4, wantCurIdx: 4,
wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls right, first is full-width, last are half-width", desc: "scrolls right, first is full-width, last are half-width",
@ -1285,6 +1330,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦def⇨", wantView: "⇦⇦def⇨",
wantContent: "a你世defgh", wantContent: "a你世defgh",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 8,
}, },
{ {
desc: "scrolls right, first is half-width, last is full-width", desc: "scrolls right, first is half-width, last is full-width",
@ -1311,6 +1357,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦你世⇨", wantView: "⇦你世⇨",
wantContent: "abc你世fgh", wantContent: "abc你世fgh",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 8,
}, },
{ {
desc: "scrolls right, first and last are full-width", desc: "scrolls right, first and last are full-width",
@ -1331,6 +1378,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦世⇨", wantView: "⇦⇦世⇨",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls right, first and last are half-width", desc: "scrolls right, first and last are half-width",
@ -1357,6 +1405,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cdef⇨", wantView: "⇦cdef⇨",
wantContent: "abcdefg", wantContent: "abcdefg",
wantCurIdx: 4, wantCurIdx: 4,
wantOnChangeCalls: 7,
}, },
{ {
desc: "scrolls left, first is full-width, last are half-width", desc: "scrolls left, first is full-width, last are half-width",
@ -1383,6 +1432,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦def⇨", wantView: "⇦⇦def⇨",
wantContent: "a你世defgh", wantContent: "a你世defgh",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 8,
}, },
{ {
desc: "scrolls left, first is half-width, last is full-width", desc: "scrolls left, first is half-width, last is full-width",
@ -1409,6 +1459,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦你世⇨", wantView: "⇦你世⇨",
wantContent: "abc你世fgh", wantContent: "abc你世fgh",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 8,
}, },
{ {
desc: "scrolls left, first and last are full-width", desc: "scrolls left, first and last are full-width",
@ -1428,6 +1479,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦世⇨", wantView: "⇦⇦世⇨",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls left, first and last are half-width", desc: "scrolls left, first and last are half-width",
@ -1453,6 +1505,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cdef⇨", wantView: "⇦cdef⇨",
wantContent: "abcdefg", wantContent: "abcdefg",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 7,
}, },
{ {
desc: "resets the field editor", desc: "resets the field editor",
@ -1470,6 +1523,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "", wantView: "",
wantContent: "", wantContent: "",
wantCurIdx: 0, wantCurIdx: 0,
wantOnChangeCalls: 3,
}, },
{ {
desc: "doesn't insert runes with rune width of zero", desc: "doesn't insert runes with rune width of zero",
@ -1486,6 +1540,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "ac", wantView: "ac",
wantContent: "ac", wantContent: "ac",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 2,
}, },
{ {
desc: "all text visible, moves cursor to position zero", desc: "all text visible, moves cursor to position zero",
@ -1503,6 +1558,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 0, wantCurIdx: 0,
wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor to position in the middle", desc: "all text visible, moves cursor to position in the middle",
@ -1520,6 +1576,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor back to the last character", desc: "all text visible, moves cursor back to the last character",
@ -1538,6 +1595,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor to the appending space", desc: "all text visible, moves cursor to the appending space",
@ -1556,6 +1614,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor before the beginning of data", desc: "all text visible, moves cursor before the beginning of data",
@ -1574,6 +1633,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 0, wantCurIdx: 0,
wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor after the appending space", desc: "all text visible, moves cursor after the appending space",
@ -1592,6 +1652,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
wantOnChangeCalls: 3,
}, },
{ {
desc: "moves cursor when there is no text", desc: "moves cursor when there is no text",
@ -1629,6 +1690,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 5,
}, },
{ {
desc: "both ends hidden, moves cursor onto the first character", desc: "both ends hidden, moves cursor onto the first character",
@ -1655,6 +1717,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
wantOnChangeCalls: 5,
}, },
{ {
desc: "both ends hidden, moves cursor onto the right arrow", desc: "both ends hidden, moves cursor onto the right arrow",
@ -1680,6 +1743,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 5,
}, },
{ {
desc: "both ends hidden, moves cursor onto the last character", desc: "both ends hidden, moves cursor onto the last character",
@ -1705,6 +1769,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 5,
}, },
{ {
desc: "moves cursor onto the first cell containing a full-width rune", desc: "moves cursor onto the first cell containing a full-width rune",
@ -1730,6 +1795,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦世界⇨", wantView: "⇦⇦世界⇨",
wantContent: "你好世界你", wantContent: "你好世界你",
wantCurIdx: 4, wantCurIdx: 4,
wantOnChangeCalls: 5,
}, },
{ {
desc: "moves cursor onto the second cell containing a full-width rune", desc: "moves cursor onto the second cell containing a full-width rune",
@ -1755,6 +1821,7 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦世界⇨", wantView: "⇦⇦世界⇨",
wantContent: "你好世界你", wantContent: "你好世界你",
wantCurIdx: 4, wantCurIdx: 4,
wantOnChangeCalls: 5,
}, },
{ {
desc: "moves cursor onto the second right arrow", desc: "moves cursor onto the second right arrow",
@ -1780,12 +1847,17 @@ func TestFieldEditor(t *testing.T) {
wantView: "⇦⇦世界⇨", wantView: "⇦⇦世界⇨",
wantContent: "你好世界你", wantContent: "你好世界你",
wantCurIdx: 2, wantCurIdx: 2,
wantOnChangeCalls: 5,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
fe := newFieldEditor() var changeCount int
fe := newFieldEditor(func(data string) {
changeCount++
})
if tc.ops != nil { if tc.ops != nil {
if err := tc.ops(fe); err != nil { if err := tc.ops(fe); err != nil {
t.Fatalf("ops => unexpected error: %v", err) t.Fatalf("ops => unexpected error: %v", err)
@ -1808,6 +1880,10 @@ func TestFieldEditor(t *testing.T) {
if gotContent != tc.wantContent { if gotContent != tc.wantContent {
t.Errorf("content -> %q, want %q", gotContent, tc.wantContent) t.Errorf("content -> %q, want %q", gotContent, tc.wantContent)
} }
if tc.wantOnChangeCalls != changeCount {
t.Errorf("unexpected number of onChange calls -> %d, want %d", changeCount, tc.wantOnChangeCalls)
}
}) })
} }
} }

View File

@ -63,6 +63,7 @@ type options struct {
filter FilterFn filter FilterFn
onSubmit SubmitFn onSubmit SubmitFn
onChange ChangeFn
clearOnSubmit bool clearOnSubmit bool
exclusiveKeyboardOnFocus bool exclusiveKeyboardOnFocus bool
} }
@ -269,6 +270,21 @@ func OnSubmit(fn SubmitFn) Option {
}) })
} }
// ChangeFn when passed to OnChage will be called with all the text in the text
// input each time it gets modified.
//
// This function must be thread-safe as the keyboard event that
// triggers the submission comes from a separate goroutine.
type ChangeFn func(data string)
// OnChange sets a function that will be called when the content of the text input
// field changes.
func OnChange(fn ChangeFn) Option {
return option(func(opts *options) {
opts.onChange = fn
})
}
// ClearOnSubmit sets the text input to be cleared when a submit of the content // ClearOnSubmit sets the text input to be cleared when a submit of the content
// is triggered by the user pressing the Enter key. // is triggered by the user pressing the Enter key.
func ClearOnSubmit() Option { func ClearOnSubmit() Option {

View File

@ -70,10 +70,9 @@ func New(opts ...Option) (*TextInput, error) {
return nil, err return nil, err
} }
ti := &TextInput{ ti := &TextInput{
editor: newFieldEditor(), editor: newFieldEditor(opt.onChange),
opts: opt, opts: opt,
} }
for _, r := range ti.opts.defaultText { for _, r := range ti.opts.defaultText {
ti.editor.insert(r) ti.editor.insert(r)
} }

View File

@ -29,13 +29,16 @@ import (
"github.com/mum4k/termdash/terminal/tcell" "github.com/mum4k/termdash/terminal/tcell"
"github.com/mum4k/termdash/widgets/button" "github.com/mum4k/termdash/widgets/button"
"github.com/mum4k/termdash/widgets/segmentdisplay" "github.com/mum4k/termdash/widgets/segmentdisplay"
"github.com/mum4k/termdash/widgets/text"
"github.com/mum4k/termdash/widgets/textinput" "github.com/mum4k/termdash/widgets/textinput"
) )
// rotate returns a new slice with inputs rotated by step. // rotate returns a new slice with inputs rotated by step.
// I.e. for a step of one: // I.e. for a step of one:
//
// inputs[0] -> inputs[len(inputs)-1] // inputs[0] -> inputs[len(inputs)-1]
// inputs[1] -> inputs[0] // inputs[1] -> inputs[0]
//
// And so on. // And so on.
func rotate(inputs []rune, step int) []rune { func rotate(inputs []rune, step int) []rune {
return append(inputs[step:], inputs[:step]...) return append(inputs[step:], inputs[:step]...)
@ -126,11 +129,20 @@ func main() {
updateText := make(chan string) updateText := make(chan string)
go rollText(ctx, rollingSD, updateText) go rollText(ctx, rollingSD, updateText)
mirror, err := text.New()
if err != nil {
panic(err)
}
input, err := textinput.New( input, err := textinput.New(
textinput.Label("New text:", cell.FgColor(cell.ColorNumber(33))), textinput.Label("New text:", cell.FgColor(cell.ColorNumber(33))),
textinput.MaxWidthCells(20), textinput.MaxWidthCells(20),
textinput.Border(linestyle.Light), textinput.Border(linestyle.Light),
textinput.PlaceHolder("Enter any text"), textinput.PlaceHolder("Enter any text"),
textinput.OnChange(func(data string) {
mirror.Reset()
mirror.Write(data)
}),
) )
if err != nil { if err != nil {
panic(err) panic(err)
@ -177,7 +189,8 @@ func main() {
), ),
) )
builder.Add( builder.Add(
grid.RowHeightPerc(20, grid.RowHeightPerc(40,
grid.RowHeightPerc(50,
grid.Widget( grid.Widget(
input, input,
container.AlignHorizontal(align.HorizontalCenter), container.AlignHorizontal(align.HorizontalCenter),
@ -185,10 +198,21 @@ func main() {
container.MarginBottom(1), container.MarginBottom(1),
), ),
), ),
grid.RowHeightPerc(50,
grid.Widget(
mirror,
container.Border(linestyle.Light),
container.BorderTitle("Text"),
container.AlignHorizontal(align.HorizontalCenter),
container.AlignHorizontal(align.Horizontal(align.VerticalBottom)),
container.MarginBottom(1),
),
),
),
) )
builder.Add( builder.Add(
grid.RowHeightPerc(40, grid.RowHeightPerc(20,
grid.ColWidthPerc(20), grid.ColWidthPerc(20),
grid.ColWidthPerc(20, grid.ColWidthPerc(20,
grid.Widget( grid.Widget(