alacritty

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 35efb4619c4b7a77b3c30de763856bc4441e236e
parent e561ae373393e919cf3dbbf123a98e0aad215dbc
Author: Christian Duerr <chrisduerr@users.noreply.github.com>
Date:   Thu,  7 Feb 2019 22:36:45 +0000

Dynamically resize terminal for errors/warnings

The warning and error messages now don't overwrite other terminal
content anymore but instead resize the terminal to make sure that text
can always be read.

Instead of just showing that there is a new error and pointing to the log,
errors will now be displayed fully in multiple lines of text, assuming that
there is enough space left in the terminal.

Explicit mouse click handling has also been added to the message bar,
which made it possible to add a simple `close` button in the form of
`[X]`.

Alacritty's log file location is now stored in the `$ALACRITTY_LOG`
environment variable which the shell inherits automatically.

Previously there were some issues with the log file only being deleted
when certain methods for closing Alacritty were used (like typing
`exit`). This has been reworked and now Ctrl+D, exit and signals should
all work properly.

Before the config is reloaded, all current messages are now dropped.
This should help with multiple terminals all getting clogged up at the
same time when the config is broken.

When one message is removed, all other duplicate messages are
automatically removed too.
Diffstat:
MCHANGELOG.md | 10++++++++++
MCargo.lock | 63++++++++++++++++++++++++++++++---------------------------------
MCargo.toml | 1+
Msrc/ansi.rs | 5+++--
Msrc/config/mod.rs | 69+++++++++++++++++++++++++++++++++------------------------------------
Msrc/display.rs | 105+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/event.rs | 35+++++++++--------------------------
Msrc/input.rs | 233+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/lib.rs | 28+---------------------------
Msrc/logging.rs | 173++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/main.rs | 102++++++++++++++++++++++++++++++++++++++++----------------------------------------
Asrc/message_bar.rs | 517+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/renderer/lines.rs | 151------------------------------------------------------------------------------
Msrc/renderer/mod.rs | 29++++++++---------------------
Asrc/renderer/rects.rs | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/term/color.rs | 31+++++++++++++++++++++++++++++--
Msrc/term/mod.rs | 92++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/url.rs | 6++++--
Mtests/ref.rs | 5+++--
19 files changed, 1200 insertions(+), 624 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -9,11 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Window class on Wayland is set to `Alacritty` by default +- Log file location is stored in the `ALACRITTY_LOG` environment variable +- Close button has been added to the error/warning messages ### Changed - Improve scrolling accuracy with devices sending fractional updates (like touchpads) - `scrolling.multiplier` now affects normal scrolling with touchpads +- Error/Warning bar doesn't overwrite the terminal anymore +- Full error/warning messages are displayed +- Config error messages are automatically removed when the config is fixed ### Fixed @@ -25,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Corrected the `window.decorations` config documentation for macOS - Fix IME position on HiDPI displays - URLs not opening while terminal is scrolled +- Reliably remove log file when Alacritty is closed and persistent logging is disabled + +### Removed + +- `clear` doesn't remove error/warning messages anymore ## Version 0.2.7 diff --git a/Cargo.lock b/Cargo.lock @@ -39,6 +39,7 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "copypasta 0.0.1", + "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "dunce 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "embed-resource 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -56,7 +57,7 @@ dependencies = [ "mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "notify 4.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "notify 4.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -65,7 +66,7 @@ dependencies = [ "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "terminfo 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -168,7 +169,7 @@ name = "backtrace-sys" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -250,13 +251,13 @@ name = "bzip2-sys" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cc" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -341,7 +342,7 @@ name = "cmake" version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -809,7 +810,7 @@ dependencies = [ [[package]] name = "fuchsia-cprng" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -970,7 +971,7 @@ dependencies = [ "tokio-reactor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1032,12 +1033,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1115,7 +1112,7 @@ name = "libloading" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1124,7 +1121,7 @@ name = "libz-sys" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1322,7 +1319,7 @@ dependencies = [ "schannel 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1341,7 +1338,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1371,7 +1368,7 @@ dependencies = [ [[package]] name = "notify" -version = "4.0.7" +version = "4.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1493,7 +1490,7 @@ name = "openssl-sys" version = "0.9.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1650,7 +1647,7 @@ name = "rand" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "fuchsia-cprng 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1668,7 +1665,7 @@ dependencies = [ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1715,7 +1712,7 @@ dependencies = [ [[package]] name = "rand_jitter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1729,7 +1726,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1878,7 +1875,7 @@ dependencies = [ "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2167,7 +2164,7 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2258,7 +2255,7 @@ dependencies = [ "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2338,7 +2335,7 @@ dependencies = [ [[package]] name = "tokio-timer" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2650,7 +2647,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bindgen 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2756,7 +2753,7 @@ dependencies = [ "checksum bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "40ade3d27603c2cb345eb0912aec461a6dec7e06a4ae48589904e808335c7afa" "checksum bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" "checksum bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6584aa36f5ad4c9247f5323b0a42f37802b37a836f0ad87084d7a33961abe25f" -"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749" +"checksum cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)" = "4390a3b5f4f6bce9c1d0c00128379df433e53777fdd30e92f16a529332baec4e" "checksum cexpr 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42aac45e9567d97474a834efdee3081b3c942b2205be932092f53354ce503d6c" "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum cgl 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" @@ -2814,7 +2811,7 @@ dependencies = [ "checksum freetype-sys 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9c8666cce7cf6e51a290623647febfbab92480b4c3e0f495cb9d4d312b5d38" "checksum fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "c4bbbf71584aeed076100b5665ac14e3d85eeb31fdbb45fbd41ef9a682b5ec05" "checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" -"checksum fuchsia-cprng 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81f7f8eb465745ea9b02e2704612a9946a59fa40572086c6fd49d6ddcf30bf31" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" @@ -2875,7 +2872,7 @@ dependencies = [ "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" "checksum nom 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b30adc557058ce00c9d0d7cb3c6e0b5bc6f36e2e2eabe74b0ba726d194abd588" -"checksum notify 4.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c968cf37cf949114b00d51b0b23536d1c3a4a3963767cf4c969c65a6af78dc7d" +"checksum notify 4.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c9b605e417814e88bb051c88a84f83655d6ad4fa32fc36d9a96296d86087692d" "checksum num-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d9fe8fcafd1b86a37ce8a1cfa15ae504817e0c8c2e7ad42767371461ac1d316d" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" @@ -2915,7 +2912,7 @@ dependencies = [ "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "080723c6145e37503a2224f801f252e14ac5531cb450f4502698542d188cb3c0" +"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" "checksum rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b7c690732391ae0abafced5015ffb53656abfaec61b342290e5eb56b286a679d" "checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" @@ -2965,7 +2962,7 @@ dependencies = [ "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" -"checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2" +"checksum tempfile 3.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "37daa55a7240c4931c84559f03b3cad7d19535840d1c4a0cc4e9b2fb0dcf70ff" "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" "checksum terminfo 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8e51065bafd2abe106b6036483b69d1741f4a1ec56ce8a2378de341637de689e" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" @@ -2980,7 +2977,7 @@ dependencies = [ "checksum tokio-reactor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "afbcdb0f0d2a1e4c440af82d7bbf0bf91a8a8c0575bcd20c05d15be7e9d3a02f" "checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" "checksum tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c3fd86cb15547d02daa2b21aadaf4e37dee3368df38a526178a5afa3c034d2fb" -"checksum tokio-timer 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "21c04a314a1f69f73c0227beba6250e06cdc1e9a62e7eff912bf54a59b6d1b94" +"checksum tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" diff --git a/Cargo.toml b/Cargo.toml @@ -49,6 +49,7 @@ static_assertions = "0.3.0" terminfo = "0.6.1" url = "1.7.1" time = "0.1.40" +crossbeam-channel = "0.3.8" [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))'.dependencies] x11-dl = "2" diff --git a/src/ansi.rs b/src/ansi.rs @@ -21,7 +21,8 @@ use vte; use base64; use crate::index::{Column, Line, Contains}; -use crate::{MouseCursor, Rgb}; +use crate::MouseCursor; +use crate::term::color::Rgb; // Parse color arguments // @@ -1395,7 +1396,7 @@ mod tests { use std::io; use crate::index::{Line, Column}; use super::{Processor, Handler, Attr, TermInfo, Color, StandardCharset, CharsetIndex, parse_rgb_color, parse_number}; - use crate::Rgb; + use crate::term::color::Rgb; /// The /dev/null of `io::Write` struct Void; diff --git a/src/config/mod.rs b/src/config/mod.rs @@ -5,7 +5,7 @@ //! the config file will also hold user and platform specific keybindings. use std::borrow::Cow; use std::{env, fmt}; -use std::fs::{self, File}; +use std::fs::File; use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -13,7 +13,6 @@ use std::sync::mpsc; use std::time::Duration; use std::collections::HashMap; -use crate::Rgb; use font::Size; use serde_yaml; use serde::{self, de, Deserialize}; @@ -26,9 +25,11 @@ use crate::cli::Options; use crate::input::{Action, Binding, MouseBinding, KeyBinding}; use crate::index::{Line, Column}; use crate::ansi::{CursorStyle, NamedColor, Color}; +use crate::term::color::Rgb; mod bindings; +pub const SOURCE_FILE_PATH: &str = file!(); const MAX_SCROLLBACK_LINES: u32 = 100_000; static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); @@ -1691,7 +1692,7 @@ impl Config { path = path.join("alacritty/alacritty.yml"); - fs::create_dir_all(path.parent().unwrap())?; + std::fs::create_dir_all(path.parent().unwrap())?; File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; @@ -1868,16 +1869,6 @@ impl Config { self.persistent_logging } - pub fn load_from<P: Into<PathBuf>>(path: P) -> Result<Config> { - let path = path.into(); - let raw = Config::read_file(path.as_path())?; - let mut config: Config = serde_yaml::from_str(&raw)?; - config.config_path = Some(path); - config.print_deprecation_warnings(); - - Ok(config) - } - /// Overrides the `dynamic_title` configuration based on `--title`. pub fn update_dynamic_title(mut self, options: &Options) -> Self { if options.title.is_some() { @@ -1886,15 +1877,35 @@ impl Config { self } - fn read_file<P: AsRef<Path>>(path: P) -> Result<String> { - let mut f = fs::File::open(path)?; + pub fn load_from(path: PathBuf) -> Config { + let mut config = Config::reload_from(&path).unwrap_or_else(|_| Config::default()); + config.config_path = Some(path); + config + } + + pub fn reload_from(path: &PathBuf) -> Result<Config> { + match Config::read_config(path) { + Ok(config) => Ok(config), + Err(err) => { + error!("Unable to load config {:?}: {}", path, err); + Err(err) + } + } + } + + fn read_config(path: &PathBuf) -> Result<Config> { let mut contents = String::new(); - f.read_to_string(&mut contents)?; + File::open(path)?.read_to_string(&mut contents)?; + + // Prevent parsing error with empty string if contents.is_empty() { - return Err(Error::Empty); + return Ok(Config::default()); } - Ok(contents) + let mut config: Config = serde_yaml::from_str(&contents)?; + config.print_deprecation_warnings(); + + Ok(config) } fn print_deprecation_warnings(&mut self) { @@ -2201,7 +2212,7 @@ impl SecondaryFontDescription { pub struct Monitor { _thread: ::std::thread::JoinHandle<()>, - rx: mpsc::Receiver<Config>, + rx: mpsc::Receiver<PathBuf>, } pub trait OnConfigReload { @@ -2216,7 +2227,7 @@ impl OnConfigReload for crate::display::Notifier { impl Monitor { /// Get pending config changes - pub fn pending_config(&self) -> Option<Config> { + pub fn pending(&self) -> Option<PathBuf> { let mut config = None; while let Ok(new) = self.rx.try_recv() { config = Some(new); @@ -2224,6 +2235,7 @@ impl Monitor { config } + pub fn new<H, P>(path: P, mut handler: H) -> Monitor where H: OnConfigReload + Send + 'static, P: Into<PathBuf> @@ -2260,22 +2272,7 @@ impl Monitor { continue; } - let config = match Config::load_from(&path) { - Ok(config) => { - config - }, - Err(err) => { - if let Error::Empty = err { - info!("Config file {:?} is empty; loading default", path); - Config::default() - } else { - error!("Ignoring invalid config: {}", err); - continue; - } - } - }; - - let _ = config_tx.send(config); + let _ = config_tx.send(path); handler.on_config_reload(); } _ => {} diff --git a/src/display.rs b/src/display.rs @@ -25,12 +25,13 @@ use crate::config::Config; use font::{self, Rasterize}; use crate::meter::Meter; use crate::renderer::{self, GlyphCache, QuadRenderer}; -use crate::renderer::lines::Lines; +use crate::renderer::rects::{Rects, Rect}; use crate::term::{Term, SizeInfo, RenderableCell}; use crate::sync::FairMutex; use crate::window::{self, Window}; -use crate::logging::LoggerProxy; -use crate::Rgb; +use crate::term::color::Rgb; +use crate::index::Line; +use crate::message_bar::Message; #[derive(Debug)] pub enum Error { @@ -101,7 +102,7 @@ pub struct Display { meter: Meter, font_size: font::Size, size_info: SizeInfo, - logger_proxy: LoggerProxy, + last_message: Option<Message>, } /// Can wakeup the render loop from other threads @@ -132,11 +133,7 @@ impl Display { &self.size_info } - pub fn new( - config: &Config, - options: &cli::Options, - logger_proxy: LoggerProxy - ) -> Result<Display, Error> { + pub fn new(config: &Config, options: &cli::Options) -> Result<Display, Error> { // Extract some properties from config let render_timer = config.render_timer(); @@ -229,7 +226,7 @@ impl Display { meter: Meter::new(), font_size: font::Size::new(0.), size_info, - logger_proxy, + last_message: None, }) } @@ -297,7 +294,8 @@ impl Display { &mut self, terminal: &mut MutexGuard<'_, Term>, config: &Config, - items: &mut [&mut dyn OnResize], + pty_resize_handle: &mut dyn OnResize, + processor_resize_handle: &mut dyn OnResize, ) { // Resize events new_size and are handled outside the poll_events // iterator. This has the effect of coalescing multiple resize @@ -313,7 +311,10 @@ impl Display { let dpr = self.window.hidpi_factor(); // Font size/DPI factor modification detected - if terminal.font_size != self.font_size || (dpr - self.size_info.dpr).abs() > f64::EPSILON { + let font_changed = terminal.font_size != self.font_size + || (dpr - self.size_info.dpr).abs() > f64::EPSILON; + + if font_changed || self.last_message != terminal.message_buffer_mut().message() { if new_size == None { // Force a resize to refresh things new_size = Some(PhysicalSize::new( @@ -323,8 +324,11 @@ impl Display { } self.font_size = terminal.font_size; + self.last_message = terminal.message_buffer_mut().message(); self.size_info.dpr = dpr; + } + if font_changed { self.update_glyph_cache(config); } @@ -350,10 +354,14 @@ impl Display { let size = &self.size_info; terminal.resize(size); + processor_resize_handle.on_resize(size); - for item in items { - item.on_resize(size) + // Subtract message bar lines for pty size + let mut pty_size = *size; + if let Some(message) = terminal.message_buffer_mut().message() { + pty_size.height -= pty_size.cell_height * message.text(&size).len() as f32; } + pty_resize_handle.on_resize(&pty_size); self.window.resize(psize); self.renderer.resize(psize, self.size_info.padding_x, self.size_info.padding_y); @@ -376,6 +384,9 @@ impl Display { .renderable_cells(config, window_focused) .collect(); + // Get message from terminal to ignore modifications after lock is dropped + let message_buffer = terminal.message_buffer_mut().message(); + // Clear dirty flag terminal.dirty = !terminal.visual_bell.completed(); @@ -413,7 +424,7 @@ impl Display { { let glyph_cache = &mut self.glyph_cache; let metrics = glyph_cache.font_metrics(); - let mut cell_line_rects = Lines::new(&metrics, &size_info); + let mut rects = Rects::new(&metrics, &size_info); // Draw grid { @@ -423,7 +434,7 @@ impl Display { // Iterate over all non-empty cells in the grid for cell in grid_cells { // Update underline/strikeout - cell_line_rects.update_lines(&cell); + rects.update_lines(&cell); // Draw the cell api.render_cell(cell, glyph_cache); @@ -431,8 +442,35 @@ impl Display { }); } - // Draw rectangles - self.renderer.draw_rects(config, &size_info, visual_bell_intensity, cell_line_rects); + if let Some(message) = message_buffer { + let text = message.text(&size_info); + + // Create a new rectangle for the background + let start_line = size_info.lines().0 - text.len(); + let y = size_info.padding_y + size_info.cell_height * start_line as f32; + let rect = Rect::new(0., y, size_info.width, size_info.height - y); + rects.push(rect, message.color()); + + // Draw rectangles including the new background + self.renderer.draw_rects(config, &size_info, visual_bell_intensity, rects); + + // Relay messages to the user + let mut offset = 1; + for message_text in text.iter().rev() { + self.renderer.with_api(config, &size_info, |mut api| { + api.render_string( + &message_text, + Line(size_info.lines().saturating_sub(offset)), + glyph_cache, + None, + ); + }); + offset += 1; + } + } else { + // Draw rectangles + self.renderer.draw_rects(config, &size_info, visual_bell_intensity, rects); + } // Draw render timer if self.render_timer { @@ -443,36 +481,7 @@ impl Display { b: 0x53, }; self.renderer.with_api(config, &size_info, |mut api| { - api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, color); - }); - } - - // Display errors and warnings - if self.logger_proxy.errors() { - let msg = match self.logger_proxy.log_path() { - Some(path) => format!(" ERROR! See log at {} ", path), - None => " ERROR! See log in stderr ".into(), - }; - let color = Rgb { - r: 0xff, - g: 0x00, - b: 0x00, - }; - self.renderer.with_api(config, &size_info, |mut api| { - api.render_string(&msg, size_info.lines() - 1, glyph_cache, color); - }); - } else if self.logger_proxy.warnings() { - let msg = match self.logger_proxy.log_path() { - Some(path) => format!(" WARNING! See log at {} ", path), - None => " WARNING! See log in stderr ".into(), - }; - let color = Rgb { - r: 0xff, - g: 0xff, - b: 0x00, - }; - self.renderer.with_api(config, &size_info, |mut api| { - api.render_string(&msg, size_info.lines() - 1, glyph_cache, color); + api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, Some(color)); }); } } diff --git a/src/event.rs b/src/event.rs @@ -16,7 +16,6 @@ use glutin::dpi::PhysicalSize; #[cfg(unix)] use crate::tty; -use crate::ansi::{Handler, ClearMode}; use crate::grid::Scroll; use crate::config::{self, Config}; use crate::cli::Options; @@ -25,7 +24,7 @@ use crate::index::{Line, Column, Side, Point}; use crate::input::{self, MouseBinding, KeyBinding}; use crate::selection::Selection; use crate::sync::FairMutex; -use crate::term::{Term, SizeInfo, TermMode, Search}; +use crate::term::{Term, SizeInfo}; use crate::term::cell::Cell; use crate::util::{limit, start_daemon}; use crate::util::fmt::Red; @@ -55,10 +54,6 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { self.notifier.notify(val); } - fn terminal_mode(&self) -> TermMode { - *self.terminal.mode() - } - fn size_info(&self) -> SizeInfo { *self.size_info } @@ -78,10 +73,6 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } } - fn clear_history(&mut self) { - self.terminal.clear_screen(ClearMode::Saved); - } - fn copy_selection(&self, buffer: ClipboardBuffer) { if let Some(selected) = self.terminal.selection_to_string() { if !selected.is_empty() { @@ -126,10 +117,6 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { self.terminal.dirty = true; } - fn url(&self, point: Point<usize>) -> Option<String> { - self.terminal.url_search(point) - } - fn line_selection(&mut self, point: Point) { let point = self.terminal.visible_to_buffer(point); *self.terminal.selection_mut() = Some(Selection::lines(point)); @@ -140,14 +127,6 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize) } - fn change_font_size(&mut self, delta: f32) { - self.terminal.change_font_size(delta); - } - - fn reset_font_size(&mut self) { - self.terminal.reset_font_size(); - } - #[inline] fn mouse_mut(&mut self) -> &mut Mouse { self.mouse @@ -179,8 +158,13 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } #[inline] - fn clear_log(&mut self) { - self.terminal.clear_log(); + fn terminal(&self) -> &Term { + self.terminal + } + + #[inline] + fn terminal_mut(&mut self) -> &mut Term { + self.terminal } fn spawn_new_instance(&mut self) { @@ -393,8 +377,7 @@ impl<N: Notify> Processor<N> { .expect("write config.json"); } - // FIXME should do a more graceful shutdown - ::std::process::exit(0); + processor.ctx.terminal.exit(); }, Resized(lsize) => { // Resize events are emitted via glutin/winit with logical sizes diff --git a/src/input.rs b/src/input.rs @@ -29,10 +29,12 @@ use crate::config::{self, Key}; use crate::grid::Scroll; use crate::event::{ClickState, Mouse}; use crate::index::{Line, Column, Side, Point}; -use crate::term::SizeInfo; +use crate::term::{Term, SizeInfo, Search}; use crate::term::mode::TermMode; use crate::util::fmt::Red; use crate::util::start_daemon; +use crate::message_bar; +use crate::ansi::{Handler, ClearMode}; pub const FONT_SIZE_STEP: f32 = 0.5; @@ -54,7 +56,6 @@ pub struct Processor<'a, A: 'a> { pub trait ActionContext { fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _: B); - fn terminal_mode(&self) -> TermMode; fn size_info(&self) -> SizeInfo; fn copy_selection(&self, _: ClipboardBuffer); fn clear_selection(&mut self); @@ -69,13 +70,10 @@ pub trait ActionContext { fn received_count(&mut self) -> &mut usize; fn suppress_chars(&mut self) -> &mut bool; fn last_modifiers(&mut self) -> &mut ModifiersState; - fn change_font_size(&mut self, delta: f32); - fn reset_font_size(&mut self); fn scroll(&mut self, scroll: Scroll); - fn clear_history(&mut self); fn hide_window(&mut self); - fn url(&self, _: Point<usize>) -> Option<String>; - fn clear_log(&mut self); + fn terminal(&self) -> &Term; + fn terminal_mut(&mut self) -> &mut Term; fn spawn_new_instance(&mut self); } @@ -296,17 +294,16 @@ impl Action { ctx.hide_window(); }, Action::Quit => { - // FIXME should do a more graceful shutdown - ::std::process::exit(0); + ctx.terminal_mut().exit(); }, Action::IncreaseFontSize => { - ctx.change_font_size(FONT_SIZE_STEP); + ctx.terminal_mut().change_font_size(FONT_SIZE_STEP); }, Action::DecreaseFontSize => { - ctx.change_font_size(-FONT_SIZE_STEP); + ctx.terminal_mut().change_font_size(-FONT_SIZE_STEP); } Action::ResetFontSize => { - ctx.reset_font_size(); + ctx.terminal_mut().reset_font_size(); }, Action::ScrollPageUp => { ctx.scroll(Scroll::PageUp); @@ -321,10 +318,10 @@ impl Action { ctx.scroll(Scroll::Bottom); }, Action::ClearHistory => { - ctx.clear_history(); + ctx.terminal_mut().clear_screen(ClearMode::Saved); }, Action::ClearLogNotice => { - ctx.clear_log(); + ctx.terminal_mut().message_buffer_mut().pop(); }, Action::SpawnNewInstance => { ctx.spawn_new_instance(); @@ -334,7 +331,7 @@ impl Action { } fn paste<A: ActionContext>(&self, ctx: &mut A, contents: &str) { - if ctx.terminal_mode().contains(TermMode::BRACKETED_PASTE) { + if ctx.terminal().mode().contains(TermMode::BRACKETED_PASTE) { ctx.write_to_pty(&b"\x1b[200~"[..]); ctx.write_to_pty(contents.replace("\x1b","").into_bytes()); ctx.write_to_pty(&b"\x1b[201~"[..]); @@ -396,8 +393,13 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { self.ctx.mouse_mut().block_url_launcher = true; } + // Ignore motions over the message bar + if self.mouse_over_message_bar(point) { + return; + } + if self.ctx.mouse().left_button_state == ElementState::Pressed - && (modifiers.shift || !self.ctx.terminal_mode().intersects(report_mode)) + && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode)) { self.ctx.update_selection( Point { @@ -406,7 +408,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { }, cell_side, ); - } else if self.ctx.terminal_mode().intersects(motion_mode) + } else if self.ctx.terminal().mode().intersects(motion_mode) // Only report motion when changing cells && (prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column) && size_info.contains_point(x, y) @@ -417,7 +419,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { self.mouse_report(33, ElementState::Pressed, modifiers); } else if self.ctx.mouse().right_button_state == ElementState::Pressed { self.mouse_report(34, ElementState::Pressed, modifiers); - } else if self.ctx.terminal_mode().contains(TermMode::MOUSE_MOTION) { + } else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) { self.mouse_report(35, ElementState::Pressed, modifiers); } } @@ -430,7 +432,8 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize; let half_cell_width = (size_info.cell_width / 2.0) as usize; - let additional_padding = (size_info.width - size_info.padding_x * 2.) % size_info.cell_width; + let additional_padding = + (size_info.width - size_info.padding_x * 2.) % size_info.cell_width; let end_of_grid = size_info.width - size_info.padding_x - additional_padding; if cell_x > half_cell_width @@ -485,7 +488,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } // Report mouse events - if self.ctx.terminal_mode().contains(TermMode::SGR_MOUSE) { + 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); @@ -494,19 +497,24 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } } - pub fn on_mouse_double_click(&mut self, button: MouseButton) { - if let (Some(point) , MouseButton::Left) = (self.ctx.mouse_coords(), button) { + pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Point) { + if button == MouseButton::Left { self.ctx.semantic_selection(point); } } - pub fn on_mouse_triple_click(&mut self, button: MouseButton) { - if let (Some(point), MouseButton::Left) = (self.ctx.mouse_coords(), button) { + pub fn on_mouse_triple_click(&mut self, button: MouseButton, point: Point) { + if button == MouseButton::Left { self.ctx.line_selection(point); } } - pub fn on_mouse_press(&mut self, button: MouseButton, modifiers: ModifiersState) { + pub fn on_mouse_press( + &mut self, + button: MouseButton, + modifiers: ModifiersState, + point: Point, + ) { let now = Instant::now(); let elapsed = self.ctx.mouse().last_click_timestamp.elapsed(); self.ctx.mouse_mut().last_click_timestamp = now; @@ -518,14 +526,14 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { if !button_changed && elapsed < self.mouse_config.double_click.threshold => { self.ctx.mouse_mut().block_url_launcher = true; - self.on_mouse_double_click(button); + self.on_mouse_double_click(button, point); ClickState::DoubleClick }, ClickState::DoubleClick if !button_changed && elapsed < self.mouse_config.triple_click.threshold => { self.ctx.mouse_mut().block_url_launcher = true; - self.on_mouse_triple_click(button); + self.on_mouse_triple_click(button, point); ClickState::TripleClick }, _ => { @@ -535,20 +543,20 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { self.ctx.clear_selection(); // Start new empty selection - if let Some(point) = self.ctx.mouse_coords() { - let side = self.ctx.mouse().cell_side; - self.ctx.simple_selection(point, side); - } - - let report_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !modifiers.shift && self.ctx.terminal_mode().intersects(report_modes) { - match button { - MouseButton::Left => self.mouse_report(0, ElementState::Pressed, modifiers), - MouseButton::Middle => self.mouse_report(1, ElementState::Pressed, modifiers), - MouseButton::Right => self.mouse_report(2, ElementState::Pressed, modifiers), + let side = self.ctx.mouse().cell_side; + self.ctx.simple_selection(point, side); + + let report_modes = + TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; + if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) { + let code = match button { + MouseButton::Left => 0, + MouseButton::Middle => 1, + MouseButton::Right => 2, // Can't properly report more than three buttons. - MouseButton::Other(_) => (), + MouseButton::Other(_) => return, }; + self.mouse_report(code, ElementState::Pressed, modifiers); return; } @@ -557,38 +565,40 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { }; } - pub fn on_mouse_release(&mut self, button: MouseButton, modifiers: ModifiersState) { - let report_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !modifiers.shift && self.ctx.terminal_mode().intersects(report_modes) - { - match button { - MouseButton::Left => self.mouse_report(0, ElementState::Released, modifiers), - MouseButton::Middle => self.mouse_report(1, ElementState::Released, modifiers), - MouseButton::Right => self.mouse_report(2, ElementState::Released, modifiers), + pub fn on_mouse_release( + &mut self, + button: MouseButton, + modifiers: ModifiersState, + point: Point, + ) { + let report_modes = + TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; + if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) { + let code = match button { + MouseButton::Left => 0, + MouseButton::Middle => 1, + MouseButton::Right => 2, // Can't properly report more than three buttons. - MouseButton::Other(_) => (), + MouseButton::Other(_) => return, }; + self.mouse_report(code, ElementState::Released, modifiers); return; } else if button == MouseButton::Left { - self.launch_url(modifiers); + self.launch_url(modifiers, point); } - if self.save_to_clipboard { - self.ctx.copy_selection(ClipboardBuffer::Primary); - } - self.ctx.copy_selection(ClipboardBuffer::Selection); + self.copy_selection(); } // Spawn URL launcher when clicking on URLs - fn launch_url(&self, modifiers: ModifiersState) -> Option<()> { + fn launch_url(&self, modifiers: ModifiersState, point: Point) -> Option<()> { if !self.mouse_config.url.modifiers.relaxed_eq(modifiers) || self.ctx.mouse().block_url_launcher { return None; } - let point = self.ctx.mouse_coords()?; - let text = self.ctx.url(point.into())?; + let text = self.ctx.terminal().url_search(point.into())?; let launcher = self.mouse_config.url.launcher.as_ref()?; let mut args = launcher.args().to_vec(); @@ -602,7 +612,12 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { Some(()) } - pub fn on_mouse_wheel(&mut self, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState) { + pub fn on_mouse_wheel( + &mut self, + delta: MouseScrollDelta, + phase: TouchPhase, + modifiers: ModifiersState, + ) { match delta { MouseScrollDelta::LineDelta(_columns, lines) => { let new_scroll_px = lines * self.ctx.size_info().cell_height; @@ -634,7 +649,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { .faux_scrollback_lines .unwrap_or(self.scrolling_config.faux_multiplier as usize); - if self.ctx.terminal_mode().intersects(mouse_modes) { + if self.ctx.terminal().mode().intersects(mouse_modes) { self.ctx.mouse_mut().scroll_px += new_scroll_px; let code = if new_scroll_px > 0 { 64 } else { 65 }; @@ -643,7 +658,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { for _ in 0..lines { self.mouse_report(code, ElementState::Pressed, modifiers); } - } else if self.ctx.terminal_mode().contains(TermMode::ALT_SCREEN) + } else if self.ctx.terminal().mode().contains(TermMode::ALT_SCREEN) && faux_multiplier > 0 && !modifiers.shift { @@ -672,7 +687,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } pub fn on_focus_change(&mut self, is_focused: bool) { - if self.ctx.terminal_mode().contains(TermMode::FOCUS_IN_OUT) { + if self.ctx.terminal().mode().contains(TermMode::FOCUS_IN_OUT) { let chr = if is_focused { "I" } else { @@ -684,7 +699,12 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } } - pub fn mouse_input(&mut self, state: ElementState, button: MouseButton, modifiers: ModifiersState) { + pub fn mouse_input( + &mut self, + state: ElementState, + button: MouseButton, + modifiers: ModifiersState + ) { match button { MouseButton::Left => self.ctx.mouse_mut().left_button_state = state, MouseButton::Middle => self.ctx.mouse_mut().middle_button_state = state, @@ -692,12 +712,22 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { _ => (), } - match state { - ElementState::Pressed => { - self.process_mouse_bindings(modifiers, button); - self.on_mouse_press(button, modifiers); - }, - ElementState::Released => self.on_mouse_release(button, modifiers), + let point = match self.ctx.mouse_coords() { + Some(point) => point, + None => return, + }; + + // Skip normal mouse events if the message bar has been clicked + if self.mouse_over_message_bar(point) { + self.on_message_bar_click(state, point); + } else { + match state { + ElementState::Pressed => { + self.process_mouse_bindings(modifiers, button); + self.on_mouse_press(button, modifiers, point); + }, + ElementState::Released => self.on_mouse_release(button, modifiers, point), + } } self.ctx.mouse_mut().last_button = button; @@ -759,14 +789,19 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { for binding in self.key_bindings { let is_triggered = match binding.trigger { Key::Scancode(_) => binding.is_triggered_by( - self.ctx.terminal_mode(), + *self.ctx.terminal().mode(), input.modifiers, &Key::Scancode(input.scancode), false, ), _ => if let Some(key) = input.virtual_keycode { let key = Key::from_glutin_input(key); - binding.is_triggered_by(self.ctx.terminal_mode(), input.modifiers, &key, false) + binding.is_triggered_by( + *self.ctx.terminal().mode(), + input.modifiers, + &key, + false + ) } else { false }, @@ -791,9 +826,9 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { fn process_mouse_bindings(&mut self, mods: ModifiersState, button: MouseButton) -> bool { let mut has_binding = false; for binding in self.mouse_bindings { - if binding.is_triggered_by(self.ctx.terminal_mode(), mods, &button, true) { + if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &button, true) { // binding was triggered; run the action - let mouse_mode = !mods.shift && self.ctx.terminal_mode().intersects( + let mouse_mode = !mods.shift && self.ctx.terminal().mode().intersects( TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION @@ -805,6 +840,43 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { has_binding } + + /// Check if a point is within the message bar + fn mouse_over_message_bar(&mut self, point: Point) -> bool { + if let Some(message) = self.ctx.terminal_mut().message_buffer_mut().message() { + let size = self.ctx.size_info(); + point.line.0 >= size.lines().saturating_sub(message.text(&size).len()) + } else { + false + } + } + + /// Handle clicks on the message bar. + fn on_message_bar_click(&mut self, button_state: ElementState, point: Point) { + match button_state { + ElementState::Released => self.copy_selection(), + ElementState::Pressed => { + let size = self.ctx.size_info(); + if let Some(message) = self.ctx.terminal_mut().message_buffer_mut().message() { + if point.col + message_bar::CLOSE_BUTTON_TEXT.len() >= size.cols() + && point.line == size.lines() - message.text(&size).len() + { + self.ctx.terminal_mut().message_buffer_mut().pop(); + } + } + + self.ctx.clear_selection(); + } + } + } + + /// Copy text selection. + fn copy_selection(&mut self) { + if self.save_to_clipboard { + self.ctx.copy_selection(ClipboardBuffer::Primary); + } + self.ctx.copy_selection(ClipboardBuffer::Selection); + } } #[cfg(test)] @@ -820,6 +892,7 @@ mod tests { use crate::index::{Point, Side}; use crate::selection::Selection; use crate::grid::Scroll; + use crate::message_bar::MessageBuffer; use super::{Action, Binding, Processor}; use copypasta::Buffer as ClipboardBuffer; @@ -851,15 +924,15 @@ mod tests { fn simple_selection(&mut self, _point: Point, _side: Side) {} fn copy_selection(&self, _buffer: ClipboardBuffer) {} fn clear_selection(&mut self) {} - fn change_font_size(&mut self, _delta: f32) {} - fn reset_font_size(&mut self) {} - fn clear_history(&mut self) {} - fn clear_log(&mut self) {} fn hide_window(&mut self) {} fn spawn_new_instance(&mut self) {} - fn terminal_mode(&self) -> TermMode { - *self.terminal.mode() + fn terminal(&self) -> &Term { + &self.terminal + } + + fn terminal_mut(&mut self) -> &mut Term { + &mut self.terminal } fn size_info(&self) -> SizeInfo { @@ -897,10 +970,6 @@ mod tests { self.mouse } - fn url(&self, _: Point<usize>) -> Option<String> { - None - } - fn received_count(&mut self) -> &mut usize { &mut self.received_count } @@ -936,7 +1005,7 @@ mod tests { dpr: 1.0, }; - let mut terminal = Term::new(&config, size); + let mut terminal = Term::new(&config, size, MessageBuffer::new()); let mut mouse = Mouse::default(); mouse.click_state = $initial_state; diff --git a/src/lib.rs b/src/lib.rs @@ -46,10 +46,9 @@ pub mod term; pub mod tty; pub mod util; pub mod window; +pub mod message_bar; mod url; -use std::ops::Mul; - pub use crate::grid::Grid; pub use crate::term::Term; @@ -60,31 +59,6 @@ pub enum MouseCursor { Text, } -#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize, Deserialize)] -pub struct Rgb { - pub r: u8, - pub g: u8, - pub b: u8, -} - -// a multiply function for Rgb, as the default dim is just *2/3 -impl Mul<f32> for Rgb { - type Output = Rgb; - - fn mul(self, rhs: f32) -> Rgb { - let result = Rgb { - r: (f32::from(self.r) * rhs).max(0.0).min(255.0) as u8, - g: (f32::from(self.g) * rhs).max(0.0).min(255.0) as u8, - b: (f32::from(self.b) * rhs).max(0.0).min(255.0) as u8 - }; - - trace!("Scaling RGB by {} from {:?} to {:?}", rhs, self, result); - - result - } -} - - pub mod gl { #![allow(clippy::all)] include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); diff --git a/src/logging.rs b/src/logging.rs @@ -17,85 +17,52 @@ //! The main executable is supposed to call `initialize()` exactly once during //! startup. All logging messages are written to stdout, given that their //! log-level is sufficient for the level configured in `cli::Options`. -use crate::cli; -use log::{self, Level}; -use time; - use std::env; -use std::fs::{self, File, OpenOptions}; +use std::fs::{File, OpenOptions}; use std::io::{self, LineWriter, Stdout, Write}; use std::path::PathBuf; use std::process; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -pub fn initialize(options: &cli::Options) -> Result<LoggerProxy, log::SetLoggerError> { +use crossbeam_channel::Sender; +use log::{self, Level}; +use time; + +use crate::cli; +use crate::message_bar::Message; +use crate::term::color; + +const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; + +pub fn initialize( + options: &cli::Options, + message_tx: Sender<Message>, +) -> Result<Option<PathBuf>, log::SetLoggerError> { // Use env_logger if RUST_LOG environment variable is defined. Otherwise, // use the alacritty-only logger. if ::std::env::var("RUST_LOG").is_ok() { ::env_logger::try_init()?; - Ok(LoggerProxy::default()) + Ok(None) } else { - let logger = Logger::new(options.log_level); - let proxy = logger.proxy(); - + let logger = Logger::new(options.log_level, message_tx); + let path = logger.file_path(); log::set_boxed_logger(Box::new(logger))?; - - Ok(proxy) + Ok(path) } } -/// Proxy object for bidirectional communicating with the global logger. -#[derive(Clone, Default)] -pub struct LoggerProxy { - errors: Arc<AtomicBool>, - warnings: Arc<AtomicBool>, - logfile_proxy: OnDemandLogFileProxy, -} - -impl LoggerProxy { - /// Check for new logged errors. - pub fn errors(&self) -> bool { - self.errors.load(Ordering::Relaxed) - } - - /// Check for new logged warnings. - pub fn warnings(&self) -> bool { - self.warnings.load(Ordering::Relaxed) - } - - /// Get the path of the log file if it has been created. - pub fn log_path(&self) -> Option<&str> { - if self.logfile_proxy.created.load(Ordering::Relaxed) { - Some(&self.logfile_proxy.path) - } else { - None - } - } - - /// Clear log warnings/errors from the Alacritty UI. - pub fn clear(&mut self) { - self.errors.store(false, Ordering::Relaxed); - self.warnings.store(false, Ordering::Relaxed); - } - - pub fn delete_log(&mut self) { - self.logfile_proxy.delete_log(); - } -} - -struct Logger { +pub struct Logger { level: log::LevelFilter, logfile: Mutex<OnDemandLogFile>, stdout: Mutex<LineWriter<Stdout>>, - errors: Arc<AtomicBool>, - warnings: Arc<AtomicBool>, + message_tx: Sender<Message>, } impl Logger { // False positive, see: https://github.com/rust-lang-nursery/rust-clippy/issues/734 #[allow(clippy::new_ret_no_self)] - fn new(level: log::LevelFilter) -> Self { + fn new(level: log::LevelFilter, message_tx: Sender<Message>) -> Self { log::set_max_level(level); let logfile = Mutex::new(OnDemandLogFile::new()); @@ -105,16 +72,15 @@ impl Logger { level, logfile, stdout, - errors: Arc::new(AtomicBool::new(false)), - warnings: Arc::new(AtomicBool::new(false)), + message_tx, } } - fn proxy(&self) -> LoggerProxy { - LoggerProxy { - errors: self.errors.clone(), - warnings: self.warnings.clone(), - logfile_proxy: self.logfile.lock().expect("").proxy(), + fn file_path(&self) -> Option<PathBuf> { + if let Ok(logfile) = self.logfile.lock() { + Some(logfile.path().clone()) + } else { + None } } } @@ -125,61 +91,62 @@ impl log::Log for Logger { } fn log(&self, record: &log::Record<'_>) { - if self.enabled(record.metadata()) && - record.target().starts_with("alacritty") - { + if self.enabled(record.metadata()) && record.target().starts_with("alacritty") { let now = time::strftime("%F %R", &time::now()).unwrap(); let msg = if record.level() >= Level::Trace { - format!("[{}] [{}] [{}:{}] {}\n", - now, - record.level(), - record.file().unwrap_or("?"), - record.line() - .map(|l| l.to_string()) - .unwrap_or_else(|| "?".into()), - record.args()) + format!( + "[{}] [{}] [{}:{}] {}\n", + now, + record.level(), + record.file().unwrap_or("?"), + record + .line() + .map(|l| l.to_string()) + .unwrap_or_else(|| "?".into()), + record.args() + ) } else { - format!("[{}] [{}] {}\n", - now, - record.level(), - record.args()) + format!("[{}] [{}] {}\n", now, record.level(), record.args()) }; if let Ok(ref mut logfile) = self.logfile.lock() { let _ = logfile.write_all(msg.as_ref()); + + if record.level() <= Level::Warn { + #[cfg(not(windows))] + let env_var = format!("${}", ALACRITTY_LOG_ENV); + #[cfg(windows)] + let env_var = format!("%{}%", ALACRITTY_LOG_ENV); + + let msg = format!( + "[{}] See log at {} ({}):\n{}", + record.level(), + logfile.path.to_string_lossy(), + env_var, + record.args(), + ); + let color = match record.level() { + Level::Error => color::RED, + Level::Warn => color::YELLOW, + _ => unreachable!(), + }; + + let mut message = Message::new(msg, color); + message.set_topic(record.file().unwrap_or("?").into()); + let _ = self.message_tx.send(message); + } } if let Ok(ref mut stdout) = self.stdout.lock() { let _ = stdout.write_all(msg.as_ref()); } - - match record.level() { - Level::Error => self.errors.store(true, Ordering::Relaxed), - Level::Warn => self.warnings.store(true, Ordering::Relaxed), - _ => (), - } } } fn flush(&self) {} } -#[derive(Clone, Default)] -struct OnDemandLogFileProxy { - created: Arc<AtomicBool>, - path: String, -} - -impl OnDemandLogFileProxy { - fn delete_log(&mut self) { - if self.created.load(Ordering::Relaxed) && fs::remove_file(&self.path).is_ok() { - let _ = writeln!(io::stdout(), "Deleted log file at {:?}", self.path); - self.created.store(false, Ordering::Relaxed); - } - } -} - struct OnDemandLogFile { file: Option<LineWriter<File>>, created: Arc<AtomicBool>, @@ -191,6 +158,9 @@ impl OnDemandLogFile { let mut path = env::temp_dir(); path.push(format!("Alacritty-{}.log", process::id())); + // Set log path as an environment variable + env::set_var(ALACRITTY_LOG_ENV, path.as_os_str()); + OnDemandLogFile { path, file: None, @@ -227,11 +197,8 @@ impl OnDemandLogFile { Ok(self.file.as_mut().unwrap()) } - fn proxy(&self) -> OnDemandLogFileProxy { - OnDemandLogFileProxy { - created: self.created.clone(), - path: self.path.to_string_lossy().to_string(), - } + fn path(&self) -> &PathBuf { + &self.path } } diff --git a/src/main.rs b/src/main.rs @@ -33,6 +33,8 @@ use log::{info, error}; use std::error::Error; use std::sync::Arc; +use std::io::{self, Write}; +use std::fs; #[cfg(target_os = "macos")] use std::env; @@ -43,15 +45,16 @@ use std::os::unix::io::AsRawFd; #[cfg(target_os = "macos")] use alacritty::locale; use alacritty::{cli, event, die}; -use alacritty::config::{self, Config, Error as ConfigError}; +use alacritty::config::{self, Config}; use alacritty::display::Display; use alacritty::event_loop::{self, EventLoop, Msg}; -use alacritty::logging::{self, LoggerProxy}; +use alacritty::logging; use alacritty::panic; use alacritty::sync::FairMutex; use alacritty::term::Term; -use alacritty::tty::{self, process_should_exit}; +use alacritty::tty; use alacritty::util::fmt::Red; +use alacritty::message_bar::MessageBuffer; fn main() { panic::attach_handler(); @@ -65,11 +68,25 @@ fn main() { // Load command line options let options = cli::Options::load(); + // Setup storage for message UI + let message_buffer = MessageBuffer::new(); + // Initialize the logger as soon as possible as to capture output from other subsystems - let logger_proxy = logging::initialize(&options).expect("Unable to initialize logger"); + let log_file = + logging::initialize(&options, message_buffer.tx()).expect("Unable to initialize logger"); // Load configuration file - let config = load_config(&options).update_dynamic_title(&options); + // If the file is a command line argument, we won't write a generated default file + let config_path = options.config_path() + .or_else(Config::installed_config) + .or_else(|| Config::write_defaults().ok()) + .map(|path| path.to_path_buf()); + let config = if let Some(path) = config_path { + Config::load_from(path).update_dynamic_title(&options) + } else { + error!("Unable to write the default config"); + Config::default() + }; // Switch to home directory #[cfg(target_os = "macos")] @@ -78,34 +95,19 @@ fn main() { #[cfg(target_os = "macos")] locale::set_locale_environment(); + // Store if log file should be deleted before moving config + let persistent_logging = options.persistent_logging || config.persistent_logging(); + // Run alacritty - if let Err(err) = run(config, &options, logger_proxy) { + if let Err(err) = run(config, &options, message_buffer) { die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err)); } -} - -/// Load configuration -/// -/// If a configuration file is given as a command line argument we don't -/// generate a default file. If an empty configuration file is given, i.e. -/// /dev/null, we load the compiled-in defaults.) -fn load_config(options: &cli::Options) -> Config { - let config_path = options.config_path() - .or_else(Config::installed_config) - .or_else(|| Config::write_defaults().ok()); - - if let Some(config_path) = config_path { - Config::load_from(&*config_path).unwrap_or_else(|err| { - match err { - ConfigError::Empty => info!("Config file {:?} is empty; loading default", config_path), - _ => error!("Unable to load default config: {}", err), - } - Config::default() - }) - } else { - error!("Unable to write the default config"); - Config::default() + // Clean up logfile + if let Some(log_file) = log_file { + if !persistent_logging && fs::remove_file(&log_file).is_ok() { + let _ = writeln!(io::stdout(), "Deleted log file at {:?}", log_file); + } } } @@ -116,7 +118,7 @@ fn load_config(options: &cli::Options) -> Config { fn run( mut config: Config, options: &cli::Options, - mut logger_proxy: LoggerProxy, + message_buffer: MessageBuffer, ) -> Result<(), Box<dyn Error>> { info!("Welcome to Alacritty"); if let Some(config_path) = config.path() { @@ -129,7 +131,7 @@ fn run( // Create a display. // // The display manages a window and can draw the terminal - let mut display = Display::new(&config, options, logger_proxy.clone())?; + let mut display = Display::new(&config, options)?; info!( "PTY Dimensions: {:?} x {:?}", @@ -142,8 +144,7 @@ fn run( // This object contains all of the state about what's being displayed. It's // wrapped in a clonable mutex since both the I/O loop and display need to // access it. - let mut terminal = Term::new(&config, display.size().to_owned()); - terminal.set_logger_proxy(logger_proxy.clone()); + let terminal = Term::new(&config, display.size().to_owned(), message_buffer); let terminal = Arc::new(FairMutex::new(terminal)); // Find the window ID for setting $WINDOWID @@ -220,17 +221,25 @@ fn run( let mut terminal_lock = processor.process_events(&terminal, display.window()); // Handle config reloads - if let Some(new_config) = config_monitor - .as_ref() - .and_then(|monitor| monitor.pending_config()) - { - config = new_config.update_dynamic_title(options); - display.update_config(&config); - processor.update_config(&config); - terminal_lock.update_config(&config); + if let Some(ref path) = config_monitor.as_ref().and_then(|monitor| monitor.pending()) { + // Clear old config messages from bar + terminal_lock.message_buffer_mut().remove_topic(config::SOURCE_FILE_PATH); + + if let Ok(new_config) = Config::reload_from(path) { + config = new_config.update_dynamic_title(options); + display.update_config(&config); + processor.update_config(&config); + terminal_lock.update_config(&config); + } + terminal_lock.dirty = true; } + // Begin shutdown if the flag was raised + if terminal_lock.should_exit() { + break; + } + // Maybe draw the terminal if terminal_lock.needs_draw() { // Try to update the position of the input method editor @@ -241,18 +250,13 @@ fn run( // // The second argument is a list of types that want to be notified // of display size changes. - display.handle_resize(&mut terminal_lock, &config, &mut [&mut resize_handle, &mut processor]); + display.handle_resize(&mut terminal_lock, &config, &mut resize_handle, &mut processor); drop(terminal_lock); // Draw the current state of the terminal display.draw(&terminal, &config); } - - // Begin shutdown if the flag was raised. - if process_should_exit() { - break; - } } loop_tx @@ -268,9 +272,5 @@ fn run( info!("Goodbye"); - if !options.persistent_logging && !config.persistent_logging() { - logger_proxy.delete_log(); - } - Ok(()) } diff --git a/src/message_bar.rs b/src/message_bar.rs @@ -0,0 +1,517 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crossbeam_channel::{Receiver, Sender}; + +use crate::term::color::Rgb; +use crate::term::SizeInfo; + +pub const CLOSE_BUTTON_TEXT: &str = "[X]"; +const CLOSE_BUTTON_PADDING: usize = 1; +const MIN_FREE_LINES: usize = 3; +const TRUNCATED_MESSAGE: &str = "[MESSAGE TRUNCATED]"; + +/// Message for display in the MessageBuffer +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Message { + text: String, + color: Rgb, + topic: Option<String>, +} + +impl Message { + /// Create a new message + pub fn new(text: String, color: Rgb) -> Message { + Message { + text, + color, + topic: None, + } + } + + /// Formatted message text lines + pub fn text(&self, size_info: &SizeInfo) -> Vec<String> { + let num_cols = size_info.cols().0; + let max_lines = size_info.lines().saturating_sub(MIN_FREE_LINES); + let button_len = CLOSE_BUTTON_TEXT.len(); + + // Split line to fit the screen + let mut lines = Vec::new(); + let mut line = String::new(); + for c in self.text.trim().chars() { + if c == '\n' + || line.len() == num_cols + // Keep space in first line for button + || (lines.is_empty() + && num_cols >= button_len + && line.len() == num_cols.saturating_sub(button_len + CLOSE_BUTTON_PADDING)) + { + // Attempt to wrap on word boundaries + if let (Some(index), true) = (line.rfind(char::is_whitespace), c != '\n') { + let split = line.split_off(index + 1); + line.pop(); + lines.push(Self::pad_text(line, num_cols)); + line = split + } else { + lines.push(Self::pad_text(line, num_cols)); + line = String::new(); + } + } + + if c != '\n' { + line.push(c); + } + } + lines.push(Self::pad_text(line, num_cols)); + + // Truncate output if it's too long + if lines.len() > max_lines { + lines.truncate(max_lines); + if TRUNCATED_MESSAGE.len() <= num_cols { + if let Some(line) = lines.iter_mut().last() { + *line = Self::pad_text(TRUNCATED_MESSAGE.into(), num_cols); + } + } + } + + // Append close button to first line + if button_len <= num_cols { + if let Some(line) = lines.get_mut(0) { + line.truncate(num_cols - button_len); + line.push_str(CLOSE_BUTTON_TEXT); + } + } + + lines + } + + /// Message color + #[inline] + pub fn color(&self) -> Rgb { + self.color + } + + /// Message topic + #[inline] + pub fn topic(&self) -> Option<&String> { + self.topic.as_ref() + } + + /// Update the message topic + #[inline] + pub fn set_topic(&mut self, topic: String) { + self.topic = Some(topic); + } + + /// Right-pad text to fit a specific number of columns + #[inline] + fn pad_text(mut text: String, num_cols: usize) -> String { + let padding_len = num_cols.saturating_sub(text.len()); + text.extend(vec![' '; padding_len]); + text + } +} + +/// Storage for message bar +#[derive(Debug)] +pub struct MessageBuffer { + current: Option<Message>, + messages: Receiver<Message>, + tx: Sender<Message>, +} + +impl MessageBuffer { + /// Create new message buffer + pub fn new() -> MessageBuffer { + let (tx, messages) = crossbeam_channel::unbounded(); + MessageBuffer { + current: None, + messages, + tx, + } + } + + /// Check if there are any messages queued + #[inline] + pub fn is_empty(&self) -> bool { + self.current.is_none() + } + + /// Current message + #[inline] + pub fn message(&mut self) -> Option<Message> { + if let Some(current) = &self.current { + Some(current.clone()) + } else { + self.current = self.messages.try_recv().ok(); + self.current.clone() + } + } + + /// Channel for adding new messages + #[inline] + pub fn tx(&self) -> Sender<Message> { + self.tx.clone() + } + + /// Remove the currently visible message + #[inline] + pub fn pop(&mut self) { + // Remove all duplicates + for msg in self + .messages + .try_iter() + .take(self.messages.len()) + .filter(|m| Some(m) != self.current.as_ref()) + { + let _ = self.tx.send(msg); + } + + // Remove the message itself + self.current = self.messages.try_recv().ok(); + } + + /// Remove all messages with a specific topic + #[inline] + pub fn remove_topic(&mut self, topic: &str) { + // Filter messages currently pending + for msg in self + .messages + .try_iter() + .take(self.messages.len()) + .filter(|m| m.topic().map(|s| s.as_str()) != Some(topic)) + { + let _ = self.tx.send(msg); + } + + // Remove the currently active message + self.current = self.messages.try_recv().ok(); + } +} + +impl Default for MessageBuffer { + fn default() -> MessageBuffer { + MessageBuffer::new() + } +} + +#[cfg(test)] +mod test { + use super::{Message, MessageBuffer, MIN_FREE_LINES}; + use crate::term::{color, SizeInfo}; + + #[test] + fn appends_close_button() { + let input = "a"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 7., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("a [X]")]); + } + + #[test] + fn multiline_close_button_first_line() { + let input = "fo\nbar"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 6., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("fo [X]"), String::from("bar ")]); + } + + #[test] + fn splits_on_newline() { + let input = "a\nb"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 6., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines.len(), 2); + } + + #[test] + fn splits_on_length() { + let input = "foobar1"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 6., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines.len(), 2); + } + + #[test] + fn empty_with_shortterm() { + let input = "foobar"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 6., + height: 0., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines.len(), 0); + } + + #[test] + fn truncates_long_messages() { + let input = "hahahahahahahahahahaha truncate this because it's too long for the term"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 22., + height: (MIN_FREE_LINES + 2) as f32, + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!( + lines, + vec![ + String::from("hahahahahahahahaha [X]"), + String::from("[MESSAGE TRUNCATED] ") + ] + ); + } + + #[test] + fn hide_button_when_too_narrow() { + let input = "ha"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 2., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("ha")]); + } + + #[test] + fn hide_truncated_when_too_narrow() { + let input = "hahahahahahahahaha"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 2., + height: (MIN_FREE_LINES + 2) as f32, + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("ha"), String::from("ha")]); + } + + #[test] + fn add_newline_for_button() { + let input = "test"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 5., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("t [X]"), String::from("est ")]); + } + + #[test] + fn remove_topic() { + let mut message_buffer = MessageBuffer::new(); + for i in 0..10 { + let mut msg = Message::new(i.to_string(), color::RED); + if i % 2 == 0 && i < 5 { + msg.set_topic("topic".into()); + } + message_buffer.tx().send(msg).unwrap(); + } + + message_buffer.remove_topic("topic"); + + // Count number of messages + let mut num_messages = 0; + while message_buffer.message().is_some() { + num_messages += 1; + message_buffer.pop(); + } + + assert_eq!(num_messages, 7); + } + + #[test] + fn pop() { + let mut message_buffer = MessageBuffer::new(); + let one = Message::new(String::from("one"), color::RED); + message_buffer.tx().send(one.clone()).unwrap(); + let two = Message::new(String::from("two"), color::YELLOW); + message_buffer.tx().send(two.clone()).unwrap(); + + assert_eq!(message_buffer.message(), Some(one)); + + message_buffer.pop(); + + assert_eq!(message_buffer.message(), Some(two)); + } + + #[test] + fn wrap_on_words() { + let input = "a\nbc defg"; + let mut message_buffer = MessageBuffer::new(); + message_buffer + .tx() + .send(Message::new(input.into(), color::RED)) + .unwrap(); + let size = SizeInfo { + width: 5., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!( + lines, + vec![ + String::from("a [X]"), + String::from("bc "), + String::from("defg ") + ] + ); + } + + #[test] + fn remove_duplicates() { + let mut message_buffer = MessageBuffer::new(); + for _ in 0..10 { + let msg = Message::new(String::from("test"), color::RED); + message_buffer.tx().send(msg).unwrap(); + } + message_buffer.tx().send(Message::new(String::from("other"), color::RED)).unwrap(); + message_buffer.tx().send(Message::new(String::from("test"), color::YELLOW)).unwrap(); + let _ = message_buffer.message(); + + message_buffer.pop(); + + // Count number of messages + let mut num_messages = 0; + while message_buffer.message().is_some() { + num_messages += 1; + message_buffer.pop(); + } + + assert_eq!(num_messages, 2); + } +} diff --git a/src/renderer/lines.rs b/src/renderer/lines.rs @@ -1,151 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use std::collections::HashMap; - -use crate::renderer::Rect; -use crate::term::cell::Flags; -use crate::term::{RenderableCell, SizeInfo}; -use crate::Rgb; -use font::Metrics; - -/// Lines for underline and strikeout. -pub struct Lines<'a> { - inner: Vec<(Rect<f32>, Rgb)>, - last_starts: HashMap<Flags, Option<RenderableCell>>, - last_cell: Option<RenderableCell>, - metrics: &'a Metrics, - size: &'a SizeInfo, -} - -impl<'a> Lines<'a> { - pub fn new(metrics: &'a Metrics, size: &'a SizeInfo) -> Self { - let mut last_starts = HashMap::new(); - last_starts.insert(Flags::UNDERLINE, None); - last_starts.insert(Flags::STRIKEOUT, None); - - Self { - inner: Vec::new(), - last_cell: None, - last_starts, - metrics, - size, - } - } - - /// Convert the stored lines to rectangles for the renderer. - pub fn rects(mut self) -> Vec<(Rect<f32>, Rgb)> { - // If there's still a line pending, draw it until the last cell - for (flag, start_cell) in self.last_starts.iter_mut() { - if let Some(start) = start_cell { - self.inner.push( - create_rect( - &start, - &self.last_cell.unwrap(), - *flag, - &self.metrics, - &self.size, - ) - ); - } - } - - self.inner - } - - /// Update the stored lines with the next cell info. - pub fn update_lines(&mut self, cell: &RenderableCell) { - for (flag, start_cell) in self.last_starts.iter_mut() { - let flag = *flag; - *start_cell = match *start_cell { - // Check for end if line is present - Some(ref mut start) => { - let last_cell = self.last_cell.unwrap(); - - // No change in line - if cell.line == start.line - && cell.flags.contains(flag) - && cell.fg == start.fg - && cell.column == last_cell.column + 1 - { - continue; - } - - self.inner.push(create_rect( - &start, - &last_cell, - flag, - &self.metrics, - &self.size, - )); - - // Start a new line if the flag is present - if cell.flags.contains(flag) { - Some(*cell) - } else { - None - } - } - // Check for new start of line - None => if cell.flags.contains(flag) { - Some(*cell) - } else { - None - }, - }; - } - - self.last_cell = Some(*cell); - } -} - -/// Create a rectangle that starts on the left of `start` and ends on the right -/// of `end`, based on the given flag and size metrics. -fn create_rect( - start: &RenderableCell, - end: &RenderableCell, - flag: Flags, - metrics: &Metrics, - size: &SizeInfo, -) -> (Rect<f32>, Rgb) { - let start_x = start.column.0 as f32 * size.cell_width; - let end_x = (end.column.0 + 1) as f32 * size.cell_width; - let width = end_x - start_x; - - let (position, mut height) = match flag { - Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness), - Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness), - _ => unimplemented!("Invalid flag for cell line drawing specified"), - }; - - // Make sure lines are always visible - height = height.max(1.); - - let cell_bottom = (start.line.0 as f32 + 1.) * size.cell_height; - let baseline = cell_bottom + metrics.descent; - - let mut y = baseline - position - height / 2.; - let max_y = cell_bottom - height; - if y > max_y { - y = max_y; - } - - let rect = Rect::new( - start_x + size.padding_x, - y.round() + size.padding_y, - width, - height.round(), - ); - - (rect, start.fg) -} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs @@ -29,12 +29,12 @@ use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use crate::gl::types::*; use crate::gl; use crate::index::{Column, Line, RangeInclusive}; -use crate::Rgb; +use crate::term::color::Rgb; use crate::config::{self, Config, Delta}; use crate::term::{self, cell, RenderableCell}; -use crate::renderer::lines::Lines; +use crate::renderer::rects::{Rect, Rects}; -pub mod lines; +pub mod rects; // Shader paths for live reload static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl"); @@ -668,7 +668,7 @@ impl QuadRenderer { config: &Config, props: &term::SizeInfo, visual_bell_intensity: f64, - cell_line_rects: Lines, + cell_line_rects: Rects, ) { // Swap to rectangle rendering program unsafe { @@ -866,20 +866,6 @@ impl QuadRenderer { } } -#[derive(Debug, Copy, Clone)] -pub struct Rect<T> { - x: T, - y: T, - width: T, - height: T, -} - -impl<T> Rect<T> { - pub fn new(x: T, y: T, width: T, height: T) -> Self { - Rect { x, y, width, height } - } -} - impl<'a> RenderApi<'a> { pub fn clear(&self, color: Rgb) { let alpha = self.config.background_opacity().get(); @@ -941,8 +927,9 @@ impl<'a> RenderApi<'a> { string: &str, line: Line, glyph_cache: &mut GlyphCache, - color: Rgb + color: Option<Rgb> ) { + let bg_alpha = color.map(|_| 1.0).unwrap_or(0.0); let col = Column(0); let cells = string @@ -956,10 +943,10 @@ impl<'a> RenderApi<'a> { chars[0] = c; chars }, - bg: color, + bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0}), fg: Rgb { r: 0, g: 0, b: 0 }, flags: cell::Flags::empty(), - bg_alpha: 1.0, + bg_alpha, }) .collect::<Vec<_>>(); diff --git a/src/renderer/rects.rs b/src/renderer/rects.rs @@ -0,0 +1,169 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use std::collections::HashMap; + +use crate::term::cell::Flags; +use crate::term::{RenderableCell, SizeInfo}; +use crate::term::color::Rgb; +use font::Metrics; + +#[derive(Debug, Copy, Clone)] +pub struct Rect<T> { + pub x: T, + pub y: T, + pub width: T, + pub height: T, +} + +impl<T> Rect<T> { + pub fn new(x: T, y: T, width: T, height: T) -> Self { + Rect { x, y, width, height } + } +} + +/// Rects for underline, strikeout and more. +pub struct Rects<'a> { + inner: Vec<(Rect<f32>, Rgb)>, + last_starts: HashMap<Flags, Option<RenderableCell>>, + last_cell: Option<RenderableCell>, + metrics: &'a Metrics, + size: &'a SizeInfo, +} + +impl<'a> Rects<'a> { + pub fn new(metrics: &'a Metrics, size: &'a SizeInfo) -> Self { + let mut last_starts = HashMap::new(); + last_starts.insert(Flags::UNDERLINE, None); + last_starts.insert(Flags::STRIKEOUT, None); + + Self { + inner: Vec::new(), + last_cell: None, + last_starts, + metrics, + size, + } + } + + /// Convert the stored rects to rectangles for the renderer. + pub fn rects(mut self) -> Vec<(Rect<f32>, Rgb)> { + // If there's still a line pending, draw it until the last cell + for (flag, start_cell) in self.last_starts.iter_mut() { + if let Some(start) = start_cell { + self.inner.push( + create_rect( + &start, + &self.last_cell.unwrap(), + *flag, + &self.metrics, + &self.size, + ) + ); + } + } + + self.inner + } + + /// Update the stored lines with the next cell info. + pub fn update_lines(&mut self, cell: &RenderableCell) { + for (flag, start_cell) in self.last_starts.iter_mut() { + let flag = *flag; + *start_cell = match *start_cell { + // Check for end if line is present + Some(ref mut start) => { + let last_cell = self.last_cell.unwrap(); + + // No change in line + if cell.line == start.line + && cell.flags.contains(flag) + && cell.fg == start.fg + && cell.column == last_cell.column + 1 + { + continue; + } + + self.inner.push(create_rect( + &start, + &last_cell, + flag, + &self.metrics, + &self.size, + )); + + // Start a new line if the flag is present + if cell.flags.contains(flag) { + Some(*cell) + } else { + None + } + } + // Check for new start of line + None => if cell.flags.contains(flag) { + Some(*cell) + } else { + None + }, + }; + } + + self.last_cell = Some(*cell); + } + + // Add a rectangle + pub fn push(&mut self, rect: Rect<f32>, color: Rgb) { + self.inner.push((rect, color)); + } +} + +/// Create a rectangle that starts on the left of `start` and ends on the right +/// of `end`, based on the given flag and size metrics. +fn create_rect( + start: &RenderableCell, + end: &RenderableCell, + flag: Flags, + metrics: &Metrics, + size: &SizeInfo, +) -> (Rect<f32>, Rgb) { + let start_x = start.column.0 as f32 * size.cell_width; + let end_x = (end.column.0 + 1) as f32 * size.cell_width; + let width = end_x - start_x; + + let (position, mut height) = match flag { + Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness), + Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness), + _ => unimplemented!("Invalid flag for cell line drawing specified"), + }; + + // Make sure lines are always visible + height = height.max(1.); + + let cell_bottom = (start.line.0 as f32 + 1.) * size.cell_height; + let baseline = cell_bottom + metrics.descent; + + let mut y = baseline - position - height / 2.; + let max_y = cell_bottom - height; + if y > max_y { + y = max_y; + } + + let rect = Rect::new( + start_x + size.padding_x, + y.round() + size.padding_y, + width, + height.round(), + ); + + (rect, start.fg) +} diff --git a/src/term/color.rs b/src/term/color.rs @@ -1,11 +1,38 @@ -use std::ops::{Index, IndexMut}; +use std::ops::{Index, IndexMut, Mul}; use std::fmt; -use crate::{Rgb, ansi}; +use crate::ansi; use crate::config::Colors; pub const COUNT: usize = 270; +pub const RED: Rgb = Rgb { r: 0xff, g: 0x0, b: 0x0 }; +pub const YELLOW: Rgb = Rgb { r: 0xff, g: 0xff, b: 0x0 }; + +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize, Deserialize)] +pub struct Rgb { + pub r: u8, + pub g: u8, + pub b: u8, +} + +// a multiply function for Rgb, as the default dim is just *2/3 +impl Mul<f32> for Rgb { + type Output = Rgb; + + fn mul(self, rhs: f32) -> Rgb { + let result = Rgb { + r: (f32::from(self.r) * rhs).max(0.0).min(255.0) as u8, + g: (f32::from(self.g) * rhs).max(0.0).min(255.0) as u8, + b: (f32::from(self.b) * rhs).max(0.0).min(255.0) as u8 + }; + + trace!("Scaling RGB by {} from {:?} to {:?}", rhs, self, result); + + result + } +} + /// List of indexed colors /// /// The first 16 entries are the standard ansi named colors. Items 16..232 are diff --git a/src/term/mod.rs b/src/term/mod.rs @@ -27,16 +27,17 @@ use crate::grid::{BidirectionalIterator, Grid, Indexed, IndexRegion, DisplayIter use crate::index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive, Linear}; use crate::selection::{self, Selection, Locations}; use crate::config::{Config, VisualBellAnimation}; -use crate::{MouseCursor, Rgb}; +use crate::MouseCursor; use copypasta::{Clipboard, Load, Store}; use crate::input::FONT_SIZE_STEP; -use crate::logging::LoggerProxy; use crate::url::UrlParser; +use crate::message_bar::MessageBuffer; +use crate::term::color::Rgb; +use crate::term::cell::{LineLength, Cell}; +use crate::tty; pub mod cell; pub mod color; -pub use self::cell::Cell; -use self::cell::LineLength; /// A type that can expand a given point to a region /// @@ -793,8 +794,11 @@ pub struct Term { /// Automatically scroll to bottom when new lines are added auto_scroll: bool, - /// Proxy object for clearing displayed errors and warnings - logger_proxy: Option<LoggerProxy>, + /// Buffer to store messages for the message bar + message_buffer: MessageBuffer, + + /// Hint that Alacritty should be closed + should_exit: bool, } /// Terminal size info @@ -835,10 +839,10 @@ impl SizeInfo { } pub fn contains_point(&self, x: usize, y:usize) -> bool { - x <= (self.width - self.padding_x) as usize && - x >= self.padding_x as usize && - y <= (self.height - self.padding_y) as usize && - y >= self.padding_y as usize + x < (self.width - self.padding_x) as usize + && x >= self.padding_x as usize + && y < (self.height - self.padding_y) as usize + && y >= self.padding_y as usize } pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { @@ -858,14 +862,6 @@ impl Term { &self.grid.selection } - /// Clear displayed errors and warnings. - pub fn clear_log(&mut self) { - if let Some(ref mut logger_proxy) = self.logger_proxy { - logger_proxy.clear(); - } - } - - pub fn selection_mut(&mut self) -> &mut Option<Selection> { &mut self.grid.selection } @@ -885,7 +881,7 @@ impl Term { self.next_mouse_cursor.take() } - pub fn new(config: &Config, size: SizeInfo) -> Term { + pub fn new(config: &Config, size: SizeInfo, message_buffer: MessageBuffer) -> Term { let num_cols = size.cols(); let num_lines = size.lines(); @@ -929,14 +925,11 @@ impl Term { dynamic_title: config.dynamic_title(), tabspaces, auto_scroll: config.scrolling().auto_scroll, - logger_proxy: None, + message_buffer, + should_exit: false, } } - pub fn set_logger_proxy(&mut self, logger_proxy: LoggerProxy) { - self.logger_proxy = Some(logger_proxy); - } - pub fn change_font_size(&mut self, delta: f32) { // Saturating addition with minimum font size FONT_SIZE_STEP let new_size = self.font_size + Size::new(delta); @@ -1169,7 +1162,7 @@ impl Term { } /// Resize terminal to new dimensions - pub fn resize(&mut self, size : &SizeInfo) { + pub fn resize(&mut self, size: &SizeInfo) { debug!("Resizing terminal"); // Bounds check; lots of math assumes width and height are > 0 @@ -1184,6 +1177,10 @@ impl Term { let mut num_cols = size.cols(); let mut num_lines = size.lines(); + if let Some(message) = self.message_buffer.message() { + num_lines -= message.text(size).len(); + } + self.size_info = *size; if old_cols == num_cols && old_lines == num_lines { @@ -1315,6 +1312,26 @@ impl Term { pub fn background_color(&self) -> Rgb { self.colors[NamedColor::Background] } + + #[inline] + pub fn message_buffer_mut(&mut self) -> &mut MessageBuffer { + &mut self.message_buffer + } + + #[inline] + pub fn message_buffer(&self) -> &MessageBuffer { + &self.message_buffer + } + + #[inline] + pub fn exit(&mut self) { + self.should_exit = true; + } + + #[inline] + pub fn should_exit(&self) -> bool { + tty::process_should_exit() || self.should_exit + } } impl ansi::TermInfo for Term { @@ -1859,10 +1876,7 @@ impl ansi::Handler for Term { .each(|cell| cell.reset(&template)); } }, - ansi::ClearMode::All => { - self.clear_log(); - self.grid.region_mut(..).each(|c| c.reset(&template)); - }, + ansi::ClearMode::All => self.grid.region_mut(..).each(|c| c.reset(&template)), ansi::ClearMode::Above => { // If clearing more than one line if self.cursor.point.line > Line(1) { @@ -2129,6 +2143,7 @@ mod tests { use crate::input::FONT_SIZE_STEP; use font::Size; use crate::config::Config; + use crate::message_bar::MessageBuffer; #[test] fn semantic_selection_works() { @@ -2141,7 +2156,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let mut term = Term::new(&Default::default(), size); + let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), 0, Cell::default()); for i in 0..5 { for j in 0..2 { @@ -2185,7 +2200,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let mut term = Term::new(&Default::default(), size); + let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), 0, Cell::default()); for i in 0..5 { grid[Line(0)][Column(i)].c = 'a'; @@ -2211,7 +2226,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let mut term = Term::new(&Default::default(), size); + let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); let mut grid: Grid<Cell> = Grid::new(Line(3), Column(3), 0, Cell::default()); for l in 0..3 { if l != 1 { @@ -2256,7 +2271,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let mut term = Term::new(&Default::default(), size); + let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); let cursor = Point::new(Line(0), Column(0)); term.configure_charset(CharsetIndex::G0, StandardCharset::SpecialCharacterAndLineDrawing); @@ -2276,7 +2291,7 @@ mod tests { dpr: 1.0, }; let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size); + let mut term: Term = Term::new(&config, size, MessageBuffer::new()); term.change_font_size(font_size); let expected_font_size: Size = config.font().size() + Size::new(font_size); @@ -2305,7 +2320,7 @@ mod tests { dpr: 1.0, }; let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size); + let mut term: Term = Term::new(&config, size, MessageBuffer::new()); term.change_font_size(-100.0); @@ -2325,7 +2340,7 @@ mod tests { dpr: 1.0, }; let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size); + let mut term: Term = Term::new(&config, size, MessageBuffer::new()); term.change_font_size(10.0); term.reset_font_size(); @@ -2346,7 +2361,7 @@ mod tests { dpr: 1.0 }; let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size); + let mut term: Term = Term::new(&config, size, MessageBuffer::new()); // Add one line of scrollback term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default()); @@ -2373,6 +2388,7 @@ mod benches { use crate::grid::Grid; use crate::config::Config; + use crate::message_bar::MessageBuffer; use super::{SizeInfo, Term}; use super::cell::Cell; @@ -2411,7 +2427,7 @@ mod benches { let config = Config::default(); - let mut terminal = Term::new(&config, size); + let mut terminal = Term::new(&config, size, MessageBuffer::new()); mem::swap(&mut terminal.grid, &mut grid); b.iter(|| { diff --git a/src/url.rs b/src/url.rs @@ -128,7 +128,9 @@ mod tests { use crate::grid::Grid; use crate::index::{Column, Line, Point}; - use crate::term::{Cell, Search, SizeInfo, Term}; + use crate::term::{Search, SizeInfo, Term}; + use crate::term::cell::Cell; + use crate::message_bar::MessageBuffer; fn url_create_term(input: &str) -> Term { let size = SizeInfo { @@ -141,7 +143,7 @@ mod tests { dpr: 1.0, }; - let mut term = Term::new(&Default::default(), size); + let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); let mut grid: Grid<Cell> = Grid::new(Line(1), Column(input.len()), 0, Cell::default()); for (i, c) in input.chars().enumerate() { diff --git a/tests/ref.rs b/tests/ref.rs @@ -10,10 +10,11 @@ use alacritty::Grid; use alacritty::Term; use alacritty::ansi; use alacritty::index::Column; -use alacritty::term::Cell; +use alacritty::term::cell::Cell; use alacritty::term::SizeInfo; use alacritty::util::fmt::{Red, Green}; use alacritty::config::Config; +use alacritty::message_bar::MessageBuffer; macro_rules! ref_tests { ($($name:ident)*) => { @@ -90,7 +91,7 @@ fn ref_test(dir: &Path) { let mut config: Config = Default::default(); config.set_history(ref_config.history_size); - let mut terminal = Term::new(&config, size); + let mut terminal = Term::new(&config, size, MessageBuffer::new()); let mut parser = ansi::Processor::new(); for byte in recording {