summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 {