diff --git a/Cargo.lock b/Cargo.lock
index a0f2f5ef..717bb7b6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -99,8 +99,6 @@ dependencies = [
"atuin-server",
"atuin-server-postgres",
"base64 0.21.0",
- "bitflags",
- "cassowary",
"chrono",
"clap",
"clap_complete",
@@ -116,6 +114,7 @@ dependencies = [
"interim",
"itertools",
"log",
+ "ratatui",
"rpassword",
"runtime-format",
"semver",
@@ -124,7 +123,6 @@ dependencies = [
"tiny-bip39",
"tokio",
"tracing-subscriber",
- "unicode-segmentation",
"unicode-width",
"whoami",
]
@@ -1974,6 +1972,19 @@ dependencies = [
"rand_core 0.5.1",
]
+[[package]]
+name = "ratatui"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce841e0486e7c2412c3740168ede33adeba8e154a15107b879d8162d77c7174e"
+dependencies = [
+ "bitflags",
+ "cassowary",
+ "crossterm",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
[[package]]
name = "redox_syscall"
version = "0.2.16"
diff --git a/atuin/Cargo.toml b/atuin/Cargo.toml
index bf038050..814de1b6 100644
--- a/atuin/Cargo.toml
+++ b/atuin/Cargo.toml
@@ -70,11 +70,7 @@ tiny-bip39 = "1"
futures-util = "0.3"
fuzzy-matcher = "0.3.7"
colored = "2.0.0"
-
-# ratatui
-bitflags = "1.3"
-cassowary = "0.3"
-unicode-segmentation = "1.2"
+ratatui = "0.21"
[dependencies.tracing-subscriber]
version = "0.3"
diff --git a/atuin/src/command/client/search/history_list.rs b/atuin/src/command/client/search/history_list.rs
index bc554900..64c6cccf 100644
--- a/atuin/src/command/client/search/history_list.rs
+++ b/atuin/src/command/client/search/history_list.rs
@@ -1,12 +1,12 @@
use std::time::Duration;
-use crate::ratatui::{
+use atuin_client::history::History;
+use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier, Style},
widgets::{Block, StatefulWidget, Widget},
};
-use atuin_client::history::History;
use super::format_duration;
diff --git a/atuin/src/command/client/search/interactive.rs b/atuin/src/command/client/search/interactive.rs
index 401dafec..710d027a 100644
--- a/atuin/src/command/client/search/interactive.rs
+++ b/atuin/src/command/client/search/interactive.rs
@@ -23,15 +23,15 @@ use super::{
engines::{SearchEngine, SearchState},
history_list::{HistoryList, ListState, PREFIX_LENGTH},
};
-use crate::ratatui::{
+use crate::{command::client::search::engines, VERSION};
+use ratatui::{
backend::{Backend, CrosstermBackend},
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style},
- text::{Span, Spans, Text},
+ text::{Line, Span, Text},
widgets::{Block, BorderType, Borders, Paragraph},
Frame, Terminal, TerminalOptions, Viewport,
};
-use crate::{command::client::search::engines, VERSION};
const RETURN_ORIGINAL: usize = usize::MAX;
const RETURN_QUERY: usize = usize::MAX - 1;
@@ -402,7 +402,7 @@ impl State {
#[allow(clippy::unused_self)]
fn build_help(&mut self) -> Paragraph {
- let help = Paragraph::new(Text::from(Spans::from(vec![
+ let help = Paragraph::new(Text::from(Line::from(vec![
Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit"),
])))
diff --git a/atuin/src/main.rs b/atuin/src/main.rs
index 9e570337..2f81f4fc 100644
--- a/atuin/src/main.rs
+++ b/atuin/src/main.rs
@@ -7,9 +7,6 @@ use eyre::Result;
use command::AtuinCmd;
mod command;
-#[allow(clippy::all)]
-mod ratatui;
-
const VERSION: &str = env!("CARGO_PKG_VERSION");
static HELP_TEMPLATE: &str = "\
diff --git a/atuin/src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md b/atuin/src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 0a0f4bc6..00000000
--- a/atuin/src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-name: Bug report
-about: Create an issue about a bug you encountered
-title: ''
-labels: bug
-assignees: ''
----
-
-
-
-## Description
-
-
-
-## To Reproduce
-
-
-
-## Expected behavior
-
-
-
-## Screenshots
-
-
-
-## Environment
-
-
-- OS:
-- Terminal Emulator:
-- Font:
-- Crate version:
-- Backend:
-
-## Additional context
-
diff --git a/atuin/src/ratatui/.github/ISSUE_TEMPLATE/config.yml b/atuin/src/ratatui/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 3ba13e0c..00000000
--- a/atuin/src/ratatui/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1 +0,0 @@
-blank_issues_enabled: false
diff --git a/atuin/src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md b/atuin/src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index ae095edb..00000000
--- a/atuin/src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: ''
-labels: enhancement
-assignees: ''
-
----
-
-## Problem
-
-
-## Solution
-
-
-## Alternatives
-
-
-## Additional context
-
diff --git a/atuin/src/ratatui/.github/workflows/cd.yml b/atuin/src/ratatui/.github/workflows/cd.yml
deleted file mode 100644
index f61e3603..00000000
--- a/atuin/src/ratatui/.github/workflows/cd.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-name: Continuous Deployment
-
-on:
- push:
- tags:
- - "v*.*.*"
-
-jobs:
- publish:
- name: Publish on crates.io
- runs-on: ubuntu-latest
- steps:
- - name: Checkout the repository
- uses: actions/checkout@v3
- - name: Publish
- uses: actions-rs/cargo@v1
- with:
- command: publish
- args: --token ${{ secrets.CARGO_TOKEN }}
diff --git a/atuin/src/ratatui/.github/workflows/ci.yml b/atuin/src/ratatui/.github/workflows/ci.yml
deleted file mode 100644
index bfa363e9..00000000
--- a/atuin/src/ratatui/.github/workflows/ci.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-on:
- push:
- branches:
- - main
- pull_request:
- branches:
- - main
-
-name: CI
-
-env:
- CI_CARGO_MAKE_VERSION: 0.35.16
-
-jobs:
- test:
- strategy:
- matrix:
- os: [ubuntu-latest, windows-latest, macos-latest]
- rust: ["1.59.0", "stable"]
- include:
- - os: ubuntu-latest
- triple: x86_64-unknown-linux-musl
- - os: windows-latest
- triple: x86_64-pc-windows-msvc
- - os: macos-latest
- triple: x86_64-apple-darwin
- runs-on: ${{ matrix.os }}
- steps:
- - uses: hecrj/setup-rust-action@50a120e4d34903c2c1383dec0e9b1d349a9cc2b1
- with:
- rust-version: ${{ matrix.rust }}
- components: rustfmt,clippy
- - uses: actions/checkout@v3
- - name: Install cargo-make on Linux or macOS
- if: ${{ runner.os != 'windows' }}
- shell: bash
- run: |
- curl -LO 'https://github.com/sagiegurari/cargo-make/releases/download/${{ env.CI_CARGO_MAKE_VERSION }}/cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}.zip'
- unzip 'cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}.zip'
- cp 'cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}/cargo-make' ~/.cargo/bin/
- cargo make --version
- - name: Install cargo-make on Windows
- if: ${{ runner.os == 'windows' }}
- shell: bash
- run: |
- # `cargo-make-v0.35.16-{target}/` directory is created on Linux and macOS, but it is not creatd on Windows.
- mkdir cargo-make-temporary
- cd cargo-make-temporary
- curl -LO 'https://github.com/sagiegurari/cargo-make/releases/download/${{ env.CI_CARGO_MAKE_VERSION }}/cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}.zip'
- unzip 'cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}.zip'
- cp cargo-make.exe ~/.cargo/bin/
- cd ..
- cargo make --version
- - name: "Format / Build / Test"
- run: cargo make ci
- env:
- RUST_BACKTRACE: full
-
- lint:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- if: github.event_name != 'pull_request'
- uses: actions/checkout@v3
- - name: Checkout
- if: github.event_name == 'pull_request'
- uses: actions/checkout@v3
- with:
- ref: ${{ github.event.pull_request.head.sha }}
- - name: "Check conventional commits"
- uses: crate-ci/committed@master
- with:
- args: "-vv"
- commits: "HEAD"
- - name: "Check typos"
- uses: crate-ci/typos@master
diff --git a/atuin/src/ratatui/.gitignore b/atuin/src/ratatui/.gitignore
deleted file mode 100644
index dcb33fbb..00000000
--- a/atuin/src/ratatui/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-target
-Cargo.lock
-*.log
-*.rs.rustfmt
-.gdb_history
-.idea/
diff --git a/atuin/src/ratatui/LICENSE b/atuin/src/ratatui/LICENSE
deleted file mode 100644
index 7a0657cb..00000000
--- a/atuin/src/ratatui/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2016 Florian Dehau
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/atuin/src/ratatui/README.md b/atuin/src/ratatui/README.md
deleted file mode 100644
index 05d4adb6..00000000
--- a/atuin/src/ratatui/README.md
+++ /dev/null
@@ -1,136 +0,0 @@
-# ratatui
-
-An actively maintained `tui`-rs fork.
-
-[![Build Status](https://github.com/tui-rs-revival/ratatui/workflows/CI/badge.svg)](https://github.com/tui-rs-revival/ratatui/actions?query=workflow%3ACI+)
-[![Crate Status](https://img.shields.io/crates/v/ratatui.svg)](https://crates.io/crates/ratatui)
-[![Docs Status](https://docs.rs/ratatui/badge.svg)](https://docs.rs/crate/ratatui/)
-
-
-
-# Install
-
-```toml
-[dependencies]
-tui = { package = "ratatui" }
-```
-
-# What is this fork?
-
-This fork was created to continue maintenance on the original TUI project. The original maintainer had created an [issue](https://github.com/fdehau/tui-rs/issues/654) explaining how he couldn't find time to continue development, which led to us creating this fork.
-
-With that in mind, **we the community** look forward to continuing the work started by [**Florian Dehau.**](https://github.com/fdehau) :rocket:
-
-In order to organize ourselves, we currently use a [discord server](https://discord.gg/pMCEU9hNEj), feel free to join and come chat ! There are also plans to implement a [matrix](https://matrix.org/) bridge in the near future.
-**Discord is not a MUST to contribute,** we follow a pretty standard github centered open source workflow keeping the most important conversations on github, open an issue or PR and it will be addressed. :smile:
-
-Please make sure you read the updated contributing guidelines, especially if you are interested in working on a PR or issue opened in the previous repository.
-
-# Introduction
-
-`ratatui`-rs is a [Rust](https://www.rust-lang.org) library to build rich terminal
-user interfaces and dashboards. It is heavily inspired by the `Javascript`
-library [blessed-contrib](https://github.com/yaronn/blessed-contrib) and the
-`Go` library [termui](https://github.com/gizak/termui).
-
-The library supports multiple backends:
-
-- [crossterm](https://github.com/crossterm-rs/crossterm) [default]
-- [termion](https://github.com/ticki/termion)
-
-The library is based on the principle of immediate rendering with intermediate
-buffers. This means that at each new frame you should build all widgets that are
-supposed to be part of the UI. While providing a great flexibility for rich and
-interactive UI, this may introduce overhead for highly dynamic content. So, the
-implementation try to minimize the number of ansi escapes sequences generated to
-draw the updated UI. In practice, given the speed of `Rust` the overhead rather
-comes from the terminal emulator than the library itself.
-
-Moreover, the library does not provide any input handling nor any event system and
-you may rely on the previously cited libraries to achieve such features.
-
-## Rust version requirements
-
-Since version 0.17.0, `ratatui` requires **rustc version 1.59.0 or greater**.
-
-# Documentation
-
-The documentation can be found on [docs.rs.](https://docs.rs/ratatui)
-
-# Demo
-
-The demo shown in the gif can be run with all available backends.
-
-```
-# crossterm
-cargo run --example demo --release -- --tick-rate 200
-# termion
-cargo run --example demo --no-default-features --features=termion --release -- --tick-rate 200
-```
-
-where `tick-rate` is the UI refresh rate in ms.
-
-The UI code is in [examples/demo/ui.rs](https://github.com/tui-rs-revival/ratatui/blob/main/examples/demo/ui.rs) while the
-application state is in [examples/demo/app.rs](https://github.com/tui-rs-revival/ratatui/blob/main/examples/demo/app.rs).
-
-If the user interface contains glyphs that are not displayed correctly by your terminal, you may want to run
-the demo without those symbols:
-
-```
-cargo run --example demo --release -- --tick-rate 200 --enhanced-graphics false
-```
-
-# Widgets
-
-## Built in
-
-The library comes with the following list of widgets:
-
-- [Block](https://github.com/tui-rs-revival/ratatui/blob/main/examples/block.rs)
-- [Gauge](https://github.com/tui-rs-revival/ratatui/blob/main/examples/gauge.rs)
-- [Sparkline](https://github.com/tui-rs-revival/ratatui/blob/main/examples/sparkline.rs)
-- [Chart](https://github.com/tui-rs-revival/ratatui/blob/main/examples/chart.rs)
-- [BarChart](https://github.com/tui-rs-revival/ratatui/blob/main/examples/barchart.rs)
-- [List](https://github.com/tui-rs-revival/ratatui/blob/main/examples/list.rs)
-- [Table](https://github.com/tui-rs-revival/ratatui/blob/main/examples/table.rs)
-- [Paragraph](https://github.com/tui-rs-revival/ratatui/blob/main/examples/paragraph.rs)
-- [Canvas (with line, point cloud, map)](https://github.com/tui-rs-revival/ratatui/blob/main/examples/canvas.rs)
-- [Tabs](https://github.com/tui-rs-revival/ratatui/blob/main/examples/tabs.rs)
-
-Click on each item to see the source of the example. Run the examples with with
-cargo (e.g. to run the gauge example `cargo run --example gauge`), and quit by pressing `q`.
-
-You can run all examples by running `cargo make run-examples` (require
-`cargo-make` that can be installed with `cargo install cargo-make`).
-
-### Third-party libraries, bootstrapping templates and widgets
-
-- [ansi-to-tui](https://github.com/uttarayan21/ansi-to-tui) — Convert ansi colored text to `tui::text::Text`
-- [color-to-tui](https://github.com/uttarayan21/color-to-tui) — Parse hex colors to `tui::style::Color`
-- [rust-tui-template](https://github.com/orhun/rust-tui-template) — A template for bootstrapping a Rust TUI application with Tui-rs & crossterm
-- [simple-tui-rs](https://github.com/pmsanford/simple-tui-rs) — A simple example tui-rs app
-- [tui-builder](https://github.com/jkelleyrtp/tui-builder) — Batteries-included MVC framework for Tui-rs + Crossterm apps
-- [tui-clap](https://github.com/kegesch/tui-clap-rs) — Use clap-rs together with Tui-rs
-- [tui-log](https://github.com/kegesch/tui-log-rs) — Example of how to use logging with Tui-rs
-- [tui-logger](https://github.com/gin66/tui-logger) — Logger and Widget for Tui-rs
-- [tui-realm](https://github.com/veeso/tui-realm) — Tui-rs framework to build stateful applications with a React/Elm inspired approach
-- [tui-realm-treeview](https://github.com/veeso/tui-realm-treeview) — Treeview component for Tui-realm
-- [tui tree widget](https://github.com/EdJoPaTo/tui-rs-tree-widget) — Tree Widget for Tui-rs
-- [tui-windows](https://github.com/markatk/tui-windows-rs) — Tui-rs abstraction to handle multiple windows and their rendering
-- [tui-textarea](https://github.com/rhysd/tui-textarea): Simple yet powerful multi-line text editor widget supporting several key shortcuts, undo/redo, text search, etc.
-- [tui-rs-tree-widgets](https://github.com/EdJoPaTo/tui-rs-tree-widget): Widget for tree data structures.
-- [tui-input](https://github.com/sayanarijit/tui-input): TUI input library supporting multiple backends and tui-rs.
-
-# Apps
-
-Check out the list of [close to 40 apps](./APPS.md) using `ratatui`!
-
-# Alternatives
-
-You might want to checkout [Cursive](https://github.com/gyscos/Cursive) for an
-alternative solution to build text user interfaces in Rust.
-
-# License
-
-[MIT](LICENSE)
-
diff --git a/atuin/src/ratatui/backend/crossterm.rs b/atuin/src/ratatui/backend/crossterm.rs
deleted file mode 100644
index 3dceb6ad..00000000
--- a/atuin/src/ratatui/backend/crossterm.rs
+++ /dev/null
@@ -1,241 +0,0 @@
-use crate::ratatui::{
- backend::{Backend, ClearType},
- buffer::Cell,
- layout::Rect,
- style::{Color, Modifier},
-};
-use crossterm::{
- cursor::{Hide, MoveTo, Show},
- execute, queue,
- style::{
- Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
- SetForegroundColor,
- },
- terminal::{self, Clear},
-};
-use std::io::{self, Write};
-
-pub struct CrosstermBackend {
- buffer: W,
-}
-
-impl CrosstermBackend
-where
- W: Write,
-{
- pub fn new(buffer: W) -> CrosstermBackend {
- CrosstermBackend { buffer }
- }
-}
-
-impl Write for CrosstermBackend
-where
- W: Write,
-{
- fn write(&mut self, buf: &[u8]) -> io::Result {
- self.buffer.write(buf)
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.buffer.flush()
- }
-}
-
-impl Backend for CrosstermBackend
-where
- W: Write,
-{
- fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
- where
- I: Iterator- ,
- {
- let mut fg = Color::Reset;
- let mut bg = Color::Reset;
- let mut modifier = Modifier::empty();
- let mut last_pos: Option<(u16, u16)> = None;
- for (x, y, cell) in content {
- // Move the cursor if the previous location was not (x - 1, y)
- if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
- map_error(queue!(self.buffer, MoveTo(x, y)))?;
- }
- last_pos = Some((x, y));
- if cell.modifier != modifier {
- let diff = ModifierDiff {
- from: modifier,
- to: cell.modifier,
- };
- diff.queue(&mut self.buffer)?;
- modifier = cell.modifier;
- }
- if cell.fg != fg {
- let color = CColor::from(cell.fg);
- map_error(queue!(self.buffer, SetForegroundColor(color)))?;
- fg = cell.fg;
- }
- if cell.bg != bg {
- let color = CColor::from(cell.bg);
- map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
- bg = cell.bg;
- }
-
- map_error(queue!(self.buffer, Print(&cell.symbol)))?;
- }
-
- map_error(queue!(
- self.buffer,
- SetForegroundColor(CColor::Reset),
- SetBackgroundColor(CColor::Reset),
- SetAttribute(CAttribute::Reset)
- ))
- }
-
- fn hide_cursor(&mut self) -> io::Result<()> {
- map_error(execute!(self.buffer, Hide))
- }
-
- fn show_cursor(&mut self) -> io::Result<()> {
- map_error(execute!(self.buffer, Show))
- }
-
- fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
- crossterm::cursor::position()
- .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
- }
-
- fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
- map_error(execute!(self.buffer, MoveTo(x, y)))
- }
-
- fn clear(&mut self) -> io::Result<()> {
- self.clear_region(ClearType::All)
- }
-
- fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
- map_error(execute!(
- self.buffer,
- Clear(match clear_type {
- ClearType::All => crossterm::terminal::ClearType::All,
- ClearType::AfterCursor => crossterm::terminal::ClearType::FromCursorDown,
- ClearType::BeforeCursor => crossterm::terminal::ClearType::FromCursorUp,
- ClearType::CurrentLine => crossterm::terminal::ClearType::CurrentLine,
- ClearType::UntilNewLine => crossterm::terminal::ClearType::UntilNewLine,
- })
- ))
- }
-
- fn append_lines(&mut self, n: u16) -> io::Result<()> {
- for _ in 0..n {
- map_error(queue!(self.buffer, Print("\n")))?;
- }
- self.buffer.flush()
- }
-
- fn size(&self) -> io::Result {
- let (width, height) =
- terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
-
- Ok(Rect::new(0, 0, width, height))
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.buffer.flush()
- }
-}
-
-fn map_error(error: crossterm::Result<()>) -> io::Result<()> {
- error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
-}
-
-impl From for CColor {
- fn from(color: Color) -> Self {
- match color {
- Color::Reset => CColor::Reset,
- Color::Black => CColor::Black,
- Color::Red => CColor::DarkRed,
- Color::Green => CColor::DarkGreen,
- Color::Yellow => CColor::DarkYellow,
- Color::Blue => CColor::DarkBlue,
- Color::Magenta => CColor::DarkMagenta,
- Color::Cyan => CColor::DarkCyan,
- Color::Gray => CColor::Grey,
- Color::DarkGray => CColor::DarkGrey,
- Color::LightRed => CColor::Red,
- Color::LightGreen => CColor::Green,
- Color::LightBlue => CColor::Blue,
- Color::LightYellow => CColor::Yellow,
- Color::LightMagenta => CColor::Magenta,
- Color::LightCyan => CColor::Cyan,
- Color::White => CColor::White,
- Color::Indexed(i) => CColor::AnsiValue(i),
- Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
- }
- }
-}
-
-#[derive(Debug)]
-struct ModifierDiff {
- pub from: Modifier,
- pub to: Modifier,
-}
-
-impl ModifierDiff {
- fn queue(&self, mut w: W) -> io::Result<()>
- where
- W: io::Write,
- {
- //use crossterm::Attribute;
- let removed = self.from - self.to;
- if removed.contains(Modifier::REVERSED) {
- map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?;
- }
- if removed.contains(Modifier::BOLD) {
- map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
- if self.to.contains(Modifier::DIM) {
- map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
- }
- }
- if removed.contains(Modifier::ITALIC) {
- map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
- }
- if removed.contains(Modifier::UNDERLINED) {
- map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
- }
- if removed.contains(Modifier::DIM) {
- map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
- }
- if removed.contains(Modifier::CROSSED_OUT) {
- map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?;
- }
- if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
- map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
- }
-
- let added = self.to - self.from;
- if added.contains(Modifier::REVERSED) {
- map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
- }
- if added.contains(Modifier::BOLD) {
- map_error(queue!(w, SetAttribute(CAttribute::Bold)))?;
- }
- if added.contains(Modifier::ITALIC) {
- map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
- }
- if added.contains(Modifier::UNDERLINED) {
- map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
- }
- if added.contains(Modifier::DIM) {
- map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
- }
- if added.contains(Modifier::CROSSED_OUT) {
- map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?;
- }
- if added.contains(Modifier::SLOW_BLINK) {
- map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?;
- }
- if added.contains(Modifier::RAPID_BLINK) {
- map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?;
- }
-
- Ok(())
- }
-}
diff --git a/atuin/src/ratatui/backend/mod.rs b/atuin/src/ratatui/backend/mod.rs
deleted file mode 100644
index a360db18..00000000
--- a/atuin/src/ratatui/backend/mod.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use std::io;
-
-use crate::ratatui::buffer::Cell;
-use crate::ratatui::layout::Rect;
-
-#[cfg(feature = "termion")]
-mod termion;
-#[cfg(feature = "termion")]
-pub use self::termion::TermionBackend;
-
-mod crossterm;
-pub use self::crossterm::CrosstermBackend;
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum ClearType {
- All,
- AfterCursor,
- BeforeCursor,
- CurrentLine,
- UntilNewLine,
-}
-
-pub trait Backend {
- fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
- where
- I: Iterator
- ;
-
- /// Insert `n` line breaks to the terminal screen
- fn append_lines(&mut self, n: u16) -> io::Result<()> {
- // to get around the unused warning
- let _n = n;
- Ok(())
- }
-
- fn hide_cursor(&mut self) -> Result<(), io::Error>;
- fn show_cursor(&mut self) -> Result<(), io::Error>;
- fn get_cursor(&mut self) -> Result<(u16, u16), io::Error>;
- fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error>;
-
- /// Clears the whole terminal screen
- fn clear(&mut self) -> Result<(), io::Error>;
-
- /// Clears a specific region of the terminal specified by the [`ClearType`] parameter
- fn clear_region(&mut self, clear_type: ClearType) -> Result<(), io::Error> {
- match clear_type {
- ClearType::All => self.clear(),
- ClearType::AfterCursor
- | ClearType::BeforeCursor
- | ClearType::CurrentLine
- | ClearType::UntilNewLine => Err(io::Error::new(
- io::ErrorKind::Other,
- format!("clear_type [{clear_type:?}] not supported with this backend"),
- )),
- }
- }
- fn size(&self) -> Result;
- fn flush(&mut self) -> Result<(), io::Error>;
-}
diff --git a/atuin/src/ratatui/backend/termion.rs b/atuin/src/ratatui/backend/termion.rs
deleted file mode 100644
index 76def792..00000000
--- a/atuin/src/ratatui/backend/termion.rs
+++ /dev/null
@@ -1,275 +0,0 @@
-use crate::{
- backend::{Backend, ClearType},
- buffer::Cell,
- layout::Rect,
- style::{Color, Modifier},
-};
-use std::{
- fmt,
- io::{self, Write},
-};
-
-pub struct TermionBackend
-where
- W: Write,
-{
- stdout: W,
-}
-
-impl TermionBackend
-where
- W: Write,
-{
- pub fn new(stdout: W) -> TermionBackend {
- TermionBackend { stdout }
- }
-}
-
-impl Write for TermionBackend
-where
- W: Write,
-{
- fn write(&mut self, buf: &[u8]) -> io::Result {
- self.stdout.write(buf)
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.stdout.flush()
- }
-}
-
-impl Backend for TermionBackend
-where
- W: Write,
-{
- fn clear(&mut self) -> io::Result<()> {
- self.clear_region(ClearType::All)
- }
-
- fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
- match clear_type {
- ClearType::All => write!(self.stdout, "{}", termion::clear::All)?,
- ClearType::AfterCursor => write!(self.stdout, "{}", termion::clear::AfterCursor)?,
- ClearType::BeforeCursor => write!(self.stdout, "{}", termion::clear::BeforeCursor)?,
- ClearType::CurrentLine => write!(self.stdout, "{}", termion::clear::CurrentLine)?,
- ClearType::UntilNewLine => write!(self.stdout, "{}", termion::clear::UntilNewline)?,
- };
- self.stdout.flush()
- }
-
- fn append_lines(&mut self, n: u16) -> io::Result<()> {
- for _ in 0..n {
- writeln!(self.stdout)?;
- }
- self.stdout.flush()
- }
-
- /// Hides cursor
- fn hide_cursor(&mut self) -> io::Result<()> {
- write!(self.stdout, "{}", termion::cursor::Hide)?;
- self.stdout.flush()
- }
-
- /// Shows cursor
- fn show_cursor(&mut self) -> io::Result<()> {
- write!(self.stdout, "{}", termion::cursor::Show)?;
- self.stdout.flush()
- }
-
- /// Gets cursor position (0-based index)
- fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
- termion::cursor::DetectCursorPos::cursor_pos(&mut self.stdout).map(|(x, y)| (x - 1, y - 1))
- }
-
- /// Sets cursor position (0-based index)
- fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
- write!(self.stdout, "{}", termion::cursor::Goto(x + 1, y + 1))?;
- self.stdout.flush()
- }
-
- fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
- where
- I: Iterator
- ,
- {
- use std::fmt::Write;
-
- let mut string = String::with_capacity(content.size_hint().0 * 3);
- let mut fg = Color::Reset;
- let mut bg = Color::Reset;
- let mut modifier = Modifier::empty();
- let mut last_pos: Option<(u16, u16)> = None;
- for (x, y, cell) in content {
- // Move the cursor if the previous location was not (x - 1, y)
- if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
- write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap();
- }
- last_pos = Some((x, y));
- if cell.modifier != modifier {
- write!(
- string,
- "{}",
- ModifierDiff {
- from: modifier,
- to: cell.modifier
- }
- )
- .unwrap();
- modifier = cell.modifier;
- }
- if cell.fg != fg {
- write!(string, "{}", Fg(cell.fg)).unwrap();
- fg = cell.fg;
- }
- if cell.bg != bg {
- write!(string, "{}", Bg(cell.bg)).unwrap();
- bg = cell.bg;
- }
- string.push_str(&cell.symbol);
- }
- write!(
- self.stdout,
- "{}{}{}{}",
- string,
- Fg(Color::Reset),
- Bg(Color::Reset),
- termion::style::Reset,
- )
- }
-
- /// Return the size of the terminal
- fn size(&self) -> io::Result {
- let terminal = termion::terminal_size()?;
- Ok(Rect::new(0, 0, terminal.0, terminal.1))
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.stdout.flush()
- }
-}
-
-struct Fg(Color);
-
-struct Bg(Color);
-
-struct ModifierDiff {
- from: Modifier,
- to: Modifier,
-}
-
-impl fmt::Display for Fg {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use termion::color::Color as TermionColor;
- match self.0 {
- Color::Reset => termion::color::Reset.write_fg(f),
- Color::Black => termion::color::Black.write_fg(f),
- Color::Red => termion::color::Red.write_fg(f),
- Color::Green => termion::color::Green.write_fg(f),
- Color::Yellow => termion::color::Yellow.write_fg(f),
- Color::Blue => termion::color::Blue.write_fg(f),
- Color::Magenta => termion::color::Magenta.write_fg(f),
- Color::Cyan => termion::color::Cyan.write_fg(f),
- Color::Gray => termion::color::White.write_fg(f),
- Color::DarkGray => termion::color::LightBlack.write_fg(f),
- Color::LightRed => termion::color::LightRed.write_fg(f),
- Color::LightGreen => termion::color::LightGreen.write_fg(f),
- Color::LightBlue => termion::color::LightBlue.write_fg(f),
- Color::LightYellow => termion::color::LightYellow.write_fg(f),
- Color::LightMagenta => termion::color::LightMagenta.write_fg(f),
- Color::LightCyan => termion::color::LightCyan.write_fg(f),
- Color::White => termion::color::LightWhite.write_fg(f),
- Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f),
- Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f),
- }
- }
-}
-impl fmt::Display for Bg {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use termion::color::Color as TermionColor;
- match self.0 {
- Color::Reset => termion::color::Reset.write_bg(f),
- Color::Black => termion::color::Black.write_bg(f),
- Color::Red => termion::color::Red.write_bg(f),
- Color::Green => termion::color::Green.write_bg(f),
- Color::Yellow => termion::color::Yellow.write_bg(f),
- Color::Blue => termion::color::Blue.write_bg(f),
- Color::Magenta => termion::color::Magenta.write_bg(f),
- Color::Cyan => termion::color::Cyan.write_bg(f),
- Color::Gray => termion::color::White.write_bg(f),
- Color::DarkGray => termion::color::LightBlack.write_bg(f),
- Color::LightRed => termion::color::LightRed.write_bg(f),
- Color::LightGreen => termion::color::LightGreen.write_bg(f),
- Color::LightBlue => termion::color::LightBlue.write_bg(f),
- Color::LightYellow => termion::color::LightYellow.write_bg(f),
- Color::LightMagenta => termion::color::LightMagenta.write_bg(f),
- Color::LightCyan => termion::color::LightCyan.write_bg(f),
- Color::White => termion::color::LightWhite.write_bg(f),
- Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f),
- Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f),
- }
- }
-}
-
-impl fmt::Display for ModifierDiff {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let remove = self.from - self.to;
- if remove.contains(Modifier::REVERSED) {
- write!(f, "{}", termion::style::NoInvert)?;
- }
- if remove.contains(Modifier::BOLD) {
- // XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant
- // terminals, and NoFaint additionally disables bold... so we use this trick to get
- // the right semantics.
- write!(f, "{}", termion::style::NoFaint)?;
-
- if self.to.contains(Modifier::DIM) {
- write!(f, "{}", termion::style::Faint)?;
- }
- }
- if remove.contains(Modifier::ITALIC) {
- write!(f, "{}", termion::style::NoItalic)?;
- }
- if remove.contains(Modifier::UNDERLINED) {
- write!(f, "{}", termion::style::NoUnderline)?;
- }
- if remove.contains(Modifier::DIM) {
- write!(f, "{}", termion::style::NoFaint)?;
-
- // XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it
- // here if we want it.
- if self.to.contains(Modifier::BOLD) {
- write!(f, "{}", termion::style::Bold)?;
- }
- }
- if remove.contains(Modifier::CROSSED_OUT) {
- write!(f, "{}", termion::style::NoCrossedOut)?;
- }
- if remove.contains(Modifier::SLOW_BLINK) || remove.contains(Modifier::RAPID_BLINK) {
- write!(f, "{}", termion::style::NoBlink)?;
- }
-
- let add = self.to - self.from;
- if add.contains(Modifier::REVERSED) {
- write!(f, "{}", termion::style::Invert)?;
- }
- if add.contains(Modifier::BOLD) {
- write!(f, "{}", termion::style::Bold)?;
- }
- if add.contains(Modifier::ITALIC) {
- write!(f, "{}", termion::style::Italic)?;
- }
- if add.contains(Modifier::UNDERLINED) {
- write!(f, "{}", termion::style::Underline)?;
- }
- if add.contains(Modifier::DIM) {
- write!(f, "{}", termion::style::Faint)?;
- }
- if add.contains(Modifier::CROSSED_OUT) {
- write!(f, "{}", termion::style::CrossedOut)?;
- }
- if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) {
- write!(f, "{}", termion::style::Blink)?;
- }
-
- Ok(())
- }
-}
diff --git a/atuin/src/ratatui/buffer.rs b/atuin/src/ratatui/buffer.rs
deleted file mode 100644
index b2a988b7..00000000
--- a/atuin/src/ratatui/buffer.rs
+++ /dev/null
@@ -1,736 +0,0 @@
-use crate::ratatui::{
- layout::Rect,
- style::{Color, Modifier, Style},
- text::{Span, Spans},
-};
-use std::cmp::min;
-use unicode_segmentation::UnicodeSegmentation;
-use unicode_width::UnicodeWidthStr;
-
-/// A buffer cell
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Cell {
- pub symbol: String,
- pub fg: Color,
- pub bg: Color,
- pub modifier: Modifier,
-}
-
-impl Cell {
- pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
- self.symbol.clear();
- self.symbol.push_str(symbol);
- self
- }
-
- pub fn set_char(&mut self, ch: char) -> &mut Cell {
- self.symbol.clear();
- self.symbol.push(ch);
- self
- }
-
- pub fn set_fg(&mut self, color: Color) -> &mut Cell {
- self.fg = color;
- self
- }
-
- pub fn set_bg(&mut self, color: Color) -> &mut Cell {
- self.bg = color;
- self
- }
-
- pub fn set_style(&mut self, style: Style) -> &mut Cell {
- if let Some(c) = style.fg {
- self.fg = c;
- }
- if let Some(c) = style.bg {
- self.bg = c;
- }
- self.modifier.insert(style.add_modifier);
- self.modifier.remove(style.sub_modifier);
- self
- }
-
- pub fn style(&self) -> Style {
- Style::default()
- .fg(self.fg)
- .bg(self.bg)
- .add_modifier(self.modifier)
- }
-
- pub fn reset(&mut self) {
- self.symbol.clear();
- self.symbol.push(' ');
- self.fg = Color::Reset;
- self.bg = Color::Reset;
- self.modifier = Modifier::empty();
- }
-}
-
-impl Default for Cell {
- fn default() -> Cell {
- Cell {
- symbol: " ".into(),
- fg: Color::Reset,
- bg: Color::Reset,
- modifier: Modifier::empty(),
- }
- }
-}
-
-/// A buffer that maps to the desired content of the terminal after the draw call
-///
-/// No widget in the library interacts directly with the terminal. Instead each of them is required
-/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains
-/// a grapheme, a foreground color and a background color. This grid will then be used to output
-/// the appropriate escape sequences and characters to draw the UI as the user has defined it.
-///
-/// # Examples:
-///
-/// ```
-/// use ratatui::buffer::{Buffer, Cell};
-/// use ratatui::layout::Rect;
-/// use ratatui::style::{Color, Style, Modifier};
-///
-/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
-/// buf.get_mut(0, 2).set_symbol("x");
-/// assert_eq!(buf.get(0, 2).symbol, "x");
-/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
-/// assert_eq!(buf.get(5, 0), &Cell{
-/// symbol: String::from("r"),
-/// fg: Color::Red,
-/// bg: Color::White,
-/// modifier: Modifier::empty()
-/// });
-/// buf.get_mut(5, 0).set_char('x');
-/// assert_eq!(buf.get(5, 0).symbol, "x");
-/// ```
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
-pub struct Buffer {
- /// The area represented by this buffer
- pub area: Rect,
- /// The content of the buffer. The length of this Vec should always be equal to area.width *
- /// area.height
- pub content: Vec
,
-}
-
-impl Buffer {
- /// Returns a Buffer with all cells set to the default one
- pub fn empty(area: Rect) -> Buffer {
- let cell: Cell = Default::default();
- Buffer::filled(area, &cell)
- }
-
- /// Returns a Buffer with all cells initialized with the attributes of the given Cell
- pub fn filled(area: Rect, cell: &Cell) -> Buffer {
- let size = area.area() as usize;
- let mut content = Vec::with_capacity(size);
- for _ in 0..size {
- content.push(cell.clone());
- }
- Buffer { area, content }
- }
-
- /// Returns a Buffer containing the given lines
- pub fn with_lines(lines: Vec) -> Buffer
- where
- S: AsRef,
- {
- let height = lines.len() as u16;
- let width = lines
- .iter()
- .map(|i| i.as_ref().width() as u16)
- .max()
- .unwrap_or_default();
- let mut buffer = Buffer::empty(Rect {
- x: 0,
- y: 0,
- width,
- height,
- });
- for (y, line) in lines.iter().enumerate() {
- buffer.set_string(0, y as u16, line, Style::default());
- }
- buffer
- }
-
- /// Returns the content of the buffer as a slice
- pub fn content(&self) -> &[Cell] {
- &self.content
- }
-
- /// Returns the area covered by this buffer
- pub fn area(&self) -> &Rect {
- &self.area
- }
-
- /// Returns a reference to Cell at the given coordinates
- pub fn get(&self, x: u16, y: u16) -> &Cell {
- let i = self.index_of(x, y);
- &self.content[i]
- }
-
- /// Returns a mutable reference to Cell at the given coordinates
- pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
- let i = self.index_of(x, y);
- &mut self.content[i]
- }
-
- /// Returns the index in the `Vec` for the given global (x, y) coordinates.
- ///
- /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
- ///
- /// # Examples
- ///
- /// ```
- /// # use ratatui::buffer::Buffer;
- /// # use ratatui::layout::Rect;
- /// let rect = Rect::new(200, 100, 10, 10);
- /// let buffer = Buffer::empty(rect);
- /// // Global coordinates to the top corner of this buffer's area
- /// assert_eq!(buffer.index_of(200, 100), 0);
- /// ```
- ///
- /// # Panics
- ///
- /// Panics when given an coordinate that is outside of this Buffer's area.
- ///
- /// ```should_panic
- /// # use ratatui::buffer::Buffer;
- /// # use ratatui::layout::Rect;
- /// let rect = Rect::new(200, 100, 10, 10);
- /// let buffer = Buffer::empty(rect);
- /// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
- /// // starts at (200, 100).
- /// buffer.index_of(0, 0); // Panics
- /// ```
- pub fn index_of(&self, x: u16, y: u16) -> usize {
- debug_assert!(
- x >= self.area.left()
- && x < self.area.right()
- && y >= self.area.top()
- && y < self.area.bottom(),
- "Trying to access position outside the buffer: x={}, y={}, area={:?}",
- x,
- y,
- self.area
- );
- ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
- }
-
- /// Returns the (global) coordinates of a cell given its index
- ///
- /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
- ///
- /// # Examples
- ///
- /// ```
- /// # use ratatui::buffer::Buffer;
- /// # use ratatui::layout::Rect;
- /// let rect = Rect::new(200, 100, 10, 10);
- /// let buffer = Buffer::empty(rect);
- /// assert_eq!(buffer.pos_of(0), (200, 100));
- /// assert_eq!(buffer.pos_of(14), (204, 101));
- /// ```
- ///
- /// # Panics
- ///
- /// Panics when given an index that is outside the Buffer's content.
- ///
- /// ```should_panic
- /// # use ratatui::buffer::Buffer;
- /// # use ratatui::layout::Rect;
- /// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
- /// let buffer = Buffer::empty(rect);
- /// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
- /// buffer.pos_of(100); // Panics
- /// ```
- pub fn pos_of(&self, i: usize) -> (u16, u16) {
- debug_assert!(
- i < self.content.len(),
- "Trying to get the coords of a cell outside the buffer: i={} len={}",
- i,
- self.content.len()
- );
- (
- self.area.x + i as u16 % self.area.width,
- self.area.y + i as u16 / self.area.width,
- )
- }
-
- /// Print a string, starting at the position (x, y)
- pub fn set_string(&mut self, x: u16, y: u16, string: S, style: Style)
- where
- S: AsRef,
- {
- self.set_stringn(x, y, string, usize::MAX, style);
- }
-
- /// Print at most the first n characters of a string if enough space is available
- /// until the end of the line
- pub fn set_stringn(
- &mut self,
- x: u16,
- y: u16,
- string: S,
- width: usize,
- style: Style,
- ) -> (u16, u16)
- where
- S: AsRef,
- {
- let mut index = self.index_of(x, y);
- let mut x_offset = x as usize;
- let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
- let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
- for s in graphemes {
- let width = s.width();
- if width == 0 {
- continue;
- }
- // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
- // change dimensions to usize or u32 and someone resizes the terminal to 1x2^32.
- if width > max_offset.saturating_sub(x_offset) {
- break;
- }
-
- self.content[index].set_symbol(s);
- self.content[index].set_style(style);
- // Reset following cells if multi-width (they would be hidden by the grapheme),
- for i in index + 1..index + width {
- self.content[i].reset();
- }
- index += width;
- x_offset += width;
- }
- (x_offset as u16, y)
- }
-
- pub fn set_spans(&mut self, x: u16, y: u16, spans: &Spans<'_>, width: u16) -> (u16, u16) {
- let mut remaining_width = width;
- let mut x = x;
- for span in &spans.0 {
- if remaining_width == 0 {
- break;
- }
- let pos = self.set_stringn(
- x,
- y,
- span.content.as_ref(),
- remaining_width as usize,
- span.style,
- );
- let w = pos.0.saturating_sub(x);
- x = pos.0;
- remaining_width = remaining_width.saturating_sub(w);
- }
- (x, y)
- }
-
- pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, width: u16) -> (u16, u16) {
- self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
- }
-
- #[deprecated(
- since = "0.10.0",
- note = "You should use styling capabilities of `Buffer::set_style`"
- )]
- pub fn set_background(&mut self, area: Rect, color: Color) {
- for y in area.top()..area.bottom() {
- for x in area.left()..area.right() {
- self.get_mut(x, y).set_bg(color);
- }
- }
- }
-
- pub fn set_style(&mut self, area: Rect, style: Style) {
- for y in area.top()..area.bottom() {
- for x in area.left()..area.right() {
- self.get_mut(x, y).set_style(style);
- }
- }
- }
-
- /// Resize the buffer so that the mapped area matches the given area and that the buffer
- /// length is equal to area.width * area.height
- pub fn resize(&mut self, area: Rect) {
- let length = area.area() as usize;
- if self.content.len() > length {
- self.content.truncate(length);
- } else {
- self.content.resize(length, Default::default());
- }
- self.area = area;
- }
-
- /// Reset all cells in the buffer
- pub fn reset(&mut self) {
- for c in &mut self.content {
- c.reset();
- }
- }
-
- /// Merge an other buffer into this one
- pub fn merge(&mut self, other: &Buffer) {
- let area = self.area.union(other.area);
- let cell: Cell = Default::default();
- self.content.resize(area.area() as usize, cell.clone());
-
- // Move original content to the appropriate space
- let size = self.area.area() as usize;
- for i in (0..size).rev() {
- let (x, y) = self.pos_of(i);
- // New index in content
- let k = ((y - area.y) * area.width + x - area.x) as usize;
- if i != k {
- self.content[k] = self.content[i].clone();
- self.content[i] = cell.clone();
- }
- }
-
- // Push content of the other buffer into this one (may erase previous
- // data)
- let size = other.area.area() as usize;
- for i in 0..size {
- let (x, y) = other.pos_of(i);
- // New index in content
- let k = ((y - area.y) * area.width + x - area.x) as usize;
- self.content[k] = other.content[i].clone();
- }
- self.area = area;
- }
-
- /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
- /// self to other.
- ///
- /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
- /// a non-blank cell.
- ///
- /// # Multi-width characters handling:
- ///
- /// ```text
- /// (Index:) `01`
- /// Prev: `コ`
- /// Next: `aa`
- /// Updates: `0: a, 1: a'
- /// ```
- ///
- /// ```text
- /// (Index:) `01`
- /// Prev: `a `
- /// Next: `コ`
- /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
- /// ```
- ///
- /// ```text
- /// (Index:) `012`
- /// Prev: `aaa`
- /// Next: `aコ`
- /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
- /// ```
- pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
- let previous_buffer = &self.content;
- let next_buffer = &other.content;
-
- let mut updates: Vec<(u16, u16, &Cell)> = vec![];
- // Cells invalidated by drawing/replacing preceding multi-width characters:
- let mut invalidated: usize = 0;
- // Cells from the current buffer to skip due to preceding multi-width characters taking their
- // place (the skipped cells should be blank anyway):
- let mut to_skip: usize = 0;
- for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
- if (current != previous || invalidated > 0) && to_skip == 0 {
- let (x, y) = self.pos_of(i);
- updates.push((x, y, &next_buffer[i]));
- }
-
- to_skip = current.symbol.width().saturating_sub(1);
-
- let affected_width = std::cmp::max(current.symbol.width(), previous.symbol.width());
- invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
- }
- updates
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- fn cell(s: &str) -> Cell {
- let mut cell = Cell::default();
- cell.set_symbol(s);
- cell
- }
-
- #[test]
- fn it_translates_to_and_from_coordinates() {
- let rect = Rect::new(200, 100, 50, 80);
- let buf = Buffer::empty(rect);
-
- // First cell is at the upper left corner.
- assert_eq!(buf.pos_of(0), (200, 100));
- assert_eq!(buf.index_of(200, 100), 0);
-
- // Last cell is in the lower right.
- assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
- assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
- }
-
- #[test]
- #[ignore]
- #[should_panic(expected = "outside the buffer")]
- fn pos_of_panics_on_out_of_bounds() {
- let rect = Rect::new(0, 0, 10, 10);
- let buf = Buffer::empty(rect);
-
- // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
- buf.pos_of(100);
- }
-
- #[test]
- #[ignore]
- #[should_panic(expected = "outside the buffer")]
- fn index_of_panics_on_out_of_bounds() {
- let rect = Rect::new(0, 0, 10, 10);
- let buf = Buffer::empty(rect);
-
- // width is 10; zero-indexed means that 10 would be the 11th cell.
- buf.index_of(10, 0);
- }
-
- #[test]
- fn buffer_set_string() {
- let area = Rect::new(0, 0, 5, 1);
- let mut buffer = Buffer::empty(area);
-
- // Zero-width
- buffer.set_stringn(0, 0, "aaa", 0, Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec![" "]));
-
- buffer.set_string(0, 0, "aaa", Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec!["aaa "]));
-
- // Width limit:
- buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
-
- buffer.set_string(0, 0, "12345", Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
-
- // Width truncation:
- buffer.set_string(0, 0, "123456", Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
- }
-
- #[test]
- fn buffer_set_string_zero_width() {
- let area = Rect::new(0, 0, 1, 1);
- let mut buffer = Buffer::empty(area);
-
- // Leading grapheme with zero width
- let s = "\u{1}a";
- buffer.set_stringn(0, 0, s, 1, Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
-
- // Trailing grapheme with zero with
- let s = "a\u{1}";
- buffer.set_stringn(0, 0, s, 1, Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
- }
-
- #[test]
- fn buffer_set_string_double_width() {
- let area = Rect::new(0, 0, 5, 1);
- let mut buffer = Buffer::empty(area);
- buffer.set_string(0, 0, "コン", Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
-
- // Only 1 space left.
- buffer.set_string(0, 0, "コンピ", Style::default());
- assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
- }
-
- #[test]
- fn buffer_with_lines() {
- let buffer =
- Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
- assert_eq!(buffer.area.x, 0);
- assert_eq!(buffer.area.y, 0);
- assert_eq!(buffer.area.width, 10);
- assert_eq!(buffer.area.height, 4);
- }
-
- #[test]
- fn buffer_diffing_empty_empty() {
- let area = Rect::new(0, 0, 40, 40);
- let prev = Buffer::empty(area);
- let next = Buffer::empty(area);
- let diff = prev.diff(&next);
- assert_eq!(diff, vec![]);
- }
-
- #[test]
- fn buffer_diffing_empty_filled() {
- let area = Rect::new(0, 0, 40, 40);
- let prev = Buffer::empty(area);
- let next = Buffer::filled(area, Cell::default().set_symbol("a"));
- let diff = prev.diff(&next);
- assert_eq!(diff.len(), 40 * 40);
- }
-
- #[test]
- fn buffer_diffing_filled_filled() {
- let area = Rect::new(0, 0, 40, 40);
- let prev = Buffer::filled(area, Cell::default().set_symbol("a"));
- let next = Buffer::filled(area, Cell::default().set_symbol("a"));
- let diff = prev.diff(&next);
- assert_eq!(diff, vec![]);
- }
-
- #[test]
- fn buffer_diffing_single_width() {
- let prev = Buffer::with_lines(vec![
- " ",
- "┌Title─┐ ",
- "│ │ ",
- "│ │ ",
- "└──────┘ ",
- ]);
- let next = Buffer::with_lines(vec![
- " ",
- "┌TITLE─┐ ",
- "│ │ ",
- "│ │ ",
- "└──────┘ ",
- ]);
- let diff = prev.diff(&next);
- assert_eq!(
- diff,
- vec![
- (2, 1, &cell("I")),
- (3, 1, &cell("T")),
- (4, 1, &cell("L")),
- (5, 1, &cell("E")),
- ]
- );
- }
-
- #[test]
- #[rustfmt::skip]
- fn buffer_diffing_multi_width() {
- let prev = Buffer::with_lines(vec![
- "┌Title─┐ ",
- "└──────┘ ",
- ]);
- let next = Buffer::with_lines(vec![
- "┌称号──┐ ",
- "└──────┘ ",
- ]);
- let diff = prev.diff(&next);
- assert_eq!(
- diff,
- vec![
- (1, 0, &cell("称")),
- // Skipped "i"
- (3, 0, &cell("号")),
- // Skipped "l"
- (5, 0, &cell("─")),
- ]
- );
- }
-
- #[test]
- fn buffer_diffing_multi_width_offset() {
- let prev = Buffer::with_lines(vec!["┌称号──┐"]);
- let next = Buffer::with_lines(vec!["┌─称号─┐"]);
-
- let diff = prev.diff(&next);
- assert_eq!(
- diff,
- vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),]
- );
- }
-
- #[test]
- fn buffer_merge() {
- let mut one = Buffer::filled(
- Rect {
- x: 0,
- y: 0,
- width: 2,
- height: 2,
- },
- Cell::default().set_symbol("1"),
- );
- let two = Buffer::filled(
- Rect {
- x: 0,
- y: 2,
- width: 2,
- height: 2,
- },
- Cell::default().set_symbol("2"),
- );
- one.merge(&two);
- assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
- }
-
- #[test]
- fn buffer_merge2() {
- let mut one = Buffer::filled(
- Rect {
- x: 2,
- y: 2,
- width: 2,
- height: 2,
- },
- Cell::default().set_symbol("1"),
- );
- let two = Buffer::filled(
- Rect {
- x: 0,
- y: 0,
- width: 2,
- height: 2,
- },
- Cell::default().set_symbol("2"),
- );
- one.merge(&two);
- assert_eq!(
- one,
- Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"])
- );
- }
-
- #[test]
- fn buffer_merge3() {
- let mut one = Buffer::filled(
- Rect {
- x: 3,
- y: 3,
- width: 2,
- height: 2,
- },
- Cell::default().set_symbol("1"),
- );
- let two = Buffer::filled(
- Rect {
- x: 1,
- y: 1,
- width: 3,
- height: 4,
- },
- Cell::default().set_symbol("2"),
- );
- one.merge(&two);
- let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
- merged.area = Rect {
- x: 1,
- y: 1,
- width: 4,
- height: 4,
- };
- assert_eq!(one, merged);
- }
-}
diff --git a/atuin/src/ratatui/layout.rs b/atuin/src/ratatui/layout.rs
deleted file mode 100644
index f5b14e35..00000000
--- a/atuin/src/ratatui/layout.rs
+++ /dev/null
@@ -1,560 +0,0 @@
-use std::cell::RefCell;
-use std::cmp::{max, min};
-use std::collections::HashMap;
-use std::rc::Rc;
-
-use cassowary::strength::{MEDIUM, REQUIRED, WEAK};
-use cassowary::WeightedRelation::*;
-use cassowary::{Constraint as CassowaryConstraint, Expression, Solver, Variable};
-
-#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
-pub enum Corner {
- TopLeft,
- TopRight,
- BottomRight,
- BottomLeft,
-}
-
-#[derive(Debug, Hash, Clone, PartialEq, Eq)]
-pub enum Direction {
- Horizontal,
- Vertical,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum Constraint {
- // TODO: enforce range 0 - 100
- Percentage(u16),
- Ratio(u32, u32),
- Length(u16),
- Max(u16),
- Min(u16),
-}
-
-impl Constraint {
- pub fn apply(&self, length: u16) -> u16 {
- match *self {
- Constraint::Percentage(p) => length * p / 100,
- Constraint::Ratio(num, den) => {
- let r = num * u32::from(length) / den;
- r as u16
- }
- Constraint::Length(l) => length.min(l),
- Constraint::Max(m) => length.min(m),
- Constraint::Min(m) => length.max(m),
- }
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct Margin {
- pub vertical: u16,
- pub horizontal: u16,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Alignment {
- Left,
- Center,
- Right,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct Layout {
- direction: Direction,
- margin: Margin,
- constraints: Vec,
- /// Whether the last chunk of the computed layout should be expanded to fill the available
- /// space.
- expand_to_fill: bool,
-}
-
-type Cache = HashMap<(Rect, Layout), Rc<[Rect]>>;
-thread_local! {
- static LAYOUT_CACHE: RefCell = RefCell::new(HashMap::new());
-}
-
-impl Default for Layout {
- fn default() -> Layout {
- Layout {
- direction: Direction::Vertical,
- margin: Margin {
- horizontal: 0,
- vertical: 0,
- },
- constraints: Vec::new(),
- expand_to_fill: true,
- }
- }
-}
-
-impl Layout {
- pub fn constraints(mut self, constraints: C) -> Layout
- where
- C: Into>,
- {
- self.constraints = constraints.into();
- self
- }
-
- pub fn margin(mut self, margin: u16) -> Layout {
- self.margin = Margin {
- horizontal: margin,
- vertical: margin,
- };
- self
- }
-
- pub fn horizontal_margin(mut self, horizontal: u16) -> Layout {
- self.margin.horizontal = horizontal;
- self
- }
-
- pub fn vertical_margin(mut self, vertical: u16) -> Layout {
- self.margin.vertical = vertical;
- self
- }
-
- pub fn direction(mut self, direction: Direction) -> Layout {
- self.direction = direction;
- self
- }
-
- pub(crate) fn expand_to_fill(mut self, expand_to_fill: bool) -> Layout {
- self.expand_to_fill = expand_to_fill;
- self
- }
-
- /// Wrapper function around the cassowary-rs solver to be able to split a given
- /// area into smaller ones based on the preferred widths or heights and the direction.
- ///
- /// # Examples
- /// ```
- /// # use ratatui::layout::{Rect, Constraint, Direction, Layout};
- /// let chunks = Layout::default()
- /// .direction(Direction::Vertical)
- /// .constraints([Constraint::Length(5), Constraint::Min(0)].as_ref())
- /// .split(Rect {
- /// x: 2,
- /// y: 2,
- /// width: 10,
- /// height: 10,
- /// });
- /// assert_eq!(
- /// chunks[..],
- /// [
- /// Rect {
- /// x: 2,
- /// y: 2,
- /// width: 10,
- /// height: 5
- /// },
- /// Rect {
- /// x: 2,
- /// y: 7,
- /// width: 10,
- /// height: 5
- /// }
- /// ]
- /// );
- ///
- /// let chunks = Layout::default()
- /// .direction(Direction::Horizontal)
- /// .constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)].as_ref())
- /// .split(Rect {
- /// x: 0,
- /// y: 0,
- /// width: 9,
- /// height: 2,
- /// });
- /// assert_eq!(
- /// chunks[..],
- /// [
- /// Rect {
- /// x: 0,
- /// y: 0,
- /// width: 3,
- /// height: 2
- /// },
- /// Rect {
- /// x: 3,
- /// y: 0,
- /// width: 6,
- /// height: 2
- /// }
- /// ]
- /// );
- /// ```
- pub fn split(&self, area: Rect) -> Rc<[Rect]> {
- // TODO: Maybe use a fixed size cache ?
- LAYOUT_CACHE.with(|c| {
- c.borrow_mut()
- .entry((area, self.clone()))
- .or_insert_with(|| split(area, self))
- .clone()
- })
- }
-}
-
-fn split(area: Rect, layout: &Layout) -> Rc<[Rect]> {
- let mut solver = Solver::new();
- let mut vars: HashMap = HashMap::new();
- let elements = layout
- .constraints
- .iter()
- .map(|_| Element::new())
- .collect::>();
- let mut res = layout
- .constraints
- .iter()
- .map(|_| Rect::default())
- .collect::>();
-
- let mut results = Rc::get_mut(&mut res).expect("newly created Rc should have no shared refs");
-
- let dest_area = area.inner(&layout.margin);
- for (i, e) in elements.iter().enumerate() {
- vars.insert(e.x, (i, 0));
- vars.insert(e.y, (i, 1));
- vars.insert(e.width, (i, 2));
- vars.insert(e.height, (i, 3));
- }
- let mut ccs: Vec =
- Vec::with_capacity(elements.len() * 4 + layout.constraints.len() * 6);
- for elt in &elements {
- ccs.push(elt.width | GE(REQUIRED) | 0f64);
- ccs.push(elt.height | GE(REQUIRED) | 0f64);
- ccs.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
- ccs.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
- ccs.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
- ccs.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
- }
- if let Some(first) = elements.first() {
- ccs.push(match layout.direction {
- Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
- Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
- });
- }
- if layout.expand_to_fill {
- if let Some(last) = elements.last() {
- ccs.push(match layout.direction {
- Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
- Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
- });
- }
- }
- match layout.direction {
- Direction::Horizontal => {
- for pair in elements.windows(2) {
- ccs.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
- }
- for (i, size) in layout.constraints.iter().enumerate() {
- ccs.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
- ccs.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
- ccs.push(match *size {
- Constraint::Length(v) => elements[i].width | EQ(MEDIUM) | f64::from(v),
- Constraint::Percentage(v) => {
- elements[i].width | EQ(MEDIUM) | (f64::from(v * dest_area.width) / 100.0)
- }
- Constraint::Ratio(n, d) => {
- elements[i].width
- | EQ(MEDIUM)
- | (f64::from(dest_area.width) * f64::from(n) / f64::from(d))
- }
- Constraint::Min(v) => elements[i].width | GE(MEDIUM) | f64::from(v),
- Constraint::Max(v) => elements[i].width | LE(MEDIUM) | f64::from(v),
- });
-
- match *size {
- Constraint::Min(v) => {
- ccs.push(elements[i].width | EQ(WEAK) | f64::from(v));
- }
- Constraint::Max(v) => {
- ccs.push(elements[i].width | EQ(WEAK) | f64::from(v));
- }
- _ => {}
- }
- }
- }
- Direction::Vertical => {
- for pair in elements.windows(2) {
- ccs.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
- }
- for (i, size) in layout.constraints.iter().enumerate() {
- ccs.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
- ccs.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
- ccs.push(match *size {
- Constraint::Length(v) => elements[i].height | EQ(MEDIUM) | f64::from(v),
- Constraint::Percentage(v) => {
- elements[i].height | EQ(MEDIUM) | (f64::from(v * dest_area.height) / 100.0)
- }
- Constraint::Ratio(n, d) => {
- elements[i].height
- | EQ(MEDIUM)
- | (f64::from(dest_area.height) * f64::from(n) / f64::from(d))
- }
- Constraint::Min(v) => elements[i].height | GE(MEDIUM) | f64::from(v),
- Constraint::Max(v) => elements[i].height | LE(MEDIUM) | f64::from(v),
- });
-
- match *size {
- Constraint::Min(v) => {
- ccs.push(elements[i].height | EQ(WEAK) | f64::from(v));
- }
- Constraint::Max(v) => {
- ccs.push(elements[i].height | EQ(WEAK) | f64::from(v));
- }
- _ => {}
- }
- }
- }
- }
- solver.add_constraints(&ccs).unwrap();
- for &(var, value) in solver.fetch_changes() {
- let (index, attr) = vars[&var];
- let value = if value.is_sign_negative() {
- 0
- } else {
- value as u16
- };
- match attr {
- 0 => {
- results[index].x = value;
- }
- 1 => {
- results[index].y = value;
- }
- 2 => {
- results[index].width = value;
- }
- 3 => {
- results[index].height = value;
- }
- _ => {}
- }
- }
-
- if layout.expand_to_fill {
- // Fix imprecision by extending the last item a bit if necessary
- if let Some(last) = results.last_mut() {
- match layout.direction {
- Direction::Vertical => {
- last.height = dest_area.bottom() - last.y;
- }
- Direction::Horizontal => {
- last.width = dest_area.right() - last.x;
- }
- }
- }
- }
- res
-}
-
-/// A container used by the solver inside split
-struct Element {
- x: Variable,
- y: Variable,
- width: Variable,
- height: Variable,
-}
-
-impl Element {
- fn new() -> Element {
- Element {
- x: Variable::new(),
- y: Variable::new(),
- width: Variable::new(),
- height: Variable::new(),
- }
- }
-
- fn left(&self) -> Variable {
- self.x
- }
-
- fn top(&self) -> Variable {
- self.y
- }
-
- fn right(&self) -> Expression {
- self.x + self.width
- }
-
- fn bottom(&self) -> Expression {
- self.y + self.height
- }
-}
-
-/// A simple rectangle used in the computation of the layout and to give widgets a hint about the
-/// area they are supposed to render to.
-#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
-pub struct Rect {
- pub x: u16,
- pub y: u16,
- pub width: u16,
- pub height: u16,
-}
-
-impl Rect {
- /// Creates a new rect, with width and height limited to keep the area under max u16.
- /// If clipped, aspect ratio will be preserved.
- pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
- let max_area = u16::max_value();
- let (clipped_width, clipped_height) =
- if u32::from(width) * u32::from(height) > u32::from(max_area) {
- let aspect_ratio = f64::from(width) / f64::from(height);
- let max_area_f = f64::from(max_area);
- let height_f = (max_area_f / aspect_ratio).sqrt();
- let width_f = height_f * aspect_ratio;
- (width_f as u16, height_f as u16)
- } else {
- (width, height)
- };
- Rect {
- x,
- y,
- width: clipped_width,
- height: clipped_height,
- }
- }
-
- pub fn area(self) -> u16 {
- self.width * self.height
- }
-
- pub fn left(self) -> u16 {
- self.x
- }
-
- pub fn right(self) -> u16 {
- self.x.saturating_add(self.width)
- }
-
- pub fn top(self) -> u16 {
- self.y
- }
-
- pub fn bottom(self) -> u16 {
- self.y.saturating_add(self.height)
- }
-
- pub fn inner(self, margin: &Margin) -> Rect {
- if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
- Rect::default()
- } else {
- Rect {
- x: self.x + margin.horizontal,
- y: self.y + margin.vertical,
- width: self.width - 2 * margin.horizontal,
- height: self.height - 2 * margin.vertical,
- }
- }
- }
-
- pub fn union(self, other: Rect) -> Rect {
- let x1 = min(self.x, other.x);
- let y1 = min(self.y, other.y);
- let x2 = max(self.x + self.width, other.x + other.width);
- let y2 = max(self.y + self.height, other.y + other.height);
- Rect {
- x: x1,
- y: y1,
- width: x2 - x1,
- height: y2 - y1,
- }
- }
-
- pub fn intersection(self, other: Rect) -> Rect {
- let x1 = max(self.x, other.x);
- let y1 = max(self.y, other.y);
- let x2 = min(self.x + self.width, other.x + other.width);
- let y2 = min(self.y + self.height, other.y + other.height);
- Rect {
- x: x1,
- y: y1,
- width: x2 - x1,
- height: y2 - y1,
- }
- }
-
- pub fn intersects(self, other: Rect) -> bool {
- self.x < other.x + other.width
- && self.x + self.width > other.x
- && self.y < other.y + other.height
- && self.y + self.height > other.y
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_vertical_split_by_height() {
- let target = Rect {
- x: 2,
- y: 2,
- width: 10,
- height: 10,
- };
-
- let chunks = Layout::default()
- .direction(Direction::Vertical)
- .constraints(
- [
- Constraint::Percentage(10),
- Constraint::Max(5),
- Constraint::Min(1),
- ]
- .as_ref(),
- )
- .split(target);
-
- assert_eq!(target.height, chunks.iter().map(|r| r.height).sum::());
- chunks.windows(2).for_each(|w| assert!(w[0].y <= w[1].y));
- }
-
- #[test]
- fn test_rect_size_truncation() {
- for width in 256u16..300u16 {
- for height in 256u16..300u16 {
- let rect = Rect::new(0, 0, width, height);
- rect.area(); // Should not panic.
- assert!(rect.width < width || rect.height < height);
- // The target dimensions are rounded down so the math will not be too precise
- // but let's make sure the ratios don't diverge crazily.
- assert!(
- (f64::from(rect.width) / f64::from(rect.height)
- - f64::from(width) / f64::from(height))
- .abs()
- < 1.0
- )
- }
- }
-
- // One dimension below 255, one above. Area above max u16.
- let width = 900;
- let height = 100;
- let rect = Rect::new(0, 0, width, height);
- assert_ne!(rect.width, 900);
- assert_ne!(rect.height, 100);
- assert!(rect.width < width || rect.height < height);
- }
-
- #[test]
- fn test_rect_size_preservation() {
- for width in 0..256u16 {
- for height in 0..256u16 {
- let rect = Rect::new(0, 0, width, height);
- rect.area(); // Should not panic.
- assert_eq!(rect.width, width);
- assert_eq!(rect.height, height);
- }
- }
-
- // One dimension below 255, one above. Area below max u16.
- let rect = Rect::new(0, 0, 300, 100);
- assert_eq!(rect.width, 300);
- assert_eq!(rect.height, 100);
- }
-}
diff --git a/atuin/src/ratatui/mod.rs b/atuin/src/ratatui/mod.rs
deleted file mode 100644
index d7926b96..00000000
--- a/atuin/src/ratatui/mod.rs
+++ /dev/null
@@ -1,177 +0,0 @@
-#![allow(clippy::all)]
-#![allow(warnings)]
-
-//! [ratatui](https://github.com/tui-rs-revival/ratatui) is a library used to build rich
-//! terminal users interfaces and dashboards.
-//!
-//! ![](https://raw.githubusercontent.com/tui-rs-revival/ratatui/master/assets/demo.gif)
-//!
-//! # Get started
-//!
-//! ## Adding `ratatui` as a dependency
-//!
-//! Add the following to your `Cargo.toml`:
-//! ```toml
-//! [dependencies]
-//! crossterm = "0.26"
-//! ratatui = "0.20"
-//! ```
-//!
-//! The crate is using the `crossterm` backend by default that works on most platforms. But if for
-//! example you want to use the `termion` backend instead. This can be done by changing your
-//! dependencies specification to the following:
-//!
-//! ```toml
-//! [dependencies]
-//! termion = "1.5"
-//! ratatui = { version = "0.20", default-features = false, features = ['termion'] }
-//!
-//! ```
-//!
-//! The same logic applies for all other available backends.
-//!
-//! ## Creating a `Terminal`
-//!
-//! Every application using `ratatui` should start by instantiating a `Terminal`. It is a light
-//! abstraction over available backends that provides basic functionalities such as clearing the
-//! screen, hiding the cursor, etc.
-//!
-//! ```rust,no_run
-//! use std::io;
-//! use ratatui::{backend::CrosstermBackend, Terminal};
-//!
-//! fn main() -> Result<(), io::Error> {
-//! let stdout = io::stdout();
-//! let backend = CrosstermBackend::new(stdout);
-//! let mut terminal = Terminal::new(backend)?;
-//! Ok(())
-//! }
-//! ```
-//!
-//! If you had previously chosen `termion` as a backend, the terminal can be created in a similar
-//! way:
-//!
-//! ```rust,ignore
-//! use std::io;
-//! use ratatui::{backend::TermionBackend, Terminal};
-//! use termion::raw::IntoRawMode;
-//!
-//! fn main() -> Result<(), io::Error> {
-//! let stdout = io::stdout().into_raw_mode()?;
-//! let backend = TermionBackend::new(stdout);
-//! let mut terminal = Terminal::new(backend)?;
-//! Ok(())
-//! }
-//! ```
-//!
-//! You may also refer to the examples to find out how to create a `Terminal` for each available
-//! backend.
-//!
-//! ## Building a User Interface (UI)
-//!
-//! Every component of your interface will be implementing the `Widget` trait. The library comes
-//! with a predefined set of widgets that should meet most of your use cases. You are also free to
-//! implement your own.
-//!
-//! Each widget follows a builder pattern API providing a default configuration along with methods
-//! to customize them. The widget is then rendered using [`Frame::render_widget`] which takes
-//! your widget instance and an area to draw to.
-//!
-//! The following example renders a block of the size of the terminal:
-//!
-//! ```rust,no_run
-//! use std::{io, thread, time::Duration};
-//! use ratatui::{
-//! backend::CrosstermBackend,
-//! widgets::{Widget, Block, Borders},
-//! layout::{Layout, Constraint, Direction},
-//! Terminal
-//! };
-//! use crossterm::{
-//! event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
-//! execute,
-//! terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
-//! };
-//!
-//! fn main() -> Result<(), io::Error> {
-//! // setup terminal
-//! enable_raw_mode()?;
-//! let mut stdout = io::stdout();
-//! execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
-//! let backend = CrosstermBackend::new(stdout);
-//! let mut terminal = Terminal::new(backend)?;
-//!
-//! terminal.draw(|f| {
-//! let size = f.size();
-//! let block = Block::default()
-//! .title("Block")
-//! .borders(Borders::ALL);
-//! f.render_widget(block, size);
-//! })?;
-//!
-//! thread::sleep(Duration::from_millis(5000));
-//!
-//! // restore terminal
-//! disable_raw_mode()?;
-//! execute!(
-//! terminal.backend_mut(),
-//! LeaveAlternateScreen,
-//! DisableMouseCapture
-//! )?;
-//! terminal.show_cursor()?;
-//!
-//! Ok(())
-//! }
-//! ```
-//!
-//! ## Layout
-//!
-//! The library comes with a basic yet useful layout management object called `Layout`. As you may
-//! see below and in the examples, the library makes heavy use of the builder pattern to provide
-//! full customization. And `Layout` is no exception:
-//!
-//! ```rust,no_run
-//! use ratatui::{
-//! backend::Backend,
-//! layout::{Constraint, Direction, Layout},
-//! widgets::{Block, Borders},
-//! Frame,
-//! };
-//! fn ui(f: &mut Frame) {
-//! let chunks = Layout::default()
-//! .direction(Direction::Vertical)
-//! .margin(1)
-//! .constraints(
-//! [
-//! Constraint::Percentage(10),
-//! Constraint::Percentage(80),
-//! Constraint::Percentage(10)
-//! ].as_ref()
-//! )
-//! .split(f.size());
-//! let block = Block::default()
-//! .title("Block")
-//! .borders(Borders::ALL);
-//! f.render_widget(block, chunks[0]);
-//! let block = Block::default()
-//! .title("Block 2")
-//! .borders(Borders::ALL);
-//! f.render_widget(block, chunks[1]);
-//! }
-//! ```
-//!
-//! This let you describe responsive terminal UI by nesting layouts. You should note that by
-//! default the computed layout tries to fill the available space completely. So if for any reason
-//! you might need a blank space somewhere, try to pass an additional constraint and don't use the
-//! corresponding area.
-
-pub mod backend;
-pub mod buffer;
-pub mod layout;
-pub mod style;
-pub mod symbols;
-pub mod terminal;
-pub mod text;
-pub mod widgets;
-
-pub use self::terminal::{Frame, Terminal, TerminalOptions, Viewport};
diff --git a/atuin/src/ratatui/style.rs b/atuin/src/ratatui/style.rs
deleted file mode 100644
index 4d74f6fc..00000000
--- a/atuin/src/ratatui/style.rs
+++ /dev/null
@@ -1,310 +0,0 @@
-//! `style` contains the primitives used to control how your user interface will look.
-
-use bitflags::bitflags;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub enum Color {
- Reset,
- Black,
- Red,
- Green,
- Yellow,
- Blue,
- Magenta,
- Cyan,
- Gray,
- DarkGray,
- LightRed,
- LightGreen,
- LightYellow,
- LightBlue,
- LightMagenta,
- LightCyan,
- White,
- Rgb(u8, u8, u8),
- Indexed(u8),
-}
-
-bitflags! {
- /// Modifier changes the way a piece of text is displayed.
- ///
- /// They are bitflags so they can easily be composed.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::style::Modifier;
- ///
- /// let m = Modifier::BOLD | Modifier::ITALIC;
- /// ```
- #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
- pub struct Modifier: u16 {
- const BOLD = 0b0000_0000_0001;
- const DIM = 0b0000_0000_0010;
- const ITALIC = 0b0000_0000_0100;
- const UNDERLINED = 0b0000_0000_1000;
- const SLOW_BLINK = 0b0000_0001_0000;
- const RAPID_BLINK = 0b0000_0010_0000;
- const REVERSED = 0b0000_0100_0000;
- const HIDDEN = 0b0000_1000_0000;
- const CROSSED_OUT = 0b0001_0000_0000;
- }
-}
-
-/// Style let you control the main characteristics of the displayed elements.
-///
-/// ```rust
-/// # use ratatui::style::{Color, Modifier, Style};
-/// Style::default()
-/// .fg(Color::Black)
-/// .bg(Color::Green)
-/// .add_modifier(Modifier::ITALIC | Modifier::BOLD);
-/// ```
-///
-/// It represents an incremental change. If you apply the styles S1, S2, S3 to a cell of the
-/// terminal buffer, the style of this cell will be the result of the merge of S1, S2 and S3, not
-/// just S3.
-///
-/// ```rust
-/// # use ratatui::style::{Color, Modifier, Style};
-/// # use ratatui::buffer::Buffer;
-/// # use ratatui::layout::Rect;
-/// let styles = [
-/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
-/// Style::default().bg(Color::Red),
-/// Style::default().fg(Color::Yellow).remove_modifier(Modifier::ITALIC),
-/// ];
-/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
-/// for style in &styles {
-/// buffer.get_mut(0, 0).set_style(*style);
-/// }
-/// assert_eq!(
-/// Style {
-/// fg: Some(Color::Yellow),
-/// bg: Some(Color::Red),
-/// add_modifier: Modifier::BOLD,
-/// sub_modifier: Modifier::empty(),
-/// },
-/// buffer.get(0, 0).style(),
-/// );
-/// ```
-///
-/// The default implementation returns a `Style` that does not modify anything. If you wish to
-/// reset all properties until that point use [`Style::reset`].
-///
-/// ```
-/// # use ratatui::style::{Color, Modifier, Style};
-/// # use ratatui::buffer::Buffer;
-/// # use ratatui::layout::Rect;
-/// let styles = [
-/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
-/// Style::reset().fg(Color::Yellow),
-/// ];
-/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
-/// for style in &styles {
-/// buffer.get_mut(0, 0).set_style(*style);
-/// }
-/// assert_eq!(
-/// Style {
-/// fg: Some(Color::Yellow),
-/// bg: Some(Color::Reset),
-/// add_modifier: Modifier::empty(),
-/// sub_modifier: Modifier::empty(),
-/// },
-/// buffer.get(0, 0).style(),
-/// );
-/// ```
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub struct Style {
- pub fg: Option,
- pub bg: Option,
- pub add_modifier: Modifier,
- pub sub_modifier: Modifier,
-}
-
-impl Default for Style {
- fn default() -> Style {
- Style {
- fg: None,
- bg: None,
- add_modifier: Modifier::empty(),
- sub_modifier: Modifier::empty(),
- }
- }
-}
-
-impl Style {
- /// Returns a `Style` resetting all properties.
- pub fn reset() -> Style {
- Style {
- fg: Some(Color::Reset),
- bg: Some(Color::Reset),
- add_modifier: Modifier::empty(),
- sub_modifier: Modifier::all(),
- }
- }
-
- /// Changes the foreground color.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::style::{Color, Style};
- /// let style = Style::default().fg(Color::Blue);
- /// let diff = Style::default().fg(Color::Red);
- /// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
- /// ```
- pub fn fg(mut self, color: Color) -> Style {
- self.fg = Some(color);
- self
- }
-
- /// Changes the background color.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::style::{Color, Style};
- /// let style = Style::default().bg(Color::Blue);
- /// let diff = Style::default().bg(Color::Red);
- /// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
- /// ```
- pub fn bg(mut self, color: Color) -> Style {
- self.bg = Some(color);
- self
- }
-
- /// Changes the text emphasis.
- ///
- /// When applied, it adds the given modifier to the `Style` modifiers.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::style::{Color, Modifier, Style};
- /// let style = Style::default().add_modifier(Modifier::BOLD);
- /// let diff = Style::default().add_modifier(Modifier::ITALIC);
- /// let patched = style.patch(diff);
- /// assert_eq!(patched.add_modifier, Modifier::BOLD | Modifier::ITALIC);
- /// assert_eq!(patched.sub_modifier, Modifier::empty());
- /// ```
- pub fn add_modifier(mut self, modifier: Modifier) -> Style {
- self.sub_modifier.remove(modifier);
- self.add_modifier.insert(modifier);
- self
- }
-
- /// Changes the text emphasis.
- ///
- /// When applied, it removes the given modifier from the `Style` modifiers.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::style::{Color, Modifier, Style};
- /// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
- /// let diff = Style::default().remove_modifier(Modifier::ITALIC);
- /// let patched = style.patch(diff);
- /// assert_eq!(patched.add_modifier, Modifier::BOLD);
- /// assert_eq!(patched.sub_modifier, Modifier::ITALIC);
- /// ```
- pub fn remove_modifier(mut self, modifier: Modifier) -> Style {
- self.add_modifier.remove(modifier);
- self.sub_modifier.insert(modifier);
- self
- }
-
- /// Results in a combined style that is equivalent to applying the two individual styles to
- /// a style one after the other.
- ///
- /// ## Examples
- /// ```
- /// # use ratatui::style::{Color, Modifier, Style};
- /// let style_1 = Style::default().fg(Color::Yellow);
- /// let style_2 = Style::default().bg(Color::Red);
- /// let combined = style_1.patch(style_2);
- /// assert_eq!(
- /// Style::default().patch(style_1).patch(style_2),
- /// Style::default().patch(combined));
- /// ```
- pub fn patch(mut self, other: Style) -> Style {
- self.fg = other.fg.or(self.fg);
- self.bg = other.bg.or(self.bg);
-
- self.add_modifier.remove(other.sub_modifier);
- self.add_modifier.insert(other.add_modifier);
- self.sub_modifier.remove(other.add_modifier);
- self.sub_modifier.insert(other.sub_modifier);
-
- self
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- fn styles() -> Vec | |