1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-18 13:55:23 -05:00

Add regex scrollback buffer search

This adds a new regex search which allows searching the entire
scrollback and jumping between matches using the vi mode.

All visible matches should be highlighted unless their lines are
excessively long. This should help with performance since highlighting
is done during render time.

Fixes #1017.
This commit is contained in:
Christian Duerr 2020-07-09 21:45:22 +00:00 committed by GitHub
parent 9974bc8baa
commit 46c0f352c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 2602 additions and 942 deletions

View file

@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Default Command+N keybinding for SpawnNewInstance on macOS
- Vi mode for copying text and opening links
- Vi mode for regex search, copying text, and opening links
- `CopySelection` action which copies into selection buffer on Linux/BSD
- Option `cursor.thickness` to set terminal cursor thickness
- Font fallback on Windows
@ -42,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dragging files into terminal now adds a space after each path
- Default binding replacement conditions
- Adjusted selection clearing granularity to more accurately match content
- To use the cell's text color for selection with a modified background, the `color.selection.text`
variable must now be set to `CellForeground` instead of omitting it
### Fixed
@ -58,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Linewrap tracking when switching between primary and alternate screen buffer
- Preservation of the alternate screen's saved cursor when swapping to primary screen and back
- Reflow of cursor during resize
- Cursor color escape ignored when its color is set to inverted in the config
## 0.4.3

231
Cargo.lock generated
View file

@ -2,7 +2,7 @@
# It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.0.4"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -26,15 +26,16 @@ dependencies = [
"font 0.1.0",
"gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glutin 0.24.1 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.4 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_tools_util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"urlocator 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"wayland-client 0.26.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -46,7 +47,7 @@ dependencies = [
name = "alacritty_terminal"
version = "0.5.0-dev"
dependencies = [
"base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"font 0.1.0",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
@ -55,14 +56,15 @@ dependencies = [
"mio-anonymous-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"terminfo 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vte 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -106,7 +108,7 @@ name = "approx"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -129,7 +131,7 @@ name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"hermit-abi 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -146,7 +148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "base64"
version = "0.12.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -165,7 +167,7 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -244,7 +246,7 @@ name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -411,7 +413,7 @@ name = "deflate"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"adler32 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -421,8 +423,8 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -464,7 +466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dtoa"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -474,8 +476,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"wio 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -506,7 +508,7 @@ name = "euclid"
version = "0.20.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -537,7 +539,7 @@ dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -587,8 +589,8 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -755,7 +757,7 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
@ -780,23 +782,15 @@ dependencies = [
[[package]]
name = "image"
version = "0.23.4"
version = "0.23.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.16.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "inflate"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.16.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -832,7 +826,7 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -899,7 +893,7 @@ name = "line_drawing"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -952,10 +946,10 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"adler32 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -982,7 +976,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"spsc-buffer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1005,7 +999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1022,7 +1016,7 @@ dependencies = [
[[package]]
name = "miow"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1110,7 +1104,7 @@ dependencies = [
[[package]]
name = "nom"
version = "5.1.1"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1136,21 +1130,21 @@ dependencies = [
[[package]]
name = "num-integer"
version = "0.1.42"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-iter"
version = "0.1.40"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1159,13 +1153,13 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1187,8 +1181,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-crate 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1249,7 +1243,7 @@ dependencies = [
"cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1257,7 +1251,7 @@ name = "ordered-float"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1341,13 +1335,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "png"
version = "0.16.4"
version = "0.16.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"deflate 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1399,7 +1393,7 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1485,6 +1479,15 @@ dependencies = [
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-automata"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
@ -1492,7 +1495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "remove_dir_all"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1592,40 +1595,40 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.111"
version = "1.0.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde_derive 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive"
version = "1.0.111"
version = "1.0.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.53"
version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_yaml"
version = "0.8.12"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1664,7 +1667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "signal-hook"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1768,11 +1771,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "1.0.30"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1785,7 +1788,7 @@ dependencies = [
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1804,7 +1807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"phf_codegen 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1839,7 +1842,7 @@ name = "toml"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1877,7 +1880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vcpkg"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -1928,7 +1931,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2004,7 +2007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wayland-client 0.26.6 (registry+https://github.com/rust-lang/crates.io-index)",
"xcursor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"xcursor 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2045,7 +2048,7 @@ version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -2136,7 +2139,7 @@ dependencies = [
"parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"raw-window-handle 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)",
"smithay-client-toolkit 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"wayland-client 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2152,7 +2155,7 @@ dependencies = [
"http_req 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winpty-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"zip 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"zip 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2219,10 +2222,10 @@ dependencies = [
[[package]]
name = "xcursor"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2245,7 +2248,7 @@ dependencies = [
[[package]]
name = "zip"
version = "0.5.5"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2256,7 +2259,7 @@ dependencies = [
]
[metadata]
"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
"checksum adler32 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
"checksum andrew 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b7f09f89872c2b6b29e319377b1fbe91c6f5947df19a25596e121cf19a7b35e"
"checksum android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407"
@ -2269,7 +2272,7 @@ dependencies = [
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
"checksum base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42"
"checksum base64 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e223af0dc48c96d4f8342ec01a4974f139df863896b316681efd36742f22cc67"
"checksum bindgen 0.53.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c72a978d268b1d70b0e963217e60fdabd9523a941457a6c42a7315d15c7e89e5"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
@ -2305,7 +2308,7 @@ dependencies = [
"checksum dispatch 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
"checksum dlib 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76"
"checksum downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52ba6eb47c2131e784a38b726eb54c1e1484904f013e576a25354d0124161af6"
"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
"checksum dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
"checksum dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
"checksum embed-resource 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1f6b0b4403da80c2fd32333937dd468292c001d778c587ae759b75432772715d"
"checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
@ -2335,16 +2338,15 @@ dependencies = [
"checksum glutin_gles2_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07e853d96bebcb8e53e445225c3009758c6f5960d44f2543245f6f07b567dae0"
"checksum glutin_glx_sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "08c243de74d6cf5ea100c788826d2fb9319de315485dd4b310811a663b3809c3"
"checksum glutin_wgl_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a93dba7ee3a0feeac0f437141ff25e71ce2066bcf1a706acab1559ffff94eb6a"
"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
"checksum hermit-abi 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
"checksum http_req 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ef9a6b5b2cd80630d9c6bda175408a86908d8a9c1eb5b2857206529d88d063a3"
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
"checksum image 0.23.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9117f4167a8f21fa2bb3f17a652a760acd7572645281c98e3b612a26242c96ee"
"checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
"checksum image 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d534e95ad8b9d5aa614322d02352b4f1bf962254adcf02ac6f2def8be18498e8"
"checksum inotify 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
"checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
"checksum instant 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7777a24a1ce5de49fcdde84ec46efa487c3af49d5b6e6e0a50367cc5c1096182"
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
"checksum jni-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
"checksum jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
@ -2362,13 +2364,13 @@ dependencies = [
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
"checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5"
"checksum miniz_oxide 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
"checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
"checksum mio-anonymous-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8c274c3c52dcd1d78c5d7ed841eca1e9ea2db8353f3b8ec25789cc62c471aaf"
"checksum mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum miow 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22dfdd1d51b2639a5abd17ed07005c3af05fb7a2a3b1a1d0d7af1000a520c1c7"
"checksum miow 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
"checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
"checksum ndk 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "95a356cafe20aee088789830bfea3a61336e84ded9e545e00d3869ce95dcb80c"
"checksum ndk-glue 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d1730ee2e3de41c3321160a6da815f008c4006d71b095880ea50e17cf52332b8"
@ -2376,12 +2378,12 @@ dependencies = [
"checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
"checksum nom 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
"checksum notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd"
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
"checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00"
"checksum num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
"checksum num-iter 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
"checksum num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
"checksum num_enum 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4"
"checksum num_enum_derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d"
"checksum objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
@ -2402,7 +2404,7 @@ dependencies = [
"checksum phf_generator 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
"checksum phf_shared 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum png 0.16.4 (registry+https://github.com/rust-lang/crates.io-index)" = "12faa637ed9ae3d3c881332e54b5ae2dba81cda9fc4bbce0faa1ba53abcead50"
"checksum png 0.16.5 (registry+https://github.com/rust-lang/crates.io-index)" = "34ccdd66f6fe4b2433b07e4728e9a013e43233120427046e93ceb709c3a439bf"
"checksum podio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b18befed8bc2b61abc79a457295e7e838417326da1586050b919414073977f19"
"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
"checksum proc-macro-crate 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e"
@ -2410,7 +2412,7 @@ dependencies = [
"checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
@ -2420,8 +2422,9 @@ dependencies = [
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
"checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
"checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
"checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
"checksum rustc-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
"checksum rustc_tools_util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b725dadae9fabc488df69a287f5a99c5eaf5d10853842a8a3dfac52476f544ee"
@ -2434,15 +2437,15 @@ dependencies = [
"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
"checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
"checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405"
"checksum serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)" = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
"checksum serde_derive 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
"checksum serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2"
"checksum serde_yaml 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)" = "16c7a592a1ec97c9c1c68d75b6e537dcbf60c7618e038e7841e00af1d9ccf0c4"
"checksum serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)" = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243"
"checksum serde_derive 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)" = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57"
"checksum serde_json 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226"
"checksum serde_yaml 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5"
"checksum servo-fontconfig 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b47fef69c52fb55838c756949c60595f0b855daa4e82fc52ad99ff3e03e2c70"
"checksum servo-fontconfig-sys 5.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "facb23c6a801c935c3bddfdd7dc4e823af853babc5b0c90ffa3419ebef5d92c7"
"checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
"checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
"checksum signal-hook 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff2db2112d6c761e12522c65f7768548bd6e8cd23d2a9dae162520626629bd6"
"checksum signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
"checksum siphasher 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
@ -2454,7 +2457,7 @@ dependencies = [
"checksum spsc-buffer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be6c3f39c37a4283ee4b43d1311c828f2e1fb0541e76ea0cb1a2abd9ef2f5b3b"
"checksum stb_truetype 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2"
"checksum syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
"checksum terminfo 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
@ -2468,7 +2471,7 @@ dependencies = [
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum urlocator 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "317bb1e85e87e72c11cb9cda7cb8909ca174a21d0abeb0f6955b8f6b0178b164"
"checksum utf8parse 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
"checksum vcpkg 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "55d1e41d56121e07f1e223db0a4def204e45c85425f6a16d462fd07c8d10d74c"
"checksum vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
@ -2505,8 +2508,8 @@ dependencies = [
"checksum x11-clipboard 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e5e937afd03b64b7be4f959cc044e09260a47241b71e56933f37db097bf7859d"
"checksum x11-dl 2.18.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8"
"checksum xcb 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62056f63138b39116f82a540c983cc11f1c90cd70b3d492a70c25eaa50bd22a6"
"checksum xcursor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "56edec90b342d0f46d1189f3fd4a3374954636f3d76c861855b675efc7ef1435"
"checksum xcursor 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a481cfdefd35e1c50073ae33a8000d695c98039544659f5dc5dd71311b0d01"
"checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
"checksum xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
"checksum yaml-rust 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
"checksum zip 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6df134e83b8f0f8153a094c7b0fd79dfebe437f1d76e7715afa18ed95ebe2fd7"
"checksum zip 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58287c28d78507f5f91f2a4cf1e8310e2c76fd4c6932f93ac60fd1ceb402db7d"

View file

@ -176,28 +176,48 @@
# Cursor colors
#
# Colors which should be used to draw the terminal cursor. If these are
# unset, the cursor color will be the inverse of the cell color.
# Colors which should be used to draw the terminal cursor.
#
# Allowed values are CellForeground and CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#cursor:
# text: '#000000'
# cursor: '#ffffff'
# text: CellBackground
# cursor: CellForeground
# Vi mode cursor colors
#
# Colors for the cursor when the vi mode is active. If these are unset, the
# cursor color will be the inverse of the cell color.
# Colors for the cursor when the vi mode is active.
#
# Allowed values are CellForeground and CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#vi_mode_cursor:
# text: '#000000'
# cursor: '#ffffff'
# text: CellBackground
# cursor: CellForeground
# Selection colors
#
# Colors which should be used to draw the selection area. If selection
# background is unset, selection color will be the inverse of the cell colors.
# If only text is unset the cell text color will remain the same.
# Colors which should be used to draw the selection area.
#
# Allowed values are CellForeground and CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#selection:
# text: '#eaeaea'
# background: '#404040'
# text: CellBackground
# background: CellForeground
# Search colors
#
# Colors used for the search bar and match highlighting.
#
# Allowed values are CellForeground and CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#search:
# matches:
# foreground: '#000000'
# background: '#ffffff'
#
# bar:
# background: CellForeground
# foreground: CellBackground
# Normal colors
#normal:
@ -445,6 +465,8 @@
# - `action`: Execute a predefined action
#
# - ToggleViMode
# - Search
# - SearchReverse
# - Copy
# - Paste
# - PasteSelection
@ -495,6 +517,10 @@
# - ToggleLineSelection
# - ToggleBlockSelection
# - ToggleSemanticSelection
# - SearchNext
# - SearchPrevious
# - SearchEndNext
# - SearchEndPrevious
#
# (macOS only):
# - ToggleSimpleFullscreen: Enters fullscreen without occupying another space
@ -595,6 +621,10 @@
#- { key: W, mods: Shift, mode: Vi, action: WordRight }
#- { key: E, mods: Shift, mode: Vi, action: WordRightEnd }
#- { key: Key5, mods: Shift, mode: Vi, action: Bracket }
#- { key: Slash, mode: Vi, action: Search }
#- { key: Slash, mods: Shift, mode: Vi, action: SearchReverse }
#- { key: N, mode: Vi, action: SearchNext }
#- { key: N, mods: Shift, mode: Vi, action: SearchPrevious }
# (Windows, Linux, and BSD only)
#- { key: V, mods: Control|Shift, action: Paste }

View file

@ -23,6 +23,7 @@ parking_lot = "0.10.2"
font = { path = "../font" }
urlocator = "0.1.3"
copypasta = { version = "0.7.0", default-features = false }
unicode-width = "0.1"
[build-dependencies]
gl_generator = "0.14.0"

View file

@ -176,6 +176,12 @@ pub enum Action {
/// Allow receiving char input.
ReceiveChar,
/// Start a buffer search.
Search,
/// Start a reverse buffer search.
SearchReverse,
/// No action.
None,
}
@ -208,6 +214,14 @@ pub enum ViAction {
ToggleBlockSelection,
/// Toggle semantic vi selection.
ToggleSemanticSelection,
/// Jump to the beginning of the next match.
SearchNext,
/// Jump to the beginning of the previous match.
SearchPrevious,
/// Jump to the end of the next match.
SearchEndNext,
/// Jump to the end of the previous match.
SearchEndPrevious,
/// Launch the URL below the vi mode cursor.
Open,
}
@ -364,10 +378,14 @@ pub fn default_key_bindings() -> Vec<KeyBinding> {
D, ModifiersState::CTRL, +TermMode::VI; Action::ScrollHalfPageDown;
Y, +TermMode::VI; Action::Copy;
Y, +TermMode::VI; Action::ClearSelection;
Slash, +TermMode::VI; Action::Search;
Slash, ModifiersState::SHIFT, +TermMode::VI; Action::SearchReverse;
V, +TermMode::VI; ViAction::ToggleNormalSelection;
V, ModifiersState::SHIFT, +TermMode::VI; ViAction::ToggleLineSelection;
V, ModifiersState::CTRL, +TermMode::VI; ViAction::ToggleBlockSelection;
V, ModifiersState::ALT, +TermMode::VI; ViAction::ToggleSemanticSelection;
N, +TermMode::VI; ViAction::SearchNext;
N, ModifiersState::SHIFT, +TermMode::VI; ViAction::SearchPrevious;
Return, +TermMode::VI; ViAction::Open;
K, +TermMode::VI; ViMotion::Up;
J, +TermMode::VI; ViMotion::Down;

View file

@ -1,6 +1,7 @@
//! The display subsystem including window management, font rasterization, and
//! GPU drawing.
use std::cmp::min;
use std::f64;
use std::fmt::{self, Formatter};
#[cfg(not(any(target_os = "macos", windows)))]
@ -15,6 +16,7 @@ use glutin::platform::unix::EventLoopWindowTargetExtUnix;
use glutin::window::CursorIcon;
use log::{debug, info};
use parking_lot::MutexGuard;
use unicode_width::UnicodeWidthChar;
#[cfg(not(any(target_os = "macos", windows)))]
use wayland_client::{Display as WaylandDisplay, EventQueue};
@ -23,21 +25,23 @@ use font::set_font_smoothing;
use font::{self, Rasterize};
use alacritty_terminal::config::{Font, StartupMode};
use alacritty_terminal::event::OnResize;
use alacritty_terminal::index::Line;
use alacritty_terminal::event::{EventListener, OnResize};
use alacritty_terminal::grid::Dimensions;
use alacritty_terminal::index::{Column, Line, Point};
use alacritty_terminal::message_bar::MessageBuffer;
use alacritty_terminal::meter::Meter;
use alacritty_terminal::selection::Selection;
use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::{RenderableCell, SizeInfo, Term, TermMode};
use crate::config::Config;
use crate::event::{DisplayUpdate, Mouse};
use crate::event::Mouse;
use crate::renderer::rects::{RenderLines, RenderRect};
use crate::renderer::{self, GlyphCache, QuadRenderer};
use crate::url::{Url, Urls};
use crate::window::{self, Window};
const SEARCH_LABEL: &str = "Search: ";
#[derive(Debug)]
pub enum Error {
/// Error with window management.
@ -99,6 +103,44 @@ impl From<glutin::ContextError> for Error {
}
}
#[derive(Default, Clone, Debug, PartialEq)]
pub struct DisplayUpdate {
pub dirty: bool,
dimensions: Option<PhysicalSize<u32>>,
font: Option<Font>,
cursor_dirty: bool,
}
impl DisplayUpdate {
pub fn dimensions(&self) -> Option<PhysicalSize<u32>> {
self.dimensions
}
pub fn font(&self) -> Option<&Font> {
self.font.as_ref()
}
pub fn cursor_dirty(&self) -> bool {
self.cursor_dirty
}
pub fn set_dimensions(&mut self, dimensions: PhysicalSize<u32>) {
self.dimensions = Some(dimensions);
self.dirty = true;
}
pub fn set_font(&mut self, font: Font) {
self.font = Some(font);
self.dirty = true;
}
pub fn set_cursor_dirty(&mut self) {
self.cursor_dirty = true;
self.dirty = true;
}
}
/// The display wraps a window, font rasterizer, and GPU renderer.
pub struct Display {
pub size_info: SizeInfo,
@ -300,7 +342,7 @@ impl Display {
}
/// Update font size and cell dimensions.
fn update_glyph_cache(&mut self, config: &Config, font: Font) {
fn update_glyph_cache(&mut self, config: &Config, font: &Font) {
let size_info = &mut self.size_info;
let cache = &mut self.glyph_cache;
@ -328,13 +370,16 @@ impl Display {
terminal: &mut Term<T>,
pty_resize_handle: &mut dyn OnResize,
message_buffer: &MessageBuffer,
search_active: bool,
config: &Config,
update_pending: DisplayUpdate,
) {
) where
T: EventListener,
{
// Update font size and cell dimensions.
if let Some(font) = update_pending.font {
if let Some(font) = update_pending.font() {
self.update_glyph_cache(config, font);
} else if update_pending.cursor {
} else if update_pending.cursor_dirty() {
self.clear_glyph_cache();
}
@ -346,7 +391,7 @@ impl Display {
let mut padding_y = f32::from(config.window.padding.y) * self.size_info.dpr as f32;
// Update the window dimensions.
if let Some(size) = update_pending.dimensions {
if let Some(size) = update_pending.dimensions() {
// Ensure we have at least one column and row.
self.size_info.width = (size.width as f32).max(cell_width + 2. * padding_x);
self.size_info.height = (size.height as f32).max(cell_height + 2. * padding_y);
@ -369,6 +414,11 @@ impl Display {
pty_size.height -= pty_size.cell_height * lines as f32;
}
// Add an extra line for the current search regex.
if search_active {
pty_size.height -= pty_size.cell_height;
}
// Resize PTY.
pty_resize_handle.on_resize(&pty_size);
@ -393,8 +443,10 @@ impl Display {
config: &Config,
mouse: &Mouse,
mods: ModifiersState,
search_regex: Option<&String>,
) {
let grid_cells: Vec<RenderableCell> = terminal.renderable_cells(config).collect();
let search_regex = search_regex.map(|regex| Self::format_search(&regex));
let visual_bell_intensity = terminal.visual_bell.intensity();
let background_color = terminal.background_color();
let metrics = self.glyph_cache.font_metrics();
@ -413,7 +465,17 @@ impl Display {
// Update IME position.
#[cfg(not(windows))]
self.window.update_ime_position(&terminal, &self.size_info);
{
let point = match &search_regex {
Some(regex) => {
let column = min(regex.len() + SEARCH_LABEL.len() - 1, terminal.cols().0 - 1);
Point::new(terminal.screen_lines() - 1, Column(column))
},
None => terminal.grid().cursor.point,
};
self.window.update_ime_position(point, &self.size_info);
}
// Drop terminal as early as possible to free lock.
drop(terminal);
@ -484,11 +546,13 @@ impl Display {
rects.push(visual_bell_rect);
}
let mut message_bar_lines = 0;
if let Some(message) = message_buffer.message() {
let text = message.text(&size_info);
message_bar_lines = text.len();
// Create a new rectangle for the background.
let start_line = size_info.lines().0 - text.len();
let start_line = size_info.lines().0 - message_bar_lines;
let y = size_info.cell_height.mul_add(start_line as f32, size_info.padding_y);
let message_bar_rect =
RenderRect::new(0., y, size_info.width, size_info.height - y, message.color(), 1.);
@ -500,31 +564,25 @@ impl Display {
self.renderer.draw_rects(&size_info, rects);
// Relay messages to the user.
let mut offset = 1;
for message_text in text.iter().rev() {
let fg = config.colors.primary.background;
for (i, message_text) in text.iter().rev().enumerate() {
self.renderer.with_api(&config, &size_info, |mut api| {
api.render_string(
&message_text,
Line(size_info.lines().saturating_sub(offset)),
glyph_cache,
Line(size_info.lines().saturating_sub(i + 1)),
&message_text,
fg,
None,
);
});
offset += 1;
}
} else {
// Draw rectangles.
self.renderer.draw_rects(&size_info, rects);
}
// Draw render timer.
if config.render_timer() {
let timing = format!("{:.3} usec", self.meter.average());
let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
self.renderer.with_api(&config, &size_info, |mut api| {
api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, Some(color));
});
}
self.draw_search(config, &size_info, message_bar_lines, search_regex);
self.draw_render_timer(config, &size_info);
// Frame event should be requested before swaping buffers, since it requires surface
// `commit`, which is done by swap buffers under the hood.
@ -546,6 +604,73 @@ impl Display {
}
}
/// Format search regex to account for the cursor and fullwidth characters.
fn format_search(search_regex: &str) -> String {
// Add spacers for wide chars.
let mut text = String::with_capacity(search_regex.len());
for c in search_regex.chars() {
text.push(c);
if c.width() == Some(2) {
text.push(' ');
}
}
// Add cursor to show whitespace.
text.push('_');
text
}
/// Draw current search regex.
fn draw_search(
&mut self,
config: &Config,
size_info: &SizeInfo,
message_bar_lines: usize,
search_regex: Option<String>,
) {
let search_regex = match search_regex {
Some(search_regex) => search_regex,
None => return,
};
let glyph_cache = &mut self.glyph_cache;
let label_len = SEARCH_LABEL.len();
let num_cols = size_info.cols().0;
// Truncate beginning of text when it exceeds viewport width.
let text_len = search_regex.len();
let truncate_len = min((text_len + label_len).saturating_sub(num_cols), text_len);
let text = &search_regex[truncate_len..];
// Assure text length is at least num_cols.
let padding_len = num_cols.saturating_sub(label_len);
let text = format!("{}{:<2$}", SEARCH_LABEL, text, padding_len);
let fg = config.colors.search_bar_foreground();
let bg = config.colors.search_bar_background();
let line = size_info.lines() - message_bar_lines - 1;
self.renderer.with_api(&config, &size_info, |mut api| {
api.render_string(glyph_cache, line, &text, fg, Some(bg));
});
}
/// Draw render timer.
fn draw_render_timer(&mut self, config: &Config, size_info: &SizeInfo) {
if !config.render_timer() {
return;
}
let glyph_cache = &mut self.glyph_cache;
let timing = format!("{:.3} usec", self.meter.average());
let fg = config.colors.normal().black;
let bg = config.colors.normal().red;
self.renderer.with_api(&config, &size_info, |mut api| {
api.render_string(glyph_cache, size_info.lines() - 2, &timing[..], fg, Some(bg));
});
}
/// Requst a new frame for a window on Wayland.
#[inline]
#[cfg(not(any(target_os = "macos", windows)))]

View file

@ -1,7 +1,7 @@
//! Process window events.
use std::borrow::Cow;
use std::cmp::max;
use std::cmp::{max, min};
use std::env;
#[cfg(unix)]
use std::fs;
@ -12,7 +12,7 @@ use std::path::PathBuf;
#[cfg(not(any(target_os = "macos", windows)))]
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Instant;
use std::time::{Duration, Instant};
use glutin::dpi::PhysicalSize;
use glutin::event::{ElementState, Event as GlutinEvent, ModifiersState, MouseButton, WindowEvent};
@ -27,12 +27,10 @@ use serde_json as json;
use font::set_font_smoothing;
use font::{self, Size};
use alacritty_terminal::config::Font;
use alacritty_terminal::config::LOG_TARGET_CONFIG;
use alacritty_terminal::event::OnResize;
use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify};
use alacritty_terminal::grid::Scroll;
use alacritty_terminal::index::{Column, Line, Point, Side};
use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify, OnResize};
use alacritty_terminal::grid::{Dimensions, Scroll};
use alacritty_terminal::index::{Column, Direction, Line, Point, Side};
use alacritty_terminal::message_bar::{Message, MessageBuffer};
use alacritty_terminal::selection::{Selection, SelectionType};
use alacritty_terminal::sync::FairMutex;
@ -46,12 +44,18 @@ use crate::cli::Options;
use crate::clipboard::Clipboard;
use crate::config;
use crate::config::Config;
use crate::display::Display;
use crate::display::{Display, DisplayUpdate};
use crate::input::{self, ActionContext as _, FONT_SIZE_STEP};
use crate::scheduler::Scheduler;
use crate::scheduler::{Scheduler, TimerId};
use crate::url::{Url, Urls};
use crate::window::Window;
/// Duration after the last user input until an unlimited search is performed.
pub const TYPING_SEARCH_DELAY: Duration = Duration::from_millis(500);
/// Maximum number of lines for the blocking search while still typing the search regex.
const MAX_SEARCH_WHILE_TYPING: Option<usize> = Some(1000);
/// Events dispatched through the UI event loop.
#[derive(Debug, Clone)]
pub enum Event {
@ -60,6 +64,7 @@ pub enum Event {
Scroll(Scroll),
ConfigReload(PathBuf),
Message(Message),
SearchNext,
}
impl From<Event> for GlutinEvent<'_, Event> {
@ -74,17 +79,35 @@ impl From<TerminalEvent> for Event {
}
}
#[derive(Default, Clone, Debug, PartialEq)]
pub struct DisplayUpdate {
pub dimensions: Option<PhysicalSize<u32>>,
pub message_buffer: bool,
pub font: Option<Font>,
pub cursor: bool,
/// Regex search state.
pub struct SearchState {
/// Search string regex.
regex: Option<String>,
/// Search direction.
direction: Direction,
/// Change in display offset since the beginning of the search.
display_offset_delta: isize,
/// Vi cursor position before search.
vi_cursor_point: Point,
}
impl DisplayUpdate {
fn is_empty(&self) -> bool {
self.dimensions.is_none() && self.font.is_none() && !self.message_buffer && !self.cursor
impl SearchState {
fn new() -> Self {
Self::default()
}
}
impl Default for SearchState {
fn default() -> Self {
Self {
direction: Direction::Right,
display_offset_delta: 0,
vi_cursor_point: Point::default(),
regex: None,
}
}
}
@ -104,6 +127,7 @@ pub struct ActionContext<'a, N, T> {
pub event_loop: &'a EventLoopWindowTarget<Event>,
pub urls: &'a Urls,
pub scheduler: &'a mut Scheduler,
pub search_state: &'a mut SearchState,
font_size: &'a mut Size,
}
@ -294,19 +318,148 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
fn change_font_size(&mut self, delta: f32) {
*self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP));
let font = self.config.font.clone().with_size(*self.font_size);
self.display_update_pending.font = Some(font);
self.display_update_pending.set_font(font);
self.terminal.dirty = true;
}
fn reset_font_size(&mut self) {
*self.font_size = self.config.font.size;
self.display_update_pending.font = Some(self.config.font.clone());
self.display_update_pending.set_font(self.config.font.clone());
self.terminal.dirty = true;
}
#[inline]
fn pop_message(&mut self) {
self.display_update_pending.message_buffer = true;
self.message_buffer.pop();
if !self.message_buffer.is_empty() {
self.display_update_pending.dirty = true;
self.message_buffer.pop();
}
}
#[inline]
fn start_search(&mut self, direction: Direction) {
let num_lines = self.terminal.screen_lines();
let num_cols = self.terminal.cols();
self.search_state.regex = Some(String::new());
self.search_state.direction = direction;
// Store original vi cursor position as search origin and for resetting.
self.search_state.vi_cursor_point = if self.terminal.mode().contains(TermMode::VI) {
self.terminal.vi_mode_cursor.point
} else {
match direction {
Direction::Right => Point::new(Line(0), Column(0)),
Direction::Left => Point::new(num_lines - 2, num_cols - 1),
}
};
self.display_update_pending.dirty = true;
self.terminal.dirty = true;
}
#[inline]
fn confirm_search(&mut self) {
// Enter vi mode once search is confirmed.
self.terminal.set_vi_mode();
// Force unlimited search if the previous one was interrupted.
if self.scheduler.scheduled(TimerId::DelayedSearch) {
self.goto_match(None);
}
// Move vi cursor down if resize will pull content from history.
if self.terminal.history_size() != 0 && self.terminal.grid().display_offset() == 0 {
self.terminal.vi_mode_cursor.point.line += 1;
}
// Clear reset state.
self.search_state.display_offset_delta = 0;
self.display_update_pending.dirty = true;
self.search_state.regex = None;
self.terminal.dirty = true;
}
#[inline]
fn cancel_search(&mut self) {
self.terminal.cancel_search();
// Recover pre-search state.
self.search_reset_state();
// Move vi cursor down if resize will pull from history.
if self.terminal.history_size() != 0 && self.terminal.grid().display_offset() == 0 {
self.terminal.vi_mode_cursor.point.line += 1;
}
self.display_update_pending.dirty = true;
self.search_state.regex = None;
self.terminal.dirty = true;
}
#[inline]
fn push_search(&mut self, c: char) {
let regex = match self.search_state.regex.as_mut() {
Some(regex) => regex,
None => return,
};
// Hide cursor while typing into the search bar.
if self.config.ui_config.mouse.hide_when_typing {
self.window.set_mouse_visible(false);
}
// Add new char to search string.
regex.push(c);
// Create terminal search from the new regex string.
self.terminal.start_search(&regex);
// Update search highlighting.
self.goto_match(MAX_SEARCH_WHILE_TYPING);
self.terminal.dirty = true;
}
#[inline]
fn pop_search(&mut self) {
let regex = match self.search_state.regex.as_mut() {
Some(regex) => regex,
None => return,
};
// Hide cursor while typing into the search bar.
if self.config.ui_config.mouse.hide_when_typing {
self.window.set_mouse_visible(false);
}
// Remove last char from search string.
regex.pop();
if regex.is_empty() {
// Stop search if there's nothing to search for.
self.search_reset_state();
self.terminal.cancel_search();
} else {
// Create terminal search from the new regex string.
self.terminal.start_search(&regex);
// Update search highlighting.
self.goto_match(MAX_SEARCH_WHILE_TYPING);
}
self.terminal.dirty = true;
}
#[inline]
fn search_direction(&self) -> Direction {
self.search_state.direction
}
#[inline]
fn search_active(&self) -> bool {
self.search_state.regex.is_some()
}
fn message(&self) -> Option<&Message> {
@ -334,6 +487,73 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
}
impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
/// Reset terminal to the state before search was started.
fn search_reset_state(&mut self) {
// Reset display offset.
self.terminal.scroll_display(Scroll::Delta(self.search_state.display_offset_delta));
self.search_state.display_offset_delta = 0;
// Reset vi mode cursor.
let mut vi_cursor_point = self.search_state.vi_cursor_point;
vi_cursor_point.line = min(vi_cursor_point.line, self.terminal.screen_lines() - 1);
vi_cursor_point.col = min(vi_cursor_point.col, self.terminal.cols() - 1);
self.terminal.vi_mode_cursor.point = vi_cursor_point;
// Unschedule pending timers.
self.scheduler.unschedule(TimerId::DelayedSearch);
}
/// Jump to the first regex match from the search origin.
fn goto_match(&mut self, mut limit: Option<usize>) {
let regex = match self.search_state.regex.take() {
Some(regex) => regex,
None => return,
};
// Limit search only when enough lines are available to run into the limit.
limit = limit.filter(|&limit| limit <= self.terminal.total_lines());
// Use original position as search origin.
let mut vi_cursor_point = self.search_state.vi_cursor_point;
vi_cursor_point.line = min(vi_cursor_point.line, self.terminal.screen_lines() - 1);
let mut origin = self.terminal.visible_to_buffer(vi_cursor_point);
origin.line = (origin.line as isize + self.search_state.display_offset_delta) as usize;
// Jump to the next match.
let direction = self.search_state.direction;
match self.terminal.search_next(origin, direction, Side::Left, limit) {
Some(regex_match) => {
let old_offset = self.terminal.grid().display_offset() as isize;
self.terminal.vi_goto_point(*regex_match.start());
// Store number of lines the viewport had to be moved.
let display_offset = self.terminal.grid().display_offset();
self.search_state.display_offset_delta += old_offset - display_offset as isize;
// Since we found a result, we require no delayed re-search.
self.scheduler.unschedule(TimerId::DelayedSearch);
},
// Reset viewport only when we know there is no match, to prevent unnecessary jumping.
None if limit.is_none() => self.search_reset_state(),
None => {
// Schedule delayed search if we ran into our search limit.
if !self.scheduler.scheduled(TimerId::DelayedSearch) {
self.scheduler.schedule(
Event::SearchNext.into(),
TYPING_SEARCH_DELAY,
false,
TimerId::DelayedSearch,
);
}
},
}
self.search_state.regex = Some(regex);
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum ClickState {
None,
@ -400,6 +620,7 @@ pub struct Processor<N> {
display: Display,
font_size: Size,
event_queue: Vec<GlutinEvent<'static, Event>>,
search_state: SearchState,
}
impl<N: Notify + OnResize> Processor<N> {
@ -429,6 +650,7 @@ impl<N: Notify + OnResize> Processor<N> {
display,
event_queue: Vec::new(),
clipboard,
search_state: SearchState::new(),
}
}
@ -513,6 +735,7 @@ impl<N: Notify + OnResize> Processor<N> {
let mut terminal = terminal.lock();
let mut display_update_pending = DisplayUpdate::default();
let old_is_searching = self.search_state.regex.is_some();
let context = ActionContext {
terminal: &mut terminal,
@ -530,6 +753,7 @@ impl<N: Notify + OnResize> Processor<N> {
config: &mut self.config,
urls: &self.display.urls,
scheduler: &mut scheduler,
search_state: &mut self.search_state,
event_loop,
};
let mut processor = input::Processor::new(context, &self.display.highlighted_url);
@ -539,14 +763,8 @@ impl<N: Notify + OnResize> Processor<N> {
}
// Process DisplayUpdate events.
if !display_update_pending.is_empty() {
self.display.handle_update(
&mut terminal,
&mut self.notifier,
&self.message_buffer,
&self.config,
display_update_pending,
);
if display_update_pending.dirty {
self.submit_display_update(&mut terminal, old_is_searching, display_update_pending);
}
#[cfg(not(any(target_os = "macos", windows)))]
@ -575,12 +793,15 @@ impl<N: Notify + OnResize> Processor<N> {
&self.config,
&self.mouse,
self.modifiers,
self.search_state.regex.as_ref(),
);
}
});
// Write ref tests to disk.
self.write_ref_test_results(&terminal.lock());
if self.config.debug.ref_test {
self.write_ref_test_results(&terminal.lock());
}
}
/// Handle events from glutin.
@ -598,20 +819,21 @@ impl<N: Notify + OnResize> Processor<N> {
let display_update_pending = &mut processor.ctx.display_update_pending;
// Push current font to update its DPR.
display_update_pending.font =
Some(processor.ctx.config.font.clone().with_size(*processor.ctx.font_size));
let font = processor.ctx.config.font.clone();
display_update_pending.set_font(font.with_size(*processor.ctx.font_size));
// Resize to event's dimensions, since no resize event is emitted on Wayland.
display_update_pending.dimensions = Some(PhysicalSize::new(width, height));
display_update_pending.set_dimensions(PhysicalSize::new(width, height));
processor.ctx.size_info.dpr = scale_factor;
processor.ctx.terminal.dirty = true;
},
Event::Message(message) => {
processor.ctx.message_buffer.push(message);
processor.ctx.display_update_pending.message_buffer = true;
processor.ctx.display_update_pending.dirty = true;
processor.ctx.terminal.dirty = true;
},
Event::SearchNext => processor.ctx.goto_match(None),
Event::ConfigReload(path) => Self::reload_config(&path, processor),
Event::Scroll(scroll) => processor.ctx.scroll(scroll),
Event::TerminalEvent(event) => match event {
@ -647,7 +869,7 @@ impl<N: Notify + OnResize> Processor<N> {
}
}
processor.ctx.display_update_pending.dimensions = Some(size);
processor.ctx.display_update_pending.set_dimensions(size);
processor.ctx.terminal.dirty = true;
},
WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => {
@ -741,14 +963,14 @@ impl<N: Notify + OnResize> Processor<N> {
}
}
pub fn reload_config<T>(
path: &PathBuf,
processor: &mut input::Processor<T, ActionContext<N, T>>,
) where
fn reload_config<T>(path: &PathBuf, processor: &mut input::Processor<T, ActionContext<N, T>>)
where
T: EventListener,
{
processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG);
processor.ctx.display_update_pending.message_buffer = true;
if !processor.ctx.message_buffer.is_empty() {
processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG);
processor.ctx.display_update_pending.dirty = true;
}
let config = match config::reload_from(&path) {
Ok(config) => config,
@ -764,7 +986,7 @@ impl<N: Notify + OnResize> Processor<N> {
if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs()
> std::f64::EPSILON
{
processor.ctx.display_update_pending.cursor = true;
processor.ctx.display_update_pending.set_cursor_dirty();
}
if processor.ctx.config.font != config.font {
@ -774,7 +996,7 @@ impl<N: Notify + OnResize> Processor<N> {
}
let font = config.font.clone().with_size(*processor.ctx.font_size);
processor.ctx.display_update_pending.font = Some(font);
processor.ctx.display_update_pending.set_font(font);
}
#[cfg(not(any(target_os = "macos", windows)))]
@ -793,12 +1015,44 @@ impl<N: Notify + OnResize> Processor<N> {
processor.ctx.terminal.dirty = true;
}
// Write the ref test results to the disk.
pub fn write_ref_test_results<T>(&self, terminal: &Term<T>) {
if !self.config.debug.ref_test {
return;
}
/// Submit the pending changes to the `Display`.
fn submit_display_update<T>(
&mut self,
terminal: &mut Term<T>,
old_is_searching: bool,
display_update_pending: DisplayUpdate,
) where
T: EventListener,
{
// Compute cursor positions before resize.
let num_lines = terminal.screen_lines();
let cursor_at_bottom = terminal.grid().cursor.point.line + 1 == num_lines;
let origin_at_bottom = (!terminal.mode().contains(TermMode::VI)
&& self.search_state.direction == Direction::Left)
|| terminal.vi_mode_cursor.point.line == num_lines - 1;
self.display.handle_update(
terminal,
&mut self.notifier,
&self.message_buffer,
self.search_state.regex.is_some(),
&self.config,
display_update_pending,
);
// Scroll to make sure search origin is visible and content moves as little as possible.
if !old_is_searching && self.search_state.regex.is_some() {
let display_offset = terminal.grid().display_offset();
if display_offset == 0 && cursor_at_bottom && !origin_at_bottom {
terminal.scroll_display(Scroll::Delta(1));
} else if display_offset != 0 && origin_at_bottom {
terminal.scroll_display(Scroll::Delta(-1));
}
}
}
/// Write the ref test results to the disk.
fn write_ref_test_results<T>(&self, terminal: &Term<T>) {
// Dump grid state.
let mut grid = terminal.grid().clone();
grid.initialize_all(Cell::default());

View file

@ -15,6 +15,7 @@ use log::{debug, trace, warn};
use glutin::dpi::PhysicalPosition;
use glutin::event::{
ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
VirtualKeyCode,
};
use glutin::event_loop::EventLoopWindowTarget;
#[cfg(target_os = "macos")]
@ -23,8 +24,8 @@ use glutin::window::CursorIcon;
use alacritty_terminal::ansi::{ClearMode, Handler};
use alacritty_terminal::event::EventListener;
use alacritty_terminal::grid::Scroll;
use alacritty_terminal::index::{Column, Line, Point, Side};
use alacritty_terminal::grid::{Dimensions, Scroll};
use alacritty_terminal::index::{Column, Direction, Line, Point, Side};
use alacritty_terminal::message_bar::{self, Message};
use alacritty_terminal::selection::SelectionType;
use alacritty_terminal::term::mode::TermMode;
@ -34,7 +35,7 @@ use alacritty_terminal::vi_mode::ViMotion;
use crate::clipboard::Clipboard;
use crate::config::{Action, Binding, Config, Key, ViAction};
use crate::event::{ClickState, Event, Mouse};
use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY};
use crate::scheduler::{Scheduler, TimerId};
use crate::url::{Url, Urls};
use crate::window::Window;
@ -93,6 +94,13 @@ pub trait ActionContext<T: EventListener> {
fn mouse_mode(&self) -> bool;
fn clipboard_mut(&mut self) -> &mut Clipboard;
fn scheduler_mut(&mut self) -> &mut Scheduler;
fn start_search(&mut self, direction: Direction);
fn confirm_search(&mut self);
fn cancel_search(&mut self);
fn push_search(&mut self, c: char);
fn pop_search(&mut self);
fn search_direction(&self) -> Direction;
fn search_active(&self) -> bool;
}
trait Execute<T: EventListener> {
@ -159,6 +167,13 @@ impl<T: EventListener> Execute<T> for Action {
},
Action::ClearSelection => ctx.clear_selection(),
Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(),
Action::ViMotion(motion) => {
if ctx.config().ui_config.mouse.hide_when_typing {
ctx.window_mut().set_mouse_visible(false);
}
ctx.terminal_mut().vi_motion(motion)
},
Action::ViAction(ViAction::ToggleNormalSelection) => {
Self::toggle_selection(ctx, SelectionType::Simple)
},
@ -177,13 +192,44 @@ impl<T: EventListener> Execute<T> for Action {
ctx.launch_url(url);
}
},
Action::ViMotion(motion) => {
if ctx.config().ui_config.mouse.hide_when_typing {
ctx.window_mut().set_mouse_visible(false);
}
Action::ViAction(ViAction::SearchNext) => {
let origin = ctx.terminal().visible_to_buffer(ctx.terminal().vi_mode_cursor.point);
let direction = ctx.search_direction();
ctx.terminal_mut().vi_motion(motion)
let regex_match = ctx.terminal().search_next(origin, direction, Side::Left, None);
if let Some(regex_match) = regex_match {
ctx.terminal_mut().vi_goto_point(*regex_match.start());
}
},
Action::ViAction(ViAction::SearchPrevious) => {
let origin = ctx.terminal().visible_to_buffer(ctx.terminal().vi_mode_cursor.point);
let direction = ctx.search_direction().opposite();
let regex_match = ctx.terminal().search_next(origin, direction, Side::Left, None);
if let Some(regex_match) = regex_match {
ctx.terminal_mut().vi_goto_point(*regex_match.start());
}
},
Action::ViAction(ViAction::SearchEndNext) => {
let origin = ctx.terminal().visible_to_buffer(ctx.terminal().vi_mode_cursor.point);
let direction = ctx.search_direction();
let regex_match = ctx.terminal().search_next(origin, direction, Side::Right, None);
if let Some(regex_match) = regex_match {
ctx.terminal_mut().vi_goto_point(*regex_match.end());
}
},
Action::ViAction(ViAction::SearchEndPrevious) => {
let origin = ctx.terminal().visible_to_buffer(ctx.terminal().vi_mode_cursor.point);
let direction = ctx.search_direction().opposite();
let regex_match = ctx.terminal().search_next(origin, direction, Side::Right, None);
if let Some(regex_match) = regex_match {
ctx.terminal_mut().vi_goto_point(*regex_match.end());
}
},
Action::Search => ctx.start_search(Direction::Right),
Action::SearchReverse => ctx.start_search(Direction::Left),
Action::ToggleFullscreen => ctx.window_mut().toggle_fullscreen(),
#[cfg(target_os = "macos")]
Action::ToggleSimpleFullscreen => ctx.window_mut().toggle_simple_fullscreen(),
@ -199,7 +245,7 @@ impl<T: EventListener> Execute<T> for Action {
Action::ScrollPageUp => {
// Move vi mode cursor.
let term = ctx.terminal_mut();
let scroll_lines = term.grid().num_lines().0 as isize;
let scroll_lines = term.grid().screen_lines().0 as isize;
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
ctx.scroll(Scroll::PageUp);
@ -207,7 +253,7 @@ impl<T: EventListener> Execute<T> for Action {
Action::ScrollPageDown => {
// Move vi mode cursor.
let term = ctx.terminal_mut();
let scroll_lines = -(term.grid().num_lines().0 as isize);
let scroll_lines = -(term.grid().screen_lines().0 as isize);
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
ctx.scroll(Scroll::PageDown);
@ -215,29 +261,29 @@ impl<T: EventListener> Execute<T> for Action {
Action::ScrollHalfPageUp => {
// Move vi mode cursor.
let term = ctx.terminal_mut();
let scroll_lines = term.grid().num_lines().0 as isize / 2;
let scroll_lines = term.grid().screen_lines().0 as isize / 2;
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
ctx.scroll(Scroll::Lines(scroll_lines));
ctx.scroll(Scroll::Delta(scroll_lines));
},
Action::ScrollHalfPageDown => {
// Move vi mode cursor.
let term = ctx.terminal_mut();
let scroll_lines = -(term.grid().num_lines().0 as isize / 2);
let scroll_lines = -(term.grid().screen_lines().0 as isize / 2);
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
ctx.scroll(Scroll::Lines(scroll_lines));
ctx.scroll(Scroll::Delta(scroll_lines));
},
Action::ScrollLineUp => {
// Move vi mode cursor.
let term = ctx.terminal();
if term.grid().display_offset() != term.grid().history_size()
&& term.vi_mode_cursor.point.line + 1 != term.grid().num_lines()
&& term.vi_mode_cursor.point.line + 1 != term.grid().screen_lines()
{
ctx.terminal_mut().vi_mode_cursor.point.line += 1;
}
ctx.scroll(Scroll::Lines(1));
ctx.scroll(Scroll::Delta(1));
},
Action::ScrollLineDown => {
// Move vi mode cursor.
@ -247,7 +293,7 @@ impl<T: EventListener> Execute<T> for Action {
ctx.terminal_mut().vi_mode_cursor.point.line -= 1;
}
ctx.scroll(Scroll::Lines(-1));
ctx.scroll(Scroll::Delta(-1));
},
Action::ScrollToTop => {
ctx.scroll(Scroll::Top);
@ -261,7 +307,7 @@ impl<T: EventListener> Execute<T> for Action {
// Move vi mode cursor.
let term = ctx.terminal_mut();
term.vi_mode_cursor.point.line = term.grid().num_lines() - 1;
term.vi_mode_cursor.point.line = term.grid().screen_lines() - 1;
// Move to beginning twice, to always jump across linewraps.
term.vi_motion(ViMotion::FirstOccupied);
@ -360,7 +406,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
self.update_url_state(&mouse_state);
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
let last_term_line = self.ctx.terminal().grid().num_lines() - 1;
let last_term_line = self.ctx.terminal().grid().screen_lines() - 1;
if (lmb_pressed || self.ctx.mouse().right_button_state == ElementState::Pressed)
&& (self.ctx.modifiers().shift() || !self.ctx.mouse_mode())
{
@ -520,7 +566,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
// Load mouse point, treating message bar and padding as the closest cell.
let mouse = self.ctx.mouse();
let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y);
point.line = min(point.line, self.ctx.terminal().grid().num_lines() - 1);
point.line = min(point.line, self.ctx.terminal().grid().screen_lines() - 1);
match button {
MouseButton::Left => self.on_left_click(point),
@ -690,7 +736,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
let term = self.ctx.terminal();
let absolute = term.visible_to_buffer(term.vi_mode_cursor.point);
self.ctx.scroll(Scroll::Lines(lines as isize));
self.ctx.scroll(Scroll::Delta(lines as isize));
// Try to restore vi mode cursor position, to keep it above its previous content.
let term = self.ctx.terminal_mut();
@ -733,7 +779,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
// Reset cursor when message bar height changed or all messages are gone.
let size = self.ctx.size_info();
let current_lines = (size.lines() - self.ctx.terminal().grid().num_lines()).0;
let current_lines = (size.lines() - self.ctx.terminal().grid().screen_lines()).0;
let new_lines = self.ctx.message().map(|m| m.text(&size).len()).unwrap_or(0);
let new_icon = match current_lines.cmp(&new_lines) {
@ -749,7 +795,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
};
self.ctx.window_mut().set_mouse_cursor(new_icon);
} else {
} else if !self.ctx.search_active() {
match state {
ElementState::Pressed => {
self.process_mouse_bindings(button);
@ -763,6 +809,28 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
/// Process key input.
pub fn key_input(&mut self, input: KeyboardInput) {
match input.state {
ElementState::Pressed if self.ctx.search_active() => {
match input.virtual_keycode {
Some(VirtualKeyCode::Back) => {
self.ctx.pop_search();
*self.ctx.suppress_chars() = true;
},
Some(VirtualKeyCode::Return) => {
self.ctx.confirm_search();
*self.ctx.suppress_chars() = true;
},
Some(VirtualKeyCode::Escape) => {
self.ctx.cancel_search();
*self.ctx.suppress_chars() = true;
},
_ => (),
}
// Reset search delay when the user is still typing.
if let Some(timer) = self.ctx.scheduler_mut().get_mut(TimerId::DelayedSearch) {
timer.deadline = Instant::now() + TYPING_SEARCH_DELAY;
}
},
ElementState::Pressed => {
*self.ctx.received_count() = 0;
self.process_key_bindings(input);
@ -783,7 +851,19 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
/// Process a received character.
pub fn received_char(&mut self, c: char) {
if *self.ctx.suppress_chars() || self.ctx.terminal().mode().contains(TermMode::VI) {
let suppress_chars = *self.ctx.suppress_chars();
let search_active = self.ctx.search_active();
if suppress_chars || self.ctx.terminal().mode().contains(TermMode::VI) || search_active {
if search_active {
// Skip control characters.
let c_decimal = c as isize;
let is_printable = (c_decimal >= 0x20 && c_decimal < 0x7f) || c_decimal >= 0xa0;
if !suppress_chars && is_printable {
self.ctx.push_search(c);
}
}
return;
}
@ -877,15 +957,19 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
/// Check if the cursor is hovering above the message bar.
fn message_at_cursor(&mut self) -> bool {
self.ctx.mouse().line >= self.ctx.terminal().grid().num_lines()
self.ctx.mouse().line >= self.ctx.terminal().grid().screen_lines()
}
/// Whether the point is over the message bar's close button.
fn message_close_at_cursor(&self) -> bool {
let mouse = self.ctx.mouse();
// Since search is above the message bar, the button is offset by search's height.
let search_height = if self.ctx.search_active() { 1 } else { 0 };
mouse.inside_grid
&& mouse.column + message_bar::CLOSE_BUTTON_TEXT.len() >= self.ctx.size_info().cols()
&& mouse.line == self.ctx.terminal().grid().num_lines()
&& mouse.line == self.ctx.terminal().grid().screen_lines() + search_height
}
/// Copy text selection.
@ -967,7 +1051,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
// Scale number of lines scrolled based on distance to boundary.
let delta = delta as isize / step as isize;
let event = Event::Scroll(Scroll::Lines(delta));
let event = Event::Scroll(Scroll::Delta(delta));
// Schedule event.
match scheduler.get_mut(TimerId::SelectionScrolling) {
@ -1036,6 +1120,24 @@ mod tests {
fn reset_font_size(&mut self) {}
fn start_search(&mut self, _direction: Direction) {}
fn confirm_search(&mut self) {}
fn cancel_search(&mut self) {}
fn push_search(&mut self, _c: char) {}
fn pop_search(&mut self) {}
fn search_direction(&self) -> Direction {
Direction::Right
}
fn search_active(&self) -> bool {
false
}
fn terminal(&self) -> &Term<T> {
&self.terminal
}

View file

@ -298,7 +298,7 @@ impl GlyphCache {
pub fn update_font_size<L: LoadGlyph>(
&mut self,
font: config::Font,
font: &config::Font,
dpr: f64,
loader: &mut L,
) -> Result<(), font::Error> {
@ -307,7 +307,7 @@ impl GlyphCache {
// Recompute font keys.
let (regular, bold, italic, bold_italic) =
Self::compute_font_keys(&font, &mut self.rasterizer)?;
Self::compute_font_keys(font, &mut self.rasterizer)?;
self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?;
let metrics = self.rasterizer.metrics(regular, font.size)?;
@ -968,29 +968,29 @@ impl<'a, C> RenderApi<'a, C> {
/// errors.
pub fn render_string(
&mut self,
string: &str,
line: Line,
glyph_cache: &mut GlyphCache,
color: Option<Rgb>,
line: Line,
string: &str,
fg: Rgb,
bg: Option<Rgb>,
) {
let bg_alpha = color.map(|_| 1.0).unwrap_or(0.0);
let col = Column(0);
let bg_alpha = bg.map(|_| 1.0).unwrap_or(0.0);
let cells = string
.chars()
.enumerate()
.map(|(i, c)| RenderableCell {
line,
column: col + i,
column: Column(i),
inner: RenderableCellContent::Chars({
let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1];
chars[0] = c;
chars
}),
bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }),
fg: Rgb { r: 0, g: 0, b: 0 },
flags: Flags::empty(),
bg_alpha,
fg,
bg: bg.unwrap_or(Rgb { r: 0, g: 0, b: 0 }),
})
.collect::<Vec<_>>();

View file

@ -9,6 +9,22 @@ use crate::event::Event as AlacrittyEvent;
type Event = GlutinEvent<'static, AlacrittyEvent>;
/// ID uniquely identifying a timer.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TimerId {
SelectionScrolling,
DelayedSearch,
}
/// Event scheduled to be emitted at a specific time.
pub struct Timer {
pub deadline: Instant,
pub event: Event,
interval: Option<Duration>,
id: TimerId,
}
/// Scheduler tracking all pending timers.
pub struct Scheduler {
timers: VecDeque<Timer>,
@ -74,23 +90,13 @@ impl Scheduler {
self.timers.remove(index).map(|timer| timer.event)
}
/// Check if a timer is already scheduled.
pub fn scheduled(&mut self, id: TimerId) -> bool {
self.timers.iter().any(|timer| timer.id == id)
}
/// Access a staged event by ID.
pub fn get_mut(&mut self, id: TimerId) -> Option<&mut Timer> {
self.timers.iter_mut().find(|timer| timer.id == id)
}
}
/// ID uniquely identifying a timer.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TimerId {
SelectionScrolling,
}
/// Event scheduled to be emitted at a specific time.
pub struct Timer {
pub deadline: Instant,
pub event: Event,
interval: Option<Duration>,
id: TimerId,
}

View file

@ -90,7 +90,7 @@ impl Urls {
self.last_point = Some(end);
// Extend current state if a wide char spacer is encountered.
if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
if cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) {
if let UrlLocation::Url(_, mut end_offset) = self.state {
if end_offset != 0 {
end_offset += 1;

View file

@ -35,7 +35,9 @@ use winapi::shared::minwindef::WORD;
use alacritty_terminal::config::{Decorations, StartupMode, WindowConfig};
#[cfg(not(windows))]
use alacritty_terminal::term::{SizeInfo, Term};
use alacritty_terminal::index::Point;
#[cfg(not(windows))]
use alacritty_terminal::term::SizeInfo;
use crate::config::Config;
use crate::gl;
@ -398,8 +400,7 @@ impl Window {
/// Adjust the IME editor position according to the new location of the cursor.
#[cfg(not(windows))]
pub fn update_ime_position<T>(&mut self, terminal: &Term<T>, size_info: &SizeInfo) {
let point = terminal.grid().cursor.point;
pub fn update_ime_position(&mut self, point: Point, size_info: &SizeInfo) {
let SizeInfo { cell_width, cell_height, padding_x, padding_y, .. } = size_info;
let nspot_x = f64::from(padding_x + point.col.0 as f32 * cell_width);

View file

@ -22,6 +22,7 @@ log = "0.4"
unicode-width = "0.1"
base64 = "0.12.0"
terminfo = "0.7.1"
regex-automata = "0.1.9"
[target.'cfg(unix)'.dependencies]
nix = "0.17.0"

View file

@ -89,13 +89,13 @@ struct ProcessorState {
///
/// Processor creates a Performer when running advance and passes the Performer
/// to `vte::Parser`.
struct Performer<'a, H: Handler + TermInfo, W: io::Write> {
struct Performer<'a, H: Handler, W: io::Write> {
state: &'a mut ProcessorState,
handler: &'a mut H,
writer: &'a mut W,
}
impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> {
impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> {
/// Create a performer.
#[inline]
pub fn new<'b>(
@ -121,7 +121,7 @@ impl Processor {
#[inline]
pub fn advance<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
where
H: Handler + TermInfo,
H: Handler,
W: io::Write,
{
let mut performer = Performer::new(&mut self.state, handler, writer);
@ -129,12 +129,6 @@ impl Processor {
}
}
/// Trait that provides properties of terminal.
pub trait TermInfo {
fn lines(&self) -> Line;
fn cols(&self) -> Column;
}
/// Type that handles actions from the parser.
///
/// XXX Should probably not provide default impls for everything, but it makes
@ -278,7 +272,7 @@ pub trait Handler {
fn unset_mode(&mut self, _: Mode) {}
/// DECSTBM - Set the terminal scrolling region.
fn set_scrolling_region(&mut self, _top: usize, _bottom: usize) {}
fn set_scrolling_region(&mut self, _top: usize, _bottom: Option<usize>) {}
/// DECKPAM - Set keypad to applications mode (ESCape instead of digits).
fn set_keypad_application_mode(&mut self) {}
@ -731,7 +725,7 @@ impl StandardCharset {
impl<'a, H, W> vte::Perform for Performer<'a, H, W>
where
H: Handler + TermInfo + 'a,
H: Handler + 'a,
W: io::Write + 'a,
{
#[inline]
@ -945,9 +939,7 @@ where
macro_rules! arg_or_default {
(idx: $idx:expr, default: $default:expr) => {
args.get($idx)
.and_then(|v| if *v == 0 { None } else { Some(*v) })
.unwrap_or($default)
args.get($idx).copied().filter(|&v| v != 0).unwrap_or($default)
};
}
@ -1099,7 +1091,7 @@ where
},
('r', None) => {
let top = arg_or_default!(idx: 0, default: 1) as usize;
let bottom = arg_or_default!(idx: 1, default: handler.lines().0 as _) as usize;
let bottom = args.get(1).map(|&b| b as usize).filter(|&b| b != 0);
handler.set_scrolling_region(top, bottom);
},
@ -1391,9 +1383,7 @@ pub mod C0 {
mod tests {
use super::{
parse_number, xparse_color, Attr, CharsetIndex, Color, Handler, Processor, StandardCharset,
TermInfo,
};
use crate::index::{Column, Line};
use crate::term::color::Rgb;
use std::io;
@ -1427,16 +1417,6 @@ mod tests {
}
}
impl TermInfo for MockHandler {
fn lines(&self) -> Line {
Line(200)
}
fn cols(&self) -> Column {
Column(90)
}
}
impl Default for MockHandler {
fn default() -> MockHandler {
MockHandler {

View file

@ -1,8 +1,9 @@
use log::error;
use serde::{Deserialize, Deserializer};
use serde_yaml::Value;
use crate::config::{failure_default, LOG_TARGET_CONFIG};
use crate::term::color::Rgb;
use crate::term::color::{CellRgb, Rgb};
#[serde(default)]
#[derive(Deserialize, Clone, Debug, Default, PartialEq, Eq)]
@ -23,6 +24,8 @@ pub struct Colors {
pub dim: Option<AnsiColors>,
#[serde(deserialize_with = "failure_default")]
pub indexed_colors: Vec<IndexedColor>,
#[serde(deserialize_with = "failure_default")]
pub search: SearchColors,
}
impl Colors {
@ -33,6 +36,32 @@ impl Colors {
pub fn bright(&self) -> &AnsiColors {
&self.bright.0
}
pub fn search_bar_foreground(&self) -> Rgb {
self.search.bar.foreground.unwrap_or(self.primary.background)
}
pub fn search_bar_background(&self) -> Rgb {
self.search.bar.background.unwrap_or(self.primary.foreground)
}
}
#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
struct DefaultForegroundCellRgb(CellRgb);
impl Default for DefaultForegroundCellRgb {
fn default() -> Self {
Self(CellRgb::CellForeground)
}
}
#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
struct DefaultBackgroundCellRgb(CellRgb);
impl Default for DefaultBackgroundCellRgb {
fn default() -> Self {
Self(CellRgb::CellBackground)
}
}
#[serde(default)]
@ -44,11 +73,11 @@ pub struct IndexedColor {
pub color: Rgb,
}
fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error>
fn deserialize_color_index<'a, D>(deserializer: D) -> Result<u8, D::Error>
where
D: Deserializer<'a>,
{
let value = serde_yaml::Value::deserialize(deserializer)?;
let value = Value::deserialize(deserializer)?;
match u8::deserialize(value) {
Ok(index) => {
if index < 16 {
@ -78,26 +107,91 @@ where
#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct CursorColors {
#[serde(deserialize_with = "failure_default")]
pub text: Option<Rgb>,
text: DefaultBackgroundCellRgb,
#[serde(deserialize_with = "failure_default")]
pub cursor: Option<Rgb>,
cursor: DefaultForegroundCellRgb,
}
impl CursorColors {
pub fn text(self) -> CellRgb {
self.text.0
}
pub fn cursor(self) -> CellRgb {
self.cursor.0
}
}
#[serde(default)]
#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct SelectionColors {
#[serde(deserialize_with = "failure_default")]
pub text: Option<Rgb>,
text: DefaultBackgroundCellRgb,
#[serde(deserialize_with = "failure_default")]
pub background: Option<Rgb>,
background: DefaultForegroundCellRgb,
}
impl SelectionColors {
pub fn text(self) -> CellRgb {
self.text.0
}
pub fn background(self) -> CellRgb {
self.background.0
}
}
#[serde(default)]
#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct SearchColors {
#[serde(deserialize_with = "failure_default")]
pub matches: MatchColors,
#[serde(deserialize_with = "failure_default")]
bar: BarColors,
}
#[serde(default)]
#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub struct MatchColors {
#[serde(deserialize_with = "failure_default")]
pub foreground: CellRgb,
#[serde(deserialize_with = "deserialize_match_background")]
pub background: CellRgb,
}
impl Default for MatchColors {
fn default() -> Self {
Self { foreground: CellRgb::default(), background: default_match_background() }
}
}
fn deserialize_match_background<'a, D>(deserializer: D) -> Result<CellRgb, D::Error>
where
D: Deserializer<'a>,
{
let value = Value::deserialize(deserializer)?;
Ok(CellRgb::deserialize(value).unwrap_or_else(|_| default_match_background()))
}
fn default_match_background() -> CellRgb {
CellRgb::Rgb(Rgb { r: 0xff, g: 0xff, b: 0xff })
}
#[serde(default)]
#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct BarColors {
#[serde(deserialize_with = "failure_default")]
foreground: Option<Rgb>,
#[serde(deserialize_with = "failure_default")]
background: Option<Rgb>,
}
#[serde(default)]
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PrimaryColors {
#[serde(default = "default_background", deserialize_with = "failure_default")]
#[serde(deserialize_with = "failure_default")]
pub background: Rgb,
#[serde(default = "default_foreground", deserialize_with = "failure_default")]
#[serde(deserialize_with = "failure_default")]
pub foreground: Rgb,
#[serde(deserialize_with = "failure_default")]
pub bright_foreground: Option<Rgb>,
@ -108,22 +202,14 @@ pub struct PrimaryColors {
impl Default for PrimaryColors {
fn default() -> Self {
PrimaryColors {
background: default_background(),
foreground: default_foreground(),
background: Rgb { r: 0x1d, g: 0x1f, b: 0x21 },
foreground: Rgb { r: 0xc5, g: 0xc8, b: 0xc6 },
bright_foreground: Default::default(),
dim_foreground: Default::default(),
}
}
}
fn default_background() -> Rgb {
Rgb { r: 0x1d, g: 0x1f, b: 0x21 }
}
fn default_foreground() -> Rgb {
Rgb { r: 0xc5, g: 0xc8, b: 0xc6 }
}
/// The 8-colors sections of config.
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct AnsiColors {

View file

@ -13,7 +13,7 @@ mod scrolling;
mod visual_bell;
mod window;
use crate::ansi::{CursorStyle, NamedColor};
use crate::ansi::CursorStyle;
pub use crate::config::colors::Colors;
pub use crate::config::debug::Debug;
@ -21,7 +21,6 @@ pub use crate::config::font::{Font, FontDescription};
pub use crate::config::scrolling::Scrolling;
pub use crate::config::visual_bell::{VisualBellAnimation, VisualBellConfig};
pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig, DEFAULT_NAME};
use crate::term::color::Rgb;
pub const LOG_TARGET_CONFIG: &str = "alacritty_config";
const MAX_SCROLLBACK_LINES: u32 = 100_000;
@ -156,30 +155,6 @@ impl<T> Config<T> {
self.dynamic_title.0
}
/// Cursor foreground color.
#[inline]
pub fn cursor_text_color(&self) -> Option<Rgb> {
self.colors.cursor.text
}
/// Cursor background color.
#[inline]
pub fn cursor_cursor_color(&self) -> Option<NamedColor> {
self.colors.cursor.cursor.map(|_| NamedColor::Cursor)
}
/// Vi mode cursor foreground color.
#[inline]
pub fn vi_mode_cursor_text_color(&self) -> Option<Rgb> {
self.colors.vi_mode_cursor.text
}
/// Vi mode cursor background color.
#[inline]
pub fn vi_mode_cursor_cursor_color(&self) -> Option<Rgb> {
self.colors.vi_mode_cursor.cursor
}
#[inline]
pub fn set_dynamic_title(&mut self, dynamic_title: bool) {
self.dynamic_title.0 = dynamic_title;

View file

@ -160,7 +160,7 @@ pub struct Grid<T> {
#[derive(Debug, Copy, Clone)]
pub enum Scroll {
Lines(isize),
Delta(isize),
PageUp,
PageDown,
Top,
@ -180,23 +180,6 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
}
/// Clamp a buffer point to the visible region.
pub fn clamp_buffer_to_visible(&self, point: Point<usize>) -> Point {
if point.line < self.display_offset {
Point::new(self.lines - 1, self.cols - 1)
} else if point.line >= self.display_offset + self.lines.0 {
Point::new(Line(0), Column(0))
} else {
// Since edge-cases are handled, conversion is identical as visible to buffer.
self.visible_to_buffer(point.into()).into()
}
}
/// Convert viewport relative point to global buffer indexing.
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, col: point.col }
}
/// Update the size of the scrollback history.
pub fn update_history(&mut self, history_size: usize) {
let current_history_size = self.history_size();
@ -208,22 +191,16 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
pub fn scroll_display(&mut self, scroll: Scroll) {
match scroll {
Scroll::Lines(count) => {
self.display_offset = min(
max((self.display_offset as isize) + count, 0isize) as usize,
self.history_size(),
);
},
Scroll::PageUp => {
self.display_offset = min(self.display_offset + self.lines.0, self.history_size());
},
Scroll::PageDown => {
self.display_offset -= min(self.display_offset, self.lines.0);
},
Scroll::Top => self.display_offset = self.history_size(),
Scroll::Bottom => self.display_offset = 0,
}
self.display_offset = match scroll {
Scroll::Delta(count) => min(
max((self.display_offset as isize) + count, 0isize) as usize,
self.history_size(),
),
Scroll::PageUp => min(self.display_offset + self.lines.0, self.history_size()),
Scroll::PageDown => self.display_offset.saturating_sub(self.lines.0),
Scroll::Top => self.history_size(),
Scroll::Bottom => 0,
};
}
fn increase_scroll_limit(&mut self, count: usize, template: T) {
@ -279,7 +256,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
///
/// This is the performance-sensitive part of scrolling.
pub fn scroll_up(&mut self, region: &Range<Line>, positions: Line, template: T) {
let num_lines = self.num_lines().0;
let num_lines = self.screen_lines().0;
if region.start == Line(0) {
// Update display offset when not pinned to active area.
@ -324,7 +301,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
pub fn clear_viewport(&mut self, template: T) {
// Determine how many lines to scroll up by.
let end = Point { line: 0, col: self.num_cols() };
let end = Point { line: 0, col: self.cols() };
let mut iter = self.iter_from(end);
while let Some(cell) = iter.prev() {
if !cell.is_empty() || iter.cur.line >= *self.lines {
@ -333,7 +310,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
debug_assert!(iter.cur.line <= *self.lines);
let positions = self.lines - iter.cur.line;
let region = Line(0)..self.num_lines();
let region = Line(0)..self.screen_lines();
// Reset display offset.
self.display_offset = 0;
@ -364,9 +341,22 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
#[allow(clippy::len_without_is_empty)]
impl<T> Grid<T> {
/// Clamp a buffer point to the visible region.
pub fn clamp_buffer_to_visible(&self, point: Point<usize>) -> Point {
if point.line < self.display_offset {
Point::new(self.lines - 1, self.cols - 1)
} else if point.line >= self.display_offset + self.lines.0 {
Point::new(Line(0), Column(0))
} else {
// Since edgecases are handled, conversion is identical as visible to buffer.
self.visible_to_buffer(point.into()).into()
}
}
/// Convert viewport relative point to global buffer indexing.
#[inline]
pub fn num_lines(&self) -> Line {
self.lines
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, col: point.col }
}
#[inline]
@ -374,28 +364,12 @@ impl<T> Grid<T> {
DisplayIter::new(self)
}
#[inline]
pub fn num_cols(&self) -> Column {
self.cols
}
#[inline]
pub fn clear_history(&mut self) {
// Explicitly purge all lines from history.
self.raw.shrink_lines(self.history_size());
}
/// Total number of lines in the buffer, this includes scrollback + visible lines.
#[inline]
pub fn len(&self) -> usize {
self.raw.len()
}
#[inline]
pub fn history_size(&self) -> usize {
self.raw.len() - *self.lines
}
/// This is used only for initializing after loading ref-tests.
#[inline]
pub fn initialize_all(&mut self, template: T)
@ -432,6 +406,56 @@ impl<T> Grid<T> {
}
}
/// Grid dimensions.
pub trait Dimensions {
/// Total number of lines in the buffer, this includes scrollback and visible lines.
fn total_lines(&self) -> usize;
/// Height of the viewport in lines.
fn screen_lines(&self) -> Line;
/// Width of the terminal in columns.
fn cols(&self) -> Column;
/// Number of invisible lines part of the scrollback history.
#[inline]
fn history_size(&self) -> usize {
self.total_lines() - self.screen_lines().0
}
}
impl<G> Dimensions for Grid<G> {
#[inline]
fn total_lines(&self) -> usize {
self.raw.len()
}
#[inline]
fn screen_lines(&self) -> Line {
self.lines
}
#[inline]
fn cols(&self) -> Column {
self.cols
}
}
#[cfg(test)]
impl Dimensions for (Line, Column) {
fn total_lines(&self) -> usize {
*self.0
}
fn screen_lines(&self) -> Line {
self.0
}
fn cols(&self) -> Column {
self.1
}
}
pub struct GridIterator<'a, T> {
/// Immutable grid reference.
grid: &'a Grid<T>,
@ -446,7 +470,7 @@ impl<'a, T> GridIterator<'a, T> {
}
pub fn cell(&self) -> &'a T {
&self.grid[self.cur.line][self.cur.col]
&self.grid[self.cur]
}
}
@ -454,38 +478,35 @@ impl<'a, T> Iterator for GridIterator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let last_col = self.grid.num_cols() - Column(1);
let last_col = self.grid.cols() - 1;
match self.cur {
Point { line, col } if line == 0 && col == last_col => None,
Point { line, col } if line == 0 && col == last_col => return None,
Point { col, .. } if (col == last_col) => {
self.cur.line -= 1;
self.cur.col = Column(0);
Some(&self.grid[self.cur.line][self.cur.col])
},
_ => {
self.cur.col += Column(1);
Some(&self.grid[self.cur.line][self.cur.col])
},
_ => self.cur.col += Column(1),
}
Some(&self.grid[self.cur])
}
}
impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
fn prev(&mut self) -> Option<Self::Item> {
let num_cols = self.grid.num_cols();
let last_col = self.grid.cols() - 1;
match self.cur {
Point { line, col: Column(0) } if line == self.grid.len() - 1 => None,
Point { line, col: Column(0) } if line == self.grid.total_lines() - 1 => return None,
Point { col: Column(0), .. } => {
self.cur.line += 1;
self.cur.col = num_cols - Column(1);
Some(&self.grid[self.cur.line][self.cur.col])
},
_ => {
self.cur.col -= Column(1);
Some(&self.grid[self.cur.line][self.cur.col])
self.cur.col = last_col;
},
_ => self.cur.col -= Column(1),
}
Some(&self.grid[self.cur])
}
}
@ -539,6 +560,22 @@ impl<'point, T> IndexMut<&'point Point> for Grid<T> {
}
}
impl<T> Index<Point<usize>> for Grid<T> {
type Output = T;
#[inline]
fn index(&self, point: Point<usize>) -> &T {
&self[point.line][point.col]
}
}
impl<T> IndexMut<Point<usize>> for Grid<T> {
#[inline]
fn index_mut(&mut self, point: Point<usize>) -> &mut T {
&mut self[point.line][point.col]
}
}
/// A subset of lines in the grid.
///
/// May be constructed using Grid::region(..).
@ -578,15 +615,15 @@ pub trait IndexRegion<I, T> {
impl<T> IndexRegion<Range<Line>, T> for Grid<T> {
fn region(&self, index: Range<Line>) -> Region<'_, T> {
assert!(index.start < self.num_lines());
assert!(index.end <= self.num_lines());
assert!(index.start < self.screen_lines());
assert!(index.end <= self.screen_lines());
assert!(index.start <= index.end);
Region { start: index.start, end: index.end, raw: &self.raw }
}
fn region_mut(&mut self, index: Range<Line>) -> RegionMut<'_, T> {
assert!(index.start < self.num_lines());
assert!(index.end <= self.num_lines());
assert!(index.start < self.screen_lines());
assert!(index.end <= self.screen_lines());
assert!(index.start <= index.end);
RegionMut { start: index.start, end: index.end, raw: &mut self.raw }
}
@ -594,35 +631,35 @@ impl<T> IndexRegion<Range<Line>, T> for Grid<T> {
impl<T> IndexRegion<RangeTo<Line>, T> for Grid<T> {
fn region(&self, index: RangeTo<Line>) -> Region<'_, T> {
assert!(index.end <= self.num_lines());
assert!(index.end <= self.screen_lines());
Region { start: Line(0), end: index.end, raw: &self.raw }
}
fn region_mut(&mut self, index: RangeTo<Line>) -> RegionMut<'_, T> {
assert!(index.end <= self.num_lines());
assert!(index.end <= self.screen_lines());
RegionMut { start: Line(0), end: index.end, raw: &mut self.raw }
}
}
impl<T> IndexRegion<RangeFrom<Line>, T> for Grid<T> {
fn region(&self, index: RangeFrom<Line>) -> Region<'_, T> {
assert!(index.start < self.num_lines());
Region { start: index.start, end: self.num_lines(), raw: &self.raw }
assert!(index.start < self.screen_lines());
Region { start: index.start, end: self.screen_lines(), raw: &self.raw }
}
fn region_mut(&mut self, index: RangeFrom<Line>) -> RegionMut<'_, T> {
assert!(index.start < self.num_lines());
RegionMut { start: index.start, end: self.num_lines(), raw: &mut self.raw }
assert!(index.start < self.screen_lines());
RegionMut { start: index.start, end: self.screen_lines(), raw: &mut self.raw }
}
}
impl<T> IndexRegion<RangeFull, T> for Grid<T> {
fn region(&self, _: RangeFull) -> Region<'_, T> {
Region { start: Line(0), end: self.num_lines(), raw: &self.raw }
Region { start: Line(0), end: self.screen_lines(), raw: &self.raw }
}
fn region_mut(&mut self, _: RangeFull) -> RegionMut<'_, T> {
RegionMut { start: Line(0), end: self.num_lines(), raw: &mut self.raw }
RegionMut { start: Line(0), end: self.screen_lines(), raw: &mut self.raw }
}
}
@ -695,7 +732,7 @@ pub struct DisplayIter<'a, T> {
impl<'a, T: 'a> DisplayIter<'a, T> {
pub fn new(grid: &'a Grid<T>) -> DisplayIter<'a, T> {
let offset = grid.display_offset + *grid.num_lines() - 1;
let offset = grid.display_offset + *grid.screen_lines() - 1;
let limit = grid.display_offset;
let col = Column(0);
let line = Line(0);
@ -722,7 +759,7 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> {
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// Return None if we've reached the end.
if self.offset == self.limit && self.grid.num_cols() == self.col {
if self.offset == self.limit && self.grid.cols() == self.col {
return None;
}
@ -735,7 +772,7 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> {
// Update line/col to point to next item.
self.col += 1;
if self.col == self.grid.num_cols() && self.offset != self.limit {
if self.col == self.grid.cols() && self.offset != self.limit {
self.offset -= 1;
self.col = Column(0);

View file

@ -6,7 +6,7 @@ use crate::index::{Column, Line};
use crate::term::cell::Flags;
use crate::grid::row::Row;
use crate::grid::{Grid, GridCell};
use crate::grid::{Dimensions, Grid, GridCell};
impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
/// Resize the grid's width and/or height.
@ -18,8 +18,8 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
match self.cols.cmp(&cols) {
Ordering::Less => self.grow_cols(cols, reflow),
Ordering::Greater => self.shrink_cols(cols, reflow),
Ordering::Less => self.grow_cols(reflow, cols),
Ordering::Greater => self.shrink_cols(reflow, cols),
Ordering::Equal => (),
}
}
@ -79,7 +79,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
/// Grow number of columns in each row, reflowing if necessary.
fn grow_cols(&mut self, cols: Column, reflow: bool) {
fn grow_cols(&mut self, reflow: bool, cols: Column) {
// Check if a row needs to be wrapped.
let should_reflow = |row: &Row<T>| -> bool {
let len = Column(row.len());
@ -116,9 +116,8 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
// Remove leading spacers when reflowing wide char to the previous line.
let mut last_len = last_row.len();
if last_len >= 2
&& !last_row[Column(last_len - 2)].flags().contains(Flags::WIDE_CHAR)
&& last_row[Column(last_len - 1)].flags().contains(Flags::WIDE_CHAR_SPACER)
if last_len >= 1
&& last_row[Column(last_len - 1)].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER)
{
last_row.shrink(Column(last_len - 1));
last_len -= 1;
@ -135,7 +134,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
let mut cells = row.front_split_off(len - 1);
let mut spacer = T::default();
spacer.flags_mut().insert(Flags::WIDE_CHAR_SPACER);
spacer.flags_mut().insert(Flags::LEADING_WIDE_CHAR_SPACER);
cells.push(spacer);
cells
@ -143,7 +142,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
row.front_split_off(len)
};
// Reflow cells to previous row.
// Add removed cells to previous row and reflow content.
last_row.append(&mut cells);
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
@ -219,7 +218,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
/// Shrink number of columns in each row, reflowing if necessary.
fn shrink_cols(&mut self, cols: Column, reflow: bool) {
fn shrink_cols(&mut self, reflow: bool, cols: Column) {
self.cols = cols;
// Remove the linewrap special case, by moving the cursor outside of the grid.
@ -268,17 +267,14 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
wrapped.insert(0, row[cols - 1]);
let mut spacer = T::default();
spacer.flags_mut().insert(Flags::WIDE_CHAR_SPACER);
spacer.flags_mut().insert(Flags::LEADING_WIDE_CHAR_SPACER);
row[cols - 1] = spacer;
}
// Remove wide char spacer before shrinking.
let len = wrapped.len();
if (len == 1 || (len >= 2 && !wrapped[len - 2].flags().contains(Flags::WIDE_CHAR)))
&& wrapped[len - 1].flags().contains(Flags::WIDE_CHAR_SPACER)
{
if wrapped[len - 1].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER) {
if len == 1 {
// Delete the wrapped content if it contains only a leading spacer.
row[cols - 1].flags_mut().insert(Flags::WRAPLINE);
new_raw.push(row);
break;

View file

@ -232,7 +232,9 @@ impl<T> Storage<T> {
/// Rotate the grid up, moving all existing lines down in history.
///
/// This is a faster, specialized version of [`rotate`].
/// This is a faster, specialized version of [`rotate_left`].
///
/// [`rotate_left`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.rotate_left
#[inline]
pub fn rotate_up(&mut self, count: usize) {
self.zero = (self.zero + count) % self.inner.len();

View file

@ -1,7 +1,6 @@
//! Tests for the Grid.
use super::{BidirectionalIterator, Grid};
use crate::grid::GridCell;
use super::{BidirectionalIterator, Dimensions, Grid, GridCell};
use crate::index::{Column, Line, Point};
use crate::term::cell::{Cell, Flags};
@ -171,7 +170,7 @@ fn shrink_reflow() {
grid.resize(true, Line(1), Column(2));
assert_eq!(grid.len(), 3);
assert_eq!(grid.total_lines(), 3);
assert_eq!(grid[2].len(), 2);
assert_eq!(grid[2][Column(0)], cell('1'));
@ -198,7 +197,7 @@ fn shrink_reflow_twice() {
grid.resize(true, Line(1), Column(4));
grid.resize(true, Line(1), Column(2));
assert_eq!(grid.len(), 3);
assert_eq!(grid.total_lines(), 3);
assert_eq!(grid[2].len(), 2);
assert_eq!(grid[2][Column(0)], cell('1'));
@ -224,7 +223,7 @@ fn shrink_reflow_empty_cell_inside_line() {
grid.resize(true, Line(1), Column(2));
assert_eq!(grid.len(), 2);
assert_eq!(grid.total_lines(), 2);
assert_eq!(grid[1].len(), 2);
assert_eq!(grid[1][Column(0)], cell('1'));
@ -236,7 +235,7 @@ fn shrink_reflow_empty_cell_inside_line() {
grid.resize(true, Line(1), Column(1));
assert_eq!(grid.len(), 4);
assert_eq!(grid.total_lines(), 4);
assert_eq!(grid[3].len(), 1);
assert_eq!(grid[3][Column(0)], wrap_cell('1'));
@ -261,7 +260,7 @@ fn grow_reflow() {
grid.resize(true, Line(2), Column(3));
assert_eq!(grid.len(), 2);
assert_eq!(grid.total_lines(), 2);
assert_eq!(grid[1].len(), 3);
assert_eq!(grid[1][Column(0)], cell('1'));
@ -287,7 +286,7 @@ fn grow_reflow_multiline() {
grid.resize(true, Line(3), Column(6));
assert_eq!(grid.len(), 3);
assert_eq!(grid.total_lines(), 3);
assert_eq!(grid[2].len(), 6);
assert_eq!(grid[2][Column(0)], cell('1'));
@ -318,7 +317,7 @@ fn grow_reflow_disabled() {
grid.resize(false, Line(2), Column(3));
assert_eq!(grid.len(), 2);
assert_eq!(grid.total_lines(), 2);
assert_eq!(grid[1].len(), 3);
assert_eq!(grid[1][Column(0)], cell('1'));
@ -342,7 +341,7 @@ fn shrink_reflow_disabled() {
grid.resize(false, Line(1), Column(2));
assert_eq!(grid.len(), 1);
assert_eq!(grid.total_lines(), 1);
assert_eq!(grid[0].len(), 2);
assert_eq!(grid[0][Column(0)], cell('1'));

View file

@ -7,16 +7,20 @@ use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign};
use serde::{Deserialize, Serialize};
use crate::grid::Dimensions;
use crate::term::RenderableCell;
/// The side of a cell.
pub type Side = Direction;
/// Horizontal direction.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Side {
pub enum Direction {
Left,
Right,
}
impl Side {
impl Direction {
pub fn opposite(self) -> Self {
match self {
Side::Right => Side::Left,
@ -25,8 +29,23 @@ impl Side {
}
}
/// Behavior for handling grid boundaries.
pub enum Boundary {
/// Clamp to grid boundaries.
///
/// When an operation exceeds the grid boundaries, the last point will be returned no matter
/// how far the boundaries were exceeded.
Clamp,
/// Wrap around grid bondaries.
///
/// When an operation exceeds the grid boundaries, the point will wrap around the entire grid
/// history and continue at the other side.
Wrap,
}
/// Index in the grid using row, column notation.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd)]
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct Point<L = Line> {
pub line: L,
pub col: Column,
@ -65,43 +84,84 @@ impl<L> Point<L> {
self.col = Column((self.col.0 + rhs) % num_cols);
self
}
}
impl Point<usize> {
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn sub_absolute(mut self, num_cols: Column, rhs: usize) -> Point<L>
pub fn sub_absolute<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Point<usize>
where
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
D: Dimensions,
{
let num_cols = num_cols.0;
self.line = self.line + ((rhs + num_cols - 1).saturating_sub(self.col.0) / num_cols);
let total_lines = dimensions.total_lines();
let num_cols = dimensions.cols().0;
self.line += (rhs + num_cols - 1).saturating_sub(self.col.0) / num_cols;
self.col = Column((num_cols + self.col.0 - rhs % num_cols) % num_cols);
self
if self.line >= total_lines {
match boundary {
Boundary::Clamp => Point::new(total_lines - 1, Column(0)),
Boundary::Wrap => Point::new(self.line - total_lines, self.col),
}
} else {
self
}
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn add_absolute(mut self, num_cols: Column, rhs: usize) -> Point<L>
pub fn add_absolute<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Point<usize>
where
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
D: Dimensions,
{
let line_changes = (rhs + self.col.0) / num_cols.0;
if self.line.into() >= Line(line_changes) {
self.line = self.line - line_changes;
let num_cols = dimensions.cols();
let line_delta = (rhs + self.col.0) / num_cols.0;
if self.line >= line_delta {
self.line -= line_delta;
self.col = Column((self.col.0 + rhs) % num_cols.0);
self
} else {
Point::new(L::default(), num_cols - 1)
match boundary {
Boundary::Clamp => Point::new(0, num_cols - 1),
Boundary::Wrap => {
let col = Column((self.col.0 + rhs) % num_cols.0);
let line = dimensions.total_lines() + self.line - line_delta;
Point::new(line, col)
},
}
}
}
}
impl PartialOrd for Point {
fn partial_cmp(&self, other: &Point) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Point {
fn cmp(&self, other: &Point) -> Ordering {
match (self.line.cmp(&other.line), self.col.cmp(&other.col)) {
(Ordering::Equal, Ordering::Equal) => Ordering::Equal,
(Ordering::Equal, ord) | (ord, Ordering::Equal) => ord,
(Ordering::Less, _) => Ordering::Less,
(Ordering::Greater, _) => Ordering::Greater,
(Ordering::Equal, ord) | (ord, _) => ord,
}
}
}
impl PartialOrd for Point<usize> {
fn partial_cmp(&self, other: &Point<usize>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Point<usize> {
fn cmp(&self, other: &Point<usize>) -> Ordering {
match (self.line.cmp(&other.line), self.col.cmp(&other.col)) {
(Ordering::Equal, ord) => ord,
(Ordering::Less, _) => Ordering::Greater,
(Ordering::Greater, _) => Ordering::Less,
}
}
}
@ -429,7 +489,7 @@ ops!(Linear, Linear);
#[cfg(test)]
mod tests {
use super::{Column, Line, Point};
use super::*;
#[test]
fn location_ordering() {
@ -493,51 +553,100 @@ mod tests {
#[test]
fn add_absolute() {
let num_cols = Column(42);
let point = Point::new(0, Column(13));
let result = point.add_absolute(num_cols, 1);
let result = point.add_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, Point::new(0, point.col + 1));
}
#[test]
fn add_absolute_wrap() {
let num_cols = Column(42);
let point = Point::new(1, num_cols - 1);
fn add_absolute_wrapline() {
let point = Point::new(1, Column(41));
let result = point.add_absolute(num_cols, 1);
let result = point.add_absolute(&(Line(2), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, Point::new(0, Column(0)));
}
#[test]
fn add_absolute_multiline_wrapline() {
let point = Point::new(2, Column(9));
let result = point.add_absolute(&(Line(3), Column(10)), Boundary::Clamp, 11);
assert_eq!(result, Point::new(0, Column(0)));
}
#[test]
fn add_absolute_clamp() {
let num_cols = Column(42);
let point = Point::new(0, num_cols - 1);
let point = Point::new(0, Column(41));
let result = point.add_absolute(num_cols, 1);
let result = point.add_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, point);
}
#[test]
fn add_absolute_wrap() {
let point = Point::new(0, Column(41));
let result = point.add_absolute(&(Line(3), Column(42)), Boundary::Wrap, 1);
assert_eq!(result, Point::new(2, Column(0)));
}
#[test]
fn add_absolute_multiline_wrap() {
let point = Point::new(0, Column(9));
let result = point.add_absolute(&(Line(3), Column(10)), Boundary::Wrap, 11);
assert_eq!(result, Point::new(1, Column(0)));
}
#[test]
fn sub_absolute() {
let num_cols = Column(42);
let point = Point::new(0, Column(13));
let result = point.sub_absolute(num_cols, 1);
let result = point.sub_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, Point::new(0, point.col - 1));
}
#[test]
fn sub_absolute_wrap() {
let num_cols = Column(42);
fn sub_absolute_wrapline() {
let point = Point::new(0, Column(0));
let result = point.sub_absolute(num_cols, 1);
let result = point.sub_absolute(&(Line(2), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, Point::new(1, num_cols - 1));
assert_eq!(result, Point::new(1, Column(41)));
}
#[test]
fn sub_absolute_multiline_wrapline() {
let point = Point::new(0, Column(0));
let result = point.sub_absolute(&(Line(3), Column(10)), Boundary::Clamp, 11);
assert_eq!(result, Point::new(2, Column(9)));
}
#[test]
fn sub_absolute_wrap() {
let point = Point::new(2, Column(0));
let result = point.sub_absolute(&(Line(3), Column(42)), Boundary::Wrap, 1);
assert_eq!(result, Point::new(0, Column(41)));
}
#[test]
fn sub_absolute_multiline_wrap() {
let point = Point::new(2, Column(0));
let result = point.sub_absolute(&(Line(3), Column(10)), Boundary::Wrap, 11);
assert_eq!(result, Point::new(1, Column(9)));
}
}

View file

@ -9,8 +9,9 @@ use std::convert::TryFrom;
use std::mem;
use std::ops::{Bound, Range, RangeBounds};
use crate::grid::Dimensions;
use crate::index::{Column, Line, Point, Side};
use crate::term::{Search, Term};
use crate::term::Term;
/// A Point and side within that point.
#[derive(Debug, Copy, Clone, PartialEq)]
@ -98,20 +99,19 @@ impl Selection {
self.region.end = Anchor::new(point, side);
}
pub fn rotate(
pub fn rotate<D: Dimensions>(
mut self,
num_lines: Line,
num_cols: Column,
dimensions: &D,
range: &Range<Line>,
delta: isize,
) -> Option<Selection> {
let num_lines = dimensions.screen_lines().0;
let num_cols = dimensions.cols().0;
let range_bottom = range.start.0;
let range_top = range.end.0;
let num_lines = num_lines.0;
let num_cols = num_cols.0;
let (mut start, mut end) = (&mut self.region.start, &mut self.region.end);
if Self::points_need_swap(start.point, end.point) {
if Selection::points_need_swap(start.point, end.point) {
mem::swap(&mut start, &mut end);
}
@ -238,7 +238,7 @@ impl Selection {
/// Convert selection to grid coordinates.
pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> {
let grid = term.grid();
let num_cols = grid.num_cols();
let num_cols = grid.cols();
// Order start above the end.
let mut start = self.region.start;
@ -250,7 +250,7 @@ impl Selection {
// Clamp to inside the grid buffer.
let is_block = self.ty == SelectionType::Block;
let (start, end) = Self::grid_clamp(start, end, is_block, grid.len()).ok()?;
let (start, end) = Self::grid_clamp(start, end, is_block, grid.total_lines()).ok()?;
match self.ty {
SelectionType::Simple => self.range_simple(start, end, num_cols),
@ -408,7 +408,7 @@ mod tests {
fn send_event(&self, _event: Event) {}
}
fn term(width: usize, height: usize) -> Term<Mock> {
fn term(height: usize, width: usize) -> Term<Mock> {
let size = SizeInfo {
width: width as f32,
height: height as f32,
@ -468,7 +468,7 @@ mod tests {
Selection::new(SelectionType::Simple, Point::new(0, Column(0)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Left);
assert_eq!(selection.to_range(&term(2, 1)), None);
assert_eq!(selection.to_range(&term(1, 2)), None);
}
/// Test adjacent cell selection from right to left.
@ -482,7 +482,7 @@ mod tests {
Selection::new(SelectionType::Simple, Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(0, Column(0)), Side::Right);
assert_eq!(selection.to_range(&term(2, 1)), None);
assert_eq!(selection.to_range(&term(1, 2)), None);
}
/// Test selection across adjacent lines.
@ -499,7 +499,7 @@ mod tests {
Selection::new(SelectionType::Simple, Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Right);
assert_eq!(selection.to_range(&term(5, 2)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(2, 5)).unwrap(), SelectionRange {
start: Point::new(1, Column(2)),
end: Point::new(0, Column(1)),
is_block: false,
@ -523,7 +523,7 @@ mod tests {
selection.update(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(1, Column(0)), Side::Right);
assert_eq!(selection.to_range(&term(5, 2)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(2, 5)).unwrap(), SelectionRange {
start: Point::new(1, Column(1)),
end: Point::new(0, Column(1)),
is_block: false,
@ -532,14 +532,13 @@ mod tests {
#[test]
fn line_selection() {
let num_lines = Line(10);
let num_cols = Column(5);
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Lines, Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(num_lines, num_cols, &(Line(0)..num_lines), 7).unwrap();
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(4)),
is_block: false,
@ -548,14 +547,13 @@ mod tests {
#[test]
fn semantic_selection() {
let num_lines = Line(10);
let num_cols = Column(5);
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Semantic, Point::new(0, Column(3)), Side::Left);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(num_lines, num_cols, &(Line(0)..num_lines), 7).unwrap();
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(3)),
is_block: false,
@ -564,14 +562,13 @@ mod tests {
#[test]
fn simple_selection() {
let num_lines = Line(10);
let num_cols = Column(5);
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(num_lines, num_cols, &(Line(0)..num_lines), 7).unwrap();
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(3)),
is_block: false,
@ -580,14 +577,13 @@ mod tests {
#[test]
fn block_selection() {
let num_lines = Line(10);
let num_cols = Column(5);
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Block, Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(num_lines, num_cols, &(Line(0)..num_lines), 7).unwrap();
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(2)),
end: Point::new(7, Column(3)),
is_block: true
@ -624,14 +620,13 @@ mod tests {
#[test]
fn rotate_in_region_up() {
let num_lines = Line(10);
let num_cols = Column(5);
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Simple, Point::new(2, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(num_lines, num_cols, &(Line(1)..(num_lines - 1)), 4).unwrap();
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), 4).unwrap();
assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(8, Column(0)),
end: Point::new(6, Column(3)),
is_block: false,
@ -640,30 +635,28 @@ mod tests {
#[test]
fn rotate_in_region_down() {
let num_lines = Line(10);
let num_cols = Column(5);
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Simple, Point::new(5, Column(3)), Side::Right);
selection.update(Point::new(8, Column(1)), Side::Left);
selection = selection.rotate(num_lines, num_cols, &(Line(1)..(num_lines - 1)), -5).unwrap();
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), -5).unwrap();
assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(3, Column(1)),
end: Point::new(1, num_cols - 1),
end: Point::new(1, size.1 - 1),
is_block: false,
});
}
#[test]
fn rotate_in_region_up_block() {
let num_lines = Line(10);
let num_cols = Column(5);
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Block, Point::new(2, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(num_lines, num_cols, &(Line(1)..(num_lines - 1)), 4).unwrap();
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), 4).unwrap();
assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange {
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(8, Column(2)),
end: Point::new(6, Column(3)),
is_block: true,

View file

@ -12,18 +12,19 @@ pub const MAX_ZEROWIDTH_CHARS: usize = 5;
bitflags! {
#[derive(Serialize, Deserialize)]
pub struct Flags: u16 {
const INVERSE = 0b00_0000_0001;
const BOLD = 0b00_0000_0010;
const ITALIC = 0b00_0000_0100;
const BOLD_ITALIC = 0b00_0000_0110;
const UNDERLINE = 0b00_0000_1000;
const WRAPLINE = 0b00_0001_0000;
const WIDE_CHAR = 0b00_0010_0000;
const WIDE_CHAR_SPACER = 0b00_0100_0000;
const DIM = 0b00_1000_0000;
const DIM_BOLD = 0b00_1000_0010;
const HIDDEN = 0b01_0000_0000;
const STRIKEOUT = 0b10_0000_0000;
const INVERSE = 0b000_0000_0001;
const BOLD = 0b000_0000_0010;
const ITALIC = 0b000_0000_0100;
const BOLD_ITALIC = 0b000_0000_0110;
const UNDERLINE = 0b000_0000_1000;
const WRAPLINE = 0b000_0001_0000;
const WIDE_CHAR = 0b000_0010_0000;
const WIDE_CHAR_SPACER = 0b000_0100_0000;
const DIM = 0b000_1000_0000;
const DIM_BOLD = 0b000_1000_0010;
const HIDDEN = 0b001_0000_0000;
const STRIKEOUT = 0b010_0000_0000;
const LEADING_WIDE_CHAR_SPACER = 0b100_0000_0000;
}
}
@ -59,7 +60,8 @@ impl GridCell for Cell {
| Flags::UNDERLINE
| Flags::STRIKEOUT
| Flags::WRAPLINE
| Flags::WIDE_CHAR_SPACER,
| Flags::WIDE_CHAR_SPACER
| Flags::LEADING_WIDE_CHAR_SPACER,
)
}

View file

@ -2,12 +2,13 @@ use std::fmt;
use std::ops::{Index, IndexMut, Mul};
use std::str::FromStr;
use log::{error, trace};
use serde::de::Visitor;
use log::trace;
use serde::de::{Error as _, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use serde_yaml::Value;
use crate::ansi;
use crate::config::{Colors, LOG_TARGET_CONFIG};
use crate::config::Colors;
pub const COUNT: usize = 269;
@ -67,7 +68,7 @@ impl<'de> Deserialize<'de> for Rgb {
f.write_str("hex color like #ff00ff")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E>
fn visit_str<E>(self, value: &str) -> Result<Rgb, E>
where
E: serde::de::Error,
{
@ -81,7 +82,7 @@ impl<'de> Deserialize<'de> for Rgb {
}
// Return an error if the syntax is incorrect.
let value = serde_yaml::Value::deserialize(deserializer)?;
let value = Value::deserialize(deserializer)?;
// Attempt to deserialize from struct form.
if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) {
@ -89,23 +90,14 @@ impl<'de> Deserialize<'de> for Rgb {
}
// Deserialize from hex notation (either 0xff00ff or #ff00ff).
match value.deserialize_str(RgbVisitor) {
Ok(rgb) => Ok(rgb),
Err(err) => {
error!(
target: LOG_TARGET_CONFIG,
"Problem with config: {}; using color #000000", err
);
Ok(Rgb::default())
},
}
value.deserialize_str(RgbVisitor).map_err(D::Error::custom)
}
}
impl FromStr for Rgb {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Rgb, ()> {
fn from_str(s: &str) -> Result<Rgb, ()> {
let chars = if s.starts_with("0x") && s.len() == 8 {
&s[2..]
} else if s.starts_with('#') && s.len() == 7 {
@ -128,6 +120,66 @@ impl FromStr for Rgb {
}
}
/// RGB color optionally referencing the cell's foreground or background.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CellRgb {
CellForeground,
CellBackground,
Rgb(Rgb),
}
impl CellRgb {
pub fn color(self, foreground: Rgb, background: Rgb) -> Rgb {
match self {
Self::CellForeground => foreground,
Self::CellBackground => background,
Self::Rgb(rgb) => rgb,
}
}
}
impl Default for CellRgb {
fn default() -> Self {
Self::Rgb(Rgb::default())
}
}
impl<'de> Deserialize<'de> for CellRgb {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
const EXPECTING: &str = "CellForeground, CellBackground, or hex color like #ff00ff";
struct CellRgbVisitor;
impl<'a> Visitor<'a> for CellRgbVisitor {
type Value = CellRgb;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(EXPECTING)
}
fn visit_str<E>(self, value: &str) -> Result<CellRgb, E>
where
E: serde::de::Error,
{
// Attempt to deserialize as enum constants.
match value {
"CellForeground" => return Ok(CellRgb::CellForeground),
"CellBackground" => return Ok(CellRgb::CellBackground),
_ => (),
}
Rgb::from_str(&value[..]).map(CellRgb::Rgb).map_err(|_| {
E::custom(format!("failed to parse color {}; expected {}", value, EXPECTING))
})
}
}
deserializer.deserialize_str(CellRgbVisitor).map_err(D::Error::custom)
}
}
/// List of indexed colors.
///
/// The first 16 entries are the standard ansi named colors. Items 16..232 are
@ -179,9 +231,6 @@ impl List {
self[ansi::NamedColor::Foreground] = colors.primary.foreground;
self[ansi::NamedColor::Background] = colors.primary.background;
// Background for custom cursor colors.
self[ansi::NamedColor::Cursor] = colors.cursor.cursor.unwrap_or_else(Rgb::default);
// Dims.
self[ansi::NamedColor::DimForeground] =
colors.primary.dim_foreground.unwrap_or(colors.primary.foreground * DIM_FACTOR);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,794 @@
use std::cmp::min;
use std::mem;
use std::ops::RangeInclusive;
use regex_automata::{dense, DenseDFA, Error as RegexError, DFA};
use crate::grid::{BidirectionalIterator, Dimensions, GridIterator};
use crate::index::{Boundary, Column, Direction, Point, Side};
use crate::term::cell::{Cell, Flags};
use crate::term::Term;
/// Used to match equal brackets, when performing a bracket-pair selection.
const BRACKET_PAIRS: [(char, char); 4] = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];
pub type Match = RangeInclusive<Point<usize>>;
/// Terminal regex search state.
pub struct RegexSearch {
/// Locate end of match searching right.
right_fdfa: DenseDFA<Vec<usize>, usize>,
/// Locate start of match searching right.
right_rdfa: DenseDFA<Vec<usize>, usize>,
/// Locate start of match searching left.
left_fdfa: DenseDFA<Vec<usize>, usize>,
/// Locate end of match searching left.
left_rdfa: DenseDFA<Vec<usize>, usize>,
}
impl RegexSearch {
/// Build the forwards and backwards search DFAs.
pub fn new(search: &str) -> Result<RegexSearch, RegexError> {
// Check case info for smart case
let has_uppercase = search.chars().any(|c| c.is_uppercase());
// Create Regex DFAs for all search directions.
let mut builder = dense::Builder::new();
let builder = builder.case_insensitive(!has_uppercase);
let left_fdfa = builder.clone().reverse(true).build(search)?;
let left_rdfa = builder.clone().anchored(true).longest_match(true).build(search)?;
let right_fdfa = builder.clone().build(search)?;
let right_rdfa = builder.anchored(true).longest_match(true).reverse(true).build(search)?;
Ok(RegexSearch { right_fdfa, right_rdfa, left_fdfa, left_rdfa })
}
}
impl<T> Term<T> {
/// Enter terminal buffer search mode.
#[inline]
pub fn start_search(&mut self, search: &str) {
self.regex_search = RegexSearch::new(search).ok();
self.dirty = true;
}
/// Cancel active terminal buffer search.
#[inline]
pub fn cancel_search(&mut self) {
self.regex_search = None;
self.dirty = true;
}
/// Get next search match in the specified direction.
pub fn search_next(
&self,
mut origin: Point<usize>,
direction: Direction,
side: Side,
mut max_lines: Option<usize>,
) -> Option<Match> {
origin = self.expand_wide(origin, direction);
max_lines = max_lines.filter(|max_lines| max_lines + 1 < self.total_lines());
match direction {
Direction::Right => self.next_match_right(origin, side, max_lines),
Direction::Left => self.next_match_left(origin, side, max_lines),
}
}
/// Find the next match to the right of the origin.
fn next_match_right(
&self,
origin: Point<usize>,
side: Side,
max_lines: Option<usize>,
) -> Option<Match> {
// Skip origin itself to exclude it from the search results.
let origin = origin.add_absolute(self, Boundary::Wrap, 1);
let start = self.line_search_left(origin);
let mut end = start;
// Limit maximum number of lines searched.
let total_lines = self.total_lines();
end = match max_lines {
Some(max_lines) => {
let line = (start.line + total_lines - max_lines) % total_lines;
Point::new(line, self.cols() - 1)
},
_ => end.sub_absolute(self, Boundary::Wrap, 1),
};
let mut regex_iter = RegexIter::new(start, end, Direction::Right, &self).peekable();
// Check if there's any match at all.
let first_match = regex_iter.peek()?.clone();
let regex_match = regex_iter
.find(|regex_match| {
let match_point = Self::match_side(&regex_match, side);
// If the match's point is beyond the origin, we're done.
match_point.line > start.line
|| match_point.line < origin.line
|| (match_point.line == origin.line && match_point.col >= origin.col)
})
.unwrap_or(first_match);
Some(regex_match)
}
/// Find the next match to the left of the origin.
fn next_match_left(
&self,
origin: Point<usize>,
side: Side,
max_lines: Option<usize>,
) -> Option<Match> {
// Skip origin itself to exclude it from the search results.
let origin = origin.sub_absolute(self, Boundary::Wrap, 1);
let start = self.line_search_right(origin);
let mut end = start;
// Limit maximum number of lines searched.
end = match max_lines {
Some(max_lines) => Point::new((start.line + max_lines) % self.total_lines(), Column(0)),
_ => end.add_absolute(self, Boundary::Wrap, 1),
};
let mut regex_iter = RegexIter::new(start, end, Direction::Left, &self).peekable();
// Check if there's any match at all.
let first_match = regex_iter.peek()?.clone();
let regex_match = regex_iter
.find(|regex_match| {
let match_point = Self::match_side(&regex_match, side);
// If the match's point is beyond the origin, we're done.
match_point.line < start.line
|| match_point.line > origin.line
|| (match_point.line == origin.line && match_point.col <= origin.col)
})
.unwrap_or(first_match);
Some(regex_match)
}
/// Get the side of a match.
fn match_side(regex_match: &Match, side: Side) -> Point<usize> {
match side {
Side::Right => *regex_match.end(),
Side::Left => *regex_match.start(),
}
}
/// Find the next regex match to the left of the origin point.
///
/// The origin is always included in the regex.
pub fn regex_search_left(&self, start: Point<usize>, end: Point<usize>) -> Option<Match> {
let RegexSearch { left_fdfa: fdfa, left_rdfa: rdfa, .. } = self.regex_search.as_ref()?;
// Find start and end of match.
let match_start = self.regex_search(start, end, Direction::Left, &fdfa)?;
let match_end = self.regex_search(match_start, start, Direction::Right, &rdfa)?;
Some(match_start..=match_end)
}
/// Find the next regex match to the right of the origin point.
///
/// The origin is always included in the regex.
pub fn regex_search_right(&self, start: Point<usize>, end: Point<usize>) -> Option<Match> {
let RegexSearch { right_fdfa: fdfa, right_rdfa: rdfa, .. } = self.regex_search.as_ref()?;
// Find start and end of match.
let match_end = self.regex_search(start, end, Direction::Right, &fdfa)?;
let match_start = self.regex_search(match_end, start, Direction::Left, &rdfa)?;
Some(match_start..=match_end)
}
/// Find the next regex match.
///
/// This will always return the side of the first match which is farthest from the start point.
fn regex_search(
&self,
start: Point<usize>,
end: Point<usize>,
direction: Direction,
dfa: &impl DFA,
) -> Option<Point<usize>> {
let last_line = self.total_lines() - 1;
let last_col = self.cols() - 1;
// Advance the iterator.
let next = match direction {
Direction::Right => GridIterator::next,
Direction::Left => GridIterator::prev,
};
let mut iter = self.grid.iter_from(start);
let mut state = dfa.start_state();
let mut regex_match = None;
let mut cell = *iter.cell();
self.skip_fullwidth(&mut iter, &mut cell, direction);
let mut point = iter.point();
loop {
// Convert char to array of bytes.
let mut buf = [0; 4];
let utf8_len = cell.c.encode_utf8(&mut buf).len();
// Pass char to DFA as individual bytes.
for i in 0..utf8_len {
// Inverse byte order when going left.
let byte = match direction {
Direction::Right => buf[i],
Direction::Left => buf[utf8_len - i - 1],
};
// Since we get the state from the DFA, it doesn't need to be checked.
state = unsafe { dfa.next_state_unchecked(state, byte) };
}
// Handle regex state changes.
if dfa.is_match_or_dead_state(state) {
if dfa.is_dead_state(state) {
break;
} else {
regex_match = Some(point);
}
}
// Stop once we've reached the target point.
if point == end {
break;
}
// Advance grid cell iterator.
let mut new_cell = match next(&mut iter) {
Some(&cell) => cell,
None => {
// Wrap around to other end of the scrollback buffer.
let start = Point::new(last_line - point.line, last_col - point.col);
iter = self.grid.iter_from(start);
*iter.cell()
},
};
self.skip_fullwidth(&mut iter, &mut new_cell, direction);
let last_point = mem::replace(&mut point, iter.point());
let last_cell = mem::replace(&mut cell, new_cell);
// Handle linebreaks.
if (last_point.col == last_col
&& point.col == Column(0)
&& !last_cell.flags.contains(Flags::WRAPLINE))
|| (last_point.col == Column(0)
&& point.col == last_col
&& !cell.flags.contains(Flags::WRAPLINE))
{
match regex_match {
Some(_) => break,
None => state = dfa.start_state(),
}
}
}
regex_match
}
/// Advance a grid iterator over fullwidth characters.
fn skip_fullwidth(
&self,
iter: &mut GridIterator<'_, Cell>,
cell: &mut Cell,
direction: Direction,
) {
match direction {
Direction::Right if cell.flags.contains(Flags::WIDE_CHAR) => {
iter.next();
},
Direction::Right if cell.flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) => {
if let Some(new_cell) = iter.next() {
*cell = *new_cell;
}
iter.next();
},
Direction::Left if cell.flags.contains(Flags::WIDE_CHAR_SPACER) => {
if let Some(new_cell) = iter.prev() {
*cell = *new_cell;
}
let prev = iter.point().sub_absolute(self, Boundary::Clamp, 1);
if self.grid[prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) {
iter.prev();
}
},
_ => (),
}
}
/// Find next matching bracket.
pub fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> {
let start_char = self.grid[point.line][point.col].c;
// Find the matching bracket we're looking for
let (forwards, end_char) = BRACKET_PAIRS.iter().find_map(|(open, close)| {
if open == &start_char {
Some((true, *close))
} else if close == &start_char {
Some((false, *open))
} else {
None
}
})?;
let mut iter = self.grid.iter_from(point);
// For every character match that equals the starting bracket, we
// ignore one bracket of the opposite type.
let mut skip_pairs = 0;
loop {
// Check the next cell
let cell = if forwards { iter.next() } else { iter.prev() };
// Break if there are no more cells
let c = match cell {
Some(cell) => cell.c,
None => break,
};
// Check if the bracket matches
if c == end_char && skip_pairs == 0 {
return Some(iter.point());
} else if c == start_char {
skip_pairs += 1;
} else if c == end_char {
skip_pairs -= 1;
}
}
None
}
/// Find left end of semantic block.
pub fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> {
// Limit the starting point to the last line in the history
point.line = min(point.line, self.total_lines() - 1);
let mut iter = self.grid.iter_from(point);
let last_col = self.cols() - Column(1);
let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
while let Some(cell) = iter.prev() {
if !cell.flags.intersects(wide) && self.semantic_escape_chars.contains(cell.c) {
break;
}
if iter.point().col == last_col && !cell.flags.contains(Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char
}
point = iter.point();
}
point
}
/// Find right end of semantic block.
pub fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> {
// Limit the starting point to the last line in the history
point.line = min(point.line, self.total_lines() - 1);
let mut iter = self.grid.iter_from(point);
let last_col = self.cols() - 1;
let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
while let Some(cell) = iter.next() {
if !cell.flags.intersects(wide) && self.semantic_escape_chars.contains(cell.c) {
break;
}
point = iter.point();
if point.col == last_col && !cell.flags.contains(Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char
}
}
point
}
/// Find the beginning of the current line across linewraps.
pub fn line_search_left(&self, mut point: Point<usize>) -> Point<usize> {
while point.line + 1 < self.total_lines()
&& self.grid[point.line + 1][self.cols() - 1].flags.contains(Flags::WRAPLINE)
{
point.line += 1;
}
point.col = Column(0);
point
}
/// Find the end of the current line across linewraps.
pub fn line_search_right(&self, mut point: Point<usize>) -> Point<usize> {
while self.grid[point.line][self.cols() - 1].flags.contains(Flags::WRAPLINE) {
point.line -= 1;
}
point.col = self.cols() - 1;
point
}
}
/// Iterator over regex matches.
pub struct RegexIter<'a, T> {
point: Point<usize>,
end: Point<usize>,
direction: Direction,
term: &'a Term<T>,
done: bool,
}
impl<'a, T> RegexIter<'a, T> {
pub fn new(
start: Point<usize>,
end: Point<usize>,
direction: Direction,
term: &'a Term<T>,
) -> Self {
Self { point: start, done: false, end, direction, term }
}
/// Skip one cell, advancing the origin point to the next one.
fn skip(&mut self) {
self.point = self.term.expand_wide(self.point, self.direction);
self.point = match self.direction {
Direction::Right => self.point.add_absolute(self.term, Boundary::Wrap, 1),
Direction::Left => self.point.sub_absolute(self.term, Boundary::Wrap, 1),
};
}
/// Get the next match in the specified direction.
fn next_match(&self) -> Option<Match> {
match self.direction {
Direction::Right => self.term.regex_search_right(self.point, self.end),
Direction::Left => self.term.regex_search_left(self.point, self.end),
}
}
}
impl<'a, T> Iterator for RegexIter<'a, T> {
type Item = Match;
fn next(&mut self) -> Option<Self::Item> {
if self.point == self.end {
self.done = true;
} else if self.done {
return None;
}
let regex_match = self.next_match()?;
self.point = *regex_match.end();
self.skip();
Some(regex_match)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::index::Column;
use crate::term::test::mock_term;
#[test]
fn regex_right() {
#[rustfmt::skip]
let mut term = mock_term("\
testing66\r\n\
Alacritty\n\
123\r\n\
Alacritty\r\n\
123\
");
// Check regex across wrapped and unwrapped lines.
term.regex_search = Some(RegexSearch::new("Ala.*123").unwrap());
let start = Point::new(3, Column(0));
let end = Point::new(0, Column(2));
let match_start = Point::new(3, Column(0));
let match_end = Point::new(2, Column(2));
assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end));
}
#[test]
fn regex_left() {
#[rustfmt::skip]
let mut term = mock_term("\
testing66\r\n\
Alacritty\n\
123\r\n\
Alacritty\r\n\
123\
");
// Check regex across wrapped and unwrapped lines.
term.regex_search = Some(RegexSearch::new("Ala.*123").unwrap());
let start = Point::new(0, Column(2));
let end = Point::new(3, Column(0));
let match_start = Point::new(3, Column(0));
let match_end = Point::new(2, Column(2));
assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
}
#[test]
fn nested_regex() {
#[rustfmt::skip]
let mut term = mock_term("\
Ala -> Alacritty -> critty\r\n\
critty\
");
// Greedy stopped at linebreak.
term.regex_search = Some(RegexSearch::new("Ala.*critty").unwrap());
let start = Point::new(1, Column(0));
let end = Point::new(1, Column(25));
assert_eq!(term.regex_search_right(start, end), Some(start..=end));
// Greedy stopped at dead state.
term.regex_search = Some(RegexSearch::new("Ala[^y]*critty").unwrap());
let start = Point::new(1, Column(0));
let end = Point::new(1, Column(15));
assert_eq!(term.regex_search_right(start, end), Some(start..=end));
}
#[test]
fn no_match_right() {
#[rustfmt::skip]
let mut term = mock_term("\
first line\n\
broken second\r\n\
third\
");
term.regex_search = Some(RegexSearch::new("nothing").unwrap());
let start = Point::new(2, Column(0));
let end = Point::new(0, Column(4));
assert_eq!(term.regex_search_right(start, end), None);
}
#[test]
fn no_match_left() {
#[rustfmt::skip]
let mut term = mock_term("\
first line\n\
broken second\r\n\
third\
");
term.regex_search = Some(RegexSearch::new("nothing").unwrap());
let start = Point::new(0, Column(4));
let end = Point::new(2, Column(0));
assert_eq!(term.regex_search_left(start, end), None);
}
#[test]
fn include_linebreak_left() {
#[rustfmt::skip]
let mut term = mock_term("\
testing123\r\n\
xxx\
");
// Make sure the cell containing the linebreak is not skipped.
term.regex_search = Some(RegexSearch::new("te.*123").unwrap());
let start = Point::new(0, Column(0));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(0));
let match_end = Point::new(1, Column(9));
assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
}
#[test]
fn include_linebreak_right() {
#[rustfmt::skip]
let mut term = mock_term("\
xxx\r\n\
testing123\
");
// Make sure the cell containing the linebreak is not skipped.
term.regex_search = Some(RegexSearch::new("te.*123").unwrap());
let start = Point::new(1, Column(2));
let end = Point::new(0, Column(9));
let match_start = Point::new(0, Column(0));
assert_eq!(term.regex_search_right(start, end), Some(match_start..=end));
}
#[test]
fn skip_dead_cell() {
let mut term = mock_term("alacritty");
// Make sure dead state cell is skipped when reversing.
term.regex_search = Some(RegexSearch::new("alacrit").unwrap());
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(6));
assert_eq!(term.regex_search_right(start, end), Some(start..=end));
}
#[test]
fn reverse_search_dead_recovery() {
let mut term = mock_term("zooo lense");
// Make sure the reverse DFA operates the same as a forwards DFA.
term.regex_search = Some(RegexSearch::new("zoo").unwrap());
let start = Point::new(0, Column(9));
let end = Point::new(0, Column(0));
let match_start = Point::new(0, Column(0));
let match_end = Point::new(0, Column(2));
assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
}
#[test]
fn multibyte_unicode() {
let mut term = mock_term("testвосибing");
term.regex_search = Some(RegexSearch::new("te.*ing").unwrap());
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(11));
assert_eq!(term.regex_search_right(start, end), Some(start..=end));
term.regex_search = Some(RegexSearch::new("te.*ing").unwrap());
let start = Point::new(0, Column(11));
let end = Point::new(0, Column(0));
assert_eq!(term.regex_search_left(start, end), Some(end..=start));
}
#[test]
fn fullwidth() {
let mut term = mock_term("a🦇x🦇");
term.regex_search = Some(RegexSearch::new("[^ ]*").unwrap());
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(5));
assert_eq!(term.regex_search_right(start, end), Some(start..=end));
term.regex_search = Some(RegexSearch::new("[^ ]*").unwrap());
let start = Point::new(0, Column(5));
let end = Point::new(0, Column(0));
assert_eq!(term.regex_search_left(start, end), Some(end..=start));
}
#[test]
fn singlecell_fullwidth() {
let mut term = mock_term("🦇");
term.regex_search = Some(RegexSearch::new("🦇").unwrap());
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(1));
assert_eq!(term.regex_search_right(start, end), Some(start..=end));
term.regex_search = Some(RegexSearch::new("🦇").unwrap());
let start = Point::new(0, Column(1));
let end = Point::new(0, Column(0));
assert_eq!(term.regex_search_left(start, end), Some(end..=start));
}
#[test]
fn wrapping() {
#[rustfmt::skip]
let mut term = mock_term("\
xxx\r\n\
xxx\
");
term.regex_search = Some(RegexSearch::new("xxx").unwrap());
let start = Point::new(0, Column(2));
let end = Point::new(1, Column(2));
let match_start = Point::new(1, Column(0));
assert_eq!(term.regex_search_right(start, end), Some(match_start..=end));
term.regex_search = Some(RegexSearch::new("xxx").unwrap());
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(0));
let match_end = Point::new(0, Column(2));
assert_eq!(term.regex_search_left(start, end), Some(end..=match_end));
}
#[test]
fn wrapping_into_fullwidth() {
#[rustfmt::skip]
let mut term = mock_term("\
🦇xx\r\n\
xx🦇\
");
term.regex_search = Some(RegexSearch::new("🦇x").unwrap());
let start = Point::new(0, Column(0));
let end = Point::new(1, Column(3));
let match_start = Point::new(1, Column(0));
let match_end = Point::new(1, Column(2));
assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end));
term.regex_search = Some(RegexSearch::new("x🦇").unwrap());
let start = Point::new(1, Column(2));
let end = Point::new(0, Column(0));
let match_start = Point::new(0, Column(1));
let match_end = Point::new(0, Column(3));
assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
}
#[test]
fn leading_spacer() {
#[rustfmt::skip]
let mut term = mock_term("\
xxx \n\
🦇xx\
");
term.grid[1][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER);
term.regex_search = Some(RegexSearch::new("🦇x").unwrap());
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(3));
let match_start = Point::new(1, Column(3));
let match_end = Point::new(0, Column(2));
assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end));
term.regex_search = Some(RegexSearch::new("🦇x").unwrap());
let start = Point::new(0, Column(3));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(3));
let match_end = Point::new(0, Column(2));
assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
term.regex_search = Some(RegexSearch::new("x🦇").unwrap());
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(3));
let match_start = Point::new(1, Column(2));
let match_end = Point::new(0, Column(1));
assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end));
term.regex_search = Some(RegexSearch::new("x🦇").unwrap());
let start = Point::new(0, Column(3));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(2));
let match_end = Point::new(0, Column(1));
assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
}
}
#[cfg(all(test, feature = "bench"))]
mod benches {
extern crate test;
use super::*;
use crate::term::test::mock_term;
#[bench]
fn regex_search(b: &mut test::Bencher) {
let input = format!("{:^10000}", "Alacritty");
let mut term = mock_term(&input);
term.regex_search = Some(RegexSearch::new(" Alacritty ").unwrap());
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(input.len() - 1));
b.iter(|| {
test::black_box(term.regex_search_right(start, end));
test::black_box(term.regex_search_left(end, start));
});
}
}

View file

@ -3,10 +3,10 @@ use std::cmp::{max, min};
use serde::Deserialize;
use crate::event::EventListener;
use crate::grid::{GridCell, Scroll};
use crate::index::{Column, Line, Point};
use crate::grid::{Dimensions, GridCell};
use crate::index::{Boundary, Column, Direction, Line, Point, Side};
use crate::term::cell::Flags;
use crate::term::{Search, Term};
use crate::term::Term;
/// Possible vi mode motion movements.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
@ -66,23 +66,23 @@ impl ViModeCursor {
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn motion<T: EventListener>(mut self, term: &mut Term<T>, motion: ViMotion) -> Self {
let display_offset = term.grid().display_offset();
let lines = term.grid().num_lines();
let cols = term.grid().num_cols();
let lines = term.grid().screen_lines();
let cols = term.grid().cols();
let mut buffer_point = term.visible_to_buffer(self.point);
match motion {
ViMotion::Up => {
if buffer_point.line + 1 < term.grid().len() {
if buffer_point.line + 1 < term.grid().total_lines() {
buffer_point.line += 1;
}
},
ViMotion::Down => buffer_point.line = buffer_point.line.saturating_sub(1),
ViMotion::Left => {
buffer_point = expand_wide(term, buffer_point, true);
buffer_point = term.expand_wide(buffer_point, Direction::Left);
let wrap_point = Point::new(buffer_point.line + 1, cols - 1);
if buffer_point.col.0 == 0
&& buffer_point.line + 1 < term.grid().len()
&& buffer_point.line + 1 < term.grid().total_lines()
&& is_wrap(term, wrap_point)
{
buffer_point = wrap_point;
@ -91,7 +91,7 @@ impl ViModeCursor {
}
},
ViMotion::Right => {
buffer_point = expand_wide(term, buffer_point, false);
buffer_point = term.expand_wide(buffer_point, Direction::Right);
if is_wrap(term, buffer_point) {
buffer_point = Point::new(buffer_point.line - 1, Column(0));
} else {
@ -99,9 +99,9 @@ impl ViModeCursor {
}
},
ViMotion::First => {
buffer_point = expand_wide(term, buffer_point, true);
buffer_point = term.expand_wide(buffer_point, Direction::Left);
while buffer_point.col.0 == 0
&& buffer_point.line + 1 < term.grid().len()
&& buffer_point.line + 1 < term.grid().total_lines()
&& is_wrap(term, Point::new(buffer_point.line + 1, cols - 1))
{
buffer_point.line += 1;
@ -125,20 +125,36 @@ impl ViModeCursor {
let col = first_occupied_in_line(term, line).unwrap_or_default().col;
buffer_point = Point::new(line, col);
},
ViMotion::SemanticLeft => buffer_point = semantic(term, buffer_point, true, true),
ViMotion::SemanticRight => buffer_point = semantic(term, buffer_point, false, true),
ViMotion::SemanticLeftEnd => buffer_point = semantic(term, buffer_point, true, false),
ViMotion::SemanticRightEnd => buffer_point = semantic(term, buffer_point, false, false),
ViMotion::WordLeft => buffer_point = word(term, buffer_point, true, true),
ViMotion::WordRight => buffer_point = word(term, buffer_point, false, true),
ViMotion::WordLeftEnd => buffer_point = word(term, buffer_point, true, false),
ViMotion::WordRightEnd => buffer_point = word(term, buffer_point, false, false),
ViMotion::SemanticLeft => {
buffer_point = semantic(term, buffer_point, Direction::Left, Side::Left);
},
ViMotion::SemanticRight => {
buffer_point = semantic(term, buffer_point, Direction::Right, Side::Left);
},
ViMotion::SemanticLeftEnd => {
buffer_point = semantic(term, buffer_point, Direction::Left, Side::Right);
},
ViMotion::SemanticRightEnd => {
buffer_point = semantic(term, buffer_point, Direction::Right, Side::Right);
},
ViMotion::WordLeft => {
buffer_point = word(term, buffer_point, Direction::Left, Side::Left);
},
ViMotion::WordRight => {
buffer_point = word(term, buffer_point, Direction::Right, Side::Left);
},
ViMotion::WordLeftEnd => {
buffer_point = word(term, buffer_point, Direction::Left, Side::Right);
},
ViMotion::WordRightEnd => {
buffer_point = word(term, buffer_point, Direction::Right, Side::Right);
},
ViMotion::Bracket => {
buffer_point = term.bracket_search(buffer_point).unwrap_or(buffer_point);
},
}
scroll_to_point(term, buffer_point);
term.scroll_to_point(buffer_point);
self.point = term.grid().clamp_buffer_to_visible(buffer_point);
self
@ -159,12 +175,12 @@ impl ViModeCursor {
// Clamp movement to within visible region.
let mut line = self.point.line.0 as isize;
line -= overscroll;
line = max(0, min(term.grid().num_lines().0 as isize - 1, line));
line = max(0, min(term.grid().screen_lines().0 as isize - 1, line));
// Find the first occupied cell after scrolling has been performed.
let buffer_point = term.visible_to_buffer(self.point);
let mut target_line = buffer_point.line as isize + lines;
target_line = max(0, min(term.grid().len() as isize - 1, target_line));
target_line = max(0, min(term.grid().total_lines() as isize - 1, target_line));
let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().col;
// Move cursor.
@ -174,27 +190,12 @@ impl ViModeCursor {
}
}
/// Scroll display if point is outside of viewport.
fn scroll_to_point<T: EventListener>(term: &mut Term<T>, point: Point<usize>) {
let display_offset = term.grid().display_offset();
let lines = term.grid().num_lines();
// Scroll once the top/bottom has been reached.
if point.line >= display_offset + lines.0 {
let lines = point.line.saturating_sub(display_offset + lines.0 - 1);
term.scroll_display(Scroll::Lines(lines as isize));
} else if point.line < display_offset {
let lines = display_offset.saturating_sub(point.line);
term.scroll_display(Scroll::Lines(-(lines as isize)));
};
}
/// Find next end of line to move to.
fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
let cols = term.grid().num_cols();
let cols = term.grid().cols();
// Expand across wide cells.
point = expand_wide(term, point, false);
point = term.expand_wide(point, Direction::Right);
// Find last non-empty cell in the current line.
let occupied = last_occupied_in_line(term, point.line).unwrap_or_default();
@ -217,10 +218,10 @@ fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
/// Find next non-empty cell to move to.
fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
let cols = term.grid().num_cols();
let cols = term.grid().cols();
// Expand left across wide chars, since we're searching lines left to right.
point = expand_wide(term, point, true);
point = term.expand_wide(point, Direction::Left);
// Find first non-empty cell in current line.
let occupied = first_occupied_in_line(term, point.line)
@ -231,7 +232,7 @@ fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
let mut occupied = None;
// Search for non-empty cell in previous lines.
for line in (point.line + 1)..term.grid().len() {
for line in (point.line + 1)..term.grid().total_lines() {
if !is_wrap(term, Point::new(line, cols - 1)) {
break;
}
@ -262,18 +263,18 @@ fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
fn semantic<T: EventListener>(
term: &mut Term<T>,
mut point: Point<usize>,
left: bool,
start: bool,
direction: Direction,
side: Side,
) -> Point<usize> {
// Expand semantically based on movement direction.
let expand_semantic = |point: Point<usize>| {
// Do not expand when currently on a semantic escape char.
let cell = term.grid()[point.line][point.col];
if term.semantic_escape_chars().contains(cell.c)
&& !cell.flags.contains(Flags::WIDE_CHAR_SPACER)
&& !cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER)
{
point
} else if left {
} else if direction == Direction::Left {
term.semantic_search_left(point)
} else {
term.semantic_search_right(point)
@ -281,27 +282,27 @@ fn semantic<T: EventListener>(
};
// Make sure we jump above wide chars.
point = expand_wide(term, point, left);
point = term.expand_wide(point, direction);
// Move to word boundary.
if left != start && !is_boundary(term, point, left) {
if direction != side && !is_boundary(term, point, direction) {
point = expand_semantic(point);
}
// Skip whitespace.
let mut next_point = advance(term, point, left);
while !is_boundary(term, point, left) && is_space(term, next_point) {
let mut next_point = advance(term, point, direction);
while !is_boundary(term, point, direction) && is_space(term, next_point) {
point = next_point;
next_point = advance(term, point, left);
next_point = advance(term, point, direction);
}
// Assure minimum movement of one cell.
if !is_boundary(term, point, left) {
point = advance(term, point, left);
if !is_boundary(term, point, direction) {
point = advance(term, point, direction);
}
// Move to word boundary.
if left == start && !is_boundary(term, point, left) {
if direction == side && !is_boundary(term, point, direction) {
point = expand_semantic(point);
}
@ -312,90 +313,71 @@ fn semantic<T: EventListener>(
fn word<T: EventListener>(
term: &mut Term<T>,
mut point: Point<usize>,
left: bool,
start: bool,
direction: Direction,
side: Side,
) -> Point<usize> {
// Make sure we jump above wide chars.
point = expand_wide(term, point, left);
point = term.expand_wide(point, direction);
if left == start {
if direction == side {
// Skip whitespace until right before a word.
let mut next_point = advance(term, point, left);
while !is_boundary(term, point, left) && is_space(term, next_point) {
let mut next_point = advance(term, point, direction);
while !is_boundary(term, point, direction) && is_space(term, next_point) {
point = next_point;
next_point = advance(term, point, left);
next_point = advance(term, point, direction);
}
// Skip non-whitespace until right inside word boundary.
let mut next_point = advance(term, point, left);
while !is_boundary(term, point, left) && !is_space(term, next_point) {
let mut next_point = advance(term, point, direction);
while !is_boundary(term, point, direction) && !is_space(term, next_point) {
point = next_point;
next_point = advance(term, point, left);
next_point = advance(term, point, direction);
}
}
if left != start {
if direction != side {
// Skip non-whitespace until just beyond word.
while !is_boundary(term, point, left) && !is_space(term, point) {
point = advance(term, point, left);
while !is_boundary(term, point, direction) && !is_space(term, point) {
point = advance(term, point, direction);
}
// Skip whitespace until right inside word boundary.
while !is_boundary(term, point, left) && is_space(term, point) {
point = advance(term, point, left);
while !is_boundary(term, point, direction) && is_space(term, point) {
point = advance(term, point, direction);
}
}
point
}
/// Jump to the end of a wide cell.
fn expand_wide<T, P>(term: &Term<T>, point: P, left: bool) -> Point<usize>
where
P: Into<Point<usize>>,
{
let mut point = point.into();
let cell = term.grid()[point.line][point.col];
if cell.flags.contains(Flags::WIDE_CHAR) && !left {
point.col += 1;
} else if cell.flags.contains(Flags::WIDE_CHAR_SPACER)
&& term.grid()[point.line][point.col - 1].flags.contains(Flags::WIDE_CHAR)
&& left
{
point.col -= 1;
}
point
}
/// Find first non-empty cell in line.
fn first_occupied_in_line<T>(term: &Term<T>, line: usize) -> Option<Point<usize>> {
(0..term.grid().num_cols().0)
(0..term.grid().cols().0)
.map(|col| Point::new(line, Column(col)))
.find(|&point| !is_space(term, point))
}
/// Find last non-empty cell in line.
fn last_occupied_in_line<T>(term: &Term<T>, line: usize) -> Option<Point<usize>> {
(0..term.grid().num_cols().0)
(0..term.grid().cols().0)
.map(|col| Point::new(line, Column(col)))
.rfind(|&point| !is_space(term, point))
}
/// Advance point based on direction.
fn advance<T>(term: &Term<T>, point: Point<usize>, left: bool) -> Point<usize> {
if left {
point.sub_absolute(term.grid().num_cols(), 1)
fn advance<T>(term: &Term<T>, point: Point<usize>, direction: Direction) -> Point<usize> {
if direction == Direction::Left {
point.sub_absolute(term, Boundary::Clamp, 1)
} else {
point.add_absolute(term.grid().num_cols(), 1)
point.add_absolute(term, Boundary::Clamp, 1)
}
}
/// Check if cell at point contains whitespace.
fn is_space<T>(term: &Term<T>, point: Point<usize>) -> bool {
let cell = term.grid()[point.line][point.col];
cell.c == ' ' || cell.c == '\t' && !cell.flags().contains(Flags::WIDE_CHAR_SPACER)
!cell.flags().intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER)
&& (cell.c == ' ' || cell.c == '\t')
}
fn is_wrap<T>(term: &Term<T>, point: Point<usize>) -> bool {
@ -403,9 +385,11 @@ fn is_wrap<T>(term: &Term<T>, point: Point<usize>) -> bool {
}
/// Check if point is at screen boundary.
fn is_boundary<T>(term: &Term<T>, point: Point<usize>, left: bool) -> bool {
(point.line == 0 && point.col + 1 >= term.grid().num_cols() && !left)
|| (point.line + 1 >= term.grid().len() && point.col.0 == 0 && left)
fn is_boundary<T>(term: &Term<T>, point: Point<usize>, direction: Direction) -> bool {
let total_lines = term.grid().total_lines();
let num_cols = term.grid().cols();
(point.line + 1 >= total_lines && point.col.0 == 0 && direction == Direction::Left)
|| (point.line == 0 && point.col + 1 >= num_cols && direction == Direction::Right)
}
#[cfg(test)]

View file

@ -8,11 +8,10 @@ use std::path::Path;
use alacritty_terminal::ansi;
use alacritty_terminal::config::MockConfig;
use alacritty_terminal::event::{Event, EventListener};
use alacritty_terminal::grid::{Dimensions, Grid};
use alacritty_terminal::index::Column;
use alacritty_terminal::term::cell::Cell;
use alacritty_terminal::term::SizeInfo;
use alacritty_terminal::Grid;
use alacritty_terminal::Term;
use alacritty_terminal::term::{SizeInfo, Term};
macro_rules! ref_tests {
($($name:ident)*) => {
@ -114,8 +113,8 @@ fn ref_test(dir: &Path) {
term_grid.truncate();
if grid != term_grid {
for i in 0..grid.len() {
for j in 0..grid.num_cols().0 {
for i in 0..grid.total_lines() {
for j in 0..grid.cols().0 {
let cell = term_grid[i][Column(j)];
let original_cell = grid[i][Column(j)];
if original_cell != cell {