summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2020-07-09 21:45:22 +0000
committerGitHub <noreply@github.com>2020-07-09 21:45:22 +0000
commit46c0f352c40ecb68653421cb178a297acaf00c6d (patch)
tree3e1985f8237f7c8268703634f8c8ccb25f7823a5
parent9974bc8baa45fda0b4ba3db2ae615fb7f90f7029 (diff)
downloadalacritty-46c0f352c40ecb68653421cb178a297acaf00c6d.tar.gz
alacritty-46c0f352c40ecb68653421cb178a297acaf00c6d.zip
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.
-rw-r--r--CHANGELOG.md5
-rw-r--r--Cargo.lock231
-rw-r--r--alacritty.yml56
-rw-r--r--alacritty/Cargo.toml1
-rw-r--r--alacritty/src/config/bindings.rs18
-rw-r--r--alacritty/src/display.rs173
-rw-r--r--alacritty/src/event.rs350
-rw-r--r--alacritty/src/input.rs154
-rw-r--r--alacritty/src/renderer/mod.rs20
-rw-r--r--alacritty/src/scheduler.rs36
-rw-r--r--alacritty/src/url.rs2
-rw-r--r--alacritty/src/window.rs7
-rw-r--r--alacritty_terminal/Cargo.toml1
-rw-r--r--alacritty_terminal/src/ansi.rs34
-rw-r--r--alacritty_terminal/src/config/colors.rs124
-rw-r--r--alacritty_terminal/src/config/mod.rs27
-rw-r--r--alacritty_terminal/src/grid/mod.rs209
-rw-r--r--alacritty_terminal/src/grid/resize.rs26
-rw-r--r--alacritty_terminal/src/grid/storage.rs4
-rw-r--r--alacritty_terminal/src/grid/tests.rs19
-rw-r--r--alacritty_terminal/src/index.rs177
-rw-r--r--alacritty_terminal/src/selection.rs79
-rw-r--r--alacritty_terminal/src/term/cell.rs28
-rw-r--r--alacritty_terminal/src/term/color.rs87
-rw-r--r--alacritty_terminal/src/term/mod.rs677
-rw-r--r--alacritty_terminal/src/term/search.rs794
-rw-r--r--alacritty_terminal/src/vi_mode.rs182
-rw-r--r--alacritty_terminal/tests/ref.rs9
28 files changed, 2595 insertions, 935 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cdfaffa6..73c0217c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/Cargo.lock b/Cargo.lock
index ef2396dd..2697f45b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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)",
@@ -1486,13 +1480,22 @@ dependencies = [
]
[[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"
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"
diff --git a/alacritty.yml b/alacritty.yml
index 2fb3b59f..ed6feafc 100644
--- a/alacritty.yml
+++ b/alacritty.yml
@@ -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 }
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml
index 184f2102..62942985 100644
--- a/alacritty/Cargo.toml
+++ b/alacritty/Cargo.toml
@@ -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"
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs
index 547e168c..81a46d66 100644
--- a/alacritty/src/config/bindings.rs
+++ b/alacritty/src/config/bindings.rs
@@ -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;
diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs
index d61a5bbd..53e6fc58 100644
--- a/alacritty/src/display.rs
+++ b/alacritty/src/display.rs
@@ -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)))]
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index 630e8ef0..edbc4086 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -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());
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index 2819c850..48450b12 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -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();
+
+ 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();
- 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::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
}
diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs
index b2940a93..7dc037a1 100644
--- a/alacritty/src/renderer/mod.rs
+++ b/alacritty/src/renderer/mod.rs
@@ -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<_>>();
diff --git a/alacritty/src/scheduler.rs b/alacritty/src/scheduler.rs
index 673029ae..db6180ca 100644
--- a/alacritty/src/scheduler.rs
+++ b/alacritty/src/scheduler.rs
@@ -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,
-}
diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs
index f7c7105c..08a85f1b 100644
--- a/alacritty/src/url.rs
+++ b/alacritty/src/url.rs
@@ -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;
diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs
index 4275f859..450329d4 100644
--- a/alacritty/src/window.rs
+++ b/alacritty/src/window.rs
@@ -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);
diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml
index 65a12c6f..9345a76f 100644
--- a/alacritty_terminal/Cargo.toml
+++ b/alacritty_terminal/Cargo.toml
@@ -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"
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
index 8240ff00..5f24dcff 100644
--- a/alacritty_terminal/src/ansi.rs
+++ b/alacritty_terminal/src/ansi.rs
@@ -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 {
diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs
index ccea9536..13a30bef 100644
--- a/alacritty_terminal/src/config/colors.rs
+++ b/alacritty_terminal/src/config/colors.rs
@@ -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")]
+ 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")]
- pub background: Option<Rgb>,
+ 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 {
diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs
index c3936c0c..e3d72fda 100644
--- a/alacritty_terminal/src/config/mod.rs
+++ b/alacritty_terminal/src/config/mod.rs
@@ -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;
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 5ad7e8d6..c1f980e8 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -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,19 +341,27 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
#[allow(clippy::len_without_is_empty)]
impl<T> Grid<T> {
- #[inline]
- pub fn num_lines(&self) -> Line {
- self.lines
+ /// 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 display_iter(&self) -> DisplayIter<'_, T> {
- DisplayIter::new(self)
+ 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]
- pub fn num_cols(&self) -> Column {
- self.cols
+ pub fn display_iter(&self) -> DisplayIter<'_, T> {
+ DisplayIter::new(self)
}
#[inline]
@@ -385,17 +370,6 @@ impl<T> Grid<T> {
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);
diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs
index a0493fc0..079fcf19 100644
--- a/alacritty_terminal/src/grid/resize.rs
+++ b/alacritty_terminal/src/grid/resize.rs
@@ -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;
diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs
index 4b7ca41a..a025a99c 100644
--- a/alacritty_terminal/src/grid/storage.rs
+++ b/alacritty_terminal/src/grid/storage.rs
@@ -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();
diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs
index dbe5f1fc..1ed279a0 100644
--- a/alacritty_terminal/src/grid/tests.rs
+++ b/alacritty_terminal/src/grid/tests.rs
@@ -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'));
diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs
index 019de83b..baed323e 100644
--- a/alacritty_terminal/src/index.rs
+++ b/alacritty_terminal/src/index.rs
@@ -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, 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, num_cols - 1));
+ assert_eq!(result, Point::new(1, Column(9)));
}
}
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs
index dbd11592..83dea824 100644
--- a/alacritty_terminal/src/selection.rs
+++ b/alacritty_terminal/src/selection.rs
@@ -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,
diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs
index 5f948b19..3fdd8cea 100644
--- a/alacritty_terminal/src/term/cell.rs
+++ b/alacritty_terminal/src/term/cell.rs
@@ -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,
)
}
diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs
index ef2c2402..f20601d6 100644
--- a/alacritty_terminal/src/term/color.rs
+++ b/alacritty_terminal/src/term/color.rs
@@ -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);
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 996f6809..d59838d4 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -1,195 +1,121 @@
//! Exports the `Term` type which is a high-level API for the Grid.
use std::cmp::{max, min};
-use std::ops::{Index, IndexMut, Range};
+use std::iter::Peekable;
+use std::ops::{Index, IndexMut, Range, RangeInclusive};
use std::sync::Arc;
use std::time::{Duration, Instant};
-use std::{io, mem, ptr, str};
+use std::{io, iter, mem, ptr, str};
use log::{debug, trace};
use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthChar;
use crate::ansi::{
- self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset, TermInfo,
+ self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset,
};
use crate::config::{Config, VisualBellAnimation};
use crate::event::{Event, EventListener};
-use crate::grid::{
- BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll,
-};
-use crate::index::{self, Column, IndexRange, Line, Point, Side};
+use crate::grid::{Dimensions, DisplayIter, Grid, IndexRegion, Indexed, Scroll};
+use crate::index::{self, Boundary, Column, Direction, IndexRange, Line, Point, Side};
use crate::selection::{Selection, SelectionRange};
use crate::term::cell::{Cell, Flags, LineLength};
-use crate::term::color::{Rgb, DIM_FACTOR};
+use crate::term::color::{CellRgb, Rgb, DIM_FACTOR};
+use crate::term::search::{RegexIter, RegexSearch};
use crate::vi_mode::{ViModeCursor, ViMotion};
pub mod cell;
pub mod color;
-
-/// Used to match equal brackets, when performing a bracket-pair selection.
-const BRACKET_PAIRS: [(char, char); 4] = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];
+mod search;
/// Max size of the window title stack.
const TITLE_STACK_MAX_DEPTH: usize = 4096;
+/// Maximum number of linewraps followed outside of the viewport during search highlighting.
+const MAX_SEARCH_LINES: usize = 100;
+
/// Default tab interval, corresponding to terminfo `it` value.
const INITIAL_TABSTOPS: usize = 8;
/// Minimum number of columns and lines.
const MIN_SIZE: usize = 2;
-/// A type that can expand a given point to a region.
-///
-/// Usually this is implemented for some 2-D array type since
-/// points are two dimensional indices.
-pub trait Search {
- /// Find the nearest semantic boundary _to the left_ of provided point.
- fn semantic_search_left(&self, _: Point<usize>) -> Point<usize>;
- /// Find the nearest semantic boundary _to the point_ of provided point.
- fn semantic_search_right(&self, _: Point<usize>) -> Point<usize>;
- /// Find the beginning of a line, following line wraps.
- fn line_search_left(&self, _: Point<usize>) -> Point<usize>;
- /// Find the end of a line, following line wraps.
- fn line_search_right(&self, _: Point<usize>) -> Point<usize>;
- /// Find the nearest matching bracket.
- fn bracket_search(&self, _: Point<usize>) -> Option<Point<usize>>;
+/// Cursor storing all information relevant for rendering.
+#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize)]
+struct RenderableCursor {
+ text_color: CellRgb,
+ cursor_color: CellRgb,
+ key: CursorKey,
+ point: Point,
+ rendered: bool,
}
-impl<T> Search for Term<T> {
- 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.grid.len() - 1);
-
- let mut iter = self.grid.iter_from(point);
- let last_col = self.grid.num_cols() - Column(1);
-
- while let Some(cell) = iter.prev() {
- if !cell.flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER)
- && self.semantic_escape_chars.contains(cell.c)
- {
- break;
- }
-
- if iter.point().col == last_col && !cell.flags.contains(Flags::WRAPLINE) {
- // Cut off if on new line or hit escape char.
- break;
- }
-
- point = iter.point();
- }
-
- point
- }
-
- 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.grid.len() - 1);
-
- let mut iter = self.grid.iter_from(point);
- let last_col = self.grid.num_cols() - 1;
+/// A key for caching cursor glyphs.
+#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
+pub struct CursorKey {
+ pub style: CursorStyle,
+ pub is_wide: bool,
+}
- while let Some(cell) = iter.next() {
- if !cell.flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER)
- && self.semantic_escape_chars.contains(cell.c)
- {
- break;
- }
+type MatchIter<'a> = Box<dyn Iterator<Item = RangeInclusive<Point<usize>>> + 'a>;
- point = iter.point();
+/// Regex search highlight tracking.
+pub struct RenderableSearch<'a> {
+ iter: Peekable<MatchIter<'a>>,
+}
- if point.col == last_col && !cell.flags.contains(Flags::WRAPLINE) {
- // Cut off if on new line or hit escape char.
- break;
+impl<'a> RenderableSearch<'a> {
+ /// Create a new renderable search iterator.
+ fn new<T>(term: &'a Term<T>) -> Self {
+ let viewport_end = term.grid().display_offset();
+ let viewport_start = viewport_end + term.grid().screen_lines().0 - 1;
+
+ // Compute start of the first and end of the last line.
+ let start_point = Point::new(viewport_start, Column(0));
+ let mut start = term.line_search_left(start_point);
+ let end_point = Point::new(viewport_end, term.grid().cols() - 1);
+ let mut end = term.line_search_right(end_point);
+
+ // Set upper bound on search before/after the viewport to prevent excessive blocking.
+ if start.line > viewport_start + MAX_SEARCH_LINES {
+ if start.line == 0 {
+ // Do not highlight anything if this line is the last.
+ let iter: MatchIter<'a> = Box::new(iter::empty());
+ return Self { iter: iter.peekable() };
+ } else {
+ // Start at next line if this one is too long.
+ start.line -= 1;
}
}
+ end.line = max(end.line, viewport_end.saturating_sub(MAX_SEARCH_LINES));
- point
- }
-
- fn line_search_left(&self, mut point: Point<usize>) -> Point<usize> {
- while point.line + 1 < self.grid.len()
- && self.grid[point.line + 1][self.grid.num_cols() - 1].flags.contains(Flags::WRAPLINE)
- {
- point.line += 1;
- }
-
- point.col = Column(0);
+ // Create an iterater for the current regex search for all visible matches.
+ let iter: MatchIter<'a> = Box::new(
+ RegexIter::new(start, end, Direction::Right, &term)
+ .skip_while(move |rm| rm.end().line > viewport_start)
+ .take_while(move |rm| rm.start().line >= viewport_end),
+ );
- point
+ Self { iter: iter.peekable() }
}
- fn line_search_right(&self, mut point: Point<usize>) -> Point<usize> {
- while self.grid[point.line][self.grid.num_cols() - 1].flags.contains(Flags::WRAPLINE) {
- point.line -= 1;
- }
-
- point.col = self.grid.num_cols() - 1;
-
- point
- }
-
- 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))
+ /// Advance the search tracker to the next point.
+ ///
+ /// This will return `true` if the point passed is part of a search match.
+ fn advance(&mut self, point: Point<usize>) -> bool {
+ while let Some(regex_match) = &self.iter.peek() {
+ if regex_match.start() > &point {
+ break;
+ } else if regex_match.end() < &point {
+ let _ = self.iter.next();
} 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;
+ return true;
}
}
-
- None
+ false
}
}
-/// Cursor storing all information relevant for rendering.
-#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize)]
-struct RenderableCursor {
- text_color: Option<Rgb>,
- cursor_color: Option<Rgb>,
- key: CursorKey,
- point: Point,
- rendered: bool,
-}
-
-/// A key for caching cursor glyphs.
-#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
-pub struct CursorKey {
- pub style: CursorStyle,
- pub is_wide: bool,
-}
-
/// Iterator that yields cells needing render.
///
/// Yields cells that require work to be displayed (that is, not a an empty
@@ -205,6 +131,7 @@ pub struct RenderableCellsIter<'a, C> {
config: &'a Config<C>,
colors: &'a color::List,
selection: Option<SelectionRange<Line>>,
+ search: RenderableSearch<'a>,
}
impl<'a, C> RenderableCellsIter<'a, C> {
@@ -212,26 +139,24 @@ impl<'a, C> RenderableCellsIter<'a, C> {
///
/// The cursor and terminal mode are required for properly displaying the
/// cursor.
- fn new<'b, T>(
- term: &'b Term<T>,
- config: &'b Config<C>,
+ fn new<T>(
+ term: &'a Term<T>,
+ config: &'a Config<C>,
selection: Option<SelectionRange>,
- ) -> RenderableCellsIter<'b, C> {
+ ) -> RenderableCellsIter<'a, C> {
let grid = &term.grid;
- let inner = grid.display_iter();
-
let selection_range = selection.and_then(|span| {
let (limit_start, limit_end) = if span.is_block {
(span.start.col, span.end.col)
} else {
- (Column(0), grid.num_cols() - 1)
+ (Column(0), grid.cols() - 1)
};
// Do not render completely offscreen selection.
- let viewport_start = grid.display_offset();
- let viewport_end = viewport_start + grid.num_lines().0;
- if span.end.line >= viewport_end || span.start.line < viewport_start {
+ let viewport_end = grid.display_offset();
+ let viewport_start = viewport_end + grid.screen_lines().0 - 1;
+ if span.end.line > viewport_start || span.start.line < viewport_end {
return None;
}
@@ -249,10 +174,11 @@ impl<'a, C> RenderableCellsIter<'a, C> {
RenderableCellsIter {
cursor: term.renderable_cursor(config),
grid,
- inner,
+ inner: grid.display_iter(),
selection: selection_range,
config,
colors: &term.colors,
+ search: RenderableSearch::new(term),
}
}
@@ -280,20 +206,18 @@ impl<'a, C> RenderableCellsIter<'a, C> {
return true;
}
- let num_cols = self.grid.num_cols();
+ let num_cols = self.grid.cols();
let cell = self.grid[&point];
// Check if wide char's spacers are selected.
if cell.flags.contains(Flags::WIDE_CHAR) {
- let prevprev = point.sub(num_cols, 2);
let prev = point.sub(num_cols, 1);
let next = point.add(num_cols, 1);
// Check trailing spacer.
selection.contains(next.col, next.line)
// Check line-wrapping, leading spacer.
- || (self.grid[&prev].flags.contains(Flags::WIDE_CHAR_SPACER)
- && !self.grid[&prevprev].flags.contains(Flags::WIDE_CHAR)
+ || (self.grid[&prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER)
&& selection.contains(prev.col, prev.line))
} else if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
// Check if spacer's wide char is selected.
@@ -312,7 +236,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
}
}
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum RenderableCellContent {
Chars([char; cell::MAX_ZEROWIDTH_CHARS + 1]),
Cursor(CursorKey),
@@ -331,38 +255,44 @@ pub struct RenderableCell {
}
impl RenderableCell {
- fn new<C>(
- config: &Config<C>,
- colors: &color::List,
- cell: Indexed<Cell>,
- selected: bool,
- ) -> Self {
+ fn new<'a, C>(iter: &mut RenderableCellsIter<'a, C>, cell: Indexed<Cell>) -> Self {
+ let point = Point::new(cell.line, cell.column);
+
// Lookup RGB values.
- let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags);
- let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg);
- let mut bg_alpha = Self::compute_bg_alpha(cell.bg);
-
- let selection_background = config.colors.selection.background;
- if let (true, Some(col)) = (selected, selection_background) {
- // Override selection background with config colors.
- bg_rgb = col;
- bg_alpha = 1.0;
- } else if selected ^ cell.inverse() {
+ let mut fg_rgb = Self::compute_fg_rgb(iter.config, iter.colors, cell.fg, cell.flags);
+ let mut bg_rgb = Self::compute_bg_rgb(iter.colors, cell.bg);
+
+ let mut bg_alpha = if cell.inverse() {
+ mem::swap(&mut fg_rgb, &mut bg_rgb);
+ 1.0
+ } else {
+ Self::compute_bg_alpha(cell.bg)
+ };
+
+ if iter.is_selected(point) {
+ let config_bg = iter.config.colors.selection.background();
+ let selected_fg = iter.config.colors.selection.text().color(fg_rgb, bg_rgb);
+ bg_rgb = config_bg.color(fg_rgb, bg_rgb);
+ fg_rgb = selected_fg;
+
if fg_rgb == bg_rgb && !cell.flags.contains(Flags::HIDDEN) {
// Reveal inversed text when fg/bg is the same.
- fg_rgb = colors[NamedColor::Background];
- bg_rgb = colors[NamedColor::Foreground];
- } else {
- // Invert cell fg and bg colors.
- mem::swap(&mut fg_rgb, &mut bg_rgb);
+ fg_rgb = iter.colors[NamedColor::Background];
+ bg_rgb = iter.colors[NamedColor::Foreground];
+ bg_alpha = 1.0;
+ } else if config_bg != CellRgb::CellBackground {
+ bg_alpha = 1.0;
+ }
+ } else if iter.search.advance(iter.grid.visible_to_buffer(point)) {
+ // Highlight the cell if it is part of a search match.
+ let config_bg = iter.config.colors.search.matches.background;
+ let matched_fg = iter.config.colors.search.matches.foreground.color(fg_rgb, bg_rgb);
+ bg_rgb = config_bg.color(fg_rgb, bg_rgb);
+ fg_rgb = matched_fg;
+
+ if config_bg != CellRgb::CellBackground {
+ bg_alpha = 1.0;
}
-
- bg_alpha = 1.0;
- }
-
- // Override selection text with config colors.
- if let (true, Some(col)) = (selected, config.colors.selection.text) {
- fg_rgb = col;
}
RenderableCell {
@@ -376,6 +306,12 @@ impl RenderableCell {
}
}
+ fn is_empty(&self) -> bool {
+ self.bg_alpha == 0.
+ && !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT)
+ && self.inner == RenderableCellContent::Chars([' '; cell::MAX_ZEROWIDTH_CHARS + 1])
+ }
+
fn compute_fg_rgb<C>(config: &Config<C>, colors: &color::List, fg: Color, flags: Flags) -> Rgb {
match fg {
Color::Spec(rgb) => match flags & Flags::DIM {
@@ -416,6 +352,11 @@ impl RenderableCell {
}
}
+ /// Compute background alpha based on cell's original color.
+ ///
+ /// Since an RGB color matching the background should not be transparent, this is computed
+ /// using the named input color, rather than checking the RGB of the background after its color
+ /// is computed.
#[inline]
fn compute_bg_alpha(bg: Color) -> f32 {
if bg == Color::Named(NamedColor::Background) {
@@ -448,19 +389,13 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
if self.cursor.point.line == self.inner.line()
&& self.cursor.point.col == self.inner.column()
{
- let selected = self.is_selected(self.cursor.point);
-
// Handle cell below cursor.
if self.cursor.rendered {
- let mut cell =
- RenderableCell::new(self.config, self.colors, self.inner.next()?, selected);
+ let cell = self.inner.next()?;
+ let mut cell = RenderableCell::new(self, cell);
if self.cursor.key.style == CursorStyle::Block {
- mem::swap(&mut cell.bg, &mut cell.fg);
-
- if let Some(color) = self.cursor.text_color {
- cell.fg = color;
- }
+ cell.fg = self.cursor.text_color.color(cell.fg, cell.bg);
}
return Some(cell);
@@ -475,24 +410,18 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
line: self.cursor.point.line,
};
- let mut renderable_cell =
- RenderableCell::new(self.config, self.colors, cell, selected);
-
- renderable_cell.inner = RenderableCellContent::Cursor(self.cursor.key);
-
- if let Some(color) = self.cursor.cursor_color {
- renderable_cell.fg = color;
- }
+ let mut cell = RenderableCell::new(self, cell);
+ cell.inner = RenderableCellContent::Cursor(self.cursor.key);
+ cell.fg = self.cursor.cursor_color.color(cell.fg, cell.bg);
- return Some(renderable_cell);
+ return Some(cell);
}
} else {
let cell = self.inner.next()?;
+ let cell = RenderableCell::new(self, cell);
- let selected = self.is_selected(Point::new(cell.line, cell.column));
-
- if !cell.is_empty() || selected {
- return Some(RenderableCell::new(self.config, self.colors, cell, selected));
+ if !cell.is_empty() {
+ return Some(cell);
}
}
}
@@ -802,6 +731,9 @@ pub struct Term<T> {
/// Stack of saved window titles. When a title is popped from this stack, the `title` for the
/// term is set, and the Glutin window's title attribute is changed through the event listener.
title_stack: Vec<Option<String>>,
+
+ /// Current forwards and backwards buffer search regexes.
+ regex_search: Option<RegexSearch>,
}
impl<T> Term<T> {
@@ -810,8 +742,8 @@ impl<T> Term<T> {
where
T: EventListener,
{
- self.event_proxy.send_event(Event::MouseCursorDirty);
self.grid.scroll_display(scroll);
+ self.event_proxy.send_event(Event::MouseCursorDirty);
self.dirty = true;
}
@@ -823,9 +755,9 @@ impl<T> Term<T> {
let grid = Grid::new(num_lines, num_cols, history_size, Cell::default());
let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default());
- let tabs = TabStops::new(grid.num_cols());
+ let tabs = TabStops::new(grid.cols());
- let scroll_region = Line(0)..grid.num_lines();
+ let scroll_region = Line(0)..grid.screen_lines();
let colors = color::List::from(&config.colors);
@@ -853,6 +785,7 @@ impl<T> Term<T> {
default_title: config.window.title.clone(),
title_stack: Vec::new(),
selection: None,
+ regex_search: None,
}
}
@@ -964,7 +897,7 @@ impl<T> Term<T> {
tab_mode = true;
}
- if !cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
+ if !cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) {
// Push cells primary character.
text.push(cell.c);
@@ -983,10 +916,9 @@ impl<T> Term<T> {
}
// If wide char is not part of the selection, but leading spacer is, include it.
- if line_length == self.grid.num_cols()
+ if line_length == self.cols()
&& line_length.0 >= 2
- && grid_line[line_length - 1].flags.contains(Flags::WIDE_CHAR_SPACER)
- && !grid_line[line_length - 2].flags.contains(Flags::WIDE_CHAR)
+ && grid_line[line_length - 1].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER)
&& include_wrapped_wide
{
text.push(self.grid[line - 1][Column(0)].c);
@@ -1026,8 +958,8 @@ impl<T> Term<T> {
/// Resize terminal to new dimensions.
pub fn resize(&mut self, size: &SizeInfo) {
- let old_cols = self.grid.num_cols();
- let old_lines = self.grid.num_lines();
+ let old_cols = self.cols();
+ let old_lines = self.screen_lines();
let num_cols = max(size.cols(), Column(MIN_SIZE));
let num_lines = max(size.lines(), Line(MIN_SIZE));
@@ -1038,6 +970,23 @@ impl<T> Term<T> {
debug!("New num_cols is {} and num_lines is {}", num_cols, num_lines);
+ // Invalidate selection and tabs only when necessary.
+ if old_cols != num_cols {
+ self.selection = None;
+
+ // Recreate tabs list.
+ self.tabs.resize(num_cols);
+ } else if let Some(selection) = self.selection.take() {
+ // Move the selection if only number of lines changed.
+ let delta = if num_lines > old_lines {
+ (num_lines - old_lines.0).saturating_sub(self.grid.history_size()) as isize
+ } else {
+ let cursor_line = self.grid.cursor.point.line;
+ -(min(old_lines - cursor_line - 1, old_lines - num_lines).0 as isize)
+ };
+ self.selection = selection.rotate(self, &(Line(0)..num_lines), delta);
+ }
+
let is_alt = self.mode.contains(TermMode::ALT_SCREEN);
self.grid.resize(!is_alt, num_lines, num_cols);
@@ -1047,14 +996,11 @@ impl<T> Term<T> {
self.vi_mode_cursor.point.col = min(self.vi_mode_cursor.point.col, num_cols - 1);
self.vi_mode_cursor.point.line = min(self.vi_mode_cursor.point.line, num_lines - 1);
- // Recreate tabs list.
- self.tabs.resize(self.grid.num_cols());
-
- // Reset scrolling region and selection.
- self.scroll_region = Line(0)..self.grid.num_lines();
- self.selection = None;
+ // Reset scrolling region.
+ self.scroll_region = Line(0)..self.screen_lines();
}
+ /// Active terminal modes.
#[inline]
pub fn mode(&self) -> &TermMode {
&self.mode
@@ -1087,8 +1033,7 @@ impl<T> Term<T> {
fn scroll_down_relative(&mut self, origin: Line, mut lines: Line) {
trace!("Scrolling down relative: origin={}, lines={}", origin, lines);
- let num_lines = self.grid.num_lines();
- let num_cols = self.grid.num_cols();
+ let num_lines = self.screen_lines();
lines = min(lines, self.scroll_region.end - self.scroll_region.start);
lines = min(lines, self.scroll_region.end - origin);
@@ -1100,7 +1045,7 @@ impl<T> Term<T> {
self.selection = self
.selection
.take()
- .and_then(|s| s.rotate(num_lines, num_cols, &absolute_region, -(lines.0 as isize)));
+ .and_then(|s| s.rotate(self, &absolute_region, -(lines.0 as isize)));
// Scroll between origin and bottom
let template = Cell { bg: self.grid.cursor.template.bg, ..Cell::default() };
@@ -1114,8 +1059,8 @@ impl<T> Term<T> {
#[inline]
fn scroll_up_relative(&mut self, origin: Line, mut lines: Line) {
trace!("Scrolling up relative: origin={}, lines={}", origin, lines);
- let num_lines = self.grid.num_lines();
- let num_cols = self.grid.num_cols();
+
+ let num_lines = self.screen_lines();
lines = min(lines, self.scroll_region.end - self.scroll_region.start);
@@ -1123,10 +1068,8 @@ impl<T> Term<T> {
let absolute_region = (num_lines - region.end)..(num_lines - region.start);
// Scroll selection.
- self.selection = self
- .selection
- .take()
- .and_then(|s| s.rotate(num_lines, num_cols, &absolute_region, lines.0 as isize));
+ self.selection =
+ self.selection.take().and_then(|s| s.rotate(self, &absolute_region, lines.0 as isize));
// Scroll from origin to bottom less number of lines.
let template = Cell { bg: self.grid.cursor.template.bg, ..Cell::default() };
@@ -1139,7 +1082,7 @@ impl<T> Term<T> {
{
// Setting 132 column font makes no sense, but run the other side effects.
// Clear scrolling region.
- self.set_scrolling_region(1, self.grid.num_lines().0);
+ self.set_scrolling_region(1, None);
// Clear grid.
let template = self.grid.cursor.template;
@@ -1163,18 +1106,33 @@ impl<T> Term<T> {
#[inline]
pub fn toggle_vi_mode(&mut self) {
self.mode ^= TermMode::VI;
- self.selection = None;
- // Reset vi mode cursor position to match primary cursor.
- if self.mode.contains(TermMode::VI) {
+ let vi_mode = self.mode.contains(TermMode::VI);
+
+ // Do not clear selection when entering search.
+ if self.regex_search.is_none() || !vi_mode {
+ self.selection = None;
+ }
+
+ if vi_mode {
+ // Reset vi mode cursor position to match primary cursor.
let cursor = self.grid.cursor.point;
- let line = min(cursor.line + self.grid.display_offset(), self.lines() - 1);
+ let line = min(cursor.line + self.grid.display_offset(), self.screen_lines() - 1);
self.vi_mode_cursor = ViModeCursor::new(Point::new(line, cursor.col));
+ } else {
+ self.cancel_search();
}
self.dirty = true;
}
+ /// Start vi mode without moving the cursor.
+ #[inline]
+ pub fn set_vi_mode(&mut self) {
+ self.mode.insert(TermMode::VI);
+ self.dirty = true;
+ }
+
/// Move vi mode cursor.
#[inline]
pub fn vi_motion(&mut self, motion: ViMotion)
@@ -1188,18 +1146,89 @@ impl<T> Term<T> {
// Move cursor.
self.vi_mode_cursor = self.vi_mode_cursor.motion(self, motion);
+ self.vi_mode_recompute_selection();
+
+ self.dirty = true;
+ }
+
+ /// Move vi cursor to absolute point in grid.
+ #[inline]
+ pub fn vi_goto_point(&mut self, point: Point<usize>)
+ where
+ T: EventListener,
+ {
+ // Move viewport to make point visible.
+ self.scroll_to_point(point);
+
+ // Move vi cursor to the point.
+ self.vi_mode_cursor.point = self.grid.clamp_buffer_to_visible(point);
+
+ self.vi_mode_recompute_selection();
+
+ self.dirty = true;
+ }
+
+ /// Update the active selection to match the vi mode cursor position.
+ #[inline]
+ fn vi_mode_recompute_selection(&mut self) {
+ // Require vi mode to be active.
+ if !self.mode.contains(TermMode::VI) {
+ return;
+ }
- // Update selection if one is active.
let viewport_point = self.visible_to_buffer(self.vi_mode_cursor.point);
- if let Some(selection) = &mut self.selection {
- // Do not extend empty selections started by a single mouse click.
- if !selection.is_empty() {
- selection.update(viewport_point, Side::Left);
- selection.include_all();
- }
+
+ // Update only if non-empty selection is present.
+ let selection = match &mut self.selection {
+ Some(selection) if !selection.is_empty() => selection,
+ _ => return,
+ };
+
+ selection.update(viewport_point, Side::Left);
+ selection.include_all();
+ }
+
+ /// Scroll display to point if it is outside of viewport.
+ pub fn scroll_to_point(&mut self, point: Point<usize>)
+ where
+ T: EventListener,
+ {
+ let display_offset = self.grid.display_offset();
+ let num_lines = self.screen_lines().0;
+
+ if point.line >= display_offset + num_lines {
+ let lines = point.line.saturating_sub(display_offset + num_lines - 1);
+ self.scroll_display(Scroll::Delta(lines as isize));
+ } else if point.line < display_offset {
+ let lines = display_offset.saturating_sub(point.line);
+ self.scroll_display(Scroll::Delta(-(lines as isize)));
}
+ }
- self.dirty = true;
+ /// Jump to the end of a wide cell.
+ pub fn expand_wide(&self, mut point: Point<usize>, direction: Direction) -> Point<usize> {
+ let flags = self.grid[point.line][point.col].flags;
+
+ match direction {
+ Direction::Right if flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) => {
+ point.col = Column(1);
+ point.line -= 1;
+ },
+ Direction::Right if flags.contains(Flags::WIDE_CHAR) => point.col += 1,
+ Direction::Left if flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER) => {
+ if flags.contains(Flags::WIDE_CHAR_SPACER) {
+ point.col -= 1;
+ }
+
+ let prev = point.sub_absolute(self, Boundary::Clamp, 1);
+ if self.grid[prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) {
+ point = prev;
+ }
+ },
+ _ => (),
+ }
+
+ point
}
#[inline]
@@ -1260,7 +1289,8 @@ impl<T> Term<T> {
};
// Cursor shape.
- let hidden = !self.mode.contains(TermMode::SHOW_CURSOR) || point.line >= self.lines();
+ let hidden =
+ !self.mode.contains(TermMode::SHOW_CURSOR) || point.line >= self.screen_lines();
let cursor_style = if hidden && !vi_mode {
point.line = Line(0);
CursorStyle::Hidden
@@ -1277,19 +1307,18 @@ impl<T> Term<T> {
};
// Cursor colors.
- let (text_color, cursor_color) = if vi_mode {
- (config.vi_mode_cursor_text_color(), config.vi_mode_cursor_cursor_color())
+ let color = if vi_mode { config.colors.vi_mode_cursor } else { config.colors.cursor };
+ let cursor_color = if self.color_modified[NamedColor::Cursor as usize] {
+ CellRgb::Rgb(self.colors[NamedColor::Cursor])
} else {
- let cursor_cursor_color = config.cursor_cursor_color().map(|c| self.colors[c]);
- (config.cursor_text_color(), cursor_cursor_color)
+ color.cursor()
};
+ let text_color = color.text();
// Expand across wide cell when inside wide char or spacer.
let buffer_point = self.visible_to_buffer(point);
let cell = self.grid[buffer_point.line][buffer_point.col];
- let is_wide = if cell.flags.contains(Flags::WIDE_CHAR_SPACER)
- && self.grid[buffer_point.line][buffer_point.col - 1].flags.contains(Flags::WIDE_CHAR)
- {
+ let is_wide = if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
point.col -= 1;
true
} else {
@@ -1306,15 +1335,20 @@ impl<T> Term<T> {
}
}
-impl<T> TermInfo for Term<T> {
+impl<T> Dimensions for Term<T> {
#[inline]
- fn lines(&self) -> Line {
- self.grid.num_lines()
+ fn cols(&self) -> Column {
+ self.grid.cols()
}
#[inline]
- fn cols(&self) -> Column {
- self.grid.num_cols()
+ fn screen_lines(&self) -> Line {
+ self.grid.screen_lines()
+ }
+
+ #[inline]
+ fn total_lines(&self) -> usize {
+ self.grid.total_lines()
}
}
@@ -1344,7 +1378,7 @@ impl<T: EventListener> Handler for Term<T> {
self.wrapline();
}
- let num_cols = self.grid.num_cols();
+ let num_cols = self.cols();
// If in insert mode, first shift cells to the right.
if self.mode.contains(TermMode::INSERT) && self.grid.cursor.point.col + width < num_cols {
@@ -1365,7 +1399,7 @@ impl<T: EventListener> Handler for Term<T> {
if self.grid.cursor.point.col + 1 >= num_cols {
if self.mode.contains(TermMode::LINE_WRAP) {
// Insert placeholder before wide char if glyph does not fit in this row.
- self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER);
+ self.write_at_cursor(' ').flags.insert(Flags::LEADING_WIDE_CHAR_SPACER);
self.wrapline();
} else {
// Prevent out of bounds crash when linewrapping is disabled.
@@ -1403,11 +1437,11 @@ impl<T: EventListener> Handler for Term<T> {
let (y_offset, max_y) = if self.mode.contains(TermMode::ORIGIN) {
(self.scroll_region.start, self.scroll_region.end - 1)
} else {
- (Line(0), self.grid.num_lines() - 1)
+ (Line(0), self.screen_lines() - 1)
};
self.grid.cursor.point.line = min(line + y_offset, max_y);
- self.grid.cursor.point.col = min(col, self.grid.num_cols() - 1);
+ self.grid.cursor.point.col = min(col, self.cols() - 1);
self.grid.cursor.input_needs_wrap = false;
}
@@ -1428,11 +1462,11 @@ impl<T: EventListener> Handler for Term<T> {
let cursor = self.grid.cursor;
// Ensure inserting within terminal bounds
- let count = min(count, self.grid.num_cols() - cursor.point.col);
+ let count = min(count, self.cols() - cursor.point.col);
let source = cursor.point.col;
let destination = cursor.point.col + count;
- let num_cells = (self.grid.num_cols() - destination).0;
+ let num_cells = (self.cols() - destination).0;
let line = &mut self.grid[cursor.point.line];
@@ -1467,7 +1501,7 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn move_forward(&mut self, cols: Column) {
trace!("Moving forward: {}", cols);
- let num_cols = self.grid.num_cols();
+ let num_cols = self.cols();
self.grid.cursor.point.col = min(self.grid.cursor.point.col + cols, num_cols - 1);
self.grid.cursor.input_needs_wrap = false;
}
@@ -1524,7 +1558,7 @@ impl<T: EventListener> Handler for Term<T> {
return;
}
- while self.grid.cursor.point.col < self.grid.num_cols() && count != 0 {
+ while self.grid.cursor.point.col < self.cols() && count != 0 {
count -= 1;
let c = self.grid.cursor.charsets[self.active_charset].map('\t');
@@ -1534,7 +1568,7 @@ impl<T: EventListener> Handler for Term<T> {
}
loop {
- if (self.grid.cursor.point.col + 1) == self.grid.num_cols() {
+ if (self.grid.cursor.point.col + 1) == self.cols() {
break;
}
@@ -1573,7 +1607,7 @@ impl<T: EventListener> Handler for Term<T> {
let next = self.grid.cursor.point.line + 1;
if next == self.scroll_region.end {
self.scroll_up(Line(1));
- } else if next < self.grid.num_lines() {
+ } else if next < self.screen_lines() {
self.grid.cursor.point.line += 1;
}
}
@@ -1653,7 +1687,7 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn delete_lines(&mut self, lines: Line) {
let origin = self.grid.cursor.point.line;
- let lines = min(self.lines() - origin, lines);
+ let lines = min(self.screen_lines() - origin, lines);
trace!("Deleting {} lines", lines);
@@ -1669,7 +1703,7 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Erasing chars: count={}, col={}", count, cursor.point.col);
let start = cursor.point.col;
- let end = min(start + count, self.grid.num_cols());
+ let end = min(start + count, self.cols());
// Cleared cells have current background color set.
let row = &mut self.grid[cursor.point.line];
@@ -1680,7 +1714,7 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn delete_chars(&mut self, count: Column) {
- let cols = self.grid.num_cols();
+ let cols = self.cols();
let cursor = self.grid.cursor;
// Ensure deleting within terminal bounds.
@@ -1768,7 +1802,7 @@ impl<T: EventListener> Handler for Term<T> {
},
}
- let cursor_buffer_line = (self.grid.num_lines() - self.grid.cursor.point.line - 1).0;
+ let cursor_buffer_line = (self.grid.screen_lines() - self.grid.cursor.point.line - 1).0;
self.selection = self
.selection
.take()
@@ -1850,7 +1884,7 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Clearing screen: {:?}", mode);
let template = self.grid.cursor.template;
- let num_lines = self.grid.num_lines().0;
+ let num_lines = self.screen_lines().0;
let cursor_buffer_line = num_lines - self.grid.cursor.point.line.0 - 1;
match mode {
@@ -1864,7 +1898,7 @@ impl<T: EventListener> Handler for Term<T> {
}
// Clear up to the current column in the current line.
- let end = min(cursor.col + 1, self.grid.num_cols());
+ let end = min(cursor.col + 1, self.cols());
for cell in &mut self.grid[cursor.line][..end] {
cell.reset(&template);
}
@@ -1933,17 +1967,18 @@ impl<T: EventListener> Handler for Term<T> {
self.cursor_style = None;
self.grid.reset(Cell::default());
self.inactive_grid.reset(Cell::default());
- self.scroll_region = Line(0)..self.grid.num_lines();
- self.tabs = TabStops::new(self.grid.num_cols());
+ self.scroll_region = Line(0)..self.screen_lines();
+ self.tabs = TabStops::new(self.cols());
self.title_stack = Vec::new();
self.title = None;
self.selection = None;
+ self.regex_search = None;
}
#[inline]
fn reverse_index(&mut self) {
trace!("Reversing index");
-
+ // If cursor is at the top.
if self.grid.cursor.point.line == self.scroll_region.start {
self.scroll_down(Line(1));
} else {
@@ -2074,7 +2109,10 @@ impl<T: EventListener> Handler for Term<T> {
}
#[inline]
- fn set_scrolling_region(&mut self, top: usize, bottom: usize) {
+ fn set_scrolling_region(&mut self, top: usize, bottom: Option<usize>) {
+ // Fallback to the last line as default.
+ let bottom = bottom.unwrap_or_else(|| self.screen_lines().0);
+
if top >= bottom {
debug!("Invalid scrolling region: ({};{})", top, bottom);
return;
@@ -2089,8 +2127,8 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Setting scrolling region: ({};{})", start, end);
- self.scroll_region.start = min(start, self.grid.num_lines());
- self.scroll_region.end = min(end, self.grid.num_lines());
+ self.scroll_region.start = min(start, self.screen_lines());
+ self.scroll_region.end = min(end, self.screen_lines());
self.goto(Line(0), Column(0));
}
@@ -2216,6 +2254,79 @@ impl IndexMut<Column> for TabStops {
}
}
+/// Terminal test helpers.
+pub mod test {
+ use super::*;
+
+ use unicode_width::UnicodeWidthChar;
+
+ use crate::config::Config;
+ use crate::index::Column;
+
+ /// Construct a terminal from its content as string.
+ ///
+ /// A `\n` will break line and `\r\n` will break line without wrapping.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use alacritty_terminal::term::test::mock_term;
+ ///
+ /// // Create a terminal with the following cells:
+ /// //
+ /// // [h][e][l][l][o] <- WRAPLINE flag set
+ /// // [:][)][ ][ ][ ]
+ /// // [t][e][s][t][ ]
+ /// mock_term(
+ /// "\
+ /// hello\n:)\r\ntest",
+ /// );
+ /// ```
+ pub fn mock_term(content: &str) -> Term<()> {
+ let lines: Vec<&str> = content.split('\n').collect();
+ let num_cols = lines
+ .iter()
+ .map(|line| line.chars().filter(|c| *c != '\r').map(|c| c.width().unwrap()).sum())
+ .max()
+ .unwrap_or(0);
+
+ // Create terminal with the appropriate dimensions.
+ let size = SizeInfo {
+ width: num_cols as f32,
+ height: lines.len() as f32,
+ cell_width: 1.,
+ cell_height: 1.,
+ padding_x: 0.,
+ padding_y: 0.,
+ dpr: 1.,
+ };
+ let mut term = Term::new(&Config::<()>::default(), &size, ());
+
+ // Fill terminal with content.
+ for (line, text) in lines.iter().rev().enumerate() {
+ if !text.ends_with('\r') && line != 0 {
+ term.grid[line][Column(num_cols - 1)].flags.insert(Flags::WRAPLINE);
+ }
+
+ let mut index = 0;
+ for c in text.chars().take_while(|c| *c != '\r') {
+ term.grid[line][Column(index)].c = c;
+
+ // Handle fullwidth characters.
+ let width = c.width().unwrap();
+ if width == 2 {
+ term.grid[line][Column(index)].flags.insert(Flags::WIDE_CHAR);
+ term.grid[line][Column(index + 1)].flags.insert(Flags::WIDE_CHAR_SPACER);
+ }
+
+ index += width;
+ }
+ }
+
+ term
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs
new file mode 100644
index 00000000..b1766b05
--- /dev/null
+++ b/alacritty_terminal/src/term/search.rs
@@ -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));
+ });
+ }
+}
diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs
index 6621eda5..985d5455 100644
--- a/alacritty_terminal/src/vi_mode.rs
+++ b/alacritty_terminal/src/vi_mode.rs
@@ -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)]
diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs
index 9c5bbda5..62439775 100644
--- a/alacritty_terminal/tests/ref.rs
+++ b/alacritty_terminal/tests/ref.rs
@@ -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 {