aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAyose <ayosec@gmail.com>2021-04-18 22:45:19 +0100
committerAyose <ayosec@gmail.com>2021-04-18 22:45:19 +0100
commite5e9c8293535ea6eaaa3d017cbfb322c7ac95c14 (patch)
treeb805d27d397246e3684224e7aac0b833d763e7b6
parentfd0218f2545762c0d13dc17a55a1b6a334e31c19 (diff)
parent28abb1f9c78ab316126bdf94e2ca12f034f1d8fd (diff)
downloadr-alacritty-e5e9c8293535ea6eaaa3d017cbfb322c7ac95c14.tar.gz
r-alacritty-e5e9c8293535ea6eaaa3d017cbfb322c7ac95c14.tar.bz2
r-alacritty-e5e9c8293535ea6eaaa3d017cbfb322c7ac95c14.zip
Merge remote-tracking branch 'vendor/master' into graphics
-rw-r--r--CHANGELOG.md5
-rw-r--r--Cargo.lock203
-rw-r--r--alacritty.yml99
-rw-r--r--alacritty/Cargo.toml1
-rw-r--r--alacritty/src/config/bindings.rs7
-rw-r--r--alacritty/src/config/mouse.rs38
-rw-r--r--alacritty/src/config/ui_config.rs101
-rw-r--r--alacritty/src/display/content.rs24
-rw-r--r--alacritty/src/display/hint.rs188
-rw-r--r--alacritty/src/display/mod.rs122
-rw-r--r--alacritty/src/event.rs169
-rw-r--r--alacritty/src/input.rs288
-rw-r--r--alacritty/src/main.rs1
-rw-r--r--alacritty_terminal/Cargo.toml2
-rw-r--r--alacritty_terminal/src/ansi.rs93
-rw-r--r--alacritty_terminal/src/event.rs4
-rw-r--r--alacritty_terminal/src/event_loop.rs4
-rw-r--r--alacritty_terminal/src/grid/resize.rs4
-rw-r--r--alacritty_terminal/src/term/mod.rs50
-rw-r--r--alacritty_terminal/src/term/search.rs4
-rw-r--r--alacritty_terminal/tests/ref.rs4
-rw-r--r--docs/features.md12
22 files changed, 791 insertions, 632 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74391ed8..2df7c779 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Synchronized terminal updates using `DCS = 1 s ST`/`DCS = 2 s ST`
- Regex terminal hints ([see features.md](./docs/features.md#hints))
- Support for Sixel protocol
+- macOS keybinding (cmd+alt+H) hiding all windows other than Alacritty
+
+### Changed
+
+- The vi mode cursor is now created in the top-left if the terminal cursor is invisible
### Fixed
diff --git a/Cargo.lock b/Cargo.lock
index 3f220b08..5c227e71 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
+version = 3
+
[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.4"
@@ -41,7 +43,6 @@ dependencies = [
"serde_yaml",
"time",
"unicode-width",
- "urlocator",
"wayland-client",
"winapi 0.3.9",
"x11-dl",
@@ -73,8 +74,8 @@ dependencies = [
"mio",
"mio-anonymous-pipes",
"mio-extras",
- "miow 0.3.6",
- "nix 0.19.1",
+ "miow 0.3.7",
+ "nix 0.20.0",
"parking_lot",
"regex-automata",
"serde",
@@ -174,9 +175,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "byteorder"
-version = "1.4.2"
+version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "calloop"
@@ -421,14 +422,13 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.2"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3"
+checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
- "loom",
]
[[package]]
@@ -530,7 +530,16 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76"
dependencies = [
- "libloading",
+ "libloading 0.6.7",
+]
+
+[[package]]
+name = "dlib"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794"
+dependencies = [
+ "libloading 0.7.0",
]
[[package]]
@@ -541,9 +550,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dtoa"
-version = "0.4.7"
+version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
+checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "dwrote"
@@ -588,7 +597,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
dependencies = [
"cfg-if 1.0.0",
"libc",
- "redox_syscall 0.2.5",
+ "redox_syscall 0.2.6",
"winapi 0.3.9",
]
@@ -698,19 +707,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
-name = "generator"
-version = "0.6.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9fed24fd1e18827652b4d55652899a1e9da8e54d91624dc3437a5bc3a9f9a9c"
-dependencies = [
- "cc",
- "libc",
- "log",
- "rustversion",
- "winapi 0.3.9",
-]
-
-[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -748,7 +744,7 @@ dependencies = [
"glutin_glx_sys",
"glutin_wgl_sys",
"lazy_static",
- "libloading",
+ "libloading 0.6.7",
"log",
"objc",
"osmesa-sys",
@@ -905,9 +901,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
-version = "0.2.86"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
+checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "libloading"
@@ -920,6 +916,16 @@ dependencies = [
]
[[package]]
+name = "libloading"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -927,9 +933,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "lock_api"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
+checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
dependencies = [
"scopeguard",
]
@@ -945,17 +951,6 @@ dependencies = [
]
[[package]]
-name = "loom"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4"
-dependencies = [
- "cfg-if 1.0.0",
- "generator",
- "scoped-tls",
-]
-
-[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1029,7 +1024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c274c3c52dcd1d78c5d7ed841eca1e9ea2db8353f3b8ec25789cc62c471aaf"
dependencies = [
"mio",
- "miow 0.3.6",
+ "miow 0.3.7",
"spsc-buffer",
"winapi 0.3.9",
]
@@ -1060,11 +1055,10 @@ dependencies = [
[[package]]
name = "miow"
-version = "0.3.6"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
- "socket2",
"winapi 0.3.9",
]
@@ -1138,9 +1132,9 @@ dependencies = [
[[package]]
name = "nix"
-version = "0.19.1"
+version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
+checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
dependencies = [
"bitflags",
"cc",
@@ -1160,9 +1154,9 @@ dependencies = [
[[package]]
name = "notify"
-version = "4.0.15"
+version = "4.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd"
+checksum = "2599080e87c9bd051ddb11b10074f4da7b1223298df65d4c2ec5bcf309af1533"
dependencies = [
"bitflags",
"filetime",
@@ -1229,9 +1223,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.5.2"
+version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
+checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "osmesa-sys"
@@ -1271,7 +1265,7 @@ dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
- "redox_syscall 0.2.5",
+ "redox_syscall 0.2.6",
"smallvec",
"winapi 0.3.9",
]
@@ -1310,9 +1304,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.24"
+version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
dependencies = [
"unicode-xid",
]
@@ -1343,9 +1337,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
+checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
dependencies = [
"bitflags",
]
@@ -1373,9 +1367,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.22"
+version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "rust-argon2"
@@ -1400,12 +1394,6 @@ dependencies = [
]
[[package]]
-name = "rustversion"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
-
-[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1434,18 +1422,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
-version = "1.0.123"
+version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
+checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.123"
+version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
+checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [
"proc-macro2",
"quote",
@@ -1454,9 +1442,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.62"
+version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
+checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
@@ -1540,14 +1528,14 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "smithay-client-toolkit"
-version = "0.12.2"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "316e13a3eb853ce7bf72ad3530dc186cb2005c57c521ef5f4ada5ee4eed74de6"
+checksum = "4750c76fd5d3ac95fa3ed80fe667d6a3d8590a960e5b575b98eea93339a80b80"
dependencies = [
"andrew",
"bitflags",
"calloop",
- "dlib",
+ "dlib 0.4.2",
"lazy_static",
"log",
"memmap2",
@@ -1568,17 +1556,6 @@ dependencies = [
]
[[package]]
-name = "socket2"
-version = "0.3.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
-dependencies = [
- "cfg-if 1.0.0",
- "libc",
- "winapi 0.3.9",
-]
-
-[[package]]
name = "spsc-buffer"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1598,9 +1575,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "syn"
-version = "1.0.60"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
+checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
dependencies = [
"proc-macro2",
"quote",
@@ -1675,12 +1652,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
-name = "urlocator"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14e39a4f106dafb0a748b951494667a44e62b55fd7942b4fc12706d63cc535a0"
-
-[[package]]
name = "utf8parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1694,9 +1665,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
-version = "0.9.2"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "vswhom"
@@ -1720,9 +1691,9 @@ dependencies = [
[[package]]
name = "vte"
-version = "0.10.0"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2df25fed2855d2fbcbdf1016c69a6ac070fa1aabc8b5d7aedaab8703dce0d2d6"
+checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
dependencies = [
"utf8parse",
"vte_generate_state_changes",
@@ -1740,9 +1711,9 @@ dependencies = [
[[package]]
name = "walkdir"
-version = "2.3.1"
+version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi 0.3.9",
@@ -1763,14 +1734,14 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wayland-client"
-version = "0.28.3"
+version = "0.28.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdbdbe01d03b2267809f3ed99495b37395387fde789e0f2ebb78e8b43f75b6d7"
+checksum = "06ca44d86554b85cf449f1557edc6cc7da935cc748c8e4bf1c507cbd43bae02c"
dependencies = [
"bitflags",
"downcast-rs",
"libc",
- "nix 0.18.0",
+ "nix 0.20.0",
"scoped-tls",
"wayland-commons",
"wayland-scanner",
@@ -1779,11 +1750,11 @@ dependencies = [
[[package]]
name = "wayland-commons"
-version = "0.28.3"
+version = "0.28.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "480450f76717edd64ad04a4426280d737fc3d10a236b982df7b1aee19f0e2d56"
+checksum = "8bd75ae380325dbcff2707f0cd9869827ea1d2d6d534cff076858d3f0460fd5a"
dependencies = [
- "nix 0.18.0",
+ "nix 0.20.0",
"once_cell",
"smallvec",
"wayland-sys",
@@ -1791,20 +1762,20 @@ dependencies = [
[[package]]
name = "wayland-cursor"
-version = "0.28.3"
+version = "0.28.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6eb122c160223a7660feeaf949d0100281d1279acaaed3720eb3c9894496e5f"
+checksum = "b37e5455ec72f5de555ec39b5c3704036ac07c2ecd50d0bffe02d5fe2d4e65ab"
dependencies = [
- "nix 0.18.0",
+ "nix 0.20.0",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-egl"
-version = "0.28.3"
+version = "0.28.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c653507447113c967a1aeee413699acb42d96d6302ec967c6d51930eae8aa7f5"
+checksum = "9461a67930ec16da7a4fd8b50e9ffa23f4417240b43ec84008bd1b2c94421c94"
dependencies = [
"wayland-client",
"wayland-sys",
@@ -1812,9 +1783,9 @@ dependencies = [
[[package]]
name = "wayland-protocols"
-version = "0.28.3"
+version = "0.28.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "319a82b4d3054dd25acc32d9aee0f84fa95b63bc983fffe4703b6b8d47e01a30"
+checksum = "95df3317872bcf9eec096c864b69aa4769a1d5d6291a5b513f8ba0af0efbd52c"
dependencies = [
"bitflags",
"wayland-client",
@@ -1824,9 +1795,9 @@ dependencies = [
[[package]]
name = "wayland-scanner"
-version = "0.28.3"
+version = "0.28.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7010ba5767b3fcd350decc59055390b4ebe6bd1b9279a9feb1f1888987f1133d"
+checksum = "389d680d7bd67512dc9c37f39560224327038deb0f0e8d33f870900441b68720"
dependencies = [
"proc-macro2",
"quote",
@@ -1835,11 +1806,11 @@ dependencies = [
[[package]]
name = "wayland-sys"
-version = "0.28.3"
+version = "0.28.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6793834e0c35d11fd96a97297abe03d37be627e1847da52e17d7e0e3b51cc099"
+checksum = "2907bd297eef464a95ba9349ea771611771aa285b932526c633dc94d5400a8e2"
dependencies = [
- "dlib",
+ "dlib 0.5.0",
"lazy_static",
"pkg-config",
]
diff --git a/alacritty.yml b/alacritty.yml
index 3df946a9..8c3a1ab0 100644
--- a/alacritty.yml
+++ b/alacritty.yml
@@ -445,29 +445,6 @@
# If this is `true`, the cursor is temporarily hidden when typing.
#hide_when_typing: false
- #url:
- # URL launcher
- #
- # This program is executed when clicking on a text which is recognized as a
- # URL. The URL is always added to the command as the last parameter.
- #
- # When set to `launcher: None`, URL launching will be disabled completely.
- #
- # Default:
- # - (macOS) open
- # - (Linux/BSD) xdg-open
- # - (Windows) cmd /c start ""
- #launcher:
- # program: xdg-open
- # args: []
-
- # URL modifiers
- #
- # These are the modifiers that need to be held down for opening URLs when
- # clicking on them. The available modifiers are documented in the key
- # binding section.
- #modifiers: None
-
# Regex hints
#
# Terminal hints can be used to find text in the visible part of the terminal
@@ -478,10 +455,18 @@
# List with all available hints
#
- # Each hint takes a `regex`, `binding` and either a `command` or an `action`.
+ # Each hint must have a `regex` and either an `action` or a `command` field.
+ # The fields `mouse`, `binding` and `post_processing` are optional.
+ #
+ # The fields `command`, `binding.key`, `binding.mods` and `mouse.mods` accept
+ # the same values as they do in the `key_bindings` section.
+ #
+ # The `mouse.enabled` field controls if the hint should be underlined while
+ # the mouse with all `mouse.mods` keys held or the vi mode cursor is above it.
#
- # The fields `command`, `binding.key` and `binding.mods` accept the same
- # values as they do in the `key_bindings` section.
+ # If the `post_processing` field is set to `true`, heuristics will be used to
+ # shorten the match if there are characters likely not to be part of the hint
+ # (e.g. a trailing `.`). This is most useful for URIs.
#
# Values for `action`:
# - Copy
@@ -490,16 +475,19 @@
# Paste the hint's text to the terminal or search.
# - Select
# Select the hint's text.
- #
- # Example
- #
- # enabled:
- # - regex: "alacritty/alacritty#\\d*"
- # command: firefox
- # binding:
- # key: G
- # mods: Control|Shift
- #enabled: []
+ # - MoveViModeCursor
+ # Move the vi mode cursor to the beginning of the hint.
+ #enabled:
+ # - regex: "(mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\
+ # [^\u0000-\u001F\u007F-\u009F<>\" {-}\\^⟨⟩`]+"
+ # command: xdg-open
+ # post_processing: true
+ # mouse:
+ # enabled: true
+ # mods: None
+ # binding:
+ # key: U
+ # mods: Control|Shift
# Mouse bindings
#
@@ -821,25 +809,26 @@
#- { key: Return, mods: Alt, action: ToggleFullscreen }
# (macOS only)
- #- { key: K, mods: Command, mode: ~Vi|~Search, chars: "\x0c" }
- #- { key: K, mods: Command, mode: ~Vi|~Search, action: ClearHistory }
- #- { key: Key0, mods: Command, action: ResetFontSize }
- #- { key: Equals, mods: Command, action: IncreaseFontSize }
- #- { key: Plus, mods: Command, action: IncreaseFontSize }
- #- { key: NumpadAdd, mods: Command, action: IncreaseFontSize }
- #- { key: Minus, mods: Command, action: DecreaseFontSize }
- #- { key: NumpadSubtract, mods: Command, action: DecreaseFontSize }
- #- { key: V, mods: Command, action: Paste }
- #- { key: C, mods: Command, action: Copy }
- #- { key: C, mods: Command, mode: Vi|~Search, action: ClearSelection }
- #- { key: H, mods: Command, action: Hide }
- #- { key: M, mods: Command, action: Minimize }
- #- { key: Q, mods: Command, action: Quit }
- #- { key: W, mods: Command, action: Quit }
- #- { key: N, mods: Command, action: SpawnNewInstance }
- #- { key: F, mods: Command|Control, action: ToggleFullscreen }
- #- { key: F, mods: Command, mode: ~Search, action: SearchForward }
- #- { key: B, mods: Command, mode: ~Search, action: SearchBackward }
+ #- { key: K, mods: Command, mode: ~Vi|~Search, chars: "\x0c" }
+ #- { key: K, mods: Command, mode: ~Vi|~Search, action: ClearHistory }
+ #- { key: Key0, mods: Command, action: ResetFontSize }
+ #- { key: Equals, mods: Command, action: IncreaseFontSize }
+ #- { key: Plus, mods: Command, action: IncreaseFontSize }
+ #- { key: NumpadAdd, mods: Command, action: IncreaseFontSize }
+ #- { key: Minus, mods: Command, action: DecreaseFontSize }
+ #- { key: NumpadSubtract, mods: Command, action: DecreaseFontSize }
+ #- { key: V, mods: Command, action: Paste }
+ #- { key: C, mods: Command, action: Copy }
+ #- { key: C, mods: Command, mode: Vi|~Search, action: ClearSelection }
+ #- { key: H, mods: Command, action: Hide }
+ #- { key: H, mods: Command|Alt, action: HideOtherApplications }
+ #- { key: M, mods: Command, action: Minimize }
+ #- { key: Q, mods: Command, action: Quit }
+ #- { key: W, mods: Command, action: Quit }
+ #- { key: N, mods: Command, action: SpawnNewInstance }
+ #- { key: F, mods: Command|Control, action: ToggleFullscreen }
+ #- { key: F, mods: Command, mode: ~Search, action: SearchForward }
+ #- { key: B, mods: Command, mode: ~Search, action: SearchBackward }
#debug:
# Display the time it takes to redraw each frame.
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml
index 5f0c06ae..31e63d87 100644
--- a/alacritty/Cargo.toml
+++ b/alacritty/Cargo.toml
@@ -29,7 +29,6 @@ glutin = { version = "0.26.0", default-features = false, features = ["serde"] }
notify = "4"
parking_lot = "0.11.0"
crossfont = { version = "0.2.0", features = ["force_system_fontconfig"] }
-urlocator = "0.1.3"
copypasta = { version = "0.7.0", default-features = false }
libc = "0.2"
unicode-width = "0.1"
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs
index 732875db..4e7c2fcb 100644
--- a/alacritty/src/config/bindings.rs
+++ b/alacritty/src/config/bindings.rs
@@ -161,6 +161,10 @@ pub enum Action {
/// Hide the Alacritty window.
Hide,
+ /// Hide all windows other than Alacritty on macOS.
+ #[cfg(target_os = "macos")]
+ HideOtherApplications,
+
/// Minimize the Alacritty window.
Minimize,
@@ -685,6 +689,7 @@ pub fn platform_key_bindings() -> Vec<KeyBinding> {
C, ModifiersState::LOGO; Action::Copy;
C, ModifiersState::LOGO, +BindingMode::VI, ~BindingMode::SEARCH; Action::ClearSelection;
H, ModifiersState::LOGO; Action::Hide;
+ H, ModifiersState::LOGO | ModifiersState::ALT; Action::HideOtherApplications;
M, ModifiersState::LOGO; Action::Minimize;
Q, ModifiersState::LOGO; Action::Quit;
W, ModifiersState::LOGO; Action::Quit;
@@ -1157,7 +1162,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper {
type Value = ModsWrapper;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str("a subset of Shift|Control|Super|Command|Alt|Option")
+ f.write_str("None or a subset of Shift|Control|Super|Command|Alt|Option")
}
fn visit_str<E>(self, value: &str) -> Result<ModsWrapper, E>
diff --git a/alacritty/src/config/mouse.rs b/alacritty/src/config/mouse.rs
index 2aa7557c..aed1ab04 100644
--- a/alacritty/src/config/mouse.rs
+++ b/alacritty/src/config/mouse.rs
@@ -1,50 +1,12 @@
use std::time::Duration;
-use glutin::event::ModifiersState;
-
use alacritty_config_derive::ConfigDeserialize;
-use alacritty_terminal::config::Program;
-
-use crate::config::bindings::ModsWrapper;
#[derive(ConfigDeserialize, Default, Clone, Debug, PartialEq, Eq)]
pub struct Mouse {
pub double_click: ClickHandler,
pub triple_click: ClickHandler,
pub hide_when_typing: bool,
- pub url: Url,
-}
-
-#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
-pub struct Url {
- /// Program for opening links.
- pub launcher: Option<Program>,
-
- /// Modifier used to open links.
- modifiers: ModsWrapper,
-}
-
-impl Url {
- pub fn mods(&self) -> ModifiersState {
- self.modifiers.into_inner()
- }
-}
-
-impl Default for Url {
- fn default() -> Url {
- Url {
- #[cfg(not(any(target_os = "macos", windows)))]
- launcher: Some(Program::Just(String::from("xdg-open"))),
- #[cfg(target_os = "macos")]
- launcher: Some(Program::Just(String::from("open"))),
- #[cfg(windows)]
- launcher: Some(Program::WithArgs {
- program: String::from("cmd"),
- args: vec!["/c".to_string(), "start".to_string(), "".to_string()],
- }),
- modifiers: Default::default(),
- }
- }
}
#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs
index 3cd2ad88..135d12c8 100644
--- a/alacritty/src/config/ui_config.rs
+++ b/alacritty/src/config/ui_config.rs
@@ -21,6 +21,11 @@ use crate::config::font::Font;
use crate::config::mouse::Mouse;
use crate::config::window::WindowConfig;
+/// Regex used for the default URL hint.
+#[rustfmt::skip]
+const URL_REGEX: &str = "(mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\
+ [^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\" {-}\\^⟨⟩`]+";
+
#[derive(ConfigDeserialize, Debug, PartialEq)]
pub struct UiConfig {
/// Font configuration.
@@ -90,13 +95,18 @@ impl Default for UiConfig {
impl UiConfig {
/// Generate key bindings for all keyboard hints.
pub fn generate_hint_bindings(&mut self) {
- for hint in self.hints.enabled.drain(..) {
+ for hint in &self.hints.enabled {
+ let binding = match hint.binding {
+ Some(binding) => binding,
+ None => continue,
+ };
+
let binding = KeyBinding {
- trigger: hint.binding.key,
- mods: hint.binding.mods.0,
+ trigger: binding.key,
+ mods: binding.mods.0,
mode: BindingMode::empty(),
notmode: BindingMode::empty(),
- action: Action::Hint(hint),
+ action: Action::Hint(hint.clone()),
};
self.key_bindings.0.push(binding);
@@ -197,13 +207,42 @@ pub struct Delta<T: Default> {
}
/// Regex terminal hints.
-#[derive(ConfigDeserialize, Default, Debug, PartialEq, Eq)]
+#[derive(ConfigDeserialize, Debug, PartialEq, Eq)]
pub struct Hints {
/// Characters for the hint labels.
alphabet: HintsAlphabet,
/// All configured terminal hints.
- enabled: Vec<Hint>,
+ pub enabled: Vec<Hint>,
+}
+
+impl Default for Hints {
+ fn default() -> Self {
+ // Add URL hint by default when no other hint is present.
+ let pattern = LazyRegexVariant::Pattern(String::from(URL_REGEX));
+ let regex = LazyRegex(Rc::new(RefCell::new(pattern)));
+
+ #[cfg(not(any(target_os = "macos", windows)))]
+ let action = HintAction::Command(Program::Just(String::from("xdg-open")));
+ #[cfg(target_os = "macos")]
+ let action = HintAction::Command(Program::Just(String::from("open")));
+ #[cfg(windows)]
+ let action = HintAction::Command(Program::WithArgs {
+ program: String::from("cmd"),
+ args: vec!["/c".to_string(), "start".to_string(), "".to_string()],
+ });
+
+ Self {
+ enabled: vec![Hint {
+ regex,
+ action,
+ post_processing: true,
+ mouse: Some(HintMouse { enabled: true, mods: Default::default() }),
+ binding: Default::default(),
+ }],
+ alphabet: Default::default(),
+ }
+ }
}
impl Hints {
@@ -254,6 +293,8 @@ pub enum HintInternalAction {
Paste,
/// Select the text matching the hint.
Select,
+ /// Move the vi mode cursor to the beginning of the hint.
+ MoveViModeCursor,
}
/// Actions for hint bindings.
@@ -271,33 +312,51 @@ pub enum HintAction {
/// Hint configuration.
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Hint {
+ /// Regex for finding matches.
+ pub regex: LazyRegex,
+
/// Action executed when this hint is triggered.
#[serde(flatten)]
pub action: HintAction,
- /// Regex for finding matches.
- pub regex: LazyRegex,
+ /// Hint text post processing.
+ #[serde(default)]
+ pub post_processing: bool,
+
+ /// Hint mouse highlighting.
+ pub mouse: Option<HintMouse>,
/// Binding required to search for this hint.
- binding: HintBinding,
+ binding: Option<HintBinding>,
}
/// Binding for triggering a keyboard hint.
#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub struct HintBinding {
pub key: Key,
+ #[serde(default)]
+ pub mods: ModsWrapper,
+}
+
+/// Hint mouse highlighting.
+#[derive(ConfigDeserialize, Default, Copy, Clone, Debug, PartialEq, Eq)]
+pub struct HintMouse {
+ /// Hint mouse highlighting availability.
+ pub enabled: bool,
+
+ /// Required mouse modifiers for hint highlighting.
pub mods: ModsWrapper,
}
/// Lazy regex with interior mutability.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LazyRegex(Rc<RefCell<LazyRegexVariant>>);
impl LazyRegex {
/// Execute a function with the compiled regex DFAs as parameter.
- pub fn with_compiled<T, F>(&self, f: F) -> T
+ pub fn with_compiled<T, F>(&self, mut f: F) -> T
where
- F: Fn(&RegexSearch) -> T,
+ F: FnMut(&RegexSearch) -> T,
{
f(self.0.borrow_mut().compiled())
}
@@ -313,14 +372,6 @@ impl<'de> Deserialize<'de> for LazyRegex {
}
}
-/// Implement placeholder to allow derive upstream, since we never need it for this struct itself.
-impl PartialEq for LazyRegex {
- fn eq(&self, _other: &Self) -> bool {
- false
- }
-}
-impl Eq for LazyRegex {}
-
/// Regex which is compiled on demand, to avoid expensive computations at startup.
#[derive(Clone, Debug)]
pub enum LazyRegexVariant {
@@ -357,3 +408,13 @@ impl LazyRegexVariant {
}
}
}
+
+impl PartialEq for LazyRegexVariant {
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Self::Pattern(regex), Self::Pattern(other_regex)) => regex == other_regex,
+ _ => false,
+ }
+ }
+}
+impl Eq for LazyRegexVariant {}
diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs
index ae4bdd26..7b412c1d 100644
--- a/alacritty/src/display/content.rs
+++ b/alacritty/src/display/content.rs
@@ -19,15 +19,12 @@ use alacritty_terminal::term::{
use crate::config::ui_config::UiConfig;
use crate::display::color::{List, DIM_FACTOR};
use crate::display::hint::HintState;
-use crate::display::Display;
+use crate::display::{self, Display, MAX_SEARCH_LINES};
use crate::event::SearchState;
/// Minimum contrast between a fixed cursor color and the cell's background.
pub const MIN_CURSOR_CONTRAST: f64 = 1.5;
-/// Maximum number of linewraps followed outside of the viewport during search highlighting.
-const MAX_SEARCH_LINES: usize = 100;
-
/// Renderable terminal content.
///
/// This provides the terminal cursor and an iterator over all non-empty cells.
@@ -139,8 +136,8 @@ impl<'a> RenderableContent<'a> {
// Convert cursor point to viewport position.
let cursor_point = self.terminal_cursor.point;
- let line = (cursor_point.line + self.terminal_content.display_offset as i32).0 as usize;
- let point = Point::new(line, cursor_point.column);
+ let display_offset = self.terminal_content.display_offset;
+ let point = display::point_to_viewport(display_offset, cursor_point).unwrap();
Some(RenderableCursor {
shape: self.terminal_cursor.shape,
@@ -221,10 +218,12 @@ impl RenderableCell {
.selection
.map_or(false, |selection| selection.contains_cell(&cell, content.terminal_cursor));
+ let display_offset = content.terminal_content.display_offset;
+ let viewport_start = Point::new(Line(-(display_offset as i32)), Column(0));
+ let colors = &content.config.ui_config.colors;
let mut character = cell.c;
- let colors = &content.config.ui_config.colors;
- if let Some((c, is_first)) = content.hint.advance(cell.point) {
+ if let Some((c, is_first)) = content.hint.advance(viewport_start, cell.point) {
let (config_fg, config_bg) = if is_first {
(colors.hints.start.foreground, colors.hints.start.background)
} else {
@@ -260,8 +259,7 @@ impl RenderableCell {
// Convert cell point to viewport position.
let cell_point = cell.point;
- let line = (cell_point.line + content.terminal_content.display_offset as i32).0 as usize;
- let point = Point::new(line, cell_point.column);
+ let point = display::point_to_viewport(display_offset, cell_point).unwrap();
RenderableCell {
zerowidth: cell.zerowidth().map(|zerowidth| zerowidth.to_vec()),
@@ -412,7 +410,7 @@ impl<'a> Hint<'a> {
/// this position will be returned.
///
/// The tuple's [`bool`] will be `true` when the character is the first for this hint.
- fn advance(&mut self, point: Point) -> Option<(char, bool)> {
+ fn advance(&mut self, viewport_start: Point, point: Point) -> Option<(char, bool)> {
// Check if we're within a match at all.
if !self.regex.advance(point) {
return None;
@@ -423,7 +421,7 @@ impl<'a> Hint<'a> {
.regex
.matches
.get(self.regex.index)
- .map(|regex_match| regex_match.start())
+ .map(|regex_match| max(*regex_match.start(), viewport_start))
.filter(|start| start.line == point.line)?;
// Position within the hint label.
@@ -444,7 +442,7 @@ impl<'a> From<&'a HintState> for Hint<'a> {
/// Wrapper for finding visible regex matches.
#[derive(Default, Clone)]
-pub struct RegexMatches(Vec<RangeInclusive<Point>>);
+pub struct RegexMatches(pub Vec<RangeInclusive<Point>>);
impl RegexMatches {
/// Find all visible matches.
diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs
index 2a5e9c65..6ab68f10 100644
--- a/alacritty/src/display/hint.rs
+++ b/alacritty/src/display/hint.rs
@@ -1,8 +1,16 @@
-use alacritty_terminal::term::search::Match;
-use alacritty_terminal::term::Term;
+use std::cmp::{max, min};
+
+use glutin::event::ModifiersState;
+
+use alacritty_terminal::grid::BidirectionalIterator;
+use alacritty_terminal::index::{Boundary, Point};
+use alacritty_terminal::term::search::{Match, RegexSearch};
+use alacritty_terminal::term::{Term, TermMode};
use crate::config::ui_config::{Hint, HintAction};
+use crate::config::Config;
use crate::display::content::RegexMatches;
+use crate::display::MAX_SEARCH_LINES;
/// Percentage of characters in the hints alphabet used for the last character.
const HINT_SPLIT_PERCENTAGE: f32 = 0.5;
@@ -63,7 +71,20 @@ impl HintState {
};
// Find visible matches.
- self.matches = hint.regex.with_compiled(|regex| RegexMatches::new(term, regex));
+ self.matches.0 = hint.regex.with_compiled(|regex| {
+ let mut matches = RegexMatches::new(term, regex);
+
+ // Apply post-processing and search for sub-matches if necessary.
+ if hint.post_processing {
+ matches
+ .drain(..)
+ .map(|rm| HintPostProcessor::new(term, regex, rm).collect::<Vec<_>>())
+ .flatten()
+ .collect()
+ } else {
+ matches.0
+ }
+ });
// Cancel highlight with no visible matches.
if self.matches.is_empty() {
@@ -144,6 +165,7 @@ impl HintState {
}
/// Hint match which was selected by the user.
+#[derive(Clone)]
pub struct HintMatch {
/// Action for handling the text.
pub action: HintAction,
@@ -217,6 +239,166 @@ impl HintLabels {
}
}
+/// Check if there is a hint highlighted at the specified point.
+pub fn highlighted_at<T>(
+ term: &Term<T>,
+ config: &Config,
+ point: Point,
+ mouse_mods: ModifiersState,
+) -> Option<HintMatch> {
+ let mouse_mode = term.mode().intersects(TermMode::MOUSE_MODE);
+
+ config.ui_config.hints.enabled.iter().find_map(|hint| {
+ // Check if all required modifiers are pressed.
+ let highlight = hint.mouse.map_or(false, |mouse| {
+ mouse.enabled
+ && mouse_mods.contains(mouse.mods.0)
+ && (!mouse_mode || mouse_mods.contains(ModifiersState::SHIFT))
+ });
+ if !highlight {
+ return None;
+ }
+
+ hint.regex.with_compiled(|regex| {
+ // Setup search boundaries.
+ let mut start = term.line_search_left(point);
+ start.line = max(start.line, point.line - MAX_SEARCH_LINES);
+ let mut end = term.line_search_right(point);
+ end.line = min(end.line, point.line + MAX_SEARCH_LINES);
+
+ // Function to verify if the specified point is inside the match.
+ let at_point = |rm: &Match| *rm.start() <= point && *rm.end() >= point;
+
+ // Check if there's any match at the specified point.
+ let regex_match = term.regex_search_right(regex, start, end).filter(at_point)?;
+
+ // Apply post-processing and search for sub-matches if necessary.
+ let regex_match = if hint.post_processing {
+ HintPostProcessor::new(term, regex, regex_match).find(at_point)
+ } else {
+ Some(regex_match)
+ };
+
+ regex_match.map(|bounds| HintMatch { action: hint.action.clone(), bounds })
+ })
+ })
+}
+
+/// Iterator over all post-processed matches inside an existing hint match.
+struct HintPostProcessor<'a, T> {
+ /// Regex search DFAs.
+ regex: &'a RegexSearch,
+
+ /// Terminal reference.
+ term: &'a Term<T>,
+
+ /// Next hint match in the iterator.
+ next_match: Option<Match>,
+
+ /// Start point for the next search.
+ start: Point,
+
+ /// End point for the hint match iterator.
+ end: Point,
+}
+
+impl<'a, T> HintPostProcessor<'a, T> {
+ /// Create a new iterator for an unprocessed match.
+ fn new(term: &'a Term<T>, regex: &'a RegexSearch, regex_match: Match) -> Self {
+ let end = *regex_match.end();
+ let mut post_processor = Self { next_match: None, start: end, end, term, regex };
+
+ // Post-process the first hint match.
+ let next_match = post_processor.hint_post_processing(&regex_match);
+ post_processor.start = next_match.end().add(term, Boundary::Grid, 1);
+ post_processor.next_match = Some(next_match);
+
+ post_processor
+ }
+
+ /// Apply some hint post processing heuristics.
+ ///
+ /// This will check the end of the hint and make it shorter if certain characters are determined
+ /// to be unlikely to be intentionally part of the hint.
+ ///
+ /// This is most useful for identifying URLs appropriately.
+ fn hint_post_processing(&self, regex_match: &Match) -> Match {
+ let mut iter = self.term.grid().iter_from(*regex_match.start());
+
+ let mut c = iter.cell().c;
+
+ // Truncate uneven number of brackets.
+ let end = *regex_match.end();
+ let mut open_parents = 0;
+ let mut open_brackets = 0;
+ loop {
+ match c {
+ '(' => open_parents += 1,
+ '[' => open_brackets += 1,
+ ')' => {
+ if open_parents == 0 {
+ iter.prev();
+ break;
+ } else {
+ open_parents -= 1;
+ }
+ },
+ ']' => {
+ if open_brackets == 0 {
+ iter.prev();
+ break;
+ } else {
+ open_brackets -= 1;
+ }
+ },
+ _ => (),
+ }
+
+ if iter.point() == end {
+ break;
+ }
+
+ match iter.next() {
+ Some(indexed) => c = indexed.cell.c,
+ None => break,
+ }
+ }
+
+ // Truncate trailing characters which are likely to be delimiters.
+ let start = *regex_match.start();
+ while iter.point() != start {
+ if !matches!(c, '.' | ',' | ':' | ';' | '?' | '!' | '(' | '[' | '\'') {
+ break;
+ }
+
+ match iter.prev() {
+ Some(indexed) => c = indexed.cell.c,
+ None => break,
+ }
+ }
+
+ start..=iter.point()
+ }
+}
+
+impl<'a, T> Iterator for HintPostProcessor<'a, T> {
+ type Item = Match;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let next_match = self.next_match.take()?;
+
+ if self.start <= self.end {
+ if let Some(rm) = self.term.regex_search_right(self.regex, self.start, self.end) {
+ let regex_match = self.hint_post_processing(&rm);
+ self.start = regex_match.end().add(self.term, Boundary::Grid, 1);
+ self.next_match = Some(regex_match);
+ }
+ }
+
+ Some(next_match)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs
index 2c74920d..f6b55e93 100644
--- a/alacritty/src/display/mod.rs
+++ b/alacritty/src/display/mod.rs
@@ -2,6 +2,7 @@
//! GPU drawing.
use std::cmp::min;
+use std::convert::TryFrom;
use std::f64;
use std::fmt::{self, Formatter};
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
@@ -27,6 +28,7 @@ use alacritty_terminal::event::{EventListener, OnResize};
use alacritty_terminal::grid::Dimensions as _;
use alacritty_terminal::index::{Column, Direction, Line, Point};
use alacritty_terminal::selection::Selection;
+use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::term::{SizeInfo, Term, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES};
use crate::config::font::Font;
@@ -38,14 +40,13 @@ use crate::display::bell::VisualBell;
use crate::display::color::List;
use crate::display::content::RenderableContent;
use crate::display::cursor::IntoRects;
-use crate::display::hint::HintState;
+use crate::display::hint::{HintMatch, HintState};
use crate::display::meter::Meter;
use crate::display::window::Window;
use crate::event::{Mouse, SearchState};
use crate::message_bar::{MessageBuffer, MessageType};
use crate::renderer::rects::{RenderLines, RenderRect};
use crate::renderer::{self, GlyphCache, QuadRenderer};
-use crate::url::{Url, Urls};
pub mod content;
pub mod cursor;
@@ -58,7 +59,13 @@ mod meter;
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
mod wayland_theme;
+/// Maximum number of linewraps followed outside of the viewport during search highlighting.
+pub const MAX_SEARCH_LINES: usize = 100;
+
+/// Label for the forward terminal search bar.
const FORWARD_SEARCH_LABEL: &str = "Search: ";
+
+/// Label for the backward terminal search bar.
const BACKWARD_SEARCH_LABEL: &str = "Backward Search: ";
#[derive(Debug)]
@@ -164,10 +171,12 @@ impl DisplayUpdate {
pub struct Display {
pub size_info: SizeInfo,
pub window: Window,
- pub urls: Urls,
- /// Currently highlighted URL.
- pub highlighted_url: Option<Url>,
+ /// Hint highlighted by the mouse.
+ pub highlighted_hint: Option<HintMatch>,
+
+ /// Hint highlighted by the vi mode cursor.
+ pub vi_highlighted_hint: Option<HintMatch>,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
pub wayland_event_queue: Option<EventQueue>,
@@ -331,8 +340,8 @@ impl Display {
hint_state,
meter: Meter::new(),
size_info,
- urls: Urls::new(),
- highlighted_url: None,
+ highlighted_hint: None,
+ vi_highlighted_hint: None,
#[cfg(not(any(target_os = "macos", windows)))]
is_x11,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
@@ -473,8 +482,6 @@ impl Display {
mut terminal: MutexGuard<'_, Term<T>>,
message_buffer: &MessageBuffer,
config: &Config,
- mouse: &Mouse,
- mods: ModifiersState,
search_state: &SearchState,
) {
// Collect renderable content before the terminal is dropped.
@@ -492,10 +499,6 @@ impl Display {
let metrics = self.glyph_cache.font_metrics();
let size_info = self.size_info;
- let selection = !terminal.selection.as_ref().map(Selection::is_empty).unwrap_or(true);
- let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE)
- && !terminal.mode().contains(TermMode::VI);
-
let vi_mode = terminal.mode().contains(TermMode::VI);
let vi_mode_cursor = if vi_mode { Some(terminal.vi_mode_cursor) } else { None };
@@ -513,7 +516,6 @@ impl Display {
}
let mut lines = RenderLines::new();
- let mut urls = Urls::new();
let mut graphics_list = renderer::graphics::RenderList::default();
// Draw grid.
@@ -521,11 +523,18 @@ impl Display {
let _sampler = self.meter.sampler();
let glyph_cache = &mut self.glyph_cache;
+ let highlighted_hint = &self.highlighted_hint;
+ let vi_highlighted_hint = &self.vi_highlighted_hint;
self.renderer.with_api(&config.ui_config, &size_info, |mut api| {
// Iterate over all non-empty cells in the grid.
- for cell in grid_cells {
- // Update URL underlines.
- urls.update(&size_info, &cell);
+ for mut cell in grid_cells {
+ // Underline hints hovered by mouse or vi mode cursor.
+ let point = viewport_to_point(display_offset, cell.point);
+ if highlighted_hint.as_ref().map_or(false, |h| h.bounds.contains(&point))
+ || vi_highlighted_hint.as_ref().map_or(false, |h| h.bounds.contains(&point))
+ {
+ cell.flags.insert(Flags::UNDERLINE);
+ }
// Update underline/strikeout.
lines.update(&cell);
@@ -543,33 +552,9 @@ impl Display {
let mut rects = lines.rects(&metrics, &size_info);
- // Update visible URLs.
- self.urls = urls;
- if let Some(url) = self.urls.highlighted(config, mouse, mods, mouse_mode, selection) {
- rects.append(&mut url.rects(&metrics, &size_info));
-
- self.window.set_mouse_cursor(CursorIcon::Hand);
-
- self.highlighted_url = Some(url);
- } else if self.highlighted_url.is_some() {
- self.highlighted_url = None;
-
- if mouse_mode {
- self.window.set_mouse_cursor(CursorIcon::Default);
- } else {
- self.window.set_mouse_cursor(CursorIcon::Text);
- }
- }
-
if let Some(vi_mode_cursor) = vi_mode_cursor {
- // Highlight URLs at the vi mode cursor position.
- let vi_point = vi_mode_cursor.point;
- let line = (vi_point.line + display_offset).0 as usize;
- if let Some(url) = self.urls.find_at(Point::new(line, vi_point.column)) {
- rects.append(&mut url.rects(&metrics, &size_info));
- }
-
// Indicate vi mode by showing the cursor's position in the top right corner.
+ let vi_point = vi_mode_cursor.point;
let line = (-vi_point.line.0 + size_info.bottommost_line().0) as usize;
self.draw_line_indicator(config, &size_info, total_lines, Some(vi_point), line);
} else if search_state.regex().is_some() {
@@ -683,6 +668,47 @@ impl Display {
self.colors = List::from(&config.ui_config.colors);
}
+ /// Update the mouse/vi mode cursor hint highlighting.
+ pub fn update_highlighted_hints<T>(
+ &mut self,
+ term: &Term<T>,
+ config: &Config,
+ mouse: &Mouse,
+ modifiers: ModifiersState,
+ ) {
+ // Update vi mode cursor hint.
+ if term.mode().contains(TermMode::VI) {
+ let mods = ModifiersState::all();
+ let point = term.vi_mode_cursor.point;
+ self.vi_highlighted_hint = hint::highlighted_at(&term, config, point, mods);
+ } else {
+ self.vi_highlighted_hint = None;
+ }
+
+ // Abort if mouse highlighting conditions are not met.
+ if !mouse.inside_text_area || !term.selection.as_ref().map_or(true, Selection::is_empty) {
+ self.highlighted_hint = None;
+ return;
+ }
+
+ // Find highlighted hint at mouse position.
+ let point = mouse.point(&self.size_info, term.grid().display_offset());
+ let highlighted_hint = hint::highlighted_at(&term, config, point, modifiers);
+
+ // Update cursor shape.
+ if highlighted_hint.is_some() {
+ self.window.set_mouse_cursor(CursorIcon::Hand);
+ } else if self.highlighted_hint.is_some() {
+ if term.mode().intersects(TermMode::MOUSE_MODE) && !term.mode().contains(TermMode::VI) {
+ self.window.set_mouse_cursor(CursorIcon::Default);
+ } else {
+ self.window.set_mouse_cursor(CursorIcon::Text);
+ }
+ }
+
+ self.highlighted_hint = highlighted_hint;
+ }
+
/// Format search regex to account for the cursor and fullwidth characters.
fn format_search(size_info: &SizeInfo, search_regex: &str, search_label: &str) -> String {
// Add spacers for wide chars.
@@ -794,6 +820,18 @@ impl Display {
}
}
+/// Convert a terminal point to a viewport relative point.
+pub fn point_to_viewport(display_offset: usize, point: Point) -> Option<Point<usize>> {
+ let viewport_line = point.line.0 + display_offset as i32;
+ usize::try_from(viewport_line).ok().map(|line| Point::new(line, point.column))
+}
+
+/// Convert a viewport relative point to a terminal point.
+pub fn viewport_to_point(display_offset: usize, point: Point<usize>) -> Point {
+ let line = Line(point.line as i32) - display_offset;
+ Point::new(line, point.column)
+}
+
/// Calculate the cell dimensions based on font metrics.
///
/// This will return a tuple of the cell width and height.
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index 341f398a..a895514f 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -44,15 +44,14 @@ use crate::clipboard::Clipboard;
use crate::config::ui_config::{HintAction, HintInternalAction};
use crate::config::{self, Config};
use crate::daemon::start_daemon;
-use crate::display::hint::{HintMatch, HintState};
+use crate::display::hint::HintMatch;
use crate::display::window::Window;
-use crate::display::{Display, DisplayUpdate};
+use crate::display::{self, Display, DisplayUpdate};
use crate::input::{self, ActionContext as _, FONT_SIZE_STEP};
#[cfg(target_os = "macos")]
use crate::macos;
use crate::message_bar::{Message, MessageBuffer};
use crate::scheduler::{Scheduler, TimerId};
-use crate::url::{Url, Urls};
/// Duration after the last user input until an unlimited search is performed.
pub const TYPING_SEARCH_DELAY: Duration = Duration::from_millis(500);
@@ -207,17 +206,17 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
// Update selection.
if self.terminal.mode().contains(TermMode::VI)
- && self.terminal.selection.as_ref().map(|s| s.is_empty()) != Some(true)
+ && self.terminal.selection.as_ref().map_or(true, |s| !s.is_empty())
{
self.update_selection(self.terminal.vi_mode_cursor.point, Side::Right);
- } else if self.mouse().left_button_state == ElementState::Pressed
- || self.mouse().right_button_state == ElementState::Pressed
+ } else if self.mouse.left_button_state == ElementState::Pressed
+ || self.mouse.right_button_state == ElementState::Pressed
{
- let point = self.mouse().point;
- let line = Line(point.line as i32) - self.terminal.grid().display_offset();
- let point = Point::new(line, point.column);
- self.update_selection(point, self.mouse().cell_side);
+ let display_offset = self.terminal.grid().display_offset();
+ let point = self.mouse.point(&self.size_info(), display_offset);
+ self.update_selection(point, self.mouse.cell_side);
}
+ self.copy_selection(ClipboardType::Selection);
*self.dirty = true;
}
@@ -264,8 +263,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
self.terminal.selection = Some(selection);
*self.dirty = true;
-
- self.copy_selection(ClipboardType::Selection);
}
fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) {
@@ -322,13 +319,13 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
#[inline]
- fn window(&self) -> &Window {
- &self.display.window
+ fn window(&mut self) -> &mut Window {
+ &mut self.display.window
}
#[inline]
- fn window_mut(&mut self) -> &mut Window {
- &mut self.display.window
+ fn display(&mut self) -> &mut Display {
+ &mut self.display
}
#[inline]
@@ -385,30 +382,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
start_daemon(&alacritty, &args);
}
- /// Spawn URL launcher when clicking on URLs.
- fn launch_url(&self, url: Url) {
- if self.mouse.block_url_launcher {
- return;
- }
-
- if let Some(ref launcher) = self.config.ui_config.mouse.url.launcher {
- let display_offset = self.terminal.grid().display_offset();
- let start = url.start();
- let start = Point::new(Line(start.line as i32 - display_offset as i32), start.column);
- let end = url.end();
- let end = Point::new(Line(end.line as i32 - display_offset as i32), end.column);
-
- let mut args = launcher.args().to_vec();
- args.push(self.terminal.bounds_to_string(start, end));
-
- start_daemon(launcher.program(), &args);
- }
- }
-
- fn highlighted_url(&self) -> Option<&Url> {
- self.display.highlighted_url.as_ref()
- }
-
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.ui_config.font.clone().with_size(*self.font_size);
@@ -487,6 +460,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
let end = *focused_match.end();
self.start_selection(SelectionType::Simple, start, Side::Left);
self.update_selection(end, Side::Right);
+ self.copy_selection(ClipboardType::Selection);
}
self.search_state.dfas = None;
@@ -645,42 +619,53 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
}
- fn hint_state(&mut self) -> &mut HintState {
- &mut self.display.hint_state
- }
-
/// Process a new character for keyboard hints.
fn hint_input(&mut self, c: char) {
- let action = self.display.hint_state.keyboard_input(self.terminal, c);
+ if let Some(hint) = self.display.hint_state.keyboard_input(self.terminal, c) {
+ self.mouse.block_hint_launcher = false;
+ self.trigger_hint(&hint);
+ }
*self.dirty = true;
+ }
- let HintMatch { action, bounds } = match action {
- Some(action) => action,
- None => return,
- };
+ /// Trigger a hint action.
+ fn trigger_hint(&mut self, hint: &HintMatch) {
+ if self.mouse.block_hint_launcher {
+ return;
+ }
- match action {
+ match &hint.action {
// Launch an external program.
HintAction::Command(command) => {
- let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end());
+ let text = self.terminal.bounds_to_string(*hint.bounds.start(), *hint.bounds.end());
let mut args = command.args().to_vec();
args.push(text);
start_daemon(command.program(), &args);
},
// Copy the text to the clipboard.
HintAction::Action(HintInternalAction::Copy) => {
- let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end());
+ let text = self.terminal.bounds_to_string(*hint.bounds.start(), *hint.bounds.end());
self.clipboard.store(ClipboardType::Clipboard, text);
},
// Write the text to the PTY/search.
HintAction::Action(HintInternalAction::Paste) => {
- let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end());
+ let text = self.terminal.bounds_to_string(*hint.bounds.start(), *hint.bounds.end());
self.paste(&text);
},
// Select the text.
HintAction::Action(HintInternalAction::Select) => {
- self.start_selection(SelectionType::Simple, *bounds.start(), Side::Left);
- self.update_selection(*bounds.end(), Side::Right);
+ self.start_selection(SelectionType::Simple, *hint.bounds.start(), Side::Left);
+ self.update_selection(*hint.bounds.end(), Side::Right);
+ self.copy_selection(ClipboardType::Selection);
+ },
+ // Move the vi mode cursor.
+ HintAction::Action(HintInternalAction::MoveViModeCursor) => {
+ // Enter vi mode if we're not in it already.
+ if !self.terminal.mode().contains(TermMode::VI) {
+ self.terminal.toggle_vi_mode();
+ }
+
+ self.terminal.vi_goto_point(*hint.bounds.start());
},
}
}
@@ -731,10 +716,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
self.event_loop
}
- fn urls(&self) -> &Urls {
- &self.display.urls
- }
-
fn clipboard_mut(&mut self) -> &mut Clipboard {
self.clipboard
}
@@ -908,9 +889,9 @@ pub struct Mouse {
pub scroll_px: f64,
pub cell_side: Side,
pub lines_scrolled: f32,
- pub block_url_launcher: bool,
+ pub block_hint_launcher: bool,
+ pub hint_highlight_dirty: bool,
pub inside_text_area: bool,
- pub point: Point<usize>,
pub x: usize,
pub y: usize,
}
@@ -925,17 +906,34 @@ impl Default for Mouse {
right_button_state: ElementState::Released,
click_state: ClickState::None,
cell_side: Side::Left,
- block_url_launcher: Default::default(),
+ hint_highlight_dirty: Default::default(),
+ block_hint_launcher: Default::default(),
inside_text_area: Default::default(),
lines_scrolled: Default::default(),
scroll_px: Default::default(),
- point: Default::default(),
x: Default::default(),
y: Default::default(),
}
}
}
+impl Mouse {
+ /// Convert mouse pixel coordinates to viewport point.
+ ///
+ /// If the coordinates are outside of the terminal grid, like positions inside the padding, the
+ /// coordinates will be clamped to the closest grid coordinates.
+ #[inline]
+ pub fn point(&self, size: &SizeInfo, display_offset: usize) -> Point {
+ let col = self.x.saturating_sub(size.padding_x() as usize) / (size.cell_width() as usize);
+ let col = min(Column(col), size.last_column());
+
+ let line = self.y.saturating_sub(size.padding_y() as usize) / (size.cell_height() as usize);
+ let line = min(line, size.bottommost_line().0 as usize);
+
+ display::viewport_to_point(display_offset, Point::new(line, col))
+ }
+}
+
/// The event processor.
///
/// Stores some state from received events and dispatches actions when they are
@@ -1115,6 +1113,17 @@ impl<N: Notify + OnResize> Processor<N> {
return;
}
+ if self.dirty || self.mouse.hint_highlight_dirty {
+ self.display.update_highlighted_hints(
+ &terminal,
+ &self.config,
+ &self.mouse,
+ self.modifiers,
+ );
+ self.mouse.hint_highlight_dirty = false;
+ self.dirty = true;
+ }
+
if self.dirty {
self.dirty = false;
@@ -1127,14 +1136,7 @@ impl<N: Notify + OnResize> Processor<N> {
}
// Redraw screen.
- self.display.draw(
- terminal,
- &self.message_buffer,
- &self.config,
- &self.mouse,
- self.modifiers,
- &self.search_state,
- );
+ self.display.draw(terminal, &self.message_buffer, &self.config, &self.search_state);
}
});
@@ -1165,7 +1167,7 @@ impl<N: Notify + OnResize> Processor<N> {
// Resize to event's dimensions, since no resize event is emitted on Wayland.
display_update_pending.set_dimensions(PhysicalSize::new(width, height));
- processor.ctx.window_mut().dpr = scale_factor;
+ processor.ctx.window().dpr = scale_factor;
*processor.ctx.dirty = true;
},
Event::Message(message) => {
@@ -1184,7 +1186,7 @@ impl<N: Notify + OnResize> Processor<N> {
TerminalEvent::Title(title) => {
let ui_config = &processor.ctx.config.ui_config;
if ui_config.window.dynamic_title {
- processor.ctx.window_mut().set_title(&title);
+ processor.ctx.window().set_title(&title);
}
},
TerminalEvent::ResetTitle => {
@@ -1198,7 +1200,7 @@ impl<N: Notify + OnResize> Processor<N> {
// Set window urgency.
if processor.ctx.terminal.mode().contains(TermMode::URGENCY_HINTS) {
let focused = processor.ctx.terminal.is_focused;
- processor.ctx.window_mut().set_urgent(!focused);
+ processor.ctx.window().set_urgent(!focused);
}
// Ring visual bell.
@@ -1220,6 +1222,7 @@ impl<N: Notify + OnResize> Processor<N> {
let text = format(processor.ctx.display.colors[index]);
processor.ctx.write_to_pty(text.into_bytes());
},
+ TerminalEvent::PtyWrite(text) => processor.ctx.write_to_pty(text.into_bytes()),
TerminalEvent::MouseCursorDirty => processor.reset_mouse_cursor(),
TerminalEvent::Exit => (),
TerminalEvent::CursorBlinkingChange(_) => {
@@ -1251,16 +1254,16 @@ impl<N: Notify + OnResize> Processor<N> {
},
WindowEvent::ReceivedCharacter(c) => processor.received_char(c),
WindowEvent::MouseInput { state, button, .. } => {
- processor.ctx.window_mut().set_mouse_visible(true);
+ processor.ctx.window().set_mouse_visible(true);
processor.mouse_input(state, button);
*processor.ctx.dirty = true;
},
WindowEvent::CursorMoved { position, .. } => {
- processor.ctx.window_mut().set_mouse_visible(true);
+ processor.ctx.window().set_mouse_visible(true);
processor.mouse_moved(position);
},
WindowEvent::MouseWheel { delta, phase, .. } => {
- processor.ctx.window_mut().set_mouse_visible(true);
+ processor.ctx.window().set_mouse_visible(true);
processor.mouse_wheel_input(delta, phase);
},
WindowEvent::Focused(is_focused) => {
@@ -1269,9 +1272,9 @@ impl<N: Notify + OnResize> Processor<N> {
*processor.ctx.dirty = true;
if is_focused {
- processor.ctx.window_mut().set_urgent(false);
+ processor.ctx.window().set_urgent(false);
} else {
- processor.ctx.window_mut().set_mouse_visible(true);
+ processor.ctx.window().set_mouse_visible(true);
}
processor.ctx.update_cursor_blinking();
@@ -1285,7 +1288,7 @@ impl<N: Notify + OnResize> Processor<N> {
WindowEvent::CursorLeft { .. } => {
processor.ctx.mouse.inside_text_area = false;
- if processor.ctx.highlighted_url().is_some() {
+ if processor.ctx.display().highlighted_hint.is_some() {
*processor.ctx.dirty = true;
}
},
@@ -1382,12 +1385,12 @@ impl<N: Notify + OnResize> Processor<N> {
if !config.ui_config.window.dynamic_title
|| processor.ctx.config.ui_config.window.title != config.ui_config.window.title
{
- processor.ctx.window_mut().set_title(&config.ui_config.window.title);
+ processor.ctx.window().set_title(&config.ui_config.window.title);
}
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
if processor.ctx.event_loop.is_wayland() {
- processor.ctx.window_mut().set_wayland_theme(&config.ui_config.colors);
+ processor.ctx.window().set_wayland_theme(&config.ui_config.colors);
}
// Set subpixel anti-aliasing.
@@ -1396,7 +1399,7 @@ impl<N: Notify + OnResize> Processor<N> {
// Disable shadows for transparent windows on macOS.
#[cfg(target_os = "macos")]
- processor.ctx.window_mut().set_has_shadow(config.ui_config.background_opacity() >= 1.0);
+ processor.ctx.window().set_has_shadow(config.ui_config.background_opacity() >= 1.0);
// Update hint keys.
processor.ctx.display.hint_state.update_alphabet(config.ui_config.hints.alphabet());
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index a66511cf..3559b85e 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -22,7 +22,7 @@ use glutin::window::CursorIcon;
use alacritty_terminal::ansi::{ClearMode, Handler};
use alacritty_terminal::event::EventListener;
use alacritty_terminal::grid::{Dimensions, Scroll};
-use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side};
+use alacritty_terminal::index::{Boundary, Column, Direction, Point, Side};
use alacritty_terminal::selection::SelectionType;
use alacritty_terminal::term::search::Match;
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
@@ -31,12 +31,12 @@ use alacritty_terminal::vi_mode::ViMotion;
use crate::clipboard::Clipboard;
use crate::config::{Action, BindingMode, Config, Key, SearchAction, ViAction};
use crate::daemon::start_daemon;
-use crate::display::hint::HintState;
+use crate::display::hint::HintMatch;
use crate::display::window::Window;
+use crate::display::Display;
use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY};
use crate::message_bar::{self, Message};
use crate::scheduler::{Scheduler, TimerId};
-use crate::url::{Url, Urls};
/// Font size change interval.
pub const FONT_SIZE_STEP: f32 = 0.5;
@@ -75,8 +75,8 @@ pub trait ActionContext<T: EventListener> {
fn suppress_chars(&mut self) -> &mut bool;
fn modifiers(&mut self) -> &mut ModifiersState;
fn scroll(&mut self, _scroll: Scroll) {}
- fn window(&self) -> &Window;
- fn window_mut(&mut self) -> &mut Window;
+ fn window(&mut self) -> &mut Window;
+ fn display(&mut self) -> &mut Display;
fn terminal(&self) -> &Term<T>;
fn terminal_mut(&mut self) -> &mut Term<T>;
fn spawn_new_instance(&mut self) {}
@@ -86,9 +86,6 @@ pub trait ActionContext<T: EventListener> {
fn message(&self) -> Option<&Message>;
fn config(&self) -> &Config;
fn event_loop(&self) -> &EventLoopWindowTarget<Event>;
- fn urls(&self) -> &Urls;
- fn launch_url(&self, _url: Url) {}
- fn highlighted_url(&self) -> Option<&Url>;
fn mouse_mode(&self) -> bool;
fn clipboard_mut(&mut self) -> &mut Clipboard;
fn scheduler_mut(&mut self) -> &mut Scheduler;
@@ -105,8 +102,8 @@ pub trait ActionContext<T: EventListener> {
fn search_active(&self) -> bool;
fn on_typing_start(&mut self) {}
fn toggle_vi_mode(&mut self) {}
- fn hint_state(&mut self) -> &mut HintState;
fn hint_input(&mut self, _character: char) {}
+ fn trigger_hint(&mut self, _hint: &HintMatch) {}
fn paste(&mut self, _text: &str) {}
}
@@ -142,7 +139,7 @@ impl<T: EventListener> Execute<T> for Action {
},
Action::Command(program) => start_daemon(program.program(), program.args()),
Action::Hint(hint) => {
- ctx.hint_state().start(hint.clone());
+ ctx.display().hint_state.start(hint.clone());
ctx.mark_dirty();
},
Action::ToggleViMode => ctx.toggle_vi_mode(),
@@ -164,12 +161,12 @@ impl<T: EventListener> Execute<T> for Action {
Self::toggle_selection(ctx, SelectionType::Semantic);
},
Action::ViAction(ViAction::Open) => {
- ctx.mouse_mut().block_url_launcher = false;
- let vi_point = ctx.terminal().vi_mode_cursor.point;
- let line = (vi_point.line + ctx.terminal().grid().display_offset()).0 as usize;
- if let Some(url) = ctx.urls().find_at(Point::new(line, vi_point.column)) {
- ctx.launch_url(url);
+ let hint = ctx.display().vi_highlighted_hint.take();
+ if let Some(hint) = &hint {
+ ctx.mouse_mut().block_hint_launcher = false;
+ ctx.trigger_hint(hint);
}
+ ctx.display().vi_highlighted_hint = hint;
},
Action::ViAction(ViAction::SearchNext) => {
let terminal = ctx.terminal();
@@ -250,11 +247,13 @@ impl<T: EventListener> Execute<T> for Action {
let text = ctx.clipboard_mut().load(ClipboardType::Selection);
ctx.paste(&text);
},
- Action::ToggleFullscreen => ctx.window_mut().toggle_fullscreen(),
+ Action::ToggleFullscreen => ctx.window().toggle_fullscreen(),
#[cfg(target_os = "macos")]
- Action::ToggleSimpleFullscreen => ctx.window_mut().toggle_simple_fullscreen(),
+ Action::ToggleSimpleFullscreen => ctx.window().toggle_simple_fullscreen(),
#[cfg(target_os = "macos")]
Action::Hide => ctx.event_loop().hide_application(),
+ #[cfg(target_os = "macos")]
+ Action::HideOtherApplications => ctx.event_loop().hide_other_applications(),
#[cfg(not(target_os = "macos"))]
Action::Hide => ctx.window().set_visible(false),
Action::Minimize => ctx.window().set_minimized(true),
@@ -325,25 +324,6 @@ impl<T: EventListener> Execute<T> for Action {
}
}
-#[derive(Debug, Clone, PartialEq)]
-pub enum MouseState {
- Url(Url),
- MessageBar,
- MessageBarButton,
- Mouse,
- Text,
-}
-
-impl From<MouseState> for CursorIcon {
- fn from(mouse_state: MouseState) -> CursorIcon {
- match mouse_state {
- MouseState::Url(_) | MouseState::MessageBarButton => CursorIcon::Hand,
- MouseState::Text => CursorIcon::Text,
- _ => CursorIcon::Default,
- }
- }
-}
-
impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
pub fn new(ctx: A) -> Self {
Self { ctx, _phantom: Default::default() }
@@ -361,22 +341,19 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
self.update_selection_scrolling(y);
}
+ let display_offset = self.ctx.terminal().grid().display_offset();
+ let old_point = self.ctx.mouse().point(&size_info, display_offset);
+
let x = min(max(x, 0), size_info.width() as i32 - 1) as usize;
let y = min(max(y, 0), size_info.height() as i32 - 1) as usize;
-
self.ctx.mouse_mut().x = x;
self.ctx.mouse_mut().y = y;
let inside_text_area = size_info.contains_point(x, y);
- let point = self.coords_to_point(x, y);
let cell_side = self.cell_side(x);
- let cell_changed = point != self.ctx.mouse().point;
-
- // Update mouse state and check for URL change.
- let mouse_state = self.mouse_state();
- self.update_url_state(&mouse_state);
- self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
+ let point = self.ctx.mouse().point(&size_info, display_offset);
+ let cell_changed = old_point != point;
// If the mouse hasn't changed cells, do nothing.
if !cell_changed
@@ -388,18 +365,21 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
self.ctx.mouse_mut().inside_text_area = inside_text_area;
self.ctx.mouse_mut().cell_side = cell_side;
- self.ctx.mouse_mut().point = point;
+
+ // Update mouse state and check for URL change.
+ let mouse_state = self.cursor_state();
+ self.ctx.window().set_mouse_cursor(mouse_state);
+
+ // Prompt hint highlight update.
+ self.ctx.mouse_mut().hint_highlight_dirty = true;
// Don't launch URLs if mouse has moved.
- self.ctx.mouse_mut().block_url_launcher = true;
+ self.ctx.mouse_mut().block_hint_launcher = true;
if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode())
{
- let line = Line(point.line as i32) - self.ctx.terminal().grid().display_offset();
- let point = Point::new(line, point.column);
self.ctx.update_selection(point, cell_side);
} else if cell_changed
- && point.line < self.ctx.terminal().screen_lines()
&& self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG)
{
if lmb_pressed {
@@ -414,23 +394,6 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
}
- /// Convert window space pixels to terminal grid coordinates.
- ///
- /// If the coordinates are outside of the terminal grid, like positions inside the padding, the
- /// coordinates will be clamped to the closest grid coordinates.
- #[inline]
- fn coords_to_point(&self, x: usize, y: usize) -> Point<usize> {
- let size = self.ctx.size_info();
-
- let column = x.saturating_sub(size.padding_x() as usize) / (size.cell_width() as usize);
- let column = min(Column(column), size.last_column());
-
- let line = y.saturating_sub(size.padding_y() as usize) / (size.cell_height() as usize);
- let line = min(line, size.bottommost_line().0 as usize);
-
- Point::new(line, column)
- }
-
/// Check which side of a cell an X coordinate lies on.
fn cell_side(&self, x: usize) -> Side {
let size_info = self.ctx.size_info();
@@ -453,13 +416,45 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
}
- fn normal_mouse_report(&mut self, button: u8) {
- let Point { line, column } = self.ctx.mouse().point;
+ fn mouse_report(&mut self, button: u8, state: ElementState) {
+ let display_offset = self.ctx.terminal().grid().display_offset();
+ let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
+
+ // Assure the mouse point is not in the scrollback.
+ if point.line < 0 {
+ return;
+ }
+
+ // Calculate modifiers value.
+ let mut mods = 0;
+ let modifiers = self.ctx.modifiers();
+ if modifiers.shift() {
+ mods += 4;
+ }
+ if modifiers.alt() {
+ mods += 8;
+ }
+ if modifiers.ctrl() {
+ mods += 16;
+ }
+
+ // Report mouse events.
+ if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
+ self.sgr_mouse_report(point, button + mods, state);
+ } else if let ElementState::Released = state {
+ self.normal_mouse_report(point, 3 + mods);
+ } else {
+ self.normal_mouse_report(point, button + mods);
+ }
+ }
+
+ fn normal_mouse_report(&mut self, point: Point, button: u8) {
+ let Point { line, column } = point;
let utf8 = self.ctx.terminal().mode().contains(TermMode::UTF8_MOUSE);
let max_point = if utf8 { 2015 } else { 223 };
- if line >= max_point || column >= Column(max_point) {
+ if line >= max_point || column >= max_point {
return;
}
@@ -479,49 +474,24 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
if utf8 && line >= 95 {
- msg.append(&mut mouse_pos_encode(line));
+ msg.append(&mut mouse_pos_encode(line.0 as usize));
} else {
- msg.push(32 + 1 + line as u8);
+ msg.push(32 + 1 + line.0 as u8);
}
self.ctx.write_to_pty(msg);
}
- fn sgr_mouse_report(&mut self, button: u8, state: ElementState) {
- let Point { line, column } = self.ctx.mouse().point;
+ fn sgr_mouse_report(&mut self, point: Point, button: u8, state: ElementState) {
let c = match state {
ElementState::Pressed => 'M',
ElementState::Released => 'm',
};
- let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c);
+ let msg = format!("\x1b[<{};{};{}{}", button, point.column + 1, point.line + 1, c);
self.ctx.write_to_pty(msg.into_bytes());
}
- fn mouse_report(&mut self, button: u8, state: ElementState) {
- // Calculate modifiers value.
- let mut mods = 0;
- let modifiers = self.ctx.modifiers();
- if modifiers.shift() {
- mods += 4;
- }
- if modifiers.alt() {
- mods += 8;
- }
- if modifiers.ctrl() {
- mods += 16;
- }
-
- // Report mouse events.
- if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
- self.sgr_mouse_report(button + mods, state);
- } else if let ElementState::Released = state {
- self.normal_mouse_report(3 + mods);
- } else {
- self.normal_mouse_report(button + mods);
- }
- }
-
fn on_mouse_press(&mut self, button: MouseButton) {
// Handle mouse mode.
if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
@@ -560,10 +530,8 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
};
// Load mouse point, treating message bar and padding as the closest cell.
- let point = self.ctx.mouse().point;
let display_offset = self.ctx.terminal().grid().display_offset();
- let absolute_line = Line(point.line as i32) - display_offset;
- let point = Point::new(absolute_line, point.column);
+ let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
match button {
MouseButton::Left => self.on_left_click(point),
@@ -617,7 +585,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
match self.ctx.mouse().click_state {
ClickState::Click => {
// Don't launch URLs if this click cleared the selection.
- self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty();
+ self.ctx.mouse_mut().block_hint_launcher = !self.ctx.selection_is_empty();
self.ctx.clear_selection();
@@ -629,11 +597,11 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
},
ClickState::DoubleClick => {
- self.ctx.mouse_mut().block_url_launcher = true;
+ self.ctx.mouse_mut().block_hint_launcher = true;
self.ctx.start_selection(SelectionType::Semantic, point, side);
},
ClickState::TripleClick => {
- self.ctx.mouse_mut().block_url_launcher = true;
+ self.ctx.mouse_mut().block_hint_launcher = true;
self.ctx.start_selection(SelectionType::Lines, point, side);
},
ClickState::None => (),
@@ -656,11 +624,19 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
};
self.mouse_report(code, ElementState::Released);
return;
- } else if let (MouseButton::Left, MouseState::Url(url)) = (button, self.mouse_state()) {
- self.ctx.launch_url(url);
}
+ // Trigger hints highlighted by the mouse.
+ let hint = self.ctx.display().highlighted_hint.take();
+ if let Some(hint) = hint.as_ref().filter(|_| button == MouseButton::Left) {
+ self.ctx.trigger_hint(hint);
+ }
+ self.ctx.display().highlighted_hint = hint;
+
self.ctx.scheduler_mut().unschedule(TimerId::SelectionScrolling);
+
+ // Copy selection on release, to prevent flooding the display server.
+ self.ctx.copy_selection(ClipboardType::Selection);
}
pub fn mouse_wheel_input(&mut self, delta: MouseScrollDelta, phase: TouchPhase) {
@@ -746,7 +722,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
// Skip normal mouse events if the message bar has been clicked.
- if self.message_bar_mouse_state() == Some(MouseState::MessageBarButton)
+ if self.message_bar_cursor_state() == Some(CursorIcon::Hand)
&& state == ElementState::Pressed
{
let size = self.ctx.size_info();
@@ -771,7 +747,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
},
};
- self.ctx.window_mut().set_mouse_cursor(new_icon);
+ self.ctx.window().set_mouse_cursor(new_icon);
} else {
match state {
ElementState::Pressed => {
@@ -786,7 +762,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
/// Process key input.
pub fn key_input(&mut self, input: KeyboardInput) {
// All key bindings are disabled while a hint is being selected.
- if self.ctx.hint_state().active() {
+ if self.ctx.display().hint_state.active() {
*self.ctx.suppress_chars() = false;
return;
}
@@ -811,17 +787,19 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
pub fn modifiers_input(&mut self, modifiers: ModifiersState) {
*self.ctx.modifiers() = modifiers;
+ // Prompt hint highlight update.
+ self.ctx.mouse_mut().hint_highlight_dirty = true;
+
// Update mouse state and check for URL change.
- let mouse_state = self.mouse_state();
- self.update_url_state(&mouse_state);
- self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
+ let mouse_state = self.cursor_state();
+ self.ctx.window().set_mouse_cursor(mouse_state);
}
/// Reset mouse cursor based on modifier and terminal state.
#[inline]
pub fn reset_mouse_cursor(&mut self) {
- let mouse_state = self.mouse_state();
- self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
+ let mouse_state = self.cursor_state();
+ self.ctx.window().set_mouse_cursor(mouse_state);
}
/// Process a received character.
@@ -829,7 +807,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
let suppress_chars = *self.ctx.suppress_chars();
// Handle hint selection over anything else.
- if self.ctx.hint_state().active() && !suppress_chars {
+ if self.ctx.display().hint_state.active() && !suppress_chars {
self.ctx.hint_input(c);
return;
}
@@ -923,8 +901,8 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
}
- /// Check mouse state in relation to the message bar.
- fn message_bar_mouse_state(&self) -> Option<MouseState> {
+ /// Check mouse icon state in relation to the message bar.
+ fn message_bar_cursor_state(&self) -> Option<CursorIcon> {
// 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 };
@@ -934,58 +912,36 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
+ size.cell_height() as usize * (size.screen_lines() + search_height);
let mouse = self.ctx.mouse();
+ let display_offset = self.ctx.terminal().grid().display_offset();
+ let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
+
if self.ctx.message().is_none() || (mouse.y <= terminal_end) {
None
} else if mouse.y <= terminal_end + size.cell_height() as usize
- && mouse.point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns()
+ && point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns()
{
- Some(MouseState::MessageBarButton)
+ Some(CursorIcon::Hand)
} else {
- Some(MouseState::MessageBar)
- }
- }
-
- /// Trigger redraw when URL highlight changed.
- #[inline]
- fn update_url_state(&mut self, mouse_state: &MouseState) {
- let highlighted_url = self.ctx.highlighted_url();
- if let MouseState::Url(url) = mouse_state {
- if Some(url) != highlighted_url {
- self.ctx.mark_dirty();
- }
- } else if highlighted_url.is_some() {
- self.ctx.mark_dirty();
+ Some(CursorIcon::Default)
}
}
- /// Location of the mouse cursor.
- fn mouse_state(&mut self) -> MouseState {
- // Check message bar before URL to ignore URLs in the message bar.
- if let Some(mouse_state) = self.message_bar_mouse_state() {
- return mouse_state;
- }
+ /// Icon state of the cursor.
+ fn cursor_state(&mut self) -> CursorIcon {
+ let display_offset = self.ctx.terminal().grid().display_offset();
+ let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
- let mouse_mode = self.ctx.mouse_mode();
+ // Function to check if mouse is on top of a hint.
+ let hint_highlighted = |hint: &HintMatch| hint.bounds.contains(&point);
- // Check for URL at mouse cursor.
- let mods = *self.ctx.modifiers();
- let highlighted_url = self.ctx.urls().highlighted(
- self.ctx.config(),
- self.ctx.mouse(),
- mods,
- mouse_mode,
- !self.ctx.selection_is_empty(),
- );
-
- if let Some(url) = highlighted_url {
- return MouseState::Url(url);
- }
-
- // Check mouse mode if location is not special.
- if !self.ctx.modifiers().shift() && mouse_mode {
- MouseState::Mouse
+ if let Some(mouse_state) = self.message_bar_cursor_state() {
+ mouse_state
+ } else if self.ctx.display().highlighted_hint.as_ref().map_or(false, hint_highlighted) {
+ CursorIcon::Hand
+ } else if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
+ CursorIcon::Default
} else {
- MouseState::Text
+ CursorIcon::Text
}
}
@@ -1127,11 +1083,11 @@ mod tests {
&mut self.modifiers
}
- fn window(&self) -> &Window {
+ fn window(&mut self) -> &mut Window {
unimplemented!();
}
- fn window_mut(&mut self) -> &mut Window {
+ fn display(&mut self) -> &mut Display {
unimplemented!();
}
@@ -1155,21 +1111,9 @@ mod tests {
unimplemented!();
}
- fn urls(&self) -> &Urls {
- unimplemented!();
- }
-
- fn highlighted_url(&self) -> Option<&Url> {
- unimplemented!();
- }
-
fn scheduler_mut(&mut self) -> &mut Scheduler {
unimplemented!();
}
-
- fn hint_state(&mut self) -> &mut HintState {
- unimplemented!();
- }
}
macro_rules! test_clickstate {
diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs
index 0914aee4..c68f3cfb 100644
--- a/alacritty/src/main.rs
+++ b/alacritty/src/main.rs
@@ -45,7 +45,6 @@ mod message_bar;
mod panic;
mod renderer;
mod scheduler;
-mod url;
mod gl {
#![allow(clippy::all)]
diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml
index c29362e7..c6947009 100644
--- a/alacritty_terminal/Cargo.toml
+++ b/alacritty_terminal/Cargo.toml
@@ -28,7 +28,7 @@ regex-automata = "0.1.9"
dirs = "3.0.1"
[target.'cfg(unix)'.dependencies]
-nix = "0.19.0"
+nix = "0.20.0"
signal-hook = { version = "0.1", features = ["mio-support"] }
[target.'cfg(windows)'.dependencies]
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
index 27b9231e..14617de1 100644
--- a/alacritty_terminal/src/ansi.rs
+++ b/alacritty_terminal/src/ansi.rs
@@ -2,7 +2,7 @@
use std::convert::TryFrom;
use std::time::{Duration, Instant};
-use std::{io, iter, str};
+use std::{iter, str};
use log::{debug, trace};
use serde::{Deserialize, Serialize};
@@ -157,29 +157,27 @@ impl Processor {
/// Process a new byte from the PTY.
#[inline]
- pub fn advance<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
+ pub fn advance<H>(&mut self, handler: &mut H, byte: u8)
where
H: Handler,
- W: io::Write,
{
if self.state.sync_state.timeout.is_none() {
- let mut performer = Performer::new(&mut self.state, handler, writer);
+ let mut performer = Performer::new(&mut self.state, handler);
self.parser.advance(&mut performer, byte);
} else {
- self.advance_sync(handler, byte, writer);
+ self.advance_sync(handler, byte);
}
}
/// End a synchronized update.
- pub fn stop_sync<H, W>(&mut self, handler: &mut H, writer: &mut W)
+ pub fn stop_sync<H>(&mut self, handler: &mut H)
where
H: Handler,
- W: io::Write,
{
// Process all synchronized bytes.
for i in 0..self.state.sync_state.buffer.len() {
let byte = self.state.sync_state.buffer[i];
- let mut performer = Performer::new(&mut self.state, handler, writer);
+ let mut performer = Performer::new(&mut self.state, handler);
self.parser.advance(&mut performer, byte);
}
@@ -202,16 +200,15 @@ impl Processor {
/// Process a new byte during a synchronized update.
#[cold]
- fn advance_sync<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
+ fn advance_sync<H>(&mut self, handler: &mut H, byte: u8)
where
H: Handler,
- W: io::Write,
{
self.state.sync_state.buffer.push(byte);
// Handle sync DCS escape sequences.
match self.state.sync_state.pending_dcs {
- Some(_) => self.advance_sync_dcs_end(handler, byte, writer),
+ Some(_) => self.advance_sync_dcs_end(handler, byte),
None => self.advance_sync_dcs_start(),
}
}
@@ -232,10 +229,9 @@ impl Processor {
}
/// Parse the DCS termination sequence for synchronized updates.
- fn advance_sync_dcs_end<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
+ fn advance_sync_dcs_end<H>(&mut self, handler: &mut H, byte: u8)
where
H: Handler,
- W: io::Write,
{
match byte {
// Ignore DCS passthrough characters.
@@ -247,7 +243,7 @@ impl Processor {
Some(Dcs::SyncStart) => {
self.state.sync_state.timeout = Some(Instant::now() + SYNC_UPDATE_TIMEOUT);
},
- Some(Dcs::SyncEnd) => self.stop_sync(handler, writer),
+ Some(Dcs::SyncEnd) => self.stop_sync(handler),
Some(Dcs::SixelData(_)) => (),
None => (),
},
@@ -259,21 +255,16 @@ impl Processor {
///
/// Processor creates a Performer when running advance and passes the Performer
/// to `vte::Parser`.
-struct Performer<'a, H: Handler, W: io::Write> {
+struct Performer<'a, H: Handler> {
state: &'a mut ProcessorState,
handler: &'a mut H,
- writer: &'a mut W,
}
-impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> {
+impl<'a, H: Handler + 'a> Performer<'a, H> {
/// Create a performer.
#[inline]
- pub fn new<'b>(
- state: &'b mut ProcessorState,
- handler: &'b mut H,
- writer: &'b mut W,
- ) -> Performer<'b, H, W> {
- Performer { state, handler, writer }
+ pub fn new<'b>(state: &'b mut ProcessorState, handler: &'b mut H) -> Performer<'b, H> {
+ Performer { state, handler }
}
}
@@ -313,10 +304,10 @@ pub trait Handler {
fn move_down(&mut self, _: usize) {}
/// Identify the terminal (should write back to the pty stream).
- fn identify_terminal<W: io::Write>(&mut self, _: &mut W, _intermediate: Option<char>) {}
+ fn identify_terminal(&mut self, _intermediate: Option<char>) {}
/// Report device status.
- fn device_status<W: io::Write>(&mut self, _: &mut W, _: usize) {}
+ fn device_status(&mut self, _: usize) {}
/// Move cursor forward `cols`.
fn move_forward(&mut self, _: Column) {}
@@ -466,13 +457,13 @@ pub trait Handler {
fn pop_title(&mut self) {}
/// Report text area size in pixels.
- fn text_area_size_pixels<W: io::Write>(&mut self, _: &mut W) {}
+ fn text_area_size_pixels(&mut self) {}
/// Report text area size in characters.
- fn text_area_size_chars<W: io::Write>(&mut self, _: &mut W) {}
+ fn text_area_size_chars(&mut self) {}
/// Report a graphics attribute.
- fn graphics_attribute<W: io::Write>(&mut self, _: &mut W, _: u16, _: u16) {}
+ fn graphics_attribute(&mut self, _: u16, _: u16) {}
/// Create a parser for Sixel data.
fn start_sixel_graphic(&mut self, _params: &Params) -> Option<Box<sixel::Parser>> {
@@ -905,10 +896,9 @@ impl StandardCharset {
}
}
-impl<'a, H, W> vte::Perform for Performer<'a, H, W>
+impl<'a, H> vte::Perform for Performer<'a, H>
where
H: Handler + 'a,
- W: io::Write + 'a,
{
#[inline]
fn print(&mut self, c: char) {
@@ -1154,7 +1144,6 @@ where
let mut params_iter = params.iter();
let handler = &mut self.handler;
- let writer = &mut self.writer;
let mut next_param_or = |default: u16| {
params_iter.next().map(|param| param[0]).filter(|&param| param != 0).unwrap_or(default)
@@ -1175,7 +1164,7 @@ where
},
('C', []) | ('a', []) => handler.move_forward(Column(next_param_or(1) as usize)),
('c', intermediates) if next_param_or(0) == 0 => {
- handler.identify_terminal(writer, intermediates.get(0).map(|&i| i as char))
+ handler.identify_terminal(intermediates.get(0).map(|&i| i as char))
},
('D', []) => handler.move_backward(Column(next_param_or(1) as usize)),
('d', []) => handler.goto_line(Line(next_param_or(1) as i32 - 1)),
@@ -1257,7 +1246,7 @@ where
}
}
},
- ('n', []) => handler.device_status(writer, next_param_or(0) as usize),
+ ('n', []) => handler.device_status(next_param_or(0) as usize),
('P', []) => handler.delete_chars(next_param_or(1) as usize),
('q', [b' ']) => {
// DECSCUSR (CSI Ps SP q) -- Set Cursor Style.
@@ -1285,12 +1274,12 @@ where
handler.set_scrolling_region(top, bottom);
},
('S', []) => handler.scroll_up(next_param_or(1) as usize),
- ('S', [b'?']) => handler.graphics_attribute(writer, next_param_or(0), next_param_or(0)),
+ ('S', [b'?']) => handler.graphics_attribute(next_param_or(0), next_param_or(0)),
('s', []) => handler.save_cursor_position(),
('T', []) => handler.scroll_down(next_param_or(1) as usize),
('t', []) => match next_param_or(1) as usize {
- 14 => handler.text_area_size_pixels(writer),
- 18 => handler.text_area_size_chars(writer),
+ 14 => handler.text_area_size_pixels(),
+ 18 => handler.text_area_size_chars(),
22 => handler.push_title(),
23 => handler.pop_title(),
_ => unhandled!(),
@@ -1338,7 +1327,7 @@ where
},
(b'H', []) => self.handler.set_horizontal_tabstop(),
(b'M', []) => self.handler.reverse_index(),
- (b'Z', []) => self.handler.identify_terminal(self.writer, None),
+ (b'Z', []) => self.handler.identify_terminal(None),
(b'c', []) => self.handler.reset_state(),
(b'0', intermediates) => {
configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing, intermediates)
@@ -1534,11 +1523,9 @@ pub mod C0 {
// Byte sequences used in these tests are recording of pty stdout.
#[cfg(test)]
mod tests {
- use super::{
- parse_number, xparse_color, Attr, CharsetIndex, Color, Handler, Processor, StandardCharset,
- };
+ use super::*;
+
use crate::term::color::Rgb;
- use std::io;
struct MockHandler {
index: CharsetIndex,
@@ -1561,7 +1548,7 @@ mod tests {
self.index = index;
}
- fn identify_terminal<W: io::Write>(&mut self, _: &mut W, _intermediate: Option<char>) {
+ fn identify_terminal(&mut self, _intermediate: Option<char>) {
self.identity_reported = true;
}
@@ -1589,7 +1576,7 @@ mod tests {
let mut handler = MockHandler::default();
for byte in BYTES {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
assert_eq!(handler.attr, Some(Attr::Bold));
@@ -1603,7 +1590,7 @@ mod tests {
let mut handler = MockHandler::default();
for byte in bytes {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
assert!(!handler.identity_reported);
@@ -1612,7 +1599,7 @@ mod tests {
let bytes: &[u8] = &[0x1b, b'[', b'c'];
for byte in bytes {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
assert!(handler.identity_reported);
@@ -1621,7 +1608,7 @@ mod tests {
let bytes: &[u8] = &[0x1b, b'[', b'0', b'c'];
for byte in bytes {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
assert!(handler.identity_reported);
@@ -1635,7 +1622,7 @@ mod tests {
let mut handler = MockHandler::default();
for byte in bytes {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
assert!(handler.identity_reported);
@@ -1647,7 +1634,7 @@ mod tests {
let mut handler = MockHandler::default();
for byte in bytes {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
assert!(!handler.identity_reported);
@@ -1665,7 +1652,7 @@ mod tests {
let mut handler = MockHandler::default();
for byte in BYTES {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
let spec = Rgb { r: 128, g: 66, b: 255 };
@@ -1696,7 +1683,7 @@ mod tests {
let mut parser = Processor::new();
for byte in BYTES {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
}
@@ -1707,7 +1694,7 @@ mod tests {
let mut handler = MockHandler::default();
for byte in BYTES {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
assert_eq!(handler.index, CharsetIndex::G0);
@@ -1721,14 +1708,14 @@ mod tests {
let mut handler = MockHandler::default();
for byte in &BYTES[..3] {
- parser.advance(&mut handler, *byte, &mut io::sink());
+ parser.advance(&mut handler, *byte);
}
assert_eq!(handler.index, CharsetIndex::G1);
assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
let mut handler = MockHandler::default();
- parser.advance(&mut handler, BYTES[3], &mut io::sink());
+ parser.advance(&mut handler, BYTES[3]);
assert_eq!(handler.index, CharsetIndex::G1);
}
diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs
index 70d16127..fac7a56a 100644
--- a/alacritty_terminal/src/event.rs
+++ b/alacritty_terminal/src/event.rs
@@ -35,6 +35,9 @@ pub enum Event {
/// expected escape sequence format.
ColorRequest(usize, Arc<dyn Fn(Rgb) -> String + Sync + Send + 'static>),
+ /// Write some text to the PTY.
+ PtyWrite(String),
+
/// Cursor blinking state has changed.
CursorBlinkingChange(bool),
@@ -57,6 +60,7 @@ impl Debug for Event {
Event::ClipboardStore(ty, text) => write!(f, "ClipboardStore({:?}, {})", ty, text),
Event::ClipboardLoad(ty, _) => write!(f, "ClipboardLoad({:?})", ty),
Event::ColorRequest(index, _) => write!(f, "ColorRequest({})", index),
+ Event::PtyWrite(text) => write!(f, "PtyWrite({})", text),
Event::Wakeup => write!(f, "Wakeup"),
Event::Bell => write!(f, "Bell"),
Event::Exit => write!(f, "Exit"),
diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs
index c3224dfe..098ee896 100644
--- a/alacritty_terminal/src/event_loop.rs
+++ b/alacritty_terminal/src/event_loop.rs
@@ -240,7 +240,7 @@ where
// Run the parser.
for byte in &buf[..got] {
- state.parser.advance(&mut **terminal, *byte, &mut self.pty.writer());
+ state.parser.advance(&mut **terminal, *byte);
}
// Exit if we've processed enough bytes.
@@ -334,7 +334,7 @@ where
// Handle synchronized update timeout.
if events.is_empty() {
- state.parser.stop_sync(&mut *self.terminal.lock(), &mut self.pty.writer());
+ state.parser.stop_sync(&mut *self.terminal.lock());
self.event_proxy.send_event(Event::Wakeup);
continue;
}
diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs
index 10bc51f9..882c0c90 100644
--- a/alacritty_terminal/src/grid/resize.rs
+++ b/alacritty_terminal/src/grid/resize.rs
@@ -187,7 +187,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
cursor_line_delta += line_delta.0 as usize;
} else if row.is_clear() {
- if i <= self.display_offset {
+ if i < self.display_offset {
// Since we removed a line, rotate down the viewport.
self.display_offset = self.display_offset.saturating_sub(1);
}
@@ -355,7 +355,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
}
row = Row::from_vec(wrapped, occ);
- if i <= self.display_offset {
+ if i < self.display_offset {
// Since we added a new line, rotate up the viewport.
self.display_offset += 1;
}
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index ef9071e7..5ca15b21 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -1,9 +1,10 @@
//! Exports the `Term` type which is a high-level API for the Grid.
use std::cmp::{max, min};
+use std::fmt::Write;
use std::ops::{Index, IndexMut, Range};
use std::sync::Arc;
-use std::{io, mem, ptr, str};
+use std::{mem, ptr, str};
use bitflags::bitflags;
use log::{debug, trace};
@@ -630,8 +631,15 @@ impl<T> Term<T> {
self.mode ^= TermMode::VI;
if self.mode.contains(TermMode::VI) {
- // Reset vi mode cursor position to match primary cursor.
- self.vi_mode_cursor = ViModeCursor::new(self.grid.cursor.point);
+ let display_offset = self.grid.display_offset() as i32;
+ if self.grid.cursor.point.line > self.bottommost_line() - display_offset {
+ // Move cursor to top-left if terminal cursor is not visible.
+ let point = Point::new(Line(-display_offset), Column(0));
+ self.vi_mode_cursor = ViModeCursor::new(point);
+ } else {
+ // Reset vi mode cursor position to match primary cursor.
+ self.vi_mode_cursor = ViModeCursor::new(self.grid.cursor.point);
+ }
}
// Update UI about cursor blinking state changes.
@@ -979,32 +987,35 @@ impl<T: EventListener> Handler for Term<T> {
}
#[inline]
- fn identify_terminal<W: io::Write>(&mut self, writer: &mut W, intermediate: Option<char>) {
+ fn identify_terminal(&mut self, intermediate: Option<char>) {
match intermediate {
None => {
trace!("Reporting primary device attributes");
- let _ = writer.write_all(b"\x1b[?6c");
+ let text = String::from("\x1b[?6c");
+ self.event_proxy.send_event(Event::PtyWrite(text));
},
Some('>') => {
trace!("Reporting secondary device attributes");
let version = version_number(env!("CARGO_PKG_VERSION"));
- let _ = writer.write_all(format!("\x1b[>0;{};1c", version).as_bytes());
+ let text = format!("\x1b[>0;{};1c", version);
+ self.event_proxy.send_event(Event::PtyWrite(text));
},
_ => debug!("Unsupported device attributes intermediate"),
}
}
#[inline]
- fn device_status<W: io::Write>(&mut self, writer: &mut W, arg: usize) {
+ fn device_status(&mut self, arg: usize) {
trace!("Reporting device status: {}", arg);
match arg {
5 => {
- let _ = writer.write_all(b"\x1b[0n");
+ let text = String::from("\x1b[0n");
+ self.event_proxy.send_event(Event::PtyWrite(text));
},
6 => {
let pos = self.grid.cursor.point;
- let response = format!("\x1b[{};{}R", pos.line + 1, pos.column + 1);
- let _ = writer.write_all(response.as_bytes());
+ let text = format!("\x1b[{};{}R", pos.line + 1, pos.column + 1);
+ self.event_proxy.send_event(Event::PtyWrite(text));
},
_ => debug!("unknown device status query: {}", arg),
};
@@ -1707,19 +1718,21 @@ impl<T: EventListener> Handler for Term<T> {
}
#[inline]
- fn text_area_size_pixels<W: io::Write>(&mut self, writer: &mut W) {
+ fn text_area_size_pixels(&mut self) {
let width = self.cell_width * self.columns();
let height = self.cell_height * self.screen_lines();
- let _ = write!(writer, "\x1b[4;{};{}t", height, width);
+ let text = format!("\x1b[4;{};{}t", height, width);
+ self.event_proxy.send_event(Event::PtyWrite(text));
}
#[inline]
- fn text_area_size_chars<W: io::Write>(&mut self, writer: &mut W) {
- let _ = write!(writer, "\x1b[8;{};{}t", self.screen_lines(), self.columns());
+ fn text_area_size_chars(&mut self) {
+ let text = format!("\x1b[8;{};{}t", self.screen_lines(), self.columns());
+ self.event_proxy.send_event(Event::PtyWrite(text));
}
#[inline]
- fn graphics_attribute<W: io::Write>(&mut self, writer: &mut W, pi: u16, pa: u16) {
+ fn graphics_attribute(&mut self, pi: u16, pa: u16) {
// From Xterm documentation:
//
// Pi = 1 -> item is number of color registers.
@@ -1740,13 +1753,14 @@ impl<T: EventListener> Handler for Term<T> {
(2, &[][..]) // Report error in Pa
};
- let _ = write!(writer, "\x1b[?{};{}", pi, ps);
+ let mut text = format!("\x1b[?{};{}", pi, ps);
for item in pv {
- let _ = write!(writer, ";{}", item);
+ let _ = write!(&mut text, ";{}", item);
}
- let _ = write!(writer, "S");
+ text.push('S');
+ self.event_proxy.send_event(Event::PtyWrite(text));
}
fn start_sixel_graphic(&mut self, params: &Params) -> Option<Box<sixel::Parser>> {
diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs
index 638df670..93345e4f 100644
--- a/alacritty_terminal/src/term/search.rs
+++ b/alacritty_terminal/src/term/search.rs
@@ -82,7 +82,7 @@ impl<T> Term<T> {
// Limit maximum number of lines searched.
end = match max_lines {
Some(max_lines) => {
- let line = (start.line + max_lines).grid_clamp(self, Boundary::Grid);
+ let line = (start.line + max_lines).grid_clamp(self, Boundary::None);
Point::new(line, self.last_column())
},
_ => end.sub(self, Boundary::None, 1),
@@ -121,7 +121,7 @@ impl<T> Term<T> {
// Limit maximum number of lines searched.
end = match max_lines {
Some(max_lines) => {
- let line = (start.line - max_lines).grid_clamp(self, Boundary::Grid);
+ let line = (start.line - max_lines).grid_clamp(self, Boundary::None);
Point::new(line, Column(0))
},
_ => end.add(self, Boundary::None, 1),
diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs
index e229b6d2..a9968736 100644
--- a/alacritty_terminal/tests/ref.rs
+++ b/alacritty_terminal/tests/ref.rs
@@ -2,7 +2,7 @@ use serde::Deserialize;
use serde_json as json;
use std::fs::{self, File};
-use std::io::{self, Read};
+use std::io::Read;
use std::path::Path;
use alacritty_terminal::ansi;
@@ -108,7 +108,7 @@ fn ref_test(dir: &Path) {
let mut parser = ansi::Processor::new();
for byte in recording {
- parser.advance(&mut terminal, byte, &mut io::sink());
+ parser.advance(&mut terminal, byte);
}
// Truncate invisible lines from the grid.
diff --git a/docs/features.md b/docs/features.md
index fe93eb0e..094210fd 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -27,13 +27,6 @@ line (<kbd>Shift</kbd> <kbd>v</kbd>) and block selection (<kbd>Ctrl</kbd>
<kbd>v</kbd>). You can also toggle between them while the selection is still
active.
-### Opening URLs
-
-While in vi mode you can open URLs using the <kbd>Enter</kbd> key. If some text
-is recognized as a URL, it will be underlined once you move the vi cursor above
-it. The program used to open these URLs can be changed in the [configuration
-file].
-
## Search
Search allows you to find anything in Alacritty's scrollback buffer. You can
@@ -61,6 +54,11 @@ start vi mode. They consist of a regex that detects these text elements and then
either feeds them to an external application or triggers one of Alacritty's
built-in actions.
+Hints can also be triggered using the mouse or vi mode cursor. If a hint is
+enabled for mouse interaction and recognized as such, it will be underlined when
+the mouse or vi mode cursor is on top of it. Using the left mouse button or
+<kbd>Enter</kbd> key in vi mode will then trigger the hint.
+
Hints can be configured in the `hints` and `colors.hints` sections in the
Alacritty configuration file.