aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorVladislav Stepanov <8uk.8ak@gmail.com>2023-04-14 23:18:58 +0400
committerGitHub <noreply@github.com>2023-04-14 20:18:58 +0100
commitc05d2850420a2c163b8f62c33a6cef7c0ae1ad8d (patch)
tree2c44a44eda7e76fa74e78ac1fd02f55c1ed4d804 /src
parentSwitch to uuidv7 (#864) (diff)
downloadatuin-c05d2850420a2c163b8f62c33a6cef7c0ae1ad8d.zip
Workspace reorder (#868)
* Try different workspace structure Move main crate (atuin) to be on the same level with other crates in this workspace * extract common dependencies to the workspace definition * fix base64 v0.21 deprecation warning * questionable: update deps & fix chrono deprecations possible panic sites are unchanged, they're just more visible now * Revert "questionable: update deps & fix chrono deprecations" This reverts commit 993e60f8dea81a1625a04285a617959ad09a0866.
Diffstat (limited to 'src')
-rw-r--r--src/command/client.rs61
-rw-r--r--src/command/client/history.rs298
-rw-r--r--src/command/client/import.rs152
-rw-r--r--src/command/client/search.rs189
-rw-r--r--src/command/client/search/cursor.rs333
-rw-r--r--src/command/client/search/duration.rs62
-rw-r--r--src/command/client/search/engines.rs46
-rw-r--r--src/command/client/search/engines/db.rs33
-rw-r--r--src/command/client/search/engines/skim.rs145
-rw-r--r--src/command/client/search/history_list.rs183
-rw-r--r--src/command/client/search/interactive.rs588
-rw-r--r--src/command/client/stats.rs181
-rw-r--r--src/command/client/sync.rs74
-rw-r--r--src/command/client/sync/login.rs147
-rw-r--r--src/command/client/sync/logout.rs19
-rw-r--r--src/command/client/sync/register.rs49
-rw-r--r--src/command/client/sync/status.rs35
-rw-r--r--src/command/contributors.rs75
-rw-r--r--src/command/init.rs144
-rw-r--r--src/command/mod.rs87
-rw-r--r--src/command/server.rs44
-rw-r--r--src/main.rs45
-rw-r--r--src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md60
-rw-r--r--src/ratatui/.github/ISSUE_TEMPLATE/config.yml1
-rw-r--r--src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md32
-rw-r--r--src/ratatui/.github/workflows/cd.yml19
-rw-r--r--src/ratatui/.github/workflows/ci.yml76
-rw-r--r--src/ratatui/.gitignore6
-rw-r--r--src/ratatui/LICENSE21
-rw-r--r--src/ratatui/README.md136
-rw-r--r--src/ratatui/backend/crossterm.rs241
-rw-r--r--src/ratatui/backend/mod.rs58
-rw-r--r--src/ratatui/backend/termion.rs275
-rw-r--r--src/ratatui/buffer.rs736
-rw-r--r--src/ratatui/layout.rs560
-rw-r--r--src/ratatui/mod.rs177
-rw-r--r--src/ratatui/style.rs310
-rw-r--r--src/ratatui/symbols.rs233
-rw-r--r--src/ratatui/terminal.rs487
-rw-r--r--src/ratatui/text.rs430
-rw-r--r--src/ratatui/widgets/barchart.rs219
-rw-r--r--src/ratatui/widgets/block.rs573
-rw-r--r--src/ratatui/widgets/canvas/line.rs95
-rw-r--r--src/ratatui/widgets/canvas/map.rs48
-rw-r--r--src/ratatui/widgets/canvas/mod.rs510
-rw-r--r--src/ratatui/widgets/canvas/points.rs30
-rw-r--r--src/ratatui/widgets/canvas/rectangle.rs52
-rw-r--r--src/ratatui/widgets/canvas/world.rs6299
-rw-r--r--src/ratatui/widgets/chart.rs660
-rw-r--r--src/ratatui/widgets/clear.rs37
-rw-r--r--src/ratatui/widgets/gauge.rs313
-rw-r--r--src/ratatui/widgets/list.rs268
-rw-r--r--src/ratatui/widgets/mod.rs184
-rw-r--r--src/ratatui/widgets/paragraph.rs214
-rw-r--r--src/ratatui/widgets/reflow.rs534
-rw-r--r--src/ratatui/widgets/sparkline.rs155
-rw-r--r--src/ratatui/widgets/table.rs504
-rw-r--r--src/ratatui/widgets/tabs.rs129
-rw-r--r--src/shell/atuin.bash34
-rw-r--r--src/shell/atuin.fish40
-rw-r--r--src/shell/atuin.nu44
-rw-r--r--src/shell/atuin.zsh57
62 files changed, 0 insertions, 17847 deletions
diff --git a/src/command/client.rs b/src/command/client.rs
deleted file mode 100644
index 2a825638..00000000
--- a/src/command/client.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-use std::path::PathBuf;
-
-use clap::Subcommand;
-use eyre::{Result, WrapErr};
-
-use atuin_client::{database::Sqlite, settings::Settings};
-use env_logger::Builder;
-
-#[cfg(feature = "sync")]
-mod sync;
-
-mod history;
-mod import;
-mod search;
-mod stats;
-
-#[derive(Subcommand)]
-#[command(infer_subcommands = true)]
-pub enum Cmd {
- /// Manipulate shell history
- #[command(subcommand)]
- History(history::Cmd),
-
- /// Import shell history from file
- #[command(subcommand)]
- Import(import::Cmd),
-
- /// Calculate statistics for your history
- Stats(stats::Cmd),
-
- /// Interactive history search
- Search(search::Cmd),
-
- #[cfg(feature = "sync")]
- #[command(flatten)]
- Sync(sync::Cmd),
-}
-
-impl Cmd {
- #[tokio::main(flavor = "current_thread")]
- pub async fn run(self) -> Result<()> {
- Builder::new()
- .filter_level(log::LevelFilter::Off)
- .parse_env("ATUIN_LOG")
- .init();
-
- let mut settings = Settings::new().wrap_err("could not load client settings")?;
-
- let db_path = PathBuf::from(settings.db_path.as_str());
- let mut db = Sqlite::new(db_path).await?;
-
- match self {
- Self::History(history) => history.run(&settings, &mut db).await,
- Self::Import(import) => import.run(&mut db).await,
- Self::Stats(stats) => stats.run(&mut db, &settings).await,
- Self::Search(search) => search.run(db, &mut settings).await,
- #[cfg(feature = "sync")]
- Self::Sync(sync) => sync.run(settings, &mut db).await,
- }
- }
-}
diff --git a/src/command/client/history.rs b/src/command/client/history.rs
deleted file mode 100644
index 76c796ef..00000000
--- a/src/command/client/history.rs
+++ /dev/null
@@ -1,298 +0,0 @@
-use std::{
- env,
- fmt::{self, Display},
- io::{StdoutLock, Write},
- time::Duration,
-};
-
-use atuin_common::utils;
-use clap::Subcommand;
-use eyre::Result;
-use runtime_format::{FormatKey, FormatKeyError, ParsedFmt};
-
-use atuin_client::{
- database::{current_context, Database},
- history::History,
- settings::Settings,
-};
-
-#[cfg(feature = "sync")]
-use atuin_client::sync;
-use log::debug;
-
-use super::search::format_duration;
-use super::search::format_duration_into;
-
-#[derive(Subcommand)]
-#[command(infer_subcommands = true)]
-pub enum Cmd {
- /// Begins a new command in the history
- Start { command: Vec<String> },
-
- /// Finishes a new command in the history (adds time, exit code)
- End {
- id: String,
- #[arg(long, short)]
- exit: i64,
- },
-
- /// List all items in history
- List {
- #[arg(long, short)]
- cwd: bool,
-
- #[arg(long, short)]
- session: bool,
-
- #[arg(long)]
- human: bool,
-
- /// Show only the text of the command
- #[arg(long)]
- cmd_only: bool,
-
- /// Available variables: {command}, {directory}, {duration}, {user}, {host} and {time}.
- /// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
- #[arg(long, short)]
- format: Option<String>,
- },
-
- /// Get the last command ran
- Last {
- #[arg(long)]
- human: bool,
-
- /// Show only the text of the command
- #[arg(long)]
- cmd_only: bool,
-
- /// Available variables: {command}, {directory}, {duration}, {user}, {host} and {time}.
- /// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
- #[arg(long, short)]
- format: Option<String>,
- },
-}
-
-#[derive(Clone, Copy, Debug)]
-pub enum ListMode {
- Human,
- CmdOnly,
- Regular,
-}
-
-impl ListMode {
- pub const fn from_flags(human: bool, cmd_only: bool) -> Self {
- if human {
- ListMode::Human
- } else if cmd_only {
- ListMode::CmdOnly
- } else {
- ListMode::Regular
- }
- }
-}
-
-#[allow(clippy::cast_sign_loss)]
-pub fn print_list(h: &[History], list_mode: ListMode, format: Option<&str>) {
- let w = std::io::stdout();
- let mut w = w.lock();
-
- match list_mode {
- ListMode::Human => print_human_list(&mut w, h, format),
- ListMode::CmdOnly => print_cmd_only(&mut w, h),
- ListMode::Regular => print_regular(&mut w, h, format),
- }
-
- w.flush().expect("failed to flush history");
-}
-
-/// type wrapper around `History` so we can implement traits
-struct FmtHistory<'a>(&'a History);
-
-/// defines how to format the history
-impl FormatKey for FmtHistory<'_> {
- #[allow(clippy::cast_sign_loss)]
- fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> {
- match key {
- "command" => f.write_str(self.0.command.trim())?,
- "directory" => f.write_str(self.0.cwd.trim())?,
- "exit" => f.write_str(&self.0.exit.to_string())?,
- "duration" => {
- let dur = Duration::from_nanos(std::cmp::max(self.0.duration, 0) as u64);
- format_duration_into(dur, f)?;
- }
- "time" => self.0.timestamp.format("%Y-%m-%d %H:%M:%S").fmt(f)?,
- "relativetime" => {
- let since = chrono::Utc::now() - self.0.timestamp;
- let time = format_duration(since.to_std().unwrap_or_default());
- f.write_str(&time)?;
- }
- "host" => f.write_str(
- self.0
- .hostname
- .split_once(':')
- .map_or(&self.0.hostname, |(host, _)| host),
- )?,
- "user" => f.write_str(self.0.hostname.split_once(':').map_or("", |(_, user)| user))?,
- _ => return Err(FormatKeyError::UnknownKey),
- }
- Ok(())
- }
-}
-
-fn print_list_with(w: &mut StdoutLock, h: &[History], format: &str) {
- let fmt = match ParsedFmt::new(format) {
- Ok(fmt) => fmt,
- Err(err) => {
- eprintln!("ERROR: History formatting failed with the following error: {err}");
- println!("If your formatting string contains curly braces (eg: {{var}}) you need to escape them this way: {{{{var}}.");
- std::process::exit(1)
- }
- };
-
- for h in h.iter().rev() {
- writeln!(w, "{}", fmt.with_args(&FmtHistory(h))).expect("failed to write history");
- }
-}
-
-pub fn print_human_list(w: &mut StdoutLock, h: &[History], format: Option<&str>) {
- let format = format
- .unwrap_or("{time} · {duration}\t{command}")
- .replace("\\t", "\t");
- print_list_with(w, h, &format);
-}
-
-pub fn print_regular(w: &mut StdoutLock, h: &[History], format: Option<&str>) {
- let format = format
- .unwrap_or("{time}\t{command}\t{duration}")
- .replace("\\t", "\t");
- print_list_with(w, h, &format);
-}
-
-pub fn print_cmd_only(w: &mut StdoutLock, h: &[History]) {
- for h in h.iter().rev() {
- writeln!(w, "{}", h.command.trim()).expect("failed to write history");
- }
-}
-
-impl Cmd {
- pub async fn run(&self, settings: &Settings, db: &mut impl Database) -> Result<()> {
- let context = current_context();
-
- match self {
- Self::Start { command: words } => {
- let command = words.join(" ");
-
- if command.starts_with(' ') || settings.history_filter.is_match(&command) {
- return Ok(());
- }
-
- // It's better for atuin to silently fail here and attempt to
- // store whatever is ran, than to throw an error to the terminal
- let cwd = utils::get_current_dir();
-
- let h = History::new(chrono::Utc::now(), command, cwd, -1, -1, None, None, None);
-
- // print the ID
- // we use this as the key for calling end
- println!("{}", h.id);
- db.save(&h).await?;
- Ok(())
- }
-
- Self::End { id, exit } => {
- if id.trim() == "" {
- return Ok(());
- }
-
- let mut h = db.load(id).await?;
-
- if h.duration > 0 {
- debug!("cannot end history - already has duration");
-
- // returning OK as this can occur if someone Ctrl-c a prompt
- return Ok(());
- }
-
- h.exit = *exit;
- h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos();
-
- db.update(&h).await?;
-
- if settings.should_sync()? {
- #[cfg(feature = "sync")]
- {
- debug!("running periodic background sync");
- sync::sync(settings, false, db).await?;
- }
- #[cfg(not(feature = "sync"))]
- debug!("not compiled with sync support");
- } else {
- debug!("sync disabled! not syncing");
- }
-
- Ok(())
- }
-
- Self::List {
- session,
- cwd,
- human,
- cmd_only,
- format,
- } => {
- let session = if *session {
- Some(env::var("ATUIN_SESSION")?)
- } else {
- None
- };
- let cwd = if *cwd {
- Some(utils::get_current_dir())
- } else {
- None
- };
-
- let history = match (session, cwd) {
- (None, None) => db.list(settings.filter_mode, &context, None, false).await?,
- (None, Some(cwd)) => {
- let query = format!("select * from history where cwd = '{cwd}';");
- db.query_history(&query).await?
- }
- (Some(session), None) => {
- let query = format!("select * from history where session = '{session}';");
- db.query_history(&query).await?
- }
- (Some(session), Some(cwd)) => {
- let query = format!(
- "select * from history where cwd = '{cwd}' and session = '{session}';",
- );
- db.query_history(&query).await?
- }
- };
-
- print_list(
- &history,
- ListMode::from_flags(*human, *cmd_only),
- format.as_deref(),
- );
-
- Ok(())
- }
-
- Self::Last {
- human,
- cmd_only,
- format,
- } => {
- let last = db.last().await?;
- print_list(
- &[last],
- ListMode::from_flags(*human, *cmd_only),
- format.as_deref(),
- );
-
- Ok(())
- }
- }
- }
-}
diff --git a/src/command/client/import.rs b/src/command/client/import.rs
deleted file mode 100644
index 7abc3d44..00000000
--- a/src/command/client/import.rs
+++ /dev/null
@@ -1,152 +0,0 @@
-use std::env;
-
-use async_trait::async_trait;
-use clap::Parser;
-use eyre::Result;
-use indicatif::ProgressBar;
-
-use atuin_client::{
- database::Database,
- history::History,
- import::{
- bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb, resh::Resh, zsh::Zsh,
- zsh_histdb::ZshHistDb, Importer, Loader,
- },
-};
-
-#[derive(Parser)]
-#[command(infer_subcommands = true)]
-pub enum Cmd {
- /// Import history for the current shell
- Auto,
-
- /// Import history from the zsh history file
- Zsh,
- /// Import history from the zsh history file
- ZshHistDb,
- /// Import history from the bash history file
- Bash,
- /// Import history from the resh history file
- Resh,
- /// Import history from the fish history file
- Fish,
- /// Import history from the nu history file
- Nu,
- /// Import history from the nu history file
- NuHistDb,
-}
-
-const BATCH_SIZE: usize = 100;
-
-impl Cmd {
- pub async fn run<DB: Database>(&self, db: &mut DB) -> Result<()> {
- println!(" Atuin ");
- println!("======================");
- println!(" \u{1f30d} ");
- println!(" \u{1f418}\u{1f418}\u{1f418}\u{1f418} ");
- println!(" \u{1f422} ");
- println!("======================");
- println!("Importing history...");
-
- match self {
- Self::Auto => {
- if cfg!(windows) {
- println!("This feature does not work on windows. Please run atuin import <SHELL>. To view a list of shells, run atuin import.");
- return Ok(());
- }
-
- let shell = env::var("SHELL").unwrap_or_else(|_| String::from("NO_SHELL"));
- if shell.ends_with("/zsh") {
- if ZshHistDb::histpath().is_ok() {
- println!(
- "Detected Zsh-HistDb, using :{}",
- ZshHistDb::histpath().unwrap().to_str().unwrap()
- );
- import::<ZshHistDb, DB>(db).await
- } else {
- println!("Detected ZSH");
- import::<Zsh, DB>(db).await
- }
- } else if shell.ends_with("/fish") {
- println!("Detected Fish");
- import::<Fish, DB>(db).await
- } else if shell.ends_with("/bash") {
- println!("Detected Bash");
- import::<Bash, DB>(db).await
- } else if shell.ends_with("/nu") {
- if NuHistDb::histpath().is_ok() {
- println!(
- "Detected Nu-HistDb, using :{}",
- NuHistDb::histpath().unwrap().to_str().unwrap()
- );
- import::<NuHistDb, DB>(db).await
- } else {
- println!("Detected Nushell");
- import::<Nu, DB>(db).await
- }
- } else {
- println!("cannot import {shell} history");
- Ok(())
- }
- }
-
- Self::Zsh => import::<Zsh, DB>(db).await,
- Self::ZshHistDb => import::<ZshHistDb, DB>(db).await,
- Self::Bash => import::<Bash, DB>(db).await,
- Self::Resh => import::<Resh, DB>(db).await,
- Self::Fish => import::<Fish, DB>(db).await,
- Self::Nu => import::<Nu, DB>(db).await,
- Self::NuHistDb => import::<NuHistDb, DB>(db).await,
- }
- }
-}
-
-pub struct HistoryImporter<'db, DB: Database> {
- pb: ProgressBar,
- buf: Vec<History>,
- db: &'db mut DB,
-}
-
-impl<'db, DB: Database> HistoryImporter<'db, DB> {
- fn new(db: &'db mut DB, len: usize) -> Self {
- Self {
- pb: ProgressBar::new(len as u64),
- buf: Vec::with_capacity(BATCH_SIZE),
- db,
- }
- }
-
- async fn flush(self) -> Result<()> {
- if !self.buf.is_empty() {
- self.db.save_bulk(&self.buf).await?;
- }
- self.pb.finish();
- Ok(())
- }
-}
-
-#[async_trait]
-impl<'db, DB: Database> Loader for HistoryImporter<'db, DB> {
- async fn push(&mut self, hist: History) -> Result<()> {
- self.pb.inc(1);
- self.buf.push(hist);
- if self.buf.len() == self.buf.capacity() {
- self.db.save_bulk(&self.buf).await?;
- self.buf.clear();
- }
- Ok(())
- }
-}
-
-async fn import<I: Importer + Send, DB: Database>(db: &mut DB) -> Result<()> {
- println!("Importing history from {}", I::NAME);
-
- let mut importer = I::new().await?;
- let len = importer.entries().await.unwrap();
- let mut loader = HistoryImporter::new(db, len);
- importer.load(&mut loader).await?;
- loader.flush().await?;
-
- println!("Import complete!");
- Ok(())
-}
diff --git a/src/command/client/search.rs b/src/command/client/search.rs
deleted file mode 100644
index 356ae251..00000000
--- a/src/command/client/search.rs
+++ /dev/null
@@ -1,189 +0,0 @@
-use atuin_common::utils;
-use clap::Parser;
-use eyre::Result;
-
-use atuin_client::{
- database::Database,
- database::{current_context, OptFilters},
- history::History,
- settings::{FilterMode, SearchMode, Settings},
-};
-
-use super::history::ListMode;
-
-mod cursor;
-mod duration;
-mod engines;
-mod history_list;
-mod interactive;
-pub use duration::{format_duration, format_duration_into};
-
-#[allow(clippy::struct_excessive_bools)]
-#[derive(Parser)]
-pub struct Cmd {
- /// Filter search result by directory
- #[arg(long, short)]
- cwd: Option<String>,
-
- /// Exclude directory from results
- #[arg(long = "exclude-cwd")]
- exclude_cwd: Option<String>,
-
- /// Filter search result by exit code
- #[arg(long, short)]
- exit: Option<i64>,
-
- /// Exclude results with this exit code
- #[arg(long = "exclude-exit")]
- exclude_exit: Option<i64>,
-
- /// Only include results added before this date
- #[arg(long, short)]
- before: Option<String>,
-
- /// Only include results after this date
- #[arg(long)]
- after: Option<String>,
-
- /// How many entries to return at most
- #[arg(long)]
- limit: Option<i64>,
-
- /// Offset from the start of the results
- #[arg(long)]
- offset: Option<i64>,
-
- /// Open interactive search UI
- #[arg(long, short)]
- interactive: bool,
-
- /// Allow overriding filter mode over config
- #[arg(long = "filter-mode")]
- filter_mode: Option<FilterMode>,
-
- /// Allow overriding search mode over config
- #[arg(long = "search-mode")]
- search_mode: Option<SearchMode>,
-
- /// Marker argument used to inform atuin that it was invoked from a shell up-key binding (hidden from help to avoid confusion)
- #[arg(long = "shell-up-key-binding", hide = true)]
- shell_up_key_binding: bool,
-
- /// Use human-readable formatting for time
- #[arg(long)]
- human: bool,
-
- query: Vec<String>,
-
- /// Show only the text of the command
- #[arg(long)]
- cmd_only: bool,
-
- /// Delete anything matching this query. Will not print out the match
- #[arg(long)]
- delete: bool,
-
- /// Reverse the order of results, oldest first
- #[arg(long, short)]
- reverse: bool,
-
- /// Available variables: {command}, {directory}, {duration}, {user}, {host}, {time}, {exit} and
- /// {relativetime}.
- /// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
- #[arg(long, short)]
- format: Option<String>,
-}
-
-impl Cmd {
- pub async fn run(self, mut db: impl Database, settings: &mut Settings) -> Result<()> {
- if self.search_mode.is_some() {
- settings.search_mode = self.search_mode.unwrap();
- }
- if self.filter_mode.is_some() {
- settings.filter_mode = self.filter_mode.unwrap();
- }
-
- settings.shell_up_key_binding = self.shell_up_key_binding;
-
- if self.interactive {
- let item = interactive::history(&self.query, settings, db).await?;
- eprintln!("{item}");
- } else {
- let list_mode = ListMode::from_flags(self.human, self.cmd_only);
-
- let opt_filter = OptFilters {
- exit: self.exit,
- exclude_exit: self.exclude_exit,
- cwd: self.cwd,
- exclude_cwd: self.exclude_cwd,
- before: self.before,
- after: self.after,
- limit: self.limit,
- offset: self.offset,
- reverse: self.reverse,
- };
-
- let mut entries =
- run_non_interactive(settings, opt_filter.clone(), &self.query, &mut db).await?;
-
- if entries.is_empty() {
- std::process::exit(1)
- }
-
- // if we aren't deleting, print it all
- if self.delete {
- // delete it
- // it only took me _years_ to add this
- // sorry
- while !entries.is_empty() {
- for entry in &entries {
- eprintln!("deleting {}", entry.id);
- db.delete(entry.clone()).await?;
- }
-
- entries =
- run_non_interactive(settings, opt_filter.clone(), &self.query, &mut db)
- .await?;
- }
- } else {
- super::history::print_list(&entries, list_mode, self.format.as_deref());
- }
- };
- Ok(())
- }
-}
-
-// This is supposed to more-or-less mirror the command line version, so ofc
-// it is going to have a lot of args
-#[allow(clippy::too_many_arguments)]
-async fn run_non_interactive(
- settings: &Settings,
- filter_options: OptFilters,
- query: &[String],
- db: &mut impl Database,
-) -> Result<Vec<History>> {
- let dir = if filter_options.cwd.as_deref() == Some(".") {
- Some(utils::get_current_dir())
- } else {
- filter_options.cwd
- };
-
- let context = current_context();
-
- let opt_filter = OptFilters {
- cwd: dir,
- ..filter_options
- };
-
- let results = db
- .search(
- settings.search_mode,
- settings.filter_mode,
- &context,
- query.join(" ").as_str(),
- opt_filter,
- )
- .await?;
-
- Ok(results)
-}
diff --git a/src/command/client/search/cursor.rs b/src/command/client/search/cursor.rs
deleted file mode 100644
index 2bce4f37..00000000
--- a/src/command/client/search/cursor.rs
+++ /dev/null
@@ -1,333 +0,0 @@
-use atuin_client::settings::WordJumpMode;
-
-pub struct Cursor {
- source: String,
- index: usize,
-}
-
-impl From<String> for Cursor {
- fn from(source: String) -> Self {
- Self { source, index: 0 }
- }
-}
-
-pub struct WordJumper<'a> {
- word_chars: &'a str,
- word_jump_mode: WordJumpMode,
-}
-
-impl WordJumper<'_> {
- fn is_word_boundary(&self, c: char, next_c: char) -> bool {
- (c.is_whitespace() && !next_c.is_whitespace())
- || (!c.is_whitespace() && next_c.is_whitespace())
- || (self.word_chars.contains(c) && !self.word_chars.contains(next_c))
- || (!self.word_chars.contains(c) && self.word_chars.contains(next_c))
- }
-
- fn emacs_get_next_word_pos(&self, source: &str, index: usize) -> usize {
- let index = (index + 1..source.len().saturating_sub(1))
- .find(|&i| self.word_chars.contains(source.chars().nth(i).unwrap()))
- .unwrap_or(source.len());
- (index + 1..source.len().saturating_sub(1))
- .find(|&i| !self.word_chars.contains(source.chars().nth(i).unwrap()))
- .unwrap_or(source.len())
- }
-
- fn emacs_get_prev_word_pos(&self, source: &str, index: usize) -> usize {
- let index = (1..index)
- .rev()
- .find(|&i| self.word_chars.contains(source.chars().nth(i).unwrap()))
- .unwrap_or(0);
- (1..index)
- .rev()
- .find(|&i| !self.word_chars.contains(source.chars().nth(i).unwrap()))
- .map_or(0, |i| i + 1)
- }
-
- fn subl_get_next_word_pos(&self, source: &str, index: usize) -> usize {
- let index = (index..source.len().saturating_sub(1)).find(|&i| {
- self.is_word_boundary(
- source.chars().nth(i).unwrap(),
- source.chars().nth(i + 1).unwrap(),
- )
- });
- if index.is_none() {
- return source.len();
- }
- (index.unwrap() + 1..source.len())
- .find(|&i| !source.chars().nth(i).unwrap().is_whitespace())
- .unwrap_or(source.len())
- }
-
- fn subl_get_prev_word_pos(&self, source: &str, index: usize) -> usize {
- let index = (1..index)
- .rev()
- .find(|&i| !source.chars().nth(i).unwrap().is_whitespace());
- if index.is_none() {
- return 0;
- }
- (1..index.unwrap())
- .rev()
- .find(|&i| {
- self.is_word_boundary(
- source.chars().nth(i - 1).unwrap(),
- source.chars().nth(i).unwrap(),
- )
- })
- .unwrap_or(0)
- }
-
- fn get_next_word_pos(&self, source: &str, index: usize) -> usize {
- match self.word_jump_mode {
- WordJumpMode::Emacs => self.emacs_get_next_word_pos(source, index),
- WordJumpMode::Subl => self.subl_get_next_word_pos(source, index),
- }
- }
-
- fn get_prev_word_pos(&self, source: &str, index: usize) -> usize {
- match self.word_jump_mode {
- WordJumpMode::Emacs => self.emacs_get_prev_word_pos(source, index),
- WordJumpMode::Subl => self.subl_get_prev_word_pos(source, index),
- }
- }
-}
-
-impl Cursor {
- pub fn as_str(&self) -> &str {
- self.source.as_str()
- }
-
- pub fn into_inner(self) -> String {
- self.source
- }
-
- /// Returns the string before the cursor
- pub fn substring(&self) -> &str {
- &self.source[..self.index]
- }
-
- /// Returns the currently selected [`char`]
- pub fn char(&self) -> Option<char> {
- self.source[self.index..].chars().next()
- }
-
- pub fn right(&mut self) {
- if self.index < self.source.len() {
- loop {
- self.index += 1;
- if self.source.is_char_boundary(self.index) {
- break;
- }
- }
- }
- }
-
- pub fn left(&mut self) -> bool {
- if self.index > 0 {
- loop {
- self.index -= 1;
- if self.source.is_char_boundary(self.index) {
- break true;
- }
- }
- } else {
- false
- }
- }
-
- pub fn next_word(&mut self, word_chars: &str, word_jump_mode: WordJumpMode) {
- let word_jumper = WordJumper {
- word_chars,
- word_jump_mode,
- };
- self.index = word_jumper.get_next_word_pos(&self.source, self.index);
- }
-
- pub fn prev_word(&mut self, word_chars: &str, word_jump_mode: WordJumpMode) {
- let word_jumper = WordJumper {
- word_chars,
- word_jump_mode,
- };
- self.index = word_jumper.get_prev_word_pos(&self.source, self.index);
- }
-
- pub fn insert(&mut self, c: char) {
- self.source.insert(self.index, c);
- self.index += c.len_utf8();
- }
-
- pub fn remove(&mut self) -> Option<char> {
- if self.index < self.source.len() {
- Some(self.source.remove(self.index))
- } else {
- None
- }
- }
-
- pub fn remove_next_word(&mut self, word_chars: &str, word_jump_mode: WordJumpMode) {
- let word_jumper = WordJumper {
- word_chars,
- word_jump_mode,
- };
- let next_index = word_jumper.get_next_word_pos(&self.source, self.index);
- self.source.replace_range(self.index..next_index, "");
- }
-
- pub fn remove_prev_word(&mut self, word_chars: &str, word_jump_mode: WordJumpMode) {
- let word_jumper = WordJumper {
- word_chars,
- word_jump_mode,
- };
- let next_index = word_jumper.get_prev_word_pos(&self.source, self.index);
- self.source.replace_range(next_index..self.index, "");
- self.index = next_index;
- }
-
- pub fn back(&mut self) -> Option<char> {
- if self.left() {
- self.remove()
- } else {
- None
- }
- }
-
- pub fn clear(&mut self) {
- self.source.clear();
- self.index = 0;
- }
-
- pub fn end(&mut self) {
- self.index = self.source.len();
- }
-
- pub fn start(&mut self) {
- self.index = 0;
- }
-}
-
-#[cfg(test)]
-mod cursor_tests {
- use super::Cursor;
- use super::*;
-
- static EMACS_WORD_JUMPER: WordJumper = WordJumper {
- word_chars: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
- word_jump_mode: WordJumpMode::Emacs,
- };
-
- static SUBL_WORD_JUMPER: WordJumper = WordJumper {
- word_chars: "./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}`~?",
- word_jump_mode: WordJumpMode::Subl,
- };
-
- #[test]
- fn right() {
- // ö is 2 bytes
- let mut c = Cursor::from(String::from("öaöböcödöeöfö"));
- let indices = [0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18, 20, 20, 20, 20];
- for i in indices {
- assert_eq!(c.index, i);
- c.right();
- }
- }
-
- #[test]
- fn left() {
- // ö is 2 bytes
- let mut c = Cursor::from(String::from("öaöböcödöeöfö"));
- c.end();
- let indices = [20, 18, 17, 15, 14, 12, 11, 9, 8, 6, 5, 3, 2, 0, 0, 0, 0];
- for i in indices {
- assert_eq!(c.index, i);
- c.left();
- }
- }
-
- #[test]
- fn test_emacs_get_next_word_pos() {
- let s = String::from(" aaa ((()))bbb ((())) ");
- let indices = [(0, 6), (3, 6), (7, 18), (19, 30)];
- for (i_src, i_dest) in indices {
- assert_eq!(EMACS_WORD_JUMPER.get_next_word_pos(&s, i_src), i_dest);
- }
- assert_eq!(EMACS_WORD_JUMPER.get_next_word_pos("", 0), 0);
- }
-
- #[test]
- fn test_emacs_get_prev_word_pos() {
- let s = String::from(" aaa ((()))bbb ((())) ");
- let indices = [(30, 15), (29, 15), (15, 3), (3, 0)];
- for (i_src, i_dest) in indices {
- assert_eq!(EMACS_WORD_JUMPER.get_prev_word_pos(&s, i_src), i_dest);
- }
- assert_eq!(EMACS_WORD_JUMPER.get_prev_word_pos("", 0), 0);
- }
-
- #[test]
- fn test_subl_get_next_word_pos() {
- let s = String::from(" aaa ((()))bbb ((())) ");
- let indices = [(0, 3), (1, 3), (3, 9), (9, 15), (15, 21), (21, 30)];
- for (i_src, i_dest) in indices {
- assert_eq!(SUBL_WORD_JUMPER.get_next_word_pos(&s, i_src), i_dest);
- }
- assert_eq!(SUBL_WORD_JUMPER.get_next_word_pos("", 0), 0);
- }
-
- #[test]
- fn test_subl_get_prev_word_pos() {
- let s = String::from(" aaa ((()))bbb ((())) ");
- let indices = [(30, 21), (21, 15), (15, 9), (9, 3), (3, 0)];
- for (i_src, i_dest) in indices {
- assert_eq!(SUBL_WORD_JUMPER.get_prev_word_pos(&s, i_src), i_dest);
- }
- assert_eq!(SUBL_WORD_JUMPER.get_prev_word_pos("", 0), 0);
- }
-
- #[test]
- fn pop() {
- let mut s = String::from("öaöböcödöeöfö");
- let mut c = Cursor::from(s.clone());
- c.end();
- while !s.is_empty() {
- let c1 = s.pop();
- let c2 = c.back();
- assert_eq!(c1, c2);
- assert_eq!(s.as_str(), c.substring());
- }
- let c1 = s.pop();
- let c2 = c.back();
- assert_eq!(c1, c2);
- }
-
- #[test]
- fn back() {
- let mut c = Cursor::from(String::from("öaöböcödöeöfö"));
- // move to ^
- for _ in 0..4 {
- c.right();
- }
- assert_eq!(c.substring(), "öaöb");
- assert_eq!(c.back(), Some('b'));
- assert_eq!(c.back(), Some('ö'));
- assert_eq!(c.back(), Some('a'));
- assert_eq!(c.back(), Some('ö'));
- assert_eq!(c.back(), None);
- assert_eq!(c.as_str(), "öcödöeöfö");
- }
-
- #[test]
- fn insert() {
- let mut c = Cursor::from(String::from("öaöböcödöeöfö"));
- // move to ^
- for _ in 0..4 {
- c.right();
- }
- assert_eq!(c.substring(), "öaöb");
- c.insert('ö');
- c.insert('g');
- c.insert('ö');
- c.insert('h');
- assert_eq!(c.substring(), "öaöbögöh");
- assert_eq!(c.as_str(), "öaöbögöhöcödöeöfö");
- }
-}
diff --git a/src/command/client/search/duration.rs b/src/command/client/search/duration.rs
deleted file mode 100644
index 08dadb95..00000000
--- a/src/command/client/search/duration.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-use core::fmt;
-use std::{ops::ControlFlow, time::Duration};
-
-#[allow(clippy::module_name_repetitions)]
-pub fn format_duration_into(dur: Duration, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- fn item(unit: &'static str, value: u64) -> ControlFlow<(&'static str, u64)> {
- if value > 0 {
- ControlFlow::Break((unit, value))
- } else {
- ControlFlow::Continue(())
- }
- }
-
- // impl taken and modified from
- // https://github.com/tailhook/humantime/blob/master/src/duration.rs#L295-L331
- // Copyright (c) 2016 The humantime Developers
- fn fmt(f: Duration) -> ControlFlow<(&'static str, u64), ()> {
- let secs = f.as_secs();
- let nanos = f.subsec_nanos();
-
- let years = secs / 31_557_600; // 365.25d
- let year_days = secs % 31_557_600;
- let months = year_days / 2_630_016; // 30.44d
- let month_days = year_days % 2_630_016;
- let days = month_days / 86400;
- let day_secs = month_days % 86400;
- let hours = day_secs / 3600;
- let minutes = day_secs % 3600 / 60;
- let seconds = day_secs % 60;
-
- let millis = nanos / 1_000_000;
-
- // a difference from our impl than the original is that
- // we only care about the most-significant segment of the duration.
- // If the item call returns `Break`, then the `?` will early-return.
- // This allows for a very consise impl
- item("y", years)?;
- item("mo", months)?;
- item("d", days)?;
- item("h", hours)?;
- item("m", minutes)?;
- item("s", seconds)?;
- item("ms", u64::from(millis))?;
- ControlFlow::Continue(())
- }
-
- match fmt(dur) {
- ControlFlow::Break((unit, value)) => write!(f, "{value}{unit}"),
- ControlFlow::Continue(()) => write!(f, "0s"),
- }
-}
-
-#[allow(clippy::module_name_repetitions)]
-pub fn format_duration(f: Duration) -> String {
- struct F(Duration);
- impl fmt::Display for F {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- format_duration_into(self.0, f)
- }
- }
- F(f).to_string()
-}
diff --git a/src/command/client/search/engines.rs b/src/command/client/search/engines.rs
deleted file mode 100644
index 878b1431..00000000
--- a/src/command/client/search/engines.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use async_trait::async_trait;
-use atuin_client::{
- database::{Context, Database},
- history::History,
- settings::{FilterMode, SearchMode},
-};
-use eyre::Result;
-
-use super::cursor::Cursor;
-
-pub mod db;
-pub mod skim;
-
-pub fn engine(search_mode: SearchMode) -> Box<dyn SearchEngine> {
- match search_mode {
- SearchMode::Skim => Box::new(skim::Search::new()) as Box<_>,
- mode => Box::new(db::Search(mode)) as Box<_>,
- }
-}
-
-pub struct SearchState {
- pub input: Cursor,
- pub filter_mode: FilterMode,
- pub context: Context,
-}
-
-#[async_trait]
-pub trait SearchEngine: Send + Sync + 'static {
- async fn full_query(
- &mut self,
- state: &SearchState,
- db: &mut dyn Database,
- ) -> Result<Vec<History>>;
-
- async fn query(&mut self, state: &SearchState, db: &mut dyn Database) -> Result<Vec<History>> {
- if state.input.as_str().is_empty() {
- Ok(db
- .list(state.filter_mode, &state.context, Some(200), true)
- .await?
- .into_iter()
- .collect::<Vec<_>>())
- } else {
- self.full_query(state, db).await
- }
- }
-}
diff --git a/src/command/client/search/engines/db.rs b/src/command/client/search/engines/db.rs
deleted file mode 100644
index b4f24561..00000000
--- a/src/command/client/search/engines/db.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use async_trait::async_trait;
-use atuin_client::{
- database::Database, database::OptFilters, history::History, settings::SearchMode,
-};
-use eyre::Result;
-
-use super::{SearchEngine, SearchState};
-
-pub struct Search(pub SearchMode);
-
-#[async_trait]
-impl SearchEngine for Search {
- async fn full_query(
- &mut self,
- state: &SearchState,
- db: &mut dyn Database,
- ) -> Result<Vec<History>> {
- Ok(db
- .search(
- self.0,
- state.filter_mode,
- &state.context,
- state.input.as_str(),
- OptFilters {
- limit: Some(200),
- ..Default::default()
- },
- )
- .await?
- .into_iter()
- .collect::<Vec<_>>())
- }
-}
diff --git a/src/command/client/search/engines/skim.rs b/src/command/client/search/engines/skim.rs
deleted file mode 100644
index 76049312..00000000
--- a/src/command/client/search/engines/skim.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use std::path::Path;
-
-use async_trait::async_trait;
-use atuin_client::{database::Database, history::History, settings::FilterMode};
-use chrono::Utc;
-use eyre::Result;
-use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
-use tokio::task::yield_now;
-
-use super::{SearchEngine, SearchState};
-
-pub struct Search {
- all_history: Vec<(History, i32)>,
- engine: SkimMatcherV2,
-}
-
-impl Search {
- pub fn new() -> Self {
- Search {
- all_history: vec![],
- engine: SkimMatcherV2::default(),
- }
- }
-}
-
-#[async_trait]
-impl SearchEngine for Search {
- async fn full_query(
- &mut self,
- state: &SearchState,
- db: &mut dyn Database,
- ) -> Result<Vec<History>> {
- if self.all_history.is_empty() {
- self.all_history = db.all_with_count().await.unwrap();
- }
-
- Ok(fuzzy_search(&self.engine, state, &self.all_history).await)
- }
-}
-
-async fn fuzzy_search(
- engine: &SkimMatcherV2,
- state: &SearchState,
- all_history: &[(History, i32)],
-) -> Vec<History> {
- let mut set = Vec::with_capacity(200);
- let mut ranks = Vec::with_capacity(200);
- let query = state.input.as_str();
- let now = Utc::now();
-
- for (i, (history, count)) in all_history.iter().enumerate() {
- if i % 256 == 0 {
- yield_now().await;
- }
- match state.filter_mode {
- FilterMode::Global => {}
- FilterMode::Host if history.hostname == state.context.hostname => {}
- FilterMode::Session if history.session == state.context.session => {}
- FilterMode::Directory if history.cwd == state.context.cwd => {}
- _ => continue,
- }
- #[allow(clippy::cast_lossless, clippy::cast_precision_loss)]
- if let Some((score, indices)) = engine.fuzzy_indices(&history.command, query) {
- let begin = indices.first().copied().unwrap_or_default();
-
- let mut duration = ((now - history.timestamp).num_seconds() as f64).log2();
- if !duration.is_finite() || duration <= 1.0 {
- duration = 1.0;
- }
- // these + X.0 just make the log result a bit smoother.
- // log is very spiky towards 1-4, but I want a gradual decay.
- // eg:
- // log2(4) = 2, log2(5) = 2.3 (16% increase)
- // log2(8) = 3, log2(9) = 3.16 (5% increase)
- // log2(16) = 4, log2(17) = 4.08 (2% increase)
- let count = (*count as f64 + 8.0).log2();
- let begin = (begin as f64 + 16.0).log2();
- let path = path_dist(history.cwd.as_ref(), state.context.cwd.as_ref());
- let path = (path as f64 + 8.0).log2();
-
- // reduce longer durations, raise higher counts, raise matches close to the start
- let score = (-score as f64) * count / path / duration / begin;
-
- 'insert: {
- // algorithm:
- // 1. find either the position that this command ranks
- // 2. find the same command positioned better than our rank.
- for i in 0..set.len() {
- // do we out score the corrent position?
- if ranks[i] > score {
- ranks.insert(i, score);
- set.insert(i, history.clone());
- let mut j = i + 1;
- while j < set.len() {
- // remove duplicates that have a worse score
- if set[j].command == history.command {
- ranks.remove(j);
- set.remove(j);
-
- // break this while loop because there won't be any other
- // duplicates.
- break;
- }
- j += 1;
- }
-
- // keep it limited
- if ranks.len() > 200 {
- ranks.pop();
- set.pop();
- }
-
- break 'insert;
- }
- // don't continue if this command has a better score already
- if set[i].command == history.command {
- break 'insert;
- }
- }
-
- if set.len() < 200 {
- ranks.push(score);
- set.push(history.clone());
- }
- }
- }
- }
-
- set
-}
-
-fn path_dist(a: &Path, b: &Path) -> usize {
- let mut a: Vec<_> = a.components().collect();
- let b: Vec<_> = b.components().collect();
-
- let mut dist = 0;
-
- // pop a until there's a common anscestor
- while !b.starts_with(&a) {
- dist += 1;
- a.pop();
- }
-
- b.len() - a.len() + dist
-}
diff --git a/src/command/client/search/history_list.rs b/src/command/client/search/history_list.rs
deleted file mode 100644
index eedab1a5..00000000
--- a/src/command/client/search/history_list.rs
+++ /dev/null
@@ -1,183 +0,0 @@
-use std::time::Duration;
-
-use crate::ratatui::{
- buffer::Buffer,
- layout::Rect,
- style::{Color, Modifier, Style},
- widgets::{Block, StatefulWidget, Widget},
-};
-use atuin_client::history::History;
-
-use super::format_duration;
-
-pub struct HistoryList<'a> {
- history: &'a [History],
- block: Option<Block<'a>>,
-}
-
-#[derive(Default)]
-pub struct ListState {
- offset: usize,
- selected: usize,
- max_entries: usize,
-}
-
-impl ListState {
- pub fn selected(&self) -> usize {
- self.selected
- }
-
- pub fn max_entries(&self) -> usize {
- self.max_entries
- }
-
- pub fn select(&mut self, index: usize) {
- self.selected = index;
- }
-}
-
-impl<'a> StatefulWidget for HistoryList<'a> {
- type State = ListState;
-
- fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
- let list_area = self.block.take().map_or(area, |b| {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- });
-
- if list_area.width < 1 || list_area.height < 1 || self.history.is_empty() {
- return;
- }
- let list_height = list_area.height as usize;
-
- let (start, end) = self.get_items_bounds(state.selected, state.offset, list_height);
- state.offset = start;
- state.max_entries = end - start;
-
- let mut s = DrawState {
- buf,
- list_area,
- x: 0,
- y: 0,
- state,
- };
-
- for item in self.history.iter().skip(state.offset).take(end - start) {
- s.index();
- s.duration(item);
- s.time(item);
- s.command(item);
-
- // reset line
- s.y += 1;
- s.x = 0;
- }
- }
-}
-
-impl<'a> HistoryList<'a> {
- pub fn new(history: &'a [History]) -> Self {
- Self {
- history,
- block: None,
- }
- }
-
- pub fn block(mut self, block: Block<'a>) -> Self {
- self.block = Some(block);
- self
- }
-
- fn get_items_bounds(&self, selected: usize, offset: usize, height: usize) -> (usize, usize) {
- let offset = offset.min(self.history.len().saturating_sub(1));
-
- let max_scroll_space = height.min(10);
- if offset + height < selected + max_scroll_space {
- let end = selected + max_scroll_space;
- (end - height, end)
- } else if selected < offset {
- (selected, selected + height)
- } else {
- (offset, offset + height)
- }
- }
-}
-
-struct DrawState<'a> {
- buf: &'a mut Buffer,
- list_area: Rect,
- x: u16,
- y: u16,
- state: &'a ListState,
-}
-
-// longest line prefix I could come up with
-#[allow(clippy::cast_possible_truncation)] // we know that this is <65536 length
-pub const PREFIX_LENGTH: u16 = " > 123ms 59s ago".len() as u16;
-
-impl DrawState<'_> {
- fn index(&mut self) {
- // these encode the slices of `" > "`, `" {n} "`, or `" "` in a compact form.
- // Yes, this is a hack, but it makes me feel happy
- static SLICES: &str = " > 1 2 3 4 5 6 7 8 9 ";
-
- let i = self.y as usize + self.state.offset;
- let i = i.checked_sub(self.state.selected);
- let i = i.unwrap_or(10).min(10) * 2;
- self.draw(&SLICES[i..i + 3], Style::default());
- }
-
- fn duration(&mut self, h: &History) {
- let status = Style::default().fg(if h.success() {
- Color::Green
- } else {
- Color::Red
- });
- let duration = Duration::from_nanos(u64::try_from(h.duration).unwrap_or(0));
- self.draw(&format_duration(duration), status);
- }
-
- #[allow(clippy::cast_possible_truncation)] // we know that time.len() will be <6
- fn time(&mut self, h: &History) {
- let style = Style::default().fg(Color::Blue);
-
- // Account for the chance that h.timestamp is "in the future"
- // This would mean that "since" is negative, and the unwrap here
- // would fail.
- // If the timestamp would otherwise be in the future, display
- // the time since as 0.
- let since = chrono::Utc::now() - h.timestamp;
- let time = format_duration(since.to_std().unwrap_or_default());
-
- // pad the time a little bit before we write. this aligns things nicely
- self.x = PREFIX_LENGTH - 4 - time.len() as u16;
-
- self.draw(&time, style);
- self.draw(" ago", style);
- }
-
- fn command(&mut self, h: &History) {
- let mut style = Style::default();
- if self.y as usize + self.state.offset == self.state.selected {
- style = style.fg(Color::Red).add_modifier(Modifier::BOLD);
- }
-
- for section in h.command.split_ascii_whitespace() {
- self.x += 1;
- if self.x > self.list_area.width {
- // Avoid attempting to draw a command section beyond the width
- // of the list
- return;
- }
- self.draw(section, style);
- }
- }
-
- fn draw(&mut self, s: &str, style: Style) {
- let cx = self.list_area.left() + self.x;
- let cy = self.list_area.bottom() - self.y - 1;
- let w = (self.list_area.width - self.x) as usize;
- self.x += self.buf.set_stringn(cx, cy, s, w, style).0 - cx;
- }
-}
diff --git a/src/command/client/search/interactive.rs b/src/command/client/search/interactive.rs
deleted file mode 100644
index 300bc791..00000000
--- a/src/command/client/search/interactive.rs
+++ /dev/null
@@ -1,588 +0,0 @@
-use std::{
- io::{stdout, Write},
- time::Duration,
-};
-
-use crossterm::{
- event::{self, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent},
- execute, terminal,
-};
-use eyre::Result;
-use futures_util::FutureExt;
-use semver::Version;
-use unicode_width::UnicodeWidthStr;
-
-use atuin_client::{
- database::{current_context, Database},
- history::History,
- settings::{ExitMode, FilterMode, SearchMode, Settings},
-};
-
-use super::{
- cursor::Cursor,
- engines::{SearchEngine, SearchState},
- history_list::{HistoryList, ListState, PREFIX_LENGTH},
-};
-use crate::ratatui::{
- backend::{Backend, CrosstermBackend},
- layout::{Alignment, Constraint, Direction, Layout},
- style::{Color, Modifier, Style},
- text::{Span, Spans, 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;
-
-struct State {
- history_count: i64,
- update_needed: Option<Version>,
- results_state: ListState,
- switched_search_mode: bool,
- search_mode: SearchMode,
-
- search: SearchState,
- engine: Box<dyn SearchEngine>,
-}
-
-impl State {
- async fn query_results(&mut self, db: &mut dyn Database) -> Result<Vec<History>> {
- let results = self.engine.query(&self.search, db).await?;
- self.results_state.select(0);
- Ok(results)
- }
-
- fn handle_input(&mut self, settings: &Settings, input: &Event, len: usize) -> Option<usize> {
- match input {
- Event::Key(k) => self.handle_key_input(settings, k, len),
- Event::Mouse(m) => self.handle_mouse_input(*m, len),
- Event::Paste(d) => self.handle_paste_input(d),
- _ => None,
- }
- }
-
- fn handle_mouse_input(&mut self, input: MouseEvent, len: usize) -> Option<usize> {
- match input.kind {
- event::MouseEventKind::ScrollDown => {
- let i = self.results_state.selected().saturating_sub(1);
- self.results_state.select(i);
- }
- event::MouseEventKind::ScrollUp => {
- let i = self.results_state.selected() + 1;
- self.results_state.select(i.min(len - 1));
- }
- _ => {}
- }
- None
- }
-
- fn handle_paste_input(&mut self, input: &str) -> Option<usize> {
- for i in input.chars() {
- self.search.input.insert(i);
- }
- None
- }
-
- #[allow(clippy::too_many_lines)]
- #[allow(clippy::cognitive_complexity)]
- fn handle_key_input(
- &mut self,
- settings: &Settings,
- input: &KeyEvent,
- len: usize,
- ) -> Option<usize> {
- if input.kind == event::KeyEventKind::Release {
- return None;
- }
-
- let ctrl = input.modifiers.contains(KeyModifiers::CONTROL);
- let alt = input.modifiers.contains(KeyModifiers::ALT);
- // reset the state, will be set to true later if user really did change it
- self.switched_search_mode = false;
- match input.code {
- KeyCode::Char('c' | 'd' | 'g') if ctrl => return Some(RETURN_ORIGINAL),
- KeyCode::Esc => {
- return Some(match settings.exit_mode {
- ExitMode::ReturnOriginal => RETURN_ORIGINAL,
- ExitMode::ReturnQuery => RETURN_QUERY,
- })
- }
- KeyCode::Enter => {
- return Some(self.results_state.selected());
- }
- KeyCode::Char(c @ '1'..='9') if alt => {
- let c = c.to_digit(10)? as usize;
- return Some(self.results_state.selected() + c);
- }
- KeyCode::Left if ctrl => self
- .search
- .input
- .prev_word(&settings.word_chars, settings.word_jump_mode),
- KeyCode::Char('b') if alt => self
- .search
- .input
- .prev_word(&settings.word_chars, settings.word_jump_mode),
- KeyCode::Left => {
- self.search.input.left();
- }
- KeyCode::Char('h') if ctrl => {
- self.search.input.left();
- }
- KeyCode::Char('b') if ctrl => {
- self.search.input.left();
- }
- KeyCode::Right if ctrl => self
- .search
- .input
- .next_word(&settings.word_chars, settings.word_jump_mode),
- KeyCode::Char('f') if alt => self
- .search
- .input
- .next_word(&settings.word_chars, settings.word_jump_mode),
- KeyCode::Right => self.search.input.right(),
- KeyCode::Char('l') if ctrl => self.search.input.right(),
- KeyCode::Char('f') if ctrl => self.search.input.right(),
- KeyCode::Char('a') if ctrl => self.search.input.start(),
- KeyCode::Home => self.search.input.start(),
- KeyCode::Char('e') if ctrl => self.search.input.end(),
- KeyCode::End => self.search.input.end(),
- KeyCode::Backspace if ctrl => self
- .search
- .input
- .remove_prev_word(&settings.word_chars, settings.word_jump_mode),
- KeyCode::Backspace => {
- self.search.input.back();
- }
- KeyCode::Delete if ctrl => self
- .search
- .input
- .remove_next_word(&settings.word_chars, settings.word_jump_mode),
- KeyCode::Delete => {
- self.search.input.remove();
- }
- KeyCode::Char('w') if ctrl => {
- // remove the first batch of whitespace
- while matches!(self.search.input.back(), Some(c) if c.is_whitespace()) {}
- while self.search.input.left() {
- if self.search.input.char().unwrap().is_whitespace() {
- self.search.input.right(); // found whitespace, go back right
- break;
- }
- self.search.input.remove();
- }
- }
- KeyCode::Char('u') if ctrl => self.search.input.clear(),
- KeyCode::Char('r') if ctrl => {
- pub static FILTER_MODES: [FilterMode; 4] = [
- FilterMode::Global,
- FilterMode::Host,
- FilterMode::Session,
- FilterMode::Directory,
- ];
- let i = self.search.filter_mode as usize;
- let i = (i + 1) % FILTER_MODES.len();
- self.search.filter_mode = FILTER_MODES[i];
- }
- KeyCode::Char('s') if ctrl => {
- self.switched_search_mode = true;
- self.search_mode = self.search_mode.next(settings);
- self.engine = engines::engine(self.search_mode);
- }
- KeyCode::Down if self.results_state.selected() == 0 => {
- return Some(match settings.exit_mode {
- ExitMode::ReturnOriginal => RETURN_ORIGINAL,
- ExitMode::ReturnQuery => RETURN_QUERY,
- })
- }
- KeyCode::Down => {
- let i = self.results_state.selected().saturating_sub(1);
- self.results_state.select(i);
- }
- KeyCode::Char('n' | 'j') if ctrl => {
- let i = self.results_state.selected().saturating_sub(1);
- self.results_state.select(i);
- }
- KeyCode::Up => {
- let i = self.results_state.selected() + 1;
- self.results_state.select(i.min(len - 1));
- }
- KeyCode::Char('p' | 'k') if ctrl => {
- let i = self.results_state.selected() + 1;
- self.results_state.select(i.min(len - 1));
- }
- KeyCode::Char(c) => self.search.input.insert(c),
- KeyCode::PageDown => {
- let scroll_len = self.results_state.max_entries() - settings.scroll_context_lines;
- let i = self.results_state.selected().saturating_sub(scroll_len);
- self.results_state.select(i);
- }
- KeyCode::PageUp => {
- let scroll_len = self.results_state.max_entries() - settings.scroll_context_lines;
- let i = self.results_state.selected() + scroll_len;
- self.results_state.select(i.min(len - 1));
- }
- _ => {}
- };
-
- None
- }
-
- #[allow(clippy::cast_possible_truncation)]
- #[allow(clippy::bool_to_int_with_if)]
- fn draw<T: Backend>(
- &mut self,
- f: &mut Frame<'_, T>,
- results: &[History],
- compact: bool,
- show_preview: bool,
- ) {
- let border_size = if compact { 0 } else { 1 };
- let preview_width = f.size().width - 2;
- let preview_height = if show_preview {
- let longest_command = results
- .iter()
- .max_by(|h1, h2| h1.command.len().cmp(&h2.command.len()));
- longest_command.map_or(0, |v| {
- std::cmp::min(
- 4,
- (v.command.len() as u16 + preview_width - 1 - border_size)
- / (preview_width - border_size),
- )
- }) + border_size * 2
- } else if compact {
- 0
- } else {
- 1
- };
- let show_help = !compact || f.size().height > 1;
- let chunks = Layout::default()
- .direction(Direction::Vertical)
- .margin(0)
- .horizontal_margin(1)
- .constraints(
- [
- Constraint::Length(if show_help { 1 } else { 0 }),
- Constraint::Min(1),
- Constraint::Length(1 + border_size),
- Constraint::Length(preview_height),
- ]
- .as_ref(),
- )
- .split(f.size());
-
- let header_chunks = Layout::default()
- .direction(Direction::Horizontal)
- .constraints(
- [
- Constraint::Ratio(1, 3),
- Constraint::Ratio(1, 3),
- Constraint::Ratio(1, 3),
- ]
- .as_ref(),
- )
- .split(chunks[0]);
-
- let title = self.build_title();
- f.render_widget(title, header_chunks[0]);
-
- let help = self.build_help();
- f.render_widget(help, header_chunks[1]);
-
- let stats = self.build_stats();
- f.render_widget(stats, header_chunks[2]);
-
- let results_list = Self::build_results_list(compact, results);
- f.render_stateful_widget(results_list, chunks[1], &mut self.results_state);
-
- let input = self.build_input(compact, chunks[2].width.into());
- f.render_widget(input, chunks[2]);
-
- let preview = self.build_preview(results, compact, preview_width, chunks[3].width.into());
- f.render_widget(preview, chunks[3]);
-
- let extra_width = UnicodeWidthStr::width(self.search.input.substring());
-
- let cursor_offset = if compact { 0 } else { 1 };
- f.set_cursor(
- // Put cursor past the end of the input text
- chunks[2].x + extra_width as u16 + PREFIX_LENGTH + 1 + cursor_offset,
- chunks[2].y + cursor_offset,
- );
- }
-
- fn build_title(&mut self) -> Paragraph {
- let title = if self.update_needed.is_some() {
- let version = self.update_needed.clone().unwrap();
-
- Paragraph::new(Text::from(Span::styled(
- format!(" Atuin v{VERSION} - UPDATE AVAILABLE {version}"),
- Style::default().add_modifier(Modifier::BOLD).fg(Color::Red),
- )))
- } else {
- Paragraph::new(Text::from(Span::styled(
- format!(" Atuin v{VERSION}"),
- Style::default().add_modifier(Modifier::BOLD),
- )))
- };
- title
- }
-
- #[allow(clippy::unused_self)]
- fn build_help(&mut self) -> Paragraph {
- let help = Paragraph::new(Text::from(Spans::from(vec![
- Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
- Span::raw(" to exit"),
- ])))
- .style(Style::default().fg(Color::DarkGray))
- .alignment(Alignment::Center);
- help
- }
-
- fn build_stats(&mut self) -> Paragraph {
- let stats = Paragraph::new(Text::from(Span::raw(format!(
- "history count: {}",
- self.history_count,
- ))))
- .style(Style::default().fg(Color::DarkGray))
- .alignment(Alignment::Right);
- stats
- }
-
- fn build_results_list(compact: bool, results: &[History]) -> HistoryList {
- let results_list = if compact {
- HistoryList::new(results)
- } else {
- HistoryList::new(results).block(
- Block::default()
- .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
- .border_type(BorderType::Rounded),
- )
- };
- results_list
- }
-
- fn build_input(&mut self, compact: bool, chunk_width: usize) -> Paragraph {
- /// Max width of the UI box showing current mode
- const MAX_WIDTH: usize = 14;
- let (pref, mode) = if self.switched_search_mode {
- (" SRCH:", self.search_mode.as_str())
- } else {
- ("", self.search.filter_mode.as_str())
- };
- let mode_width = MAX_WIDTH - pref.len();
- // sanity check to ensure we don't exceed the layout limits
- debug_assert!(mode_width >= mode.len(), "mode name '{mode}' is too long!");
- let input = format!("[{pref}{mode:^mode_width$}] {}", self.search.input.as_str(),);
- let input = if compact {
- Paragraph::new(input)
- } else {
- Paragraph::new(input).block(
- Block::default()
- .borders(Borders::LEFT | Borders::RIGHT)
- .border_type(BorderType::Rounded)
- .title(format!("{:─>width$}", "", width = chunk_width - 2)),
- )
- };
- input
- }
-
- fn build_preview(
- &mut self,
- results: &[History],
- compact: bool,
- preview_width: u16,
- chunk_width: usize,
- ) -> Paragraph {
- let selected = self.results_state.selected();
- let command = if results.is_empty() {
- String::new()
- } else {
- use itertools::Itertools as _;
- let s = &results[selected].command;
- s.char_indices()
- .step_by(preview_width.into())
- .map(|(i, _)| i)
- .chain(Some(s.len()))
- .tuple_windows()
- .map(|(a, b)| &s[a..b])
- .join("\n")
- };
- let preview = if compact {
- Paragraph::new(command).style(Style::default().fg(Color::DarkGray))
- } else {
- Paragraph::new(command).block(
- Block::default()
- .borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT)
- .border_type(BorderType::Rounded)
- .title(format!("{:─>width$}", "", width = chunk_width - 2)),
- )
- };
- preview
- }
-}
-
-struct Stdout {
- stdout: std::io::Stdout,
- inline_mode: bool,
-}
-
-impl Stdout {
- pub fn new(inline_mode: bool) -> std::io::Result<Self> {
- terminal::enable_raw_mode()?;
- let mut stdout = stdout();
- if !inline_mode {
- execute!(stdout, terminal::EnterAlternateScreen)?;
- }
- execute!(
- stdout,
- event::EnableMouseCapture,
- event::EnableBracketedPaste,
- )?;
- Ok(Self {
- stdout,
- inline_mode,
- })
- }
-}
-
-impl Drop for Stdout {
- fn drop(&mut self) {
- if !self.inline_mode {
- execute!(self.stdout, terminal::LeaveAlternateScreen).unwrap();
- }
- execute!(
- self.stdout,
- event::DisableMouseCapture,
- event::DisableBracketedPaste,
- )
- .unwrap();
- terminal::disable_raw_mode().unwrap();
- }
-}
-
-impl Write for Stdout {
- fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
- self.stdout.write(buf)
- }
-
- fn flush(&mut self) -> std::io::Result<()> {
- self.stdout.flush()
- }
-}
-
-// this is a big blob of horrible! clean it up!
-// for now, it works. But it'd be great if it were more easily readable, and
-// modular. I'd like to add some more stats and stuff at some point
-#[allow(clippy::cast_possible_truncation)]
-pub async fn history(
- query: &[String],
- settings: &Settings,
- mut db: impl Database,
-) -> Result<String> {
- let stdout = Stdout::new(settings.inline_height > 0)?;
- let backend = CrosstermBackend::new(stdout);
- let mut terminal = Terminal::with_options(
- backend,
- TerminalOptions {
- viewport: if settings.inline_height > 0 {
- Viewport::Inline(settings.inline_height)
- } else {
- Viewport::Fullscreen
- },
- },
- )?;
-
- let mut input = Cursor::from(query.join(" "));
- // Put the cursor at the end of the query by default
- input.end();
-
- let settings2 = settings.clone();
- let update_needed = tokio::spawn(async move { settings2.needs_update().await }).fuse();
- tokio::pin!(update_needed);
-
- let context = current_context();
-
- let history_count = db.history_count().await?;
-
- let mut app = State {
- history_count,
- results_state: ListState::default(),
- update_needed: None,
- switched_search_mode: false,
- search_mode: settings.search_mode,
- search: SearchState {
- input,
- context,
- filter_mode: if settings.shell_up_key_binding {
- settings
- .filter_mode_shell_up_key_binding
- .unwrap_or(settings.filter_mode)
- } else {
- settings.filter_mode
- },
- },
- engine: engines::engine(settings.search_mode),
- };
-
- let mut results = app.query_results(&mut db).await?;
-
- let index = 'render: loop {
- let compact = match settings.style {
- atuin_client::settings::Style::Auto => {
- terminal.size().map(|size| size.height < 14).unwrap_or(true)
- }
- atuin_client::settings::Style::Compact => true,
- atuin_client::settings::Style::Full => false,
- };
- terminal.draw(|f| app.draw(f, &results, compact, settings.show_preview))?;
-
- let initial_input = app.search.input.as_str().to_owned();
- let initial_filter_mode = app.search.filter_mode;
- let initial_search_mode = app.search_mode;
-
- let event_ready = tokio::task::spawn_blocking(|| event::poll(Duration::from_millis(250)));
-
- tokio::select! {
- event_ready = event_ready => {
- if event_ready?? {
- loop {
- if let Some(i) = app.handle_input(settings, &event::read()?, results.len()) {
- break 'render i;
- }
- if !event::poll(Duration::ZERO)? {
- break;
- }
- }
- }
- }
- update_needed = &mut update_needed => {
- app.update_needed = update_needed?;
- }
- }
-
- if initial_input != app.search.input.as_str()
- || initial_filter_mode != app.search.filter_mode
- || initial_search_mode != app.search_mode
- {
- results = app.query_results(&mut db).await?;
- }
- };
-
- if settings.inline_height > 0 {
- terminal.clear()?;
- }
-
- if index < results.len() {
- // index is in bounds so we return that entry
- Ok(results.swap_remove(index).command)
- } else if index == RETURN_ORIGINAL {
- Ok(String::new())
- } else {
- // Either:
- // * index == RETURN_QUERY, in which case we should return the input
- // * out of bounds -> usually implies no selected entry so we return the input
- Ok(app.search.input.into_inner())
- }
-}
diff --git a/src/command/client/stats.rs b/src/command/client/stats.rs
deleted file mode 100644
index 5134f22f..00000000
--- a/src/command/client/stats.rs
+++ /dev/null
@@ -1,181 +0,0 @@
-use std::collections::{HashMap, HashSet};
-
-use chrono::{prelude::*, Duration};
-use clap::Parser;
-use crossterm::style::{Color, ResetColor, SetAttribute, SetForegroundColor};
-use eyre::{bail, Result};
-use interim::parse_date_string;
-
-use atuin_client::{
- database::{current_context, Database},
- history::History,
- settings::{FilterMode, Settings},
-};
-
-#[derive(Parser)]
-#[command(infer_subcommands = true)]
-pub struct Cmd {
- /// compute statistics for the specified period, leave blank for statistics since the beginning
- period: Vec<String>,
-
- /// How many top commands to list
- #[arg(long, short, default_value = "10")]
- count: usize,
-}
-
-fn compute_stats(history: &[History], count: usize) -> Result<()> {
- let mut commands = HashSet::<&str>::with_capacity(history.len());
- let mut prefixes = HashMap::<&str, usize>::with_capacity(history.len());
- for i in history {
- // just in case it somehow has a leading tab or space or something (legacy atuin didn't ignore space prefixes)
- let command = i.command.trim();
- commands.insert(command);
- *prefixes.entry(interesting_command(command)).or_default() += 1;
- }
-
- let unique = commands.len();
- let mut top = prefixes.into_iter().collect::<Vec<_>>();
- top.sort_unstable_by_key(|x| std::cmp::Reverse(x.1));
- top.truncate(count);
- if top.is_empty() {
- bail!("No commands found");
- }
-
- let max = top.iter().map(|x| x.1).max().unwrap();
- let num_pad = max.ilog10() as usize + 1;
-
- for (command, count) in top {
- let gray = SetForegroundColor(Color::Grey);
- let bold = SetAttribute(crossterm::style::Attribute::Bold);
-
- let in_ten = 10 * count / max;
- print!("[");
- print!("{}", SetForegroundColor(Color::Red));
- for i in 0..in_ten {
- if i == 2 {
- print!("{}", SetForegroundColor(Color::Yellow));
- }
- if i == 5 {
- print!("{}", SetForegroundColor(Color::Green));
- }
- print!("▮");
- }
- for _ in in_ten..10 {
- print!(" ");
- }
-
- println!("{ResetColor}] {gray}{count:num_pad$}{ResetColor} {bold}{command}{ResetColor}");
- }
- println!("Total commands: {}", history.len());
- println!("Unique commands: {unique}");
-
- Ok(())
-}
-
-impl Cmd {
- pub async fn run(&self, db: &mut impl Database, settings: &Settings) -> Result<()> {
- let context = current_context();
- let words = if self.period.is_empty() {
- String::from("all")
- } else {
- self.period.join(" ")
- };
- let history = if words.as_str() == "all" {
- db.list(FilterMode::Global, &context, None, false).await?
- } else if words.trim() == "today" {
- let start = Local::now().date().and_hms(0, 0, 0);
- let end = start + Duration::days(1);
- db.range(start.into(), end.into()).await?
- } else if words.trim() == "month" {
- let end = Local::now().date().and_hms(0, 0, 0);
- let start = end - Duration::days(31);
- db.range(start.into(), end.into()).await?
- } else if words.trim() == "week" {
- let end = Local::now().date().and_hms(0, 0, 0);
- let start = end - Duration::days(7);
- db.range(start.into(), end.into()).await?
- } else if words.trim() == "year" {
- let end = Local::now().date().and_hms(0, 0, 0);
- let start = end - Duration::days(365);
- db.range(start.into(), end.into()).await?
- } else {
- let start = parse_date_string(&words, Local::now(), settings.dialect.into())?;
- let end = start + Duration::days(1);
- db.range(start.into(), end.into()).await?
- };
- compute_stats(&history, self.count)?;
- Ok(())
- }
-}
-
-// TODO: make this configurable?
-static COMMON_COMMAND_PREFIX: &[&str] = &["sudo"];
-static COMMON_SUBCOMMAND_PREFIX: &[&str] = &["cargo", "go", "git", "npm", "yarn", "pnpm"];
-
-fn first_non_whitespace(s: &str) -> Option<usize> {
- s.char_indices()
- // find the first non whitespace char
- .find(|(_, c)| !c.is_ascii_whitespace())
- // return the index of that char
- .map(|(i, _)| i)
-}
-
-fn first_whitespace(s: &str) -> usize {
- s.char_indices()
- // find the first whitespace char
- .find(|(_, c)| c.is_ascii_whitespace())
- // return the index of that char, (or the max length of the string)
- .map_or(s.len(), |(i, _)| i)
-}
-
-fn interesting_command(mut command: &str) -> &str {
- // compute command prefix
- // we loop here because we might be working with a common command prefix (eg sudo) that we want to trim off
- let (i, prefix) = loop {
- let i = first_whitespace(command);
- let prefix = &command[..i];
-
- // is it a common prefix
- if COMMON_COMMAND_PREFIX.contains(&prefix) {
- command = command[i..].trim_start();
- if command.is_empty() {
- // no commands following, just use the prefix
- return prefix;
- }
- } else {
- break (i, prefix);
- }
- };
-
- // compute subcommand
- let subcommand_indices = command
- // after the end of the command prefix
- .get(i..)
- // find the first non whitespace character (start of subcommand)
- .and_then(first_non_whitespace)
- // then find the end of that subcommand
- .map(|j| i + j + first_whitespace(&command[i + j..]));
-
- match subcommand_indices {
- // if there is a subcommand and it's a common one, then count the full prefix + subcommand
- Some(end) if COMMON_SUBCOMMAND_PREFIX.contains(&prefix) => &command[..end],
- // otherwise just count the main command
- _ => prefix,
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::interesting_command;
-
- #[test]
- fn interesting_commands() {
- assert_eq!(interesting_command("cargo"), "cargo");
- assert_eq!(interesting_command("cargo build foo bar"), "cargo build");
- assert_eq!(
- interesting_command("sudo cargo build foo bar"),
- "cargo build"
- );
- assert_eq!(interesting_command("sudo"), "sudo");
- }
-}
diff --git a/src/command/client/sync.rs b/src/command/client/sync.rs
deleted file mode 100644
index 419177a5..00000000
--- a/src/command/client/sync.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-use clap::Subcommand;
-use eyre::{Result, WrapErr};
-
-use atuin_client::{database::Database, settings::Settings};
-
-mod login;
-mod logout;
-mod register;
-mod status;
-
-#[derive(Subcommand)]
-#[command(infer_subcommands = true)]
-pub enum Cmd {
- /// Sync with the configured server
- Sync {
- /// Force re-download everything
- #[arg(long, short)]
- force: bool,
- },
-
- /// Login to the configured server
- Login(login::Cmd),
-
- /// Log out
- Logout,
-
- /// Register with the configured server
- Register(register::Cmd),
-
- /// Print the encryption key for transfer to another machine
- Key {
- /// Switch to base64 output of the key
- #[arg(long)]
- base64: bool,
- },
-
- Status,
-}
-
-impl Cmd {
- pub async fn run(self, settings: Settings, db: &mut impl Database) -> Result<()> {
- match self {
- Self::Sync { force } => run(&settings, force, db).await,
- Self::Login(l) => l.run(&settings).await,
- Self::Logout => logout::run(&settings),
- Self::Register(r) => r.run(&settings).await,
- Self::Status => status::run(&settings, db).await,
- Self::Key { base64 } => {
- use atuin_client::encryption::{encode_key, load_key};
- let key = load_key(&settings).wrap_err("could not load encryption key")?;
-
- if base64 {
- let encode = encode_key(key).wrap_err("could not encode encryption key")?;
- println!("{encode}");
- } else {
- let mnemonic = bip39::Mnemonic::from_entropy(&key.0, bip39::Language::English)
- .map_err(|_| eyre::eyre!("invalid key"))?;
- println!("{mnemonic}");
- }
- Ok(())
- }
- }
- }
-}
-
-async fn run(settings: &Settings, force: bool, db: &mut impl Database) -> Result<()> {
- atuin_client::sync::sync(settings, force, db).await?;
- println!(
- "Sync complete! {} items in database, force: {}",
- db.history_count().await?,
- force
- );
- Ok(())
-}
diff --git a/src/command/client/sync/login.rs b/src/command/client/sync/login.rs
deleted file mode 100644
index 6aa2d847..00000000
--- a/src/command/client/sync/login.rs
+++ /dev/null
@@ -1,147 +0,0 @@
-use std::{io, path::PathBuf};
-
-use clap::Parser;
-use eyre::{bail, Context, ContextCompat, Result};
-use tokio::{fs::File, io::AsyncWriteExt};
-
-use atuin_client::{
- api_client,
- encryption::{decode_key, encode_key, new_key, Key},
- settings::Settings,
-};
-use atuin_common::api::LoginRequest;
-use rpassword::prompt_password;
-
-#[derive(Parser)]
-pub struct Cmd {
- #[clap(long, short)]
- pub username: Option<String>,
-
- #[clap(long, short)]
- pub password: Option<String>,
-
- /// The encryption key for your account
- #[clap(long, short)]
- pub key: Option<String>,
-}
-
-fn get_input() -> Result<String> {
- let mut input = String::new();
- io::stdin().read_line(&mut input)?;
- Ok(input.trim_end_matches(&['\r', '\n'][..]).to_string())
-}
-
-impl Cmd {
- pub async fn run(&self, settings: &Settings) -> Result<()> {
- let session_path = settings.session_path.as_str();
-
- if PathBuf::from(session_path).exists() {
- println!(
- "You are already logged in! Please run 'atuin logout' if you wish to login again"
- );
-
- return Ok(());
- }
-
- let username = or_user_input(&self.username, "username");
- let key = or_user_input(&self.key, "encryption key [blank to use existing key file]");
- let password = self.password.clone().unwrap_or_else(read_user_password);
-
- let key_path = settings.key_path.as_str();
- if key.is_empty() {
- if PathBuf::from(key_path).exists() {
- let bytes = fs_err::read_to_string(key_path)
- .context("existing key file couldn't be read")?;
- if decode_key(bytes).is_err() {
- bail!("the key in existing key file was invalid");
- }
- } else {
- println!("No key file exists, creating a new");
- let _key = new_key(settings)?;
- }
- } else {
- // try parse the key as a mnemonic...
- let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) {
- Ok(mnemonic) => encode_key(
- Key::from_slice(mnemonic.entropy())
- .context("key was not the correct length")?,
- )?,
- Err(err) => {
- if let Some(err) = err.downcast_ref::<bip39::ErrorKind>() {
- match err {
- // assume they copied in the base64 key
- bip39::ErrorKind::InvalidWord => key,
- bip39::ErrorKind::InvalidChecksum => {
- bail!("key mnemonic was not valid")
- }
- bip39::ErrorKind::InvalidKeysize(_)
- | bip39::ErrorKind::InvalidWordLength(_)
- | bip39::ErrorKind::InvalidEntropyLength(_, _) => {
- bail!("key was not the correct length")
- }
- }
- } else {
- // unknown error. assume they copied the base64 key
- key
- }
- }
- };
-
- if decode_key(key.clone()).is_err() {
- bail!("the specified key was invalid");
- }
-
- let mut file = File::create(key_path).await?;
- file.write_all(key.as_bytes()).await?;
- }
-
- let session = api_client::login(
- settings.sync_address.as_str(),
- LoginRequest { username, password },
- )
- .await?;
-
- let session_path = settings.session_path.as_str();
- let mut file = File::create(session_path).await?;
- file.write_all(session.session.as_bytes()).await?;
-
- println!("Logged in!");
-
- Ok(())
- }
-}
-
-pub(super) fn or_user_input(value: &'_ Option<String>, name: &'static str) -> String {
- value.clone().unwrap_or_else(|| read_user_input(name))
-}
-
-pub(super) fn read_user_password() -> String {
- let password = prompt_password("Please enter password: ");
- password.expect("Failed to read from input")
-}
-
-fn read_user_input(name: &'static str) -> String {
- eprint!("Please enter {name}: ");
- get_input().expect("Failed to read from input")
-}
-
-#[cfg(test)]
-mod tests {
- use atuin_client::encryption::Key;
-
- #[test]
- fn mnemonic_round_trip() {
- let key = Key {
- 0: [
- 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3,
- 2, 7, 9, 5,
- ],
- };
- let phrase = bip39::Mnemonic::from_entropy(&key.0, bip39::Language::English)
- .unwrap()
- .into_phrase();
- let mnemonic = bip39::Mnemonic::from_phrase(&phrase, bip39::Language::English).unwrap();
- assert_eq!(mnemonic.entropy(), &key.0);
- assert_eq!(phrase, "adapt amused able anxiety mother adapt beef gaze amount else seat alcohol cage lottery avoid scare alcohol cactus school avoid coral adjust catch pink");
- }
-}
diff --git a/src/command/client/sync/logout.rs b/src/command/client/sync/logout.rs
deleted file mode 100644
index 90b49d6d..00000000
--- a/src/command/client/sync/logout.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use std::path::PathBuf;
-
-use eyre::{Context, Result};
-use fs_err::remove_file;
-
-use atuin_client::settings::Settings;
-
-pub fn run(settings: &Settings) -> Result<()> {
- let session_path = settings.session_path.as_str();
-
- if PathBuf::from(session_path).exists() {
- remove_file(session_path).context("Failed to remove session file")?;
- println!("You have logged out!");
- } else {
- println!("You are not logged in");
- }
-
- Ok(())
-}
diff --git a/src/command/client/sync/register.rs b/src/command/client/sync/register.rs
deleted file mode 100644
index 6b51fac8..00000000
--- a/src/command/client/sync/register.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use clap::Parser;
-use eyre::Result;
-use tokio::{fs::File, io::AsyncWriteExt};
-
-use atuin_client::{api_client, settings::Settings};
-
-#[derive(Parser)]
-pub struct Cmd {
- #[clap(long, short)]
- pub username: Option<String>,
-
- #[clap(long, short)]
- pub password: Option<String>,
-
- #[clap(long, short)]
- pub email: Option<String>,
-}
-
-impl Cmd {
- pub async fn run(self, settings: &Settings) -> Result<()> {
- run(settings, &self.username, &self.email, &self.password).await
- }
-}
-
-pub async fn run(
- settings: &Settings,
- username: &Option<String>,
- email: &Option<String>,
- password: &Option<String>,
-) -> Result<()> {
- use super::login::or_user_input;
- let username = or_user_input(username, "username");
- let email = or_user_input(email, "email");
- let password = password
- .clone()
- .unwrap_or_else(super::login::read_user_password);
-
- let session =
- api_client::register(settings.sync_address.as_str(), &username, &email, &password).await?;
-
- let path = settings.session_path.as_str();
- let mut file = File::create(path).await?;
- file.write_all(session.session.as_bytes()).await?;
-
- // Create a new key, and save it to disk
- let _key = atuin_client::encryption::new_key(settings)?;
-
- Ok(())
-}
diff --git a/src/command/client/sync/status.rs b/src/command/client/sync/status.rs
deleted file mode 100644
index b3e73e8e..00000000
--- a/src/command/client/sync/status.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-use atuin_client::{
- api_client, database::Database, encryption::load_encoded_key, settings::Settings,
-};
-use colored::Colorize;
-use eyre::Result;
-
-pub async fn run(settings: &Settings, db: &impl Database) -> Result<()> {
- let client = api_client::Client::new(
- &settings.sync_address,
- &settings.session_token,
- load_encoded_key(settings)?,
- )?;
-
- let status = client.status().await?;
- let last_sync = Settings::last_sync()?;
- let local_count = db.history_count().await?;
-
- println!("{}", "[Local]".green());
-
- if settings.auto_sync {
- println!("Sync frequency: {}", settings.sync_frequency);
- println!("Last sync: {last_sync}");
- }
-
- println!("History count: {local_count}\n");
-
- if settings.auto_sync {
- println!("{}", "[Remote]".green());
- println!("Address: {}", settings.sync_address);
- println!("Username: {}", status.username);
- println!("History count: {}", status.count);
- }
-
- Ok(())
-}
diff --git a/src/command/contributors.rs b/src/command/contributors.rs
deleted file mode 100644
index 6f21b5fe..00000000
--- a/src/command/contributors.rs
+++ /dev/null
@@ -1,75 +0,0 @@
-const CONTRIBUTORS: &str = r#"
-Baptiste
-Benjamin Vergnaud
-Brad Robel-Forrest <brad@bitpony.com>
-Bruce Huang <helbingxxx@gmail.com>
-Conrad Ludgate <conradludgate@gmail.com>
-CosmicHorror <LovecraftianHorror@pm.me>
-Daniel <daniel.hub@outlook.de>
-Ellie Huxtable <ellie@elliehuxtable.com>
-Eric Crosson
-Eric Ripa <eric@ripa.io>
-Erwin Kroon
-Evan Purkhiser <evanpurkhiser@gmail.com>
-Frank Hamand <frankhamand@gmail.com>
-Herby Gillot <herby.gillot@gmail.com>
-Ian Smith <ismith@mit.edu>
-Ilkin Bayramli
-Violet Shreve <github@shreve.io>
-Jakob Schrettenbrunner <dev@schrej.net>
-Jakob-Niklas See <github@nwex.de>
-Jakub Jirutka <jakub@jirutka.cz>
-Jakub Panek <me@panekj.dev>
-Jamie Quigley <jamie@quigley.xyz>
-Jannik <jannik.peters@posteo.de>
-Jerome Ducret <jdiphone34@gmail.com>
-Johannes Baiter <johannes.baiter@gmail.com>
-Klas Mellbourn <klas@mellbourn.net>
-Laurent le Beau-Martin
-Lucas Burns
-Lucy <lucy@absolucy.moe>
-Luke Baker <lukebaker@gmail.com>
-Manel Vilar <manelvf@gmail.com>
-Mark Wotton <mwotton@gmail.com>
-Martin Indra <martin.indra@mgn.cz>
-Martin Junghanns <m.junghanns@mailbox.org>
-Mat Jones <mat@mjones.network>
-Michael Bianco <iloveitaly@gmail.com>
-Michael Mior <michael.mior@gmail.com>
-Omer Katz <omer.drow@gmail.com>
-Orhun Parmaksız <orhunparmaksiz@gmail.com>
-Patrick
-Patrick Decat <pdecat@gmail.com>
-Patrick Jackson <patrick@jackson.dev>
-Plamen Dimitrov <pdimitrov@pevogam.com>
-Sam Edwards <sam@samedwards.ca>
-Sam Lanning <sam@samlanning.com>
-Sandro <sandro.jaeckel@gmail.com>
-Satyarth Sampath <satyarth.23@gmail.com>
-Simon Elsbrock <simon@iodev.org>
-Tobias Hunger <tobias.hunger@gmail.com>
-Trygve Aaberge <trygveaa@gmail.com>
-TymanWasTaken <tbeckman530@gmail.com>
-Ubiquitous Photon
-Webmaster At Cosmic DNA
-Will Fancher <elvishjerricco@gmail.com>
-Yolo <noah.chang@outlook.com>
-Yuvi Panda <yuvipanda@gmail.com>
-ZhiHong Li <joker_lizhih@163.com>
-avinassh
-b3nj5m1n
-c-14 <git@c-14.de>
-frukto <fruktopus@gmail.com>
-jean-santos <jeanpnsantos@gmail.com>
-lchausmann <jazz-github@zqz.dk>
-mb6ockatf
-morguldir <morguldir@protonmail.com>
-mundry
-noyez <noyez@ithryn.net>
-wpbrz
-xfzv
-"#;
-
-pub fn run() {
- println!("{CONTRIBUTORS}");
-}
diff --git a/src/command/init.rs b/src/command/init.rs
deleted file mode 100644
index a9c24b09..00000000
--- a/src/command/init.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-use clap::{Parser, ValueEnum};
-
-#[derive(Parser)]
-pub struct Cmd {
- shell: Shell,
-
- /// Disable the binding of CTRL-R to atuin
- #[clap(long)]
- disable_ctrl_r: bool,
-
- /// Disable the binding of the Up Arrow key to atuin
- #[clap(long)]
- disable_up_arrow: bool,
-}
-
-#[derive(Clone, Copy, ValueEnum)]
-pub enum Shell {
- /// Zsh setup
- Zsh,
- /// Bash setup
- Bash,
- /// Fish setup
- Fish,
- /// Nu setup
- Nu,
-}
-
-impl Cmd {
- fn init_zsh(&self) {
- let base = include_str!("../shell/atuin.zsh");
-
- println!("{base}");
-
- if std::env::var("ATUIN_NOBIND").is_err() {
- const BIND_CTRL_R: &str = "bindkey '^r' _atuin_search_widget";
- const BIND_UP_ARROW: &str = "bindkey '^[[A' _atuin_up_search_widget
-bindkey '^[OA' _atuin_up_search_widget";
- if !self.disable_ctrl_r {
- println!("{BIND_CTRL_R}");
- }
- if !self.disable_up_arrow {
- println!("{BIND_UP_ARROW}");
- }
- }
- }
-
- fn init_bash(&self) {
- let base = include_str!("../shell/atuin.bash");
- println!("{base}");
-
- if std::env::var("ATUIN_NOBIND").is_err() {
- const BIND_CTRL_R: &str = r#"bind -x '"\C-r": __atuin_history'"#;
- const BIND_UP_ARROW: &str = r#"bind -x '"\e[A": __atuin_history --shell-up-key-binding'
-bind -x '"\eOA": __atuin_history --shell-up-key-binding'"#;
- if !self.disable_ctrl_r {
- println!("{BIND_CTRL_R}");
- }
- if !self.disable_up_arrow {
- println!("{BIND_UP_ARROW}");
- }
- }
- }
-
- fn init_fish(&self) {
- let full = include_str!("../shell/atuin.fish");
- println!("{full}");
-
- if std::env::var("ATUIN_NOBIND").is_err() {
- const BIND_CTRL_R: &str = r"bind \cr _atuin_search";
- const BIND_UP_ARROW: &str = r"bind -k up _atuin_bind_up
-bind \eOA _atuin_bind_up
-bind \e\[A _atuin_bind_up";
- const BIND_CTRL_R_INS: &str = r"bind -M insert \cr _atuin_search";
- const BIND_UP_ARROW_INS: &str = r"bind -M insert -k up _atuin_bind_up
-bind -M insert \eOA _atuin_bind_up
-bind -M insert \e\[A _atuin_bind_up";
-
- if !self.disable_ctrl_r {
- println!("{BIND_CTRL_R}");
- }
- if !self.disable_up_arrow {
- println!("{BIND_UP_ARROW}");
- }
-
- println!("if bind -M insert > /dev/null 2>&1");
- if !self.disable_ctrl_r {
- println!("{BIND_CTRL_R_INS}");
- }
- if !self.disable_up_arrow {
- println!("{BIND_UP_ARROW_INS}");
- }
- println!("end");
- }
- }
-
- fn init_nu(&self) {
- let full = include_str!("../shell/atuin.nu");
- println!("{full}");
-
- if std::env::var("ATUIN_NOBIND").is_err() {
- const BIND_CTRL_R: &str = r#"let-env config = (
- $env.config | upsert keybindings (
- $env.config.keybindings
- | append {
- name: atuin
- modifier: control
- keycode: char_r
- mode: [emacs, vi_normal, vi_insert]
- event: { send: executehostcommand cmd: (_atuin_search_cmd) }
- }
- )
-)
-"#;
- const BIND_UP_ARROW: &str = r#"let-env config = (
- $env.config | upsert keybindings (
- $env.config.keybindings
- | append {
- name: atuin
- modifier: none
- keycode: up
- mode: [emacs, vi_normal, vi_insert]
- event: { send: executehostcommand cmd: (_atuin_search_cmd '--shell-up-key-binding') }
- }
- )
-)
-"#;
- if !self.disable_ctrl_r {
- println!("{BIND_CTRL_R}");
- }
- if !self.disable_up_arrow {
- println!("{BIND_UP_ARROW}");
- }
- }
- }
-
- pub fn run(self) {
- match self.shell {
- Shell::Zsh => self.init_zsh(),
- Shell::Bash => self.init_bash(),
- Shell::Fish => self.init_fish(),
- Shell::Nu => self.init_nu(),
- }
- }
-}
diff --git a/src/command/mod.rs b/src/command/mod.rs
deleted file mode 100644
index 4ed1691a..00000000
--- a/src/command/mod.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-use clap::{CommandFactory, Subcommand};
-use clap_complete::{generate, generate_to, Shell};
-use eyre::Result;
-
-#[cfg(feature = "client")]
-mod client;
-
-#[cfg(feature = "server")]
-mod server;
-
-mod init;
-
-mod contributors;
-
-#[derive(Subcommand)]
-#[command(infer_subcommands = true)]
-pub enum AtuinCmd {
- #[cfg(feature = "client")]
- #[command(flatten)]
- Client(client::Cmd),
-
- /// Start an atuin server
- #[cfg(feature = "server")]
- #[command(subcommand)]
- Server(server::Cmd),
-
- /// Output shell setup
- Init(init::Cmd),
-
- /// Generate a UUID
- Uuid,
-
- Contributors,
-
- /// Generate shell completions
- GenCompletions {
- /// Set the shell for generating completions
- #[arg(long, short)]
- shell: Shell,
-
- /// Set the output directory
- #[arg(long, short)]
- out_dir: Option<String>,
- },
-}
-
-impl AtuinCmd {
- pub fn run(self) -> Result<()> {
- match self {
- #[cfg(feature = "client")]
- Self::Client(client) => client.run(),
- #[cfg(feature = "server")]
- Self::Server(server) => server.run(),
- Self::Contributors => {
- contributors::run();
- Ok(())
- }
- Self::Init(init) => {
- init.run();
- Ok(())
- }
- Self::Uuid => {
- println!("{}", atuin_common::utils::uuid_v7().as_simple());
- Ok(())
- }
- Self::GenCompletions { shell, out_dir } => {
- let mut cli = crate::Atuin::command();
-
- match out_dir {
- Some(out_dir) => {
- generate_to(shell, &mut cli, env!("CARGO_PKG_NAME"), &out_dir)?;
- }
- None => {
- generate(
- shell,
- &mut cli,
- env!("CARGO_PKG_NAME"),
- &mut std::io::stdout(),
- );
- }
- }
-
- Ok(())
- }
- }
- }
-}
diff --git a/src/command/server.rs b/src/command/server.rs
deleted file mode 100644
index 495f85d0..00000000
--- a/src/command/server.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-use tracing_subscriber::{fmt, prelude::*, EnvFilter};
-
-use clap::Parser;
-use eyre::{Context, Result};
-
-use atuin_server::{launch, settings::Settings};
-
-#[derive(Parser)]
-#[clap(infer_subcommands = true)]
-pub enum Cmd {
- /// Start the server
- Start {
- /// The host address to bind
- #[clap(long)]
- host: Option<String>,
-
- /// The port to bind
- #[clap(long, short)]
- port: Option<u16>,
- },
-}
-
-impl Cmd {
- #[tokio::main]
- pub async fn run(self) -> Result<()> {
- tracing_subscriber::registry()
- .with(fmt::layer())
- .with(EnvFilter::from_default_env())
- .init();
-
- let settings = Settings::new().wrap_err("could not load server settings")?;
-
- match self {
- Self::Start { host, port } => {
- let host = host
- .as_ref()
- .map_or(settings.host.clone(), std::string::ToString::to_string);
- let port = port.map_or(settings.port, |p| p);
-
- launch(settings, host, port).await
- }
- }
- }
-}
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index 9e570337..00000000
--- a/src/main.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-#![warn(clippy::pedantic, clippy::nursery)]
-#![allow(clippy::use_self, clippy::missing_const_for_fn)] // not 100% reliable
-
-use clap::Parser;
-use eyre::Result;
-
-use command::AtuinCmd;
-mod command;
-
-#[allow(clippy::all)]
-mod ratatui;
-
-const VERSION: &str = env!("CARGO_PKG_VERSION");
-
-static HELP_TEMPLATE: &str = "\
-{before-help}{name} {version}
-{author}
-{about}
-
-{usage-heading}
- {usage}
-
-{all-args}{after-help}";
-
-/// Magical shell history
-#[derive(Parser)]
-#[command(
- author = "Ellie Huxtable <e@elm.sh>",
- version = VERSION,
- help_template(HELP_TEMPLATE),
-)]
-struct Atuin {
- #[command(subcommand)]
- atuin: AtuinCmd,
-}
-
-impl Atuin {
- fn run(self) -> Result<()> {
- self.atuin.run()
- }
-}
-
-fn main() -> Result<()> {
- Atuin::parse().run()
-}
diff --git a/src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md b/src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 0a0f4bc6..00000000
--- a/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: ''
----
-
-<!--
-Hi there, sorry `ratatui` is not working as expected.
-Please fill this bug report conscientiously.
-A detailed and complete issue is more likely to be processed quickly.
--->
-
-## Description
-<!--
-A clear and concise description of what the bug is.
--->
-
-
-## To Reproduce
-<!--
-Try to reduce the issue to a simple code sample exhibiting the problem.
-Ideally, fork the project and add a test or an example.
--->
-
-
-## Expected behavior
-<!--
-A clear and concise description of what you expected to happen.
--->
-
-
-## Screenshots
-<!--
-If applicable, add screenshots, gifs or videos to help explain your problem.
--->
-
-
-## Environment
-<!--
-Add a description of the systems where you are observing the issue. For example:
-- OS: Linux
-- Terminal Emulator: xterm
-- Font: Inconsolata (Patched)
-- Crate version: 0.7
-- Backend: termion
--->
-
-- OS:
-- Terminal Emulator:
-- Font:
-- Crate version:
-- Backend:
-
-## Additional context
-<!--
-Add any other context about the problem here.
-If you already looked into the issue, include all the leads you have explored.
--->
diff --git a/src/ratatui/.github/ISSUE_TEMPLATE/config.yml b/src/ratatui/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 3ba13e0c..00000000
--- a/src/ratatui/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1 +0,0 @@
-blank_issues_enabled: false
diff --git a/src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md b/src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index ae095edb..00000000
--- a/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
-<!--
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
--->
-
-## Solution
-<!--
-A clear and concise description of what you want to happen.
-Things to consider:
-- backward compatibility
-- ease of use of the API (https://rust-lang.github.io/api-guidelines/)
-- consistency with the rest of the crate
--->
-
-## Alternatives
-<!--
-A clear and concise description of any alternative solutions or features you've considered.
--->
-
-## Additional context
-<!--
-Add any other context or screenshots about the feature request here.
--->
diff --git a/src/ratatui/.github/workflows/cd.yml b/src/ratatui/.github/workflows/cd.yml
deleted file mode 100644
index f61e3603..00000000
--- a/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/src/ratatui/.github/workflows/ci.yml b/src/ratatui/.github/workflows/ci.yml
deleted file mode 100644
index bfa363e9..00000000
--- a/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/src/ratatui/.gitignore b/src/ratatui/.gitignore
deleted file mode 100644
index dcb33fbb..00000000
--- a/src/ratatui/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-target
-Cargo.lock
-*.log
-*.rs.rustfmt
-.gdb_history
-.idea/
diff --git a/src/ratatui/LICENSE b/src/ratatui/LICENSE
deleted file mode 100644
index 7a0657cb..00000000
--- a/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/src/ratatui/README.md b/src/ratatui/README.md
deleted file mode 100644
index 05d4adb6..00000000
--- a/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/)
-
-<img src="./assets/demo.gif" alt="Demo cast under Linux Termite with Inconsolata font 12pt">
-
-# 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/src/ratatui/backend/crossterm.rs b/src/ratatui/backend/crossterm.rs
deleted file mode 100644
index 3dceb6ad..00000000
--- a/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<W: Write> {
- buffer: W,
-}
-
-impl<W> CrosstermBackend<W>
-where
- W: Write,
-{
- pub fn new(buffer: W) -> CrosstermBackend<W> {
- CrosstermBackend { buffer }
- }
-}
-
-impl<W> Write for CrosstermBackend<W>
-where
- W: Write,
-{
- fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- self.buffer.write(buf)
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.buffer.flush()
- }
-}
-
-impl<W> Backend for CrosstermBackend<W>
-where
- W: Write,
-{
- fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
- where
- I: Iterator<Item = (u16, u16, &'a Cell)>,
- {
- 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<Rect> {
- 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<Color> 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<W>(&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/src/ratatui/backend/mod.rs b/src/ratatui/backend/mod.rs
deleted file mode 100644
index a360db18..00000000
--- a/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<Item = (u16, u16, &'a Cell)>;
-
- /// 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<Rect, io::Error>;
- fn flush(&mut self) -> Result<(), io::Error>;
-}
diff --git a/src/ratatui/backend/termion.rs b/src/ratatui/backend/termion.rs
deleted file mode 100644
index 76def792..00000000
--- a/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<W>
-where
- W: Write,
-{
- stdout: W,
-}
-
-impl<W> TermionBackend<W>
-where
- W: Write,
-{
- pub fn new(stdout: W) -> TermionBackend<W> {
- TermionBackend { stdout }
- }
-}
-
-impl<W> Write for TermionBackend<W>
-where
- W: Write,
-{
- fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- self.stdout.write(buf)
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.stdout.flush()
- }
-}
-
-impl<W> Backend for TermionBackend<W>
-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<Item = (u16, u16, &'a Cell)>,
- {
- 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<Rect> {
- 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/src/ratatui/buffer.rs b/src/ratatui/buffer.rs
deleted file mode 100644
index b2a988b7..00000000
--- a/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<Cell>,
-}
-
-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<S>(lines: Vec<S>) -> Buffer
- where
- S: AsRef<str>,
- {
- 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<Cell>` 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<S>(&mut self, x: u16, y: u16, string: S, style: Style)
- where
- S: AsRef<str>,
- {
- 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<S>(
- &mut self,
- x: u16,
- y: u16,
- string: S,
- width: usize,
- style: Style,
- ) -> (u16, u16)
- where
- S: AsRef<str>,
- {
- 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/src/ratatui/layout.rs b/src/ratatui/layout.rs
deleted file mode 100644
index f5b14e35..00000000
--- a/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<Constraint>,
- /// 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<Cache> = 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<C>(mut self, constraints: C) -> Layout
- where
- C: Into<Vec<Constraint>>,
- {
- 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<Variable, (usize, usize)> = HashMap::new();
- let elements = layout
- .constraints
- .iter()
- .map(|_| Element::new())
- .collect::<Vec<Element>>();
- let mut res = layout
- .constraints
- .iter()
- .map(|_| Rect::default())
- .collect::<Rc<[Rect]>>();
-
- 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<CassowaryConstraint> =
- 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::<u16>());
- 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/src/ratatui/mod.rs b/src/ratatui/mod.rs
deleted file mode 100644
index d7926b96..00000000
--- a/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<B: Backend>(f: &mut Frame<B>) {
-//! 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/src/ratatui/style.rs b/src/ratatui/style.rs
deleted file mode 100644
index 4d74f6fc..00000000
--- a/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<Color>,
- pub bg: Option<Color>,
- 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<Style> {
- vec![
- Style::default(),
- Style::default().fg(Color::Yellow),
- Style::default().bg(Color::Yellow),
- Style::default().add_modifier(Modifier::BOLD),
- Style::default().remove_modifier(Modifier::BOLD),
- Style::default().add_modifier(Modifier::ITALIC),
- Style::default().remove_modifier(Modifier::ITALIC),
- Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
- Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
- ]
- }
-
- #[test]
- fn combined_patch_gives_same_result_as_individual_patch() {
- let styles = styles();
- for &a in &styles {
- for &b in &styles {
- for &c in &styles {
- for &d in &styles {
- let combined = a.patch(b.patch(c.patch(d)));
-
- assert_eq!(
- Style::default().patch(a).patch(b).patch(c).patch(d),
- Style::default().patch(combined)
- );
- }
- }
- }
- }
- }
-
- #[test]
- fn combine_individual_modifiers() {
- use crate::ratatui::{buffer::Buffer, layout::Rect};
-
- let mods = vec![
- Modifier::BOLD,
- Modifier::DIM,
- Modifier::ITALIC,
- Modifier::UNDERLINED,
- Modifier::SLOW_BLINK,
- Modifier::RAPID_BLINK,
- Modifier::REVERSED,
- Modifier::HIDDEN,
- Modifier::CROSSED_OUT,
- ];
-
- let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
-
- for m in &mods {
- buffer.get_mut(0, 0).set_style(Style::reset());
- buffer
- .get_mut(0, 0)
- .set_style(Style::default().add_modifier(*m));
- let style = buffer.get(0, 0).style();
- assert!(style.add_modifier.contains(*m));
- assert!(!style.sub_modifier.contains(*m));
- }
- }
-}
diff --git a/src/ratatui/symbols.rs b/src/ratatui/symbols.rs
deleted file mode 100644
index 040e77f6..00000000
--- a/src/ratatui/symbols.rs
+++ /dev/null
@@ -1,233 +0,0 @@
-pub mod block {
- pub const FULL: &str = "█";
- pub const SEVEN_EIGHTHS: &str = "▉";
- pub const THREE_QUARTERS: &str = "▊";
- pub const FIVE_EIGHTHS: &str = "▋";
- pub const HALF: &str = "▌";
- pub const THREE_EIGHTHS: &str = "▍";
- pub const ONE_QUARTER: &str = "▎";
- pub const ONE_EIGHTH: &str = "▏";
-
- #[derive(Debug, Clone)]
- pub struct Set {
- pub full: &'static str,
- pub seven_eighths: &'static str,
- pub three_quarters: &'static str,
- pub five_eighths: &'static str,
- pub half: &'static str,
- pub three_eighths: &'static str,
- pub one_quarter: &'static str,
- pub one_eighth: &'static str,
- pub empty: &'static str,
- }
-
- pub const THREE_LEVELS: Set = Set {
- full: FULL,
- seven_eighths: FULL,
- three_quarters: HALF,
- five_eighths: HALF,
- half: HALF,
- three_eighths: HALF,
- one_quarter: HALF,
- one_eighth: " ",
- empty: " ",
- };
-
- pub const NINE_LEVELS: Set = Set {
- full: FULL,
- seven_eighths: SEVEN_EIGHTHS,
- three_quarters: THREE_QUARTERS,
- five_eighths: FIVE_EIGHTHS,
- half: HALF,
- three_eighths: THREE_EIGHTHS,
- one_quarter: ONE_QUARTER,
- one_eighth: ONE_EIGHTH,
- empty: " ",
- };
-}
-
-pub mod bar {
- pub const FULL: &str = "█";
- pub const SEVEN_EIGHTHS: &str = "▇";
- pub const THREE_QUARTERS: &str = "▆";
- pub const FIVE_EIGHTHS: &str = "▅";
- pub const HALF: &str = "▄";
- pub const THREE_EIGHTHS: &str = "▃";
- pub const ONE_QUARTER: &str = "▂";
- pub const ONE_EIGHTH: &str = "▁";
-
- #[derive(Debug, Clone)]
- pub struct Set {
- pub full: &'static str,
- pub seven_eighths: &'static str,
- pub three_quarters: &'static str,
- pub five_eighths: &'static str,
- pub half: &'static str,
- pub three_eighths: &'static str,
- pub one_quarter: &'static str,
- pub one_eighth: &'static str,
- pub empty: &'static str,
- }
-
- pub const THREE_LEVELS: Set = Set {
- full: FULL,
- seven_eighths: FULL,
- three_quarters: HALF,
- five_eighths: HALF,
- half: HALF,
- three_eighths: HALF,
- one_quarter: HALF,
- one_eighth: " ",
- empty: " ",
- };
-
- pub const NINE_LEVELS: Set = Set {
- full: FULL,
- seven_eighths: SEVEN_EIGHTHS,
- three_quarters: THREE_QUARTERS,
- five_eighths: FIVE_EIGHTHS,
- half: HALF,
- three_eighths: THREE_EIGHTHS,
- one_quarter: ONE_QUARTER,
- one_eighth: ONE_EIGHTH,
- empty: " ",
- };
-}
-
-pub mod line {
- pub const VERTICAL: &str = "│";
- pub const DOUBLE_VERTICAL: &str = "║";
- pub const THICK_VERTICAL: &str = "┃";
-
- pub const HORIZONTAL: &str = "─";
- pub const DOUBLE_HORIZONTAL: &str = "═";
- pub const THICK_HORIZONTAL: &str = "━";
-
- pub const TOP_RIGHT: &str = "┐";
- pub const ROUNDED_TOP_RIGHT: &str = "╮";
- pub const DOUBLE_TOP_RIGHT: &str = "╗";
- pub const THICK_TOP_RIGHT: &str = "┓";
-
- pub const TOP_LEFT: &str = "┌";
- pub const ROUNDED_TOP_LEFT: &str = "╭";
- pub const DOUBLE_TOP_LEFT: &str = "╔";
- pub const THICK_TOP_LEFT: &str = "┏";
-
- pub const BOTTOM_RIGHT: &str = "┘";
- pub const ROUNDED_BOTTOM_RIGHT: &str = "╯";
- pub const DOUBLE_BOTTOM_RIGHT: &str = "╝";
- pub const THICK_BOTTOM_RIGHT: &str = "┛";
-
- pub const BOTTOM_LEFT: &str = "└";
- pub const ROUNDED_BOTTOM_LEFT: &str = "╰";
- pub const DOUBLE_BOTTOM_LEFT: &str = "╚";
- pub const THICK_BOTTOM_LEFT: &str = "┗";
-
- pub const VERTICAL_LEFT: &str = "┤";
- pub const DOUBLE_VERTICAL_LEFT: &str = "╣";
- pub const THICK_VERTICAL_LEFT: &str = "┫";
-
- pub const VERTICAL_RIGHT: &str = "├";
- pub const DOUBLE_VERTICAL_RIGHT: &str = "╠";
- pub const THICK_VERTICAL_RIGHT: &str = "┣";
-
- pub const HORIZONTAL_DOWN: &str = "┬";
- pub const DOUBLE_HORIZONTAL_DOWN: &str = "╦";
- pub const THICK_HORIZONTAL_DOWN: &str = "┳";
-
- pub const HORIZONTAL_UP: &str = "┴";
- pub const DOUBLE_HORIZONTAL_UP: &str = "╩";
- pub const THICK_HORIZONTAL_UP: &str = "┻";
-
- pub const CROSS: &str = "┼";
- pub const DOUBLE_CROSS: &str = "╬";
- pub const THICK_CROSS: &str = "╋";
-
- #[derive(Debug, Clone)]
- pub struct Set {
- pub vertical: &'static str,
- pub horizontal: &'static str,
- pub top_right: &'static str,
- pub top_left: &'static str,
- pub bottom_right: &'static str,
- pub bottom_left: &'static str,
- pub vertical_left: &'static str,
- pub vertical_right: &'static str,
- pub horizontal_down: &'static str,
- pub horizontal_up: &'static str,
- pub cross: &'static str,
- }
-
- pub const NORMAL: Set = Set {
- vertical: VERTICAL,
- horizontal: HORIZONTAL,
- top_right: TOP_RIGHT,
- top_left: TOP_LEFT,
- bottom_right: BOTTOM_RIGHT,
- bottom_left: BOTTOM_LEFT,
- vertical_left: VERTICAL_LEFT,
- vertical_right: VERTICAL_RIGHT,
- horizontal_down: HORIZONTAL_DOWN,
- horizontal_up: HORIZONTAL_UP,
- cross: CROSS,
- };
-
- pub const ROUNDED: Set = Set {
- top_right: ROUNDED_TOP_RIGHT,
- top_left: ROUNDED_TOP_LEFT,
- bottom_right: ROUNDED_BOTTOM_RIGHT,
- bottom_left: ROUNDED_BOTTOM_LEFT,
- ..NORMAL
- };
-
- pub const DOUBLE: Set = Set {
- vertical: DOUBLE_VERTICAL,
- horizontal: DOUBLE_HORIZONTAL,
- top_right: DOUBLE_TOP_RIGHT,
- top_left: DOUBLE_TOP_LEFT,
- bottom_right: DOUBLE_BOTTOM_RIGHT,
- bottom_left: DOUBLE_BOTTOM_LEFT,
- vertical_left: DOUBLE_VERTICAL_LEFT,
- vertical_right: DOUBLE_VERTICAL_RIGHT,
- horizontal_down: DOUBLE_HORIZONTAL_DOWN,
- horizontal_up: DOUBLE_HORIZONTAL_UP,
- cross: DOUBLE_CROSS,
- };
-
- pub const THICK: Set = Set {
- vertical: THICK_VERTICAL,
- horizontal: THICK_HORIZONTAL,
- top_right: THICK_TOP_RIGHT,
- top_left: THICK_TOP_LEFT,
- bottom_right: THICK_BOTTOM_RIGHT,
- bottom_left: THICK_BOTTOM_LEFT,
- vertical_left: THICK_VERTICAL_LEFT,
- vertical_right: THICK_VERTICAL_RIGHT,
- horizontal_down: THICK_HORIZONTAL_DOWN,
- horizontal_up: THICK_HORIZONTAL_UP,
- cross: THICK_CROSS,
- };
-}
-
-pub const DOT: &str = "•";
-
-pub mod braille {
- pub const BLANK: u16 = 0x2800;
- pub const DOTS: [[u16; 2]; 4] = [
- [0x0001, 0x0008],
- [0x0002, 0x0010],
- [0x0004, 0x0020],
- [0x0040, 0x0080],
- ];
-}
-
-/// Marker to use when plotting data points
-#[derive(Debug, Clone, Copy)]
-pub enum Marker {
- /// One point per cell in shape of dot
- Dot,
- /// One point per cell in shape of a block
- Block,
- /// Up to 8 points per cell
- Braille,
-}
diff --git a/src/ratatui/terminal.rs b/src/ratatui/terminal.rs
deleted file mode 100644
index 32accffa..00000000
--- a/src/ratatui/terminal.rs
+++ /dev/null
@@ -1,487 +0,0 @@
-use crate::ratatui::{
- backend::{Backend, ClearType},
- buffer::Buffer,
- layout::Rect,
- widgets::{StatefulWidget, Widget},
-};
-use std::io;
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum Viewport {
- Fullscreen,
- Inline(u16),
- Fixed(Rect),
-}
-
-impl Viewport {
- pub fn fixed(area: Rect) -> Viewport {
- Self::Fixed(area)
- }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-/// Options to pass to [`Terminal::with_options`]
-pub struct TerminalOptions {
- /// Viewport used to draw to the terminal
- pub viewport: Viewport,
-}
-
-/// Interface to the terminal backed by Termion
-#[derive(Debug)]
-pub struct Terminal<B>
-where
- B: Backend,
-{
- backend: B,
- /// Holds the results of the current and previous draw calls. The two are compared at the end
- /// of each draw pass to output the necessary updates to the terminal
- buffers: [Buffer; 2],
- /// Index of the current buffer in the previous array
- current: usize,
- /// Whether the cursor is currently hidden
- hidden_cursor: bool,
- /// Viewport
- viewport: Viewport,
- viewport_area: Rect,
- /// Last known size of the terminal. Used to detect if the internal buffers have to be resized.
- last_known_size: Rect,
- /// Last known position of the cursor. Used to find the new area when the viewport is inlined
- /// and the terminal resized.
- last_known_cursor_pos: (u16, u16),
-}
-
-/// Represents a consistent terminal interface for rendering.
-pub struct Frame<'a, B: 'a>
-where
- B: Backend,
-{
- terminal: &'a mut Terminal<B>,
-
- /// Where should the cursor be after drawing this frame?
- ///
- /// If `None`, the cursor is hidden and its position is controlled by the backend. If `Some((x,
- /// y))`, the cursor is shown and placed at `(x, y)` after the call to `Terminal::draw()`.
- cursor_position: Option<(u16, u16)>,
-}
-
-impl<'a, B> Frame<'a, B>
-where
- B: Backend,
-{
- /// Frame size, guaranteed not to change when rendering.
- pub fn size(&self) -> Rect {
- self.terminal.viewport_area
- }
-
- /// Render a [`Widget`] to the current buffer using [`Widget::render`].
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use ratatui::Terminal;
- /// # use ratatui::backend::TestBackend;
- /// # use ratatui::layout::Rect;
- /// # use ratatui::widgets::Block;
- /// # let backend = TestBackend::new(5, 5);
- /// # let mut terminal = Terminal::new(backend).unwrap();
- /// let block = Block::default();
- /// let area = Rect::new(0, 0, 5, 5);
- /// let mut frame = terminal.get_frame();
- /// frame.render_widget(block, area);
- /// ```
- pub fn render_widget<W>(&mut self, widget: W, area: Rect)
- where
- W: Widget,
- {
- widget.render(area, self.terminal.current_buffer_mut());
- }
-
- /// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`].
- ///
- /// The last argument should be an instance of the [`StatefulWidget::State`] associated to the
- /// given [`StatefulWidget`].
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use ratatui::Terminal;
- /// # use ratatui::backend::TestBackend;
- /// # use ratatui::layout::Rect;
- /// # use ratatui::widgets::{List, ListItem, ListState};
- /// # let backend = TestBackend::new(5, 5);
- /// # let mut terminal = Terminal::new(backend).unwrap();
- /// let mut state = ListState::default();
- /// state.select(Some(1));
- /// let items = vec![
- /// ListItem::new("Item 1"),
- /// ListItem::new("Item 2"),
- /// ];
- /// let list = List::new(items);
- /// let area = Rect::new(0, 0, 5, 5);
- /// let mut frame = terminal.get_frame();
- /// frame.render_stateful_widget(list, area, &mut state);
- /// ```
- pub fn render_stateful_widget<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
- where
- W: StatefulWidget,
- {
- widget.render(area, self.terminal.current_buffer_mut(), state);
- }
-
- /// After drawing this frame, make the cursor visible and put it at the specified (x, y)
- /// coordinates. If this method is not called, the cursor will be hidden.
- ///
- /// Note that this will interfere with calls to `Terminal::hide_cursor()`,
- /// `Terminal::show_cursor()`, and `Terminal::set_cursor()`. Pick one of the APIs and stick
- /// with it.
- pub fn set_cursor(&mut self, x: u16, y: u16) {
- self.cursor_position = Some((x, y));
- }
-}
-
-/// CompletedFrame represents the state of the terminal after all changes performed in the last
-/// [`Terminal::draw`] call have been applied. Therefore, it is only valid until the next call to
-/// [`Terminal::draw`].
-pub struct CompletedFrame<'a> {
- pub buffer: &'a Buffer,
- pub area: Rect,
-}
-
-impl<B> Drop for Terminal<B>
-where
- B: Backend,
-{
- fn drop(&mut self) {
- // Attempt to restore the cursor state
- if self.hidden_cursor {
- if let Err(err) = self.show_cursor() {
- eprintln!("Failed to show the cursor: {}", err);
- }
- }
- }
-}
-
-impl<B> Terminal<B>
-where
- B: Backend,
-{
- /// Wrapper around Terminal initialization. Each buffer is initialized with a blank string and
- /// default colors for the foreground and the background
- pub fn new(backend: B) -> io::Result<Terminal<B>> {
- Terminal::with_options(
- backend,
- TerminalOptions {
- viewport: Viewport::Fullscreen,
- },
- )
- }
-
- pub fn with_options(mut backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
- let size = match options.viewport {
- Viewport::Fullscreen | Viewport::Inline(_) => backend.size()?,
- Viewport::Fixed(area) => area,
- };
- let (viewport_area, cursor_pos) = match options.viewport {
- Viewport::Fullscreen => (size, (0, 0)),
- Viewport::Inline(height) => compute_inline_size(&mut backend, height, size, 0)?,
- Viewport::Fixed(area) => (area, (area.left(), area.top())),
- };
- Ok(Terminal {
- backend,
- buffers: [Buffer::empty(viewport_area), Buffer::empty(viewport_area)],
- current: 0,
- hidden_cursor: false,
- viewport: options.viewport,
- viewport_area,
- last_known_size: size,
- last_known_cursor_pos: cursor_pos,
- })
- }
-
- /// Get a Frame object which provides a consistent view into the terminal state for rendering.
- pub fn get_frame(&mut self) -> Frame<B> {
- Frame {
- terminal: self,
- cursor_position: None,
- }
- }
-
- pub fn current_buffer_mut(&mut self) -> &mut Buffer {
- &mut self.buffers[self.current]
- }
-
- pub fn backend(&self) -> &B {
- &self.backend
- }
-
- pub fn backend_mut(&mut self) -> &mut B {
- &mut self.backend
- }
-
- /// Obtains a difference between the previous and the current buffer and passes it to the
- /// current backend for drawing.
- pub fn flush(&mut self) -> io::Result<()> {
- let previous_buffer = &self.buffers[1 - self.current];
- let current_buffer = &self.buffers[self.current];
- let updates = previous_buffer.diff(current_buffer);
- if let Some((col, row, _)) = updates.last() {
- self.last_known_cursor_pos = (*col, *row);
- }
- self.backend.draw(updates.into_iter())
- }
-
- /// Updates the Terminal so that internal buffers match the requested size. Requested size will
- /// be saved so the size can remain consistent when rendering.
- /// This leads to a full clear of the screen.
- pub fn resize(&mut self, size: Rect) -> io::Result<()> {
- let next_area = match self.viewport {
- Viewport::Fullscreen => size,
- Viewport::Inline(height) => {
- let offset_in_previous_viewport = self
- .last_known_cursor_pos
- .1
- .saturating_sub(self.viewport_area.top());
- compute_inline_size(&mut self.backend, height, size, offset_in_previous_viewport)?.0
- }
- Viewport::Fixed(area) => area,
- };
- self.set_viewport_area(next_area);
- self.clear()?;
-
- self.last_known_size = size;
- Ok(())
- }
-
- fn set_viewport_area(&mut self, area: Rect) {
- self.buffers[self.current].resize(area);
- self.buffers[1 - self.current].resize(area);
- self.viewport_area = area;
- }
-
- /// Queries the backend for size and resizes if it doesn't match the previous size.
- pub fn autoresize(&mut self) -> io::Result<()> {
- // fixed viewports do not get autoresized
- if matches!(self.viewport, Viewport::Fullscreen | Viewport::Inline(_)) {
- let size = self.size()?;
- if size != self.last_known_size {
- self.resize(size)?;
- }
- };
- Ok(())
- }
-
- /// Synchronizes terminal size, calls the rendering closure, flushes the current internal state
- /// and prepares for the next draw call.
- pub fn draw<F>(&mut self, f: F) -> io::Result<CompletedFrame>
- where
- F: FnOnce(&mut Frame<B>),
- {
- // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
- // and the terminal (if growing), which may OOB.
- self.autoresize()?;
-
- let mut frame = self.get_frame();
- f(&mut frame);
- // We can't change the cursor position right away because we have to flush the frame to
- // stdout first. But we also can't keep the frame around, since it holds a &mut to
- // Terminal. Thus, we're taking the important data out of the Frame and dropping it.
- let cursor_position = frame.cursor_position;
-
- // Draw to stdout
- self.flush()?;
-
- match cursor_position {
- None => self.hide_cursor()?,
- Some((x, y)) => {
- self.show_cursor()?;
- self.set_cursor(x, y)?;
- }
- }
-
- // Swap buffers
- self.buffers[1 - self.current].reset();
- self.current = 1 - self.current;
-
- // Flush
- self.backend.flush()?;
-
- Ok(CompletedFrame {
- buffer: &self.buffers[1 - self.current],
- area: self.last_known_size,
- })
- }
-
- pub fn hide_cursor(&mut self) -> io::Result<()> {
- self.backend.hide_cursor()?;
- self.hidden_cursor = true;
- Ok(())
- }
-
- pub fn show_cursor(&mut self) -> io::Result<()> {
- self.backend.show_cursor()?;
- self.hidden_cursor = false;
- Ok(())
- }
-
- pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
- self.backend.get_cursor()
- }
-
- pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
- self.backend.set_cursor(x, y)?;
- self.last_known_cursor_pos = (x, y);
- Ok(())
- }
-
- /// Clear the terminal and force a full redraw on the next draw call.
- pub fn clear(&mut self) -> io::Result<()> {
- match self.viewport {
- Viewport::Fullscreen => self.backend.clear_region(ClearType::All)?,
- Viewport::Inline(_) => {
- self.backend
- .set_cursor(self.viewport_area.left(), self.viewport_area.top())?;
- self.backend.clear_region(ClearType::AfterCursor)?;
- }
- Viewport::Fixed(area) => {
- for row in area.top()..area.bottom() {
- self.backend.set_cursor(0, row)?;
- self.backend.clear_region(ClearType::AfterCursor)?;
- }
- }
- }
- // Reset the back buffer to make sure the next update will redraw everything.
- self.buffers[1 - self.current].reset();
- Ok(())
- }
-
- /// Queries the real size of the backend.
- pub fn size(&self) -> io::Result<Rect> {
- self.backend.size()
- }
-
- /// Insert some content before the current inline viewport. This has no effect when the
- /// viewport is fullscreen.
- ///
- /// This function scrolls down the current viewport by the given height. The newly freed space is
- /// then made available to the `draw_fn` closure through a writable `Buffer`.
- ///
- /// Before:
- /// ```ignore
- /// +-------------------+
- /// | |
- /// | viewport |
- /// | |
- /// +-------------------+
- /// ```
- ///
- /// After:
- /// ```ignore
- /// +-------------------+
- /// | buffer |
- /// +-------------------+
- /// +-------------------+
- /// | |
- /// | viewport |
- /// | |
- /// +-------------------+
- /// ```
- ///
- /// # Examples
- ///
- /// ## Insert a single line before the current viewport
- ///
- /// ```rust
- /// # use ratatui::widgets::{Paragraph, Widget};
- /// # use ratatui::text::{Spans, Span};
- /// # use ratatui::style::{Color, Style};
- /// # use ratatui::{Terminal};
- /// # use ratatui::backend::TestBackend;
- /// # let backend = TestBackend::new(10, 10);
- /// # let mut terminal = Terminal::new(backend).unwrap();
- /// terminal.insert_before(1, |buf| {
- /// Paragraph::new(Spans::from(vec![
- /// Span::raw("This line will be added "),
- /// Span::styled("before", Style::default().fg(Color::Blue)),
- /// Span::raw(" the current viewport")
- /// ])).render(buf.area, buf);
- /// });
- /// ```
- pub fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> io::Result<()>
- where
- F: FnOnce(&mut Buffer),
- {
- if !matches!(self.viewport, Viewport::Inline(_)) {
- return Ok(());
- }
-
- self.clear()?;
- let height = height.min(self.last_known_size.height);
- self.backend.append_lines(height)?;
- let missing_lines =
- height.saturating_sub(self.last_known_size.bottom() - self.viewport_area.top());
- let area = Rect {
- x: self.viewport_area.left(),
- y: self.viewport_area.top().saturating_sub(missing_lines),
- width: self.viewport_area.width,
- height,
- };
- let mut buffer = Buffer::empty(area);
-
- draw_fn(&mut buffer);
-
- let iter = buffer.content.iter().enumerate().map(|(i, c)| {
- let (x, y) = buffer.pos_of(i);
- (x, y, c)
- });
- self.backend.draw(iter)?;
- self.backend.flush()?;
-
- let remaining_lines = self.last_known_size.height - area.bottom();
- let missing_lines = self.viewport_area.height.saturating_sub(remaining_lines);
- self.backend.append_lines(self.viewport_area.height)?;
-
- self.set_viewport_area(Rect {
- x: area.left(),
- y: area.bottom().saturating_sub(missing_lines),
- width: area.width,
- height: self.viewport_area.height,
- });
-
- Ok(())
- }
-}
-
-fn compute_inline_size<B: Backend>(
- backend: &mut B,
- height: u16,
- size: Rect,
- offset_in_previous_viewport: u16,
-) -> io::Result<(Rect, (u16, u16))> {
- let pos = backend.get_cursor()?;
- let mut row = pos.1;
-
- let max_height = size.height.min(height);
-
- let lines_after_cursor = height
- .saturating_sub(offset_in_previous_viewport)
- .saturating_sub(1);
-
- backend.append_lines(lines_after_cursor)?;
-
- let available_lines = size.height.saturating_sub(row).saturating_sub(1);
- let missing_lines = lines_after_cursor.saturating_sub(available_lines);
- if missing_lines > 0 {
- row = row.saturating_sub(missing_lines);
- }
- row = row.saturating_sub(offset_in_previous_viewport);
-
- Ok((
- Rect {
- x: 0,
- y: row,
- width: size.width,
- height: max_height,
- },
- pos,
- ))
-}
diff --git a/src/ratatui/text.rs b/src/ratatui/text.rs
deleted file mode 100644
index 50e3d5cf..00000000
--- a/src/ratatui/text.rs
+++ /dev/null
@@ -1,430 +0,0 @@
-//! Primitives for styled text.
-//!
-//! A terminal UI is at its root a lot of strings. In order to make it accessible and stylish,
-//! those strings may be associated to a set of styles. `ratatui` has three ways to represent them:
-//! - A single line string where all graphemes have the same style is represented by a [`Span`].
-//! - A single line string where each grapheme may have its own style is represented by [`Spans`].
-//! - A multiple line string where each grapheme may have its own style is represented by a
-//! [`Text`].
-//!
-//! These types form a hierarchy: [`Spans`] is a collection of [`Span`] and each line of [`Text`]
-//! is a [`Spans`].
-//!
-//! Keep it mind that a lot of widgets will use those types to advertise what kind of string is
-//! supported for their properties. Moreover, `ratatui` provides convenient `From` implementations so
-//! that you can start by using simple `String` or `&str` and then promote them to the previous
-//! primitives when you need additional styling capabilities.
-//!
-//! For example, for the [`crate::widgets::Block`] widget, all the following calls are valid to set
-//! its `title` property (which is a [`Spans`] under the hood):
-//!
-//! ```rust
-//! # use ratatui::widgets::Block;
-//! # use ratatui::text::{Span, Spans};
-//! # use ratatui::style::{Color, Style};
-//! // A simple string with no styling.
-//! // Converted to Spans(vec![
-//! // Span { content: Cow::Borrowed("My title"), style: Style { .. } }
-//! // ])
-//! let block = Block::default().title("My title");
-//!
-//! // A simple string with a unique style.
-//! // Converted to Spans(vec![
-//! // Span { content: Cow::Borrowed("My title"), style: Style { fg: Some(Color::Yellow), .. }
-//! // ])
-//! let block = Block::default().title(
-//! Span::styled("My title", Style::default().fg(Color::Yellow))
-//! );
-//!
-//! // A string with multiple styles.
-//! // Converted to Spans(vec![
-//! // Span { content: Cow::Borrowed("My"), style: Style { fg: Some(Color::Yellow), .. } },
-//! // Span { content: Cow::Borrowed(" title"), .. }
-//! // ])
-//! let block = Block::default().title(vec![
-//! Span::styled("My", Style::default().fg(Color::Yellow)),
-//! Span::raw(" title"),
-//! ]);
-//! ```
-use crate::ratatui::style::Style;
-use std::borrow::Cow;
-use unicode_segmentation::UnicodeSegmentation;
-use unicode_width::UnicodeWidthStr;
-
-/// A grapheme associated to a style.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct StyledGrapheme<'a> {
- pub symbol: &'a str,
- pub style: Style,
-}
-
-/// A string where all graphemes have the same style.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Span<'a> {
- pub content: Cow<'a, str>,
- pub style: Style,
-}
-
-impl<'a> Span<'a> {
- /// Create a span with no style.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::text::Span;
- /// Span::raw("My text");
- /// Span::raw(String::from("My text"));
- /// ```
- pub fn raw<T>(content: T) -> Span<'a>
- where
- T: Into<Cow<'a, str>>,
- {
- Span {
- content: content.into(),
- style: Style::default(),
- }
- }
-
- /// Create a span with a style.
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use ratatui::text::Span;
- /// # use ratatui::style::{Color, Modifier, Style};
- /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
- /// Span::styled("My text", style);
- /// Span::styled(String::from("My text"), style);
- /// ```
- pub fn styled<T>(content: T, style: Style) -> Span<'a>
- where
- T: Into<Cow<'a, str>>,
- {
- Span {
- content: content.into(),
- style,
- }
- }
-
- /// Returns the width of the content held by this span.
- pub fn width(&self) -> usize {
- self.content.width()
- }
-
- /// Returns an iterator over the graphemes held by this span.
- ///
- /// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
- /// the resulting [`Style`].
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::text::{Span, StyledGrapheme};
- /// # use ratatui::style::{Color, Modifier, Style};
- /// # use std::iter::Iterator;
- /// let style = Style::default().fg(Color::Yellow);
- /// let span = Span::styled("Text", style);
- /// let style = Style::default().fg(Color::Green).bg(Color::Black);
- /// let styled_graphemes = span.styled_graphemes(style);
- /// assert_eq!(
- /// vec![
- /// StyledGrapheme {
- /// symbol: "T",
- /// style: Style {
- /// fg: Some(Color::Yellow),
- /// bg: Some(Color::Black),
- /// add_modifier: Modifier::empty(),
- /// sub_modifier: Modifier::empty(),
- /// },
- /// },
- /// StyledGrapheme {
- /// symbol: "e",
- /// style: Style {
- /// fg: Some(Color::Yellow),
- /// bg: Some(Color::Black),
- /// add_modifier: Modifier::empty(),
- /// sub_modifier: Modifier::empty(),
- /// },
- /// },
- /// StyledGrapheme {
- /// symbol: "x",
- /// style: Style {
- /// fg: Some(Color::Yellow),
- /// bg: Some(Color::Black),
- /// add_modifier: Modifier::empty(),
- /// sub_modifier: Modifier::empty(),
- /// },
- /// },
- /// StyledGrapheme {
- /// symbol: "t",
- /// style: Style {
- /// fg: Some(Color::Yellow),
- /// bg: Some(Color::Black),
- /// add_modifier: Modifier::empty(),
- /// sub_modifier: Modifier::empty(),
- /// },
- /// },
- /// ],
- /// styled_graphemes.collect::<Vec<StyledGrapheme>>()
- /// );
- /// ```
- pub fn styled_graphemes(
- &'a self,
- base_style: Style,
- ) -> impl Iterator<Item = StyledGrapheme<'a>> {
- UnicodeSegmentation::graphemes(self.content.as_ref(), true)
- .map(move |g| StyledGrapheme {
- symbol: g,
- style: base_style.patch(self.style),
- })
- .filter(|s| s.symbol != "\n")
- }
-}
-
-impl<'a> From<String> for Span<'a> {
- fn from(s: String) -> Span<'a> {
- Span::raw(s)
- }
-}
-
-impl<'a> From<&'a str> for Span<'a> {
- fn from(s: &'a str) -> Span<'a> {
- Span::raw(s)
- }
-}
-
-/// A string composed of clusters of graphemes, each with their own style.
-#[derive(Debug, Clone, PartialEq, Default, Eq)]
-pub struct Spans<'a>(pub Vec<Span<'a>>);
-
-impl<'a> Spans<'a> {
- /// Returns the width of the underlying string.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::text::{Span, Spans};
- /// # use ratatui::style::{Color, Style};
- /// let spans = Spans::from(vec![
- /// Span::styled("My", Style::default().fg(Color::Yellow)),
- /// Span::raw(" text"),
- /// ]);
- /// assert_eq!(7, spans.width());
- /// ```
- pub fn width(&self) -> usize {
- self.0.iter().map(Span::width).sum()
- }
-}
-
-impl<'a> From<String> for Spans<'a> {
- fn from(s: String) -> Spans<'a> {
- Spans(vec![Span::from(s)])
- }
-}
-
-impl<'a> From<&'a str> for Spans<'a> {
- fn from(s: &'a str) -> Spans<'a> {
- Spans(vec![Span::from(s)])
- }
-}
-
-impl<'a> From<Vec<Span<'a>>> for Spans<'a> {
- fn from(spans: Vec<Span<'a>>) -> Spans<'a> {
- Spans(spans)
- }
-}
-
-impl<'a> From<Span<'a>> for Spans<'a> {
- fn from(span: Span<'a>) -> Spans<'a> {
- Spans(vec![span])
- }
-}
-
-impl<'a> From<Spans<'a>> for String {
- fn from(line: Spans<'a>) -> String {
- line.0.iter().fold(String::new(), |mut acc, s| {
- acc.push_str(s.content.as_ref());
- acc
- })
- }
-}
-
-/// A string split over multiple lines where each line is composed of several clusters, each with
-/// their own style.
-///
-/// A [`Text`], like a [`Span`], can be constructed using one of the many `From` implementations
-/// or via the [`Text::raw`] and [`Text::styled`] methods. Helpfully, [`Text`] also implements
-/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
-///
-/// ```rust
-/// # use ratatui::text::Text;
-/// # use ratatui::style::{Color, Modifier, Style};
-/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
-///
-/// // An initial two lines of `Text` built from a `&str`
-/// let mut text = Text::from("The first line\nThe second line");
-/// assert_eq!(2, text.height());
-///
-/// // Adding two more unstyled lines
-/// text.extend(Text::raw("These are two\nmore lines!"));
-/// assert_eq!(4, text.height());
-///
-/// // Adding a final two styled lines
-/// text.extend(Text::styled("Some more lines\nnow with more style!", style));
-/// assert_eq!(6, text.height());
-/// ```
-#[derive(Debug, Clone, PartialEq, Default, Eq)]
-pub struct Text<'a> {
- pub lines: Vec<Spans<'a>>,
-}
-
-impl<'a> Text<'a> {
- /// Create some text (potentially multiple lines) with no style.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// # use ratatui::text::Text;
- /// Text::raw("The first line\nThe second line");
- /// Text::raw(String::from("The first line\nThe second line"));
- /// ```
- pub fn raw<T>(content: T) -> Text<'a>
- where
- T: Into<Cow<'a, str>>,
- {
- let lines: Vec<_> = match content.into() {
- Cow::Borrowed("") => vec![Spans::from("")],
- Cow::Borrowed(s) => s.lines().map(Spans::from).collect(),
- Cow::Owned(s) if s.is_empty() => vec![Spans::from("")],
- Cow::Owned(s) => s.lines().map(|l| Spans::from(l.to_owned())).collect(),
- };
-
- Text { lines }
- }
-
- /// Create some text (potentially multiple lines) with a style.
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use ratatui::text::Text;
- /// # use ratatui::style::{Color, Modifier, Style};
- /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
- /// Text::styled("The first line\nThe second line", style);
- /// Text::styled(String::from("The first line\nThe second line"), style);
- /// ```
- pub fn styled<T>(content: T, style: Style) -> Text<'a>
- where
- T: Into<Cow<'a, str>>,
- {
- let mut text = Text::raw(content);
- text.patch_style(style);
- text
- }
-
- /// Returns the max width of all the lines.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// use ratatui::text::Text;
- /// let text = Text::from("The first line\nThe second line");
- /// assert_eq!(15, text.width());
- /// ```
- pub fn width(&self) -> usize {
- self.lines
- .iter()
- .map(Spans::width)
- .max()
- .unwrap_or_default()
- }
-
- /// Returns the height.
- ///
- /// ## Examples
- ///
- /// ```rust
- /// use ratatui::text::Text;
- /// let text = Text::from("The first line\nThe second line");
- /// assert_eq!(2, text.height());
- /// ```
- pub fn height(&self) -> usize {
- self.lines.len()
- }
-
- /// Apply a new style to existing text.
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use ratatui::text::Text;
- /// # use ratatui::style::{Color, Modifier, Style};
- /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
- /// let mut raw_text = Text::raw("The first line\nThe second line");
- /// let styled_text = Text::styled(String::from("The first line\nThe second line"), style);
- /// assert_ne!(raw_text, styled_text);
- ///
- /// raw_text.patch_style(style);
- /// assert_eq!(raw_text, styled_text);
- /// ```
- pub fn patch_style(&mut self, style: Style) {
- for line in &mut self.lines {
- for span in &mut line.0 {
- span.style = span.style.patch(style);
- }
- }
- }
-}
-
-impl<'a> From<String> for Text<'a> {
- fn from(s: String) -> Text<'a> {
- Text::raw(s)
- }
-}
-
-impl<'a> From<&'a str> for Text<'a> {
- fn from(s: &'a str) -> Text<'a> {
- Text::raw(s)
- }
-}
-
-impl<'a> From<Cow<'a, str>> for Text<'a> {
- fn from(s: Cow<'a, str>) -> Text<'a> {
- Text::raw(s)
- }
-}
-
-impl<'a> From<Span<'a>> for Text<'a> {
- fn from(span: Span<'a>) -> Text<'a> {
- Text {
- lines: vec![Spans::from(span)],
- }
- }
-}
-
-impl<'a> From<Spans<'a>> for Text<'a> {
- fn from(spans: Spans<'a>) -> Text<'a> {
- Text { lines: vec![spans] }
- }
-}
-
-impl<'a> From<Vec<Spans<'a>>> for Text<'a> {
- fn from(lines: Vec<Spans<'a>>) -> Text<'a> {
- Text { lines }
- }
-}
-
-impl<'a> IntoIterator for Text<'a> {
- type Item = Spans<'a>;
- type IntoIter = std::vec::IntoIter<Self::Item>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.lines.into_iter()
- }
-}
-
-impl<'a> Extend<Spans<'a>> for Text<'a> {
- fn extend<T: IntoIterator<Item = Spans<'a>>>(&mut self, iter: T) {
- self.lines.extend(iter);
- }
-}
diff --git a/src/ratatui/widgets/barchart.rs b/src/ratatui/widgets/barchart.rs
deleted file mode 100644
index 13f386a4..00000000
--- a/src/ratatui/widgets/barchart.rs
+++ /dev/null
@@ -1,219 +0,0 @@
-use crate::ratatui::{
- buffer::Buffer,
- layout::Rect,
- style::Style,
- symbols,
- widgets::{Block, Widget},
-};
-use std::cmp::min;
-use unicode_width::UnicodeWidthStr;
-
-/// Display multiple bars in a single widgets
-///
-/// # Examples
-///
-/// ```
-/// # use ratatui::widgets::{Block, Borders, BarChart};
-/// # use ratatui::style::{Style, Color, Modifier};
-/// BarChart::default()
-/// .block(Block::default().title("BarChart").borders(Borders::ALL))
-/// .bar_width(3)
-/// .bar_gap(1)
-/// .bar_style(Style::default().fg(Color::Yellow).bg(Color::Red))
-/// .value_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
-/// .label_style(Style::default().fg(Color::White))
-/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
-/// .max(4);
-/// ```
-#[derive(Debug, Clone)]
-pub struct BarChart<'a> {
- /// Block to wrap the widget in
- block: Option<Block<'a>>,
- /// The width of each bar
- bar_width: u16,
- /// The gap between each bar
- bar_gap: u16,
- /// Set of symbols used to display the data
- bar_set: symbols::bar::Set,
- /// Style of the bars
- bar_style: Style,
- /// Style of the values printed at the bottom of each bar
- value_style: Style,
- /// Style of the labels printed under each bar
- label_style: Style,
- /// Style for the widget
- style: Style,
- /// Slice of (label, value) pair to plot on the chart
- data: &'a [(&'a str, u64)],
- /// Value necessary for a bar to reach the maximum height (if no value is specified,
- /// the maximum value in the data is taken as reference)
- max: Option<u64>,
- /// Values to display on the bar (computed when the data is passed to the widget)
- values: Vec<String>,
-}
-
-impl<'a> Default for BarChart<'a> {
- fn default() -> BarChart<'a> {
- BarChart {
- block: None,
- max: None,
- data: &[],
- values: Vec::new(),
- bar_style: Style::default(),
- bar_width: 1,
- bar_gap: 1,
- bar_set: symbols::bar::NINE_LEVELS,
- value_style: Default::default(),
- label_style: Default::default(),
- style: Default::default(),
- }
- }
-}
-
-impl<'a> BarChart<'a> {
- pub fn data(mut self, data: &'a [(&'a str, u64)]) -> BarChart<'a> {
- self.data = data;
- self.values = Vec::with_capacity(self.data.len());
- for &(_, v) in self.data {
- self.values.push(format!("{}", v));
- }
- self
- }
-
- pub fn block(mut self, block: Block<'a>) -> BarChart<'a> {
- self.block = Some(block);
- self
- }
-
- pub fn max(mut self, max: u64) -> BarChart<'a> {
- self.max = Some(max);
- self
- }
-
- pub fn bar_style(mut self, style: Style) -> BarChart<'a> {
- self.bar_style = style;
- self
- }
-
- pub fn bar_width(mut self, width: u16) -> BarChart<'a> {
- self.bar_width = width;
- self
- }
-
- pub fn bar_gap(mut self, gap: u16) -> BarChart<'a> {
- self.bar_gap = gap;
- self
- }
-
- pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> BarChart<'a> {
- self.bar_set = bar_set;
- self
- }
-
- pub fn value_style(mut self, style: Style) -> BarChart<'a> {
- self.value_style = style;
- self
- }
-
- pub fn label_style(mut self, style: Style) -> BarChart<'a> {
- self.label_style = style;
- self
- }
-
- pub fn style(mut self, style: Style) -> BarChart<'a> {
- self.style = style;
- self
- }
-}
-
-impl<'a> Widget for BarChart<'a> {
- fn render(mut self, area: Rect, buf: &mut Buffer) {
- buf.set_style(area, self.style);
-
- let chart_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- if chart_area.height < 2 {
- return;
- }
-
- let max = self
- .max
- .unwrap_or_else(|| self.data.iter().map(|t| t.1).max().unwrap_or_default());
- let max_index = min(
- (chart_area.width / (self.bar_width + self.bar_gap)) as usize,
- self.data.len(),
- );
- let mut data = self
- .data
- .iter()
- .take(max_index)
- .map(|&(l, v)| {
- (
- l,
- v * u64::from(chart_area.height - 1) * 8 / std::cmp::max(max, 1),
- )
- })
- .collect::<Vec<(&str, u64)>>();
- for j in (0..chart_area.height - 1).rev() {
- for (i, d) in data.iter_mut().enumerate() {
- let symbol = match d.1 {
- 0 => self.bar_set.empty,
- 1 => self.bar_set.one_eighth,
- 2 => self.bar_set.one_quarter,
- 3 => self.bar_set.three_eighths,
- 4 => self.bar_set.half,
- 5 => self.bar_set.five_eighths,
- 6 => self.bar_set.three_quarters,
- 7 => self.bar_set.seven_eighths,
- _ => self.bar_set.full,
- };
-
- for x in 0..self.bar_width {
- buf.get_mut(
- chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x,
- chart_area.top() + j,
- )
- .set_symbol(symbol)
- .set_style(self.bar_style);
- }
-
- if d.1 > 8 {
- d.1 -= 8;
- } else {
- d.1 = 0;
- }
- }
- }
-
- for (i, &(label, value)) in self.data.iter().take(max_index).enumerate() {
- if value != 0 {
- let value_label = &self.values[i];
- let width = value_label.width() as u16;
- if width < self.bar_width {
- buf.set_string(
- chart_area.left()
- + i as u16 * (self.bar_width + self.bar_gap)
- + (self.bar_width - width) / 2,
- chart_area.bottom() - 2,
- value_label,
- self.value_style,
- );
- }
- }
- buf.set_stringn(
- chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
- chart_area.bottom() - 1,
- label,
- self.bar_width as usize,
- self.label_style,
- );
- }
- }
-}
diff --git a/src/ratatui/widgets/block.rs b/src/ratatui/widgets/block.rs
deleted file mode 100644
index 20b43614..00000000
--- a/src/ratatui/widgets/block.rs
+++ /dev/null
@@ -1,573 +0,0 @@
-use crate::ratatui::{
- buffer::Buffer,
- layout::{Alignment, Rect},
- style::Style,
- symbols::line,
- text::{Span, Spans},
- widgets::{Borders, Widget},
-};
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum BorderType {
- Plain,
- Rounded,
- Double,
- Thick,
-}
-
-impl BorderType {
- pub fn line_symbols(border_type: BorderType) -> line::Set {
- match border_type {
- BorderType::Plain => line::NORMAL,
- BorderType::Rounded => line::ROUNDED,
- BorderType::Double => line::DOUBLE,
- BorderType::Thick => line::THICK,
- }
- }
-}
-
-/// Base widget to be used with all upper level ones. It may be used to display a box border around
-/// the widget and/or add a title.
-///
-/// # Examples
-///
-/// ```
-/// # use ratatui::widgets::{Block, BorderType, Borders};
-/// # use ratatui::style::{Style, Color};
-/// Block::default()
-/// .title("Block")
-/// .borders(Borders::LEFT | Borders::RIGHT)
-/// .border_style(Style::default().fg(Color::White))
-/// .border_type(BorderType::Rounded)
-/// .style(Style::default().bg(Color::Black));
-/// ```
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Block<'a> {
- /// Optional title place on the upper left of the block
- title: Option<Spans<'a>>,
- /// Title alignment. The default is top left of the block, but one can choose to place
- /// title in the top middle, or top right of the block
- title_alignment: Alignment,
- /// Visible borders
- borders: Borders,
- /// Border style
- border_style: Style,
- /// Type of the border. The default is plain lines but one can choose to have rounded corners
- /// or doubled lines instead.
- border_type: BorderType,
- /// Widget style
- style: Style,
-}
-
-impl<'a> Default for Block<'a> {
- fn default() -> Block<'a> {
- Block {
- title: None,
- title_alignment: Alignment::Left,
- borders: Borders::NONE,
- border_style: Default::default(),
- border_type: BorderType::Plain,
- style: Default::default(),
- }
- }
-}
-
-impl<'a> Block<'a> {
- pub fn title<T>(mut self, title: T) -> Block<'a>
- where
- T: Into<Spans<'a>>,
- {
- self.title = Some(title.into());
- self
- }
-
- #[deprecated(
- since = "0.10.0",
- note = "You should use styling capabilities of `text::Spans` given as argument of the `title` method to apply styling to the title."
- )]
- pub fn title_style(mut self, style: Style) -> Block<'a> {
- if let Some(t) = self.title {
- let title = String::from(t);
- self.title = Some(Spans::from(Span::styled(title, style)));
- }
- self
- }
-
- pub fn title_alignment(mut self, alignment: Alignment) -> Block<'a> {
- self.title_alignment = alignment;
- self
- }
-
- pub fn border_style(mut self, style: Style) -> Block<'a> {
- self.border_style = style;
- self
- }
-
- pub fn style(mut self, style: Style) -> Block<'a> {
- self.style = style;
- self
- }
-
- pub fn borders(mut self, flag: Borders) -> Block<'a> {
- self.borders = flag;
- self
- }
-
- pub fn border_type(mut self, border_type: BorderType) -> Block<'a> {
- self.border_type = border_type;
- self
- }
-
- /// Compute the inner area of a block based on its border visibility rules.
- pub fn inner(&self, area: Rect) -> Rect {
- let mut inner = area;
- if self.borders.intersects(Borders::LEFT) {
- inner.x = inner.x.saturating_add(1).min(inner.right());
- inner.width = inner.width.saturating_sub(1);
- }
- if self.borders.intersects(Borders::TOP) || self.title.is_some() {
- inner.y = inner.y.saturating_add(1).min(inner.bottom());
- inner.height = inner.height.saturating_sub(1);
- }
- if self.borders.intersects(Borders::RIGHT) {
- inner.width = inner.width.saturating_sub(1);
- }
- if self.borders.intersects(Borders::BOTTOM) {
- inner.height = inner.height.saturating_sub(1);
- }
- inner
- }
-}
-
-impl<'a> Widget for Block<'a> {
- fn render(self, area: Rect, buf: &mut Buffer) {
- if area.area() == 0 {
- return;
- }
- buf.set_style(area, self.style);
- let symbols = BorderType::line_symbols(self.border_type);
-
- // Sides
- if self.borders.intersects(Borders::LEFT) {
- for y in area.top()..area.bottom() {
- buf.get_mut(area.left(), y)
- .set_symbol(symbols.vertical)
- .set_style(self.border_style);
- }
- }
- if self.borders.intersects(Borders::TOP) {
- for x in area.left()..area.right() {
- buf.get_mut(x, area.top())
- .set_symbol(symbols.horizontal)
- .set_style(self.border_style);
- }
- }
- if self.borders.intersects(Borders::RIGHT) {
- let x = area.right() - 1;
- for y in area.top()..area.bottom() {
- buf.get_mut(x, y)
- .set_symbol(symbols.vertical)
- .set_style(self.border_style);
- }
- }
- if self.borders.intersects(Borders::BOTTOM) {
- let y = area.bottom() - 1;
- for x in area.left()..area.right() {
- buf.get_mut(x, y)
- .set_symbol(symbols.horizontal)
- .set_style(self.border_style);
- }
- }
-
- // Corners
- if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
- buf.get_mut(area.right() - 1, area.bottom() - 1)
- .set_symbol(symbols.bottom_right)
- .set_style(self.border_style);
- }
- if self.borders.contains(Borders::RIGHT | Borders::TOP) {
- buf.get_mut(area.right() - 1, area.top())
- .set_symbol(symbols.top_right)
- .set_style(self.border_style);
- }
- if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
- buf.get_mut(area.left(), area.bottom() - 1)
- .set_symbol(symbols.bottom_left)
- .set_style(self.border_style);
- }
- if self.borders.contains(Borders::LEFT | Borders::TOP) {
- buf.get_mut(area.left(), area.top())
- .set_symbol(symbols.top_left)
- .set_style(self.border_style);
- }
-
- // Title
- if let Some(title) = self.title {
- let left_border_dx = if self.borders.intersects(Borders::LEFT) {
- 1
- } else {
- 0
- };
-
- let right_border_dx = if self.borders.intersects(Borders::RIGHT) {
- 1
- } else {
- 0
- };
-
- let title_area_width = area
- .width
- .saturating_sub(left_border_dx)
- .saturating_sub(right_border_dx);
-
- let title_dx = match self.title_alignment {
- Alignment::Left => left_border_dx,
- Alignment::Center => area.width.saturating_sub(title.width() as u16) / 2,
- Alignment::Right => area
- .width
- .saturating_sub(title.width() as u16)
- .saturating_sub(right_border_dx),
- };
-
- let title_x = area.left() + title_dx;
- let title_y = area.top();
-
- buf.set_spans(title_x, title_y, &title, title_area_width);
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::ratatui::layout::Rect;
-
- #[test]
- fn inner_takes_into_account_the_borders() {
- // No borders
- assert_eq!(
- Block::default().inner(Rect::default()),
- Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- },
- "no borders, width=0, height=0"
- );
- assert_eq!(
- Block::default().inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- }),
- Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- },
- "no borders, width=1, height=1"
- );
-
- // Left border
- assert_eq!(
- Block::default().borders(Borders::LEFT).inner(Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 1
- }),
- Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 1
- },
- "left, width=0"
- );
- assert_eq!(
- Block::default().borders(Borders::LEFT).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- }),
- Rect {
- x: 1,
- y: 0,
- width: 0,
- height: 1
- },
- "left, width=1"
- );
- assert_eq!(
- Block::default().borders(Borders::LEFT).inner(Rect {
- x: 0,
- y: 0,
- width: 2,
- height: 1
- }),
- Rect {
- x: 1,
- y: 0,
- width: 1,
- height: 1
- },
- "left, width=2"
- );
-
- // Top border
- assert_eq!(
- Block::default().borders(Borders::TOP).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 0
- }),
- Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 0
- },
- "top, height=0"
- );
- assert_eq!(
- Block::default().borders(Borders::TOP).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- }),
- Rect {
- x: 0,
- y: 1,
- width: 1,
- height: 0
- },
- "top, height=1"
- );
- assert_eq!(
- Block::default().borders(Borders::TOP).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 2
- }),
- Rect {
- x: 0,
- y: 1,
- width: 1,
- height: 1
- },
- "top, height=2"
- );
-
- // Right border
- assert_eq!(
- Block::default().borders(Borders::RIGHT).inner(Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 1
- }),
- Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 1
- },
- "right, width=0"
- );
- assert_eq!(
- Block::default().borders(Borders::RIGHT).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- }),
- Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 1
- },
- "right, width=1"
- );
- assert_eq!(
- Block::default().borders(Borders::RIGHT).inner(Rect {
- x: 0,
- y: 0,
- width: 2,
- height: 1
- }),
- Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- },
- "right, width=2"
- );
-
- // Bottom border
- assert_eq!(
- Block::default().borders(Borders::BOTTOM).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 0
- }),
- Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 0
- },
- "bottom, height=0"
- );
- assert_eq!(
- Block::default().borders(Borders::BOTTOM).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- }),
- Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 0
- },
- "bottom, height=1"
- );
- assert_eq!(
- Block::default().borders(Borders::BOTTOM).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 2
- }),
- Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- },
- "bottom, height=2"
- );
-
- // All borders
- assert_eq!(
- Block::default()
- .borders(Borders::ALL)
- .inner(Rect::default()),
- Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- },
- "all borders, width=0, height=0"
- );
- assert_eq!(
- Block::default().borders(Borders::ALL).inner(Rect {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- }),
- Rect {
- x: 1,
- y: 1,
- width: 0,
- height: 0,
- },
- "all borders, width=1, height=1"
- );
- assert_eq!(
- Block::default().borders(Borders::ALL).inner(Rect {
- x: 0,
- y: 0,
- width: 2,
- height: 2,
- }),
- Rect {
- x: 1,
- y: 1,
- width: 0,
- height: 0,
- },
- "all borders, width=2, height=2"
- );
- assert_eq!(
- Block::default().borders(Borders::ALL).inner(Rect {
- x: 0,
- y: 0,
- width: 3,
- height: 3,
- }),
- Rect {
- x: 1,
- y: 1,
- width: 1,
- height: 1,
- },
- "all borders, width=3, height=3"
- );
- }
-
- #[test]
- fn inner_takes_into_account_the_title() {
- assert_eq!(
- Block::default().title("Test").inner(Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 1,
- }),
- Rect {
- x: 0,
- y: 1,
- width: 0,
- height: 0,
- },
- );
- assert_eq!(
- Block::default()
- .title("Test")
- .title_alignment(Alignment::Center)
- .inner(Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 1,
- }),
- Rect {
- x: 0,
- y: 1,
- width: 0,
- height: 0,
- },
- );
- assert_eq!(
- Block::default()
- .title("Test")
- .title_alignment(Alignment::Right)
- .inner(Rect {
- x: 0,
- y: 0,
- width: 0,
- height: 1,
- }),
- Rect {
- x: 0,
- y: 1,
- width: 0,
- height: 0,
- },
- );
- }
-}
diff --git a/src/ratatui/widgets/canvas/line.rs b/src/ratatui/widgets/canvas/line.rs
deleted file mode 100644
index 84d121ce..00000000
--- a/src/ratatui/widgets/canvas/line.rs
+++ /dev/null
@@ -1,95 +0,0 @@
-use crate::ratatui::{
- style::Color,
- widgets::canvas::{Painter, Shape},
-};
-
-/// Shape to draw a line from (x1, y1) to (x2, y2) with the given color
-#[derive(Debug, Clone)]
-pub struct Line {
- pub x1: f64,
- pub y1: f64,
- pub x2: f64,
- pub y2: f64,
- pub color: Color,
-}
-
-impl Shape for Line {
- fn draw(&self, painter: &mut Painter) {
- let (x1, y1) = match painter.get_point(self.x1, self.y1) {
- Some(c) => c,
- None => return,
- };
- let (x2, y2) = match painter.get_point(self.x2, self.y2) {
- Some(c) => c,
- None => return,
- };
- let (dx, x_range) = if x2 >= x1 {
- (x2 - x1, x1..=x2)
- } else {
- (x1 - x2, x2..=x1)
- };
- let (dy, y_range) = if y2 >= y1 {
- (y2 - y1, y1..=y2)
- } else {
- (y1 - y2, y2..=y1)
- };
-
- if dx == 0 {
- for y in y_range {
- painter.paint(x1, y, self.color);
- }
- } else if dy == 0 {
- for x in x_range {
- painter.paint(x, y1, self.color);
- }
- } else if dy < dx {
- if x1 > x2 {
- draw_line_low(painter, x2, y2, x1, y1, self.color);
- } else {
- draw_line_low(painter, x1, y1, x2, y2, self.color);
- }
- } else if y1 > y2 {
- draw_line_high(painter, x2, y2, x1, y1, self.color);
- } else {
- draw_line_high(painter, x1, y1, x2, y2, self.color);
- }
- }
-}
-
-fn draw_line_low(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) {
- let dx = (x2 - x1) as isize;
- let dy = (y2 as isize - y1 as isize).abs();
- let mut d = 2 * dy - dx;
- let mut y = y1;
- for x in x1..=x2 {
- painter.paint(x, y, color);
- if d > 0 {
- y = if y1 > y2 {
- y.saturating_sub(1)
- } else {
- y.saturating_add(1)
- };
- d -= 2 * dx;
- }
- d += 2 * dy;
- }
-}
-
-fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) {
- let dx = (x2 as isize - x1 as isize).abs();
- let dy = (y2 - y1) as isize;
- let mut d = 2 * dx - dy;
- let mut x = x1;
- for y in y1..=y2 {
- painter.paint(x, y, color);
- if d > 0 {
- x = if x1 > x2 {
- x.saturating_sub(1)
- } else {
- x.saturating_add(1)
- };
- d -= 2 * dy;
- }
- d += 2 * dx;
- }
-}
diff --git a/src/ratatui/widgets/canvas/map.rs b/src/ratatui/widgets/canvas/map.rs
deleted file mode 100644
index d835dd31..00000000
--- a/src/ratatui/widgets/canvas/map.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use crate::ratatui::{
- style::Color,
- widgets::canvas::{
- world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION},
- Painter, Shape,
- },
-};
-
-#[derive(Debug, Clone, Copy)]
-pub enum MapResolution {
- Low,
- High,
-}
-
-impl MapResolution {
- fn data(self) -> &'static [(f64, f64)] {
- match self {
- MapResolution::Low => &WORLD_LOW_RESOLUTION,
- MapResolution::High => &WORLD_HIGH_RESOLUTION,
- }
- }
-}
-
-/// Shape to draw a world map with the given resolution and color
-#[derive(Debug, Clone)]
-pub struct Map {
- pub resolution: MapResolution,
- pub color: Color,
-}
-
-impl Default for Map {
- fn default() -> Map {
- Map {
- resolution: MapResolution::Low,
- color: Color::Reset,
- }
- }
-}
-
-impl Shape for Map {
- fn draw(&self, painter: &mut Painter) {
- for (x, y) in self.resolution.data() {
- if let Some((x, y)) = painter.get_point(*x, *y) {
- painter.paint(x, y, self.color);
- }
- }
- }
-}
diff --git a/src/ratatui/widgets/canvas/mod.rs b/src/ratatui/widgets/canvas/mod.rs
deleted file mode 100644
index 7b174596..00000000
--- a/src/ratatui/widgets/canvas/mod.rs
+++ /dev/null
@@ -1,510 +0,0 @@
-mod line;
-mod map;
-mod points;
-mod rectangle;
-mod world;
-
-pub use self::line::Line;
-pub use self::map::{Map, MapResolution};
-pub use self::points::Points;
-pub use self::rectangle::Rectangle;
-
-use crate::ratatui::{
- buffer::Buffer,
- layout::Rect,
- style::{Color, Style},
- symbols,
- text::Spans,
- widgets::{Block, Widget},
-};
-use std::fmt::Debug;
-
-/// Interface for all shapes that may be drawn on a Canvas widget.
-pub trait Shape {
- fn draw(&self, painter: &mut Painter);
-}
-
-/// Label to draw some text on the canvas
-#[derive(Debug, Clone)]
-pub struct Label<'a> {
- x: f64,
- y: f64,
- spans: Spans<'a>,
-}
-
-#[derive(Debug, Clone)]
-struct Layer {
- string: String,
- colors: Vec<Color>,
-}
-
-trait Grid: Debug {
- fn width(&self) -> u16;
- fn height(&self) -> u16;
- fn resolution(&self) -> (f64, f64);
- fn paint(&mut self, x: usize, y: usize, color: Color);
- fn save(&self) -> Layer;
- fn reset(&mut self);
-}
-
-#[derive(Debug, Clone)]
-struct BrailleGrid {
- width: u16,
- height: u16,
- cells: Vec<u16>,
- colors: Vec<Color>,
-}
-
-impl BrailleGrid {
- fn new(width: u16, height: u16) -> BrailleGrid {
- let length = usize::from(width * height);
- BrailleGrid {
- width,
- height,
- cells: vec![symbols::braille::BLANK; length],
- colors: vec![Color::Reset; length],
- }
- }
-}
-
-impl Grid for BrailleGrid {
- fn width(&self) -> u16 {
- self.width
- }
-
- fn height(&self) -> u16 {
- self.height
- }
-
- fn resolution(&self) -> (f64, f64) {
- (
- f64::from(self.width) * 2.0 - 1.0,
- f64::from(self.height) * 4.0 - 1.0,
- )
- }
-
- fn save(&self) -> Layer {
- Layer {
- string: String::from_utf16(&self.cells).unwrap(),
- colors: self.colors.clone(),
- }
- }
-
- fn reset(&mut self) {
- for c in &mut self.cells {
- *c = symbols::braille::BLANK;
- }
- for c in &mut self.colors {
- *c = Color::Reset;
- }
- }
-
- fn paint(&mut self, x: usize, y: usize, color: Color) {
- let index = y / 4 * self.width as usize + x / 2;
- if let Some(c) = self.cells.get_mut(index) {
- *c |= symbols::braille::DOTS[y % 4][x % 2];
- }
- if let Some(c) = self.colors.get_mut(index) {
- *c = color;
- }
- }
-}
-
-#[derive(Debug, Clone)]
-struct CharGrid {
- width: u16,
- height: u16,
- cells: Vec<char>,
- colors: Vec<Color>,
- cell_char: char,
-}
-
-impl CharGrid {
- fn new(width: u16, height: u16, cell_char: char) -> CharGrid {
- let length = usize::from(width * height);
- CharGrid {
- width,
- height,
- cells: vec![' '; length],
- colors: vec![Color::Reset; length],
- cell_char,
- }
- }
-}
-
-impl Grid for CharGrid {
- fn width(&self) -> u16 {
- self.width
- }
-
- fn height(&self) -> u16 {
- self.height
- }
-
- fn resolution(&self) -> (f64, f64) {
- (f64::from(self.width) - 1.0, f64::from(self.height) - 1.0)
- }
-
- fn save(&self) -> Layer {
- Layer {
- string: self.cells.iter().collect(),
- colors: self.colors.clone(),
- }
- }
-
- fn reset(&mut self) {
- for c in &mut self.cells {
- *c = ' ';
- }
- for c in &mut self.colors {
- *c = Color::Reset;
- }
- }
-
- fn paint(&mut self, x: usize, y: usize, color: Color) {
- let index = y * self.width as usize + x;
- if let Some(c) = self.cells.get_mut(index) {
- *c = self.cell_char;
- }
- if let Some(c) = self.colors.get_mut(index) {
- *c = color;
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Painter<'a, 'b> {
- context: &'a mut Context<'b>,
- resolution: (f64, f64),
-}
-
-impl<'a, 'b> Painter<'a, 'b> {
- /// Convert the (x, y) coordinates to location of a point on the grid
- ///
- /// # Examples:
- /// ```
- /// use ratatui::{symbols, widgets::canvas::{Painter, Context}};
- ///
- /// let mut ctx = Context::new(2, 2, [1.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
- /// let mut painter = Painter::from(&mut ctx);
- /// let point = painter.get_point(1.0, 0.0);
- /// assert_eq!(point, Some((0, 7)));
- /// let point = painter.get_point(1.5, 1.0);
- /// assert_eq!(point, Some((1, 3)));
- /// let point = painter.get_point(0.0, 0.0);
- /// assert_eq!(point, None);
- /// let point = painter.get_point(2.0, 2.0);
- /// assert_eq!(point, Some((3, 0)));
- /// let point = painter.get_point(1.0, 2.0);
- /// assert_eq!(point, Some((0, 0)));
- /// ```
- pub fn get_point(&self, x: f64, y: f64) -> Option<(usize, usize)> {
- let left = self.context.x_bounds[0];
- let right = self.context.x_bounds[1];
- let top = self.context.y_bounds[1];
- let bottom = self.context.y_bounds[0];
- if x < left || x > right || y < bottom || y > top {
- return None;
- }
- let width = (self.context.x_bounds[1] - self.context.x_bounds[0]).abs();
- let height = (self.context.y_bounds[1] - self.context.y_bounds[0]).abs();
- if width == 0.0 || height == 0.0 {
- return None;
- }
- let x = ((x - left) * self.resolution.0 / width) as usize;
- let y = ((top - y) * self.resolution.1 / height) as usize;
- Some((x, y))
- }
-
- /// Paint a point of the grid
- ///
- /// # Examples:
- /// ```
- /// use ratatui::{style::Color, symbols, widgets::canvas::{Painter, Context}};
- ///
- /// let mut ctx = Context::new(1, 1, [0.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
- /// let mut painter = Painter::from(&mut ctx);
- /// let cell = painter.paint(1, 3, Color::Red);
- /// ```
- pub fn paint(&mut self, x: usize, y: usize, color: Color) {
- self.context.grid.paint(x, y, color);
- }
-}
-
-impl<'a, 'b> From<&'a mut Context<'b>> for Painter<'a, 'b> {
- fn from(context: &'a mut Context<'b>) -> Painter<'a, 'b> {
- let resolution = context.grid.resolution();
- Painter {
- context,
- resolution,
- }
- }
-}
-
-/// Holds the state of the Canvas when painting to it.
-#[derive(Debug)]
-pub struct Context<'a> {
- x_bounds: [f64; 2],
- y_bounds: [f64; 2],
- grid: Box<dyn Grid>,
- dirty: bool,
- layers: Vec<Layer>,
- labels: Vec<Label<'a>>,
-}
-
-impl<'a> Context<'a> {
- pub fn new(
- width: u16,
- height: u16,
- x_bounds: [f64; 2],
- y_bounds: [f64; 2],
- marker: symbols::Marker,
- ) -> Context<'a> {
- let grid: Box<dyn Grid> = match marker {
- symbols::Marker::Dot => Box::new(CharGrid::new(width, height, '•')),
- symbols::Marker::Block => Box::new(CharGrid::new(width, height, '▄')),
- symbols::Marker::Braille => Box::new(BrailleGrid::new(width, height)),
- };
- Context {
- x_bounds,
- y_bounds,
- grid,
- dirty: false,
- layers: Vec::new(),
- labels: Vec::new(),
- }
- }
-
- /// Draw any object that may implement the Shape trait
- pub fn draw<S>(&mut self, shape: &S)
- where
- S: Shape,
- {
- self.dirty = true;
- let mut painter = Painter::from(self);
- shape.draw(&mut painter);
- }
-
- /// Go one layer above in the canvas.
- pub fn layer(&mut self) {
- self.layers.push(self.grid.save());
- self.grid.reset();
- self.dirty = false;
- }
-
- /// Print a string on the canvas at the given position
- pub fn print<T>(&mut self, x: f64, y: f64, spans: T)
- where
- T: Into<Spans<'a>>,
- {
- self.labels.push(Label {
- x,
- y,
- spans: spans.into(),
- });
- }
-
- /// Push the last layer if necessary
- fn finish(&mut self) {
- if self.dirty {
- self.layer()
- }
- }
-}
-
-/// The Canvas widget may be used to draw more detailed figures using braille patterns (each
-/// cell can have a braille character in 8 different positions).
-/// # Examples
-///
-/// ```
-/// # use ratatui::widgets::{Block, Borders};
-/// # use ratatui::layout::Rect;
-/// # use ratatui::widgets::canvas::{Canvas, Shape, Line, Rectangle, Map, MapResolution};
-/// # use ratatui::style::Color;
-/// Canvas::default()
-/// .block(Block::default().title("Canvas").borders(Borders::ALL))
-/// .x_bounds([-180.0, 180.0])
-/// .y_bounds([-90.0, 90.0])
-/// .paint(|ctx| {
-/// ctx.draw(&Map {
-/// resolution: MapResolution::High,
-/// color: Color::White
-/// });
-/// ctx.layer();
-/// ctx.draw(&Line {
-/// x1: 0.0,
-/// y1: 10.0,
-/// x2: 10.0,
-/// y2: 10.0,
-/// color: Color::White,
-/// });
-/// ctx.draw(&Rectangle {
-/// x: 10.0,
-/// y: 20.0,
-/// width: 10.0,
-/// height: 10.0,
-/// color: Color::Red
-/// });
-/// });
-/// ```
-pub struct Canvas<'a, F>
-where
- F: Fn(&mut Context),
-{
- block: Option<Block<'a>>,
- x_bounds: [f64; 2],
- y_bounds: [f64; 2],
- painter: Option<F>,
- background_color: Color,
- marker: symbols::Marker,
-}
-
-impl<'a, F> Default for Canvas<'a, F>
-where
- F: Fn(&mut Context),
-{
- fn default() -> Canvas<'a, F> {
- Canvas {
- block: None,
- x_bounds: [0.0, 0.0],
- y_bounds: [0.0, 0.0],
- painter: None,
- background_color: Color::Reset,
- marker: symbols::Marker::Braille,
- }
- }
-}
-
-impl<'a, F> Canvas<'a, F>
-where
- F: Fn(&mut Context),
-{
- pub fn block(mut self, block: Block<'a>) -> Canvas<'a, F> {
- self.block = Some(block);
- self
- }
-
- /// Define the viewport of the canvas.
- /// If you were to "zoom" to a certain part of the world you may want to choose different
- /// bounds.
- pub fn x_bounds(mut self, bounds: [f64; 2]) -> Canvas<'a, F> {
- self.x_bounds = bounds;
- self
- }
-
- /// Define the viewport of the canvas.
- ///
- /// If you were to "zoom" to a certain part of the world you may want to choose different
- /// bounds.
- pub fn y_bounds(mut self, bounds: [f64; 2]) -> Canvas<'a, F> {
- self.y_bounds = bounds;
- self
- }
-
- /// Store the closure that will be used to draw to the Canvas
- pub fn paint(mut self, f: F) -> Canvas<'a, F> {
- self.painter = Some(f);
- self
- }
-
- pub fn background_color(mut self, color: Color) -> Canvas<'a, F> {
- self.background_color = color;
- self
- }
-
- /// Change the type of points used to draw the shapes. By default the braille patterns are used
- /// as they provide a more fine grained result but you might want to use the simple dot or
- /// block instead if the targeted terminal does not support those symbols.
- ///
- /// # Examples
- ///
- /// ```
- /// # use ratatui::widgets::canvas::Canvas;
- /// # use ratatui::symbols;
- /// Canvas::default().marker(symbols::Marker::Braille).paint(|ctx| {});
- ///
- /// Canvas::default().marker(symbols::Marker::Dot).paint(|ctx| {});
- ///
- /// Canvas::default().marker(symbols::Marker::Block).paint(|ctx| {});
- /// ```
- pub fn marker(mut self, marker: symbols::Marker) -> Canvas<'a, F> {
- self.marker = marker;
- self
- }
-}
-
-impl<'a, F> Widget for Canvas<'a, F>
-where
- F: Fn(&mut Context),
-{
- fn render(mut self, area: Rect, buf: &mut Buffer) {
- let canvas_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- buf.set_style(canvas_area, Style::default().bg(self.background_color));
-
- let width = canvas_area.width as usize;
-
- let painter = match self.painter {
- Some(ref p) => p,
- None => return,
- };
-
- // Create a blank context that match the size of the canvas
- let mut ctx = Context::new(
- canvas_area.width,
- canvas_area.height,
- self.x_bounds,
- self.y_bounds,
- self.marker,
- );
- // Paint to this context
- painter(&mut ctx);
- ctx.finish();
-
- // Retrieve painted points for each layer
- for layer in ctx.layers {
- for (i, (ch, color)) in layer
- .string
- .chars()
- .zip(layer.colors.into_iter())
- .enumerate()
- {
- if ch != ' ' && ch != '\u{2800}' {
- let (x, y) = (i % width, i / width);
- buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top())
- .set_char(ch)
- .set_fg(color);
- }
- }
- }
-
- // Finally draw the labels
- let left = self.x_bounds[0];
- let right = self.x_bounds[1];
- let top = self.y_bounds[1];
- let bottom = self.y_bounds[0];
- let width = (self.x_bounds[1] - self.x_bounds[0]).abs();
- let height = (self.y_bounds[1] - self.y_bounds[0]).abs();
- let resolution = {
- let width = f64::from(canvas_area.width - 1);
- let height = f64::from(canvas_area.height - 1);
- (width, height)
- };
- for label in ctx
- .labels
- .iter()
- .filter(|l| l.x >= left && l.x <= right && l.y <= top && l.y >= bottom)
- {
- let x = ((label.x - left) * resolution.0 / width) as u16 + canvas_area.left();
- let y = ((top - label.y) * resolution.1 / height) as u16 + canvas_area.top();
- buf.set_spans(x, y, &label.spans, canvas_area.right() - x);
- }
- }
-}
diff --git a/src/ratatui/widgets/canvas/points.rs b/src/ratatui/widgets/canvas/points.rs
deleted file mode 100644
index d9f97522..00000000
--- a/src/ratatui/widgets/canvas/points.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use crate::ratatui::{
- style::Color,
- widgets::canvas::{Painter, Shape},
-};
-
-/// A shape to draw a group of points with the given color
-#[derive(Debug, Clone)]
-pub struct Points<'a> {
- pub coords: &'a [(f64, f64)],
- pub color: Color,
-}
-
-impl<'a> Shape for Points<'a> {
- fn draw(&self, painter: &mut Painter) {
- for (x, y) in self.coords {
- if let Some((x, y)) = painter.get_point(*x, *y) {
- painter.paint(x, y, self.color);
- }
- }
- }
-}
-
-impl<'a> Default for Points<'a> {
- fn default() -> Points<'a> {
- Points {
- coords: &[],
- color: Color::Reset,
- }
- }
-}
diff --git a/src/ratatui/widgets/canvas/rectangle.rs b/src/ratatui/widgets/canvas/rectangle.rs
deleted file mode 100644
index 07ac9137..00000000
--- a/src/ratatui/widgets/canvas/rectangle.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-use crate::ratatui::{
- style::Color,
- widgets::canvas::{Line, Painter, Shape},
-};
-
-/// Shape to draw a rectangle from a `Rect` with the given color
-#[derive(Debug, Clone)]
-pub struct Rectangle {
- pub x: f64,
- pub y: f64,
- pub width: f64,
- pub height: f64,
- pub color: Color,
-}
-
-impl Shape for Rectangle {
- fn draw(&self, painter: &mut Painter) {
- let lines: [Line; 4] = [
- Line {
- x1: self.x,
- y1: self.y,
- x2: self.x,
- y2: self.y + self.height,
- color: self.color,
- },
- Line {
- x1: self.x,
- y1: self.y + self.height,
- x2: self.x + self.width,
- y2: self.y + self.height,
- color: self.color,
- },
- Line {
- x1: self.x + self.width,
- y1: self.y,
- x2: self.x + self.width,
- y2: self.y + self.height,
- color: self.color,
- },
- Line {
- x1: self.x,
- y1: self.y,
- x2: self.x + self.width,
- y2: self.y,
- color: self.color,
- },
- ];
- for line in &lines {
- line.draw(painter);
- }
- }
-}
diff --git a/src/ratatui/widgets/canvas/world.rs b/src/ratatui/widgets/canvas/world.rs
deleted file mode 100644
index b15b7c32..00000000
--- a/src/ratatui/widgets/canvas/world.rs
+++ /dev/null
@@ -1,6299 +0,0 @@
-/// [Source data](http://www.gnuplotting.org/plotting-the-world-revisited)
-
-pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
- (-163.7128, -78.5956),
- (-163.1058, -78.2233),
- (-161.2451, -78.3801),
- (-160.2462, -78.6936),
- (-159.4824, -79.0463),
- (-159.2081, -79.4970),
- (-161.1276, -79.6342),
- (-162.4398, -79.2814),
- (-163.0274, -78.9287),
- (-163.0666, -78.8699),
- (-163.7128, -78.5956),
- (-6.1978, 53.8675),
- (-6.0329, 53.1531),
- (-6.7888, 52.2601),
- (-8.5616, 51.6693),
- (-9.9770, 51.8204),
- (-9.1662, 52.8646),
- (-9.6885, 53.8813),
- (-8.3279, 54.6645),
- (-7.5721, 55.1316),
- (-6.7338, 55.1728),
- (-5.6619, 54.5546),
- (-6.1978, 53.8675),
- (141.0002, -2.6001),
- (142.7352, -3.2891),
- (144.5839, -3.8614),
- (145.2731, -4.3737),
- (145.8297, -4.8764),
- (145.9819, -5.4656),
- (147.6480, -6.0836),
- (147.8911, -6.6140),
- (146.9709, -6.7216),
- (147.1918, -7.3880),
- (148.0846, -8.0441),
- (148.7341, -9.1046),
- (149.3068, -9.0714),
- (149.2666, -9.5144),
- (150.0387, -9.6843),
- (149.7387, -9.8729),
- (150.8016, -10.2936),
- (150.6905, -10.5827),
- (150.0283, -10.6524),
- (149.7823, -10.3932),
- (148.9231, -10.2809),
- (147.9130, -10.1304),
- (147.1354, -9.4924),
- (146.5678, -8.9425),
- (146.0484, -8.0674),
- (144.7441, -7.6301),
- (143.8970, -7.9153),
- (143.2863, -8.2454),
- (143.4139, -8.9830),
- (142.6284, -9.3268),
- (142.0682, -9.1595),
- (141.0338, -9.1178),
- (140.1434, -8.2971),
- (139.1277, -8.0960),
- (138.8814, -8.3809),
- (137.6144, -8.4116),
- (138.0390, -7.5978),
- (138.6686, -7.3202),
- (138.4079, -6.2328),
- (137.9278, -5.3933),
- (135.9892, -4.5465),
- (135.1645, -4.4629),
- (133.6628, -3.5388),
- (133.3677, -4.0248),
- (132.9839, -4.1129),
- (132.7569, -3.7462),
- (132.7537, -3.3117),
- (131.9898, -2.8205),
- (133.0668, -2.4604),
- (133.7800, -2.4798),
- (133.6962, -2.2145),
- (132.2323, -2.2125),
- (131.8362, -1.6171),
- (130.9428, -1.4325),
- (130.5195, -0.9377),
- (131.8675, -0.6954),
- (132.3801, -0.3695),
- (133.9855, -0.7802),
- (134.1433, -1.1518),
- (134.4226, -2.7691),
- (135.4576, -3.3677),
- (136.2933, -2.3070),
- (137.4407, -1.7035),
- (138.3297, -1.7026),
- (139.1849, -2.0512),
- (139.9266, -2.4089),
- (141.0002, -2.6001),
- (114.2040, 4.5258),
- (114.5999, 4.9000),
- (115.4507, 5.4477),
- (116.2207, 6.1431),
- (116.7251, 6.9247),
- (117.1296, 6.9280),
- (117.6433, 6.4221),
- (117.6890, 5.9874),
- (118.3476, 5.7086),
- (119.1819, 5.4078),
- (119.1106, 5.0161),
- (118.4397, 4.9665),
- (118.6183, 4.4782),
- (117.8820, 4.1375),
- (117.3132, 3.2344),
- (118.0483, 2.2876),
- (117.8756, 1.8276),
- (118.9967, 0.9022),
- (117.8118, 0.7842),
- (117.4783, 0.1024),
- (117.5216, -0.8037),
- (116.5600, -1.4876),
- (116.5337, -2.4835),
- (116.1480, -4.0127),
- (116.0008, -3.6570),
- (114.8648, -4.1069),
- (114.4686, -3.4957),
- (113.7556, -3.4391),
- (113.2569, -3.1187),
- (112.0681, -3.4783),
- (111.7032, -2.9944),
- (111.0482, -3.0494),
- (110.2238, -2.9340),
- (110.0709, -1.5928),
- (109.5719, -1.3149),
- (109.0918, -0.4595),
- (108.9526, 0.4153),
- (109.0691, 1.3419),
- (109.6632, 2.0064),
- (110.3961, 1.6637),
- (111.1688, 1.8506),
- (111.3700, 2.6973),
- (111.7969, 2.8858),
- (112.9956, 3.1023),
- (113.7129, 3.8935),
- (114.2040, 4.5258),
- (-93.6127, 74.9799),
- (-94.1569, 74.5923),
- (-95.6086, 74.6668),
- (-96.8209, 74.9276),
- (-96.2885, 75.3778),
- (-94.8508, 75.6472),
- (-93.9777, 75.2964),
- (-93.6127, 74.9799),
- (-93.8400, 77.5199),
- (-94.2956, 77.4913),
- (-96.1696, 77.5551),
- (-96.4363, 77.8346),
- (-94.4225, 77.8200),
- (-93.7206, 77.6343),
- (-93.8400, 77.5199),
- (-96.7543, 78.7658),
- (-95.5592, 78.4183),
- (-95.8302, 78.0569),
- (-97.3098, 77.8505),
- (-98.1242, 78.0828),
- (-98.5528, 78.4581),
- (-98.6319, 78.8719),
- (-97.3372, 78.8319),
- (-96.7543, 78.7658),
- (-88.1503, 74.3923),
- (-89.7647, 74.5155),
- (-92.4224, 74.8377),
- (-92.7682, 75.3868),
- (-92.8899, 75.8826),
- (-93.8938, 76.3192),
- (-95.9624, 76.4413),
- (-97.1213, 76.7510),
- (-96.7451, 77.1613),
- (-94.6840, 77.0978),
- (-93.5739, 76.7762),
- (-91.6050, 76.7785),
- (-90.7418, 76.4495),
- (-90.9696, 76.0740),
- (-89.8222, 75.8477),
- (-89.1870, 75.6101),
- (-87.8382, 75.5661),
- (-86.3791, 75.4824),
- (-84.7896, 75.6992),
- (-82.7534, 75.7843),
- (-81.1285, 75.7139),
- (-80.0575, 75.3368),
- (-79.8339, 74.9231),
- (-80.4577, 74.6573),
- (-81.9488, 74.4424),
- (-83.2288, 74.5640),
- (-86.0974, 74.4100),
- (-88.1503, 74.3923),
- (-111.2644, 78.1529),
- (-109.8544, 77.9963),
- (-110.1869, 77.6970),
- (-112.0511, 77.4092),
- (-113.5342, 77.7322),
- (-112.7245, 78.0510),
- (-111.2644, 78.1529),
- (-110.9636, 78.8044),
- (-109.6631, 78.6019),
- (-110.8813, 78.4069),
- (-112.5420, 78.4079),
- (-112.5258, 78.5505),
- (-111.5000, 78.8499),
- (-110.9636, 78.8044),
- (-66.2824, 18.5147),
- (-65.7713, 18.4266),
- (-65.5910, 18.2280),
- (-65.8471, 17.9759),
- (-66.5999, 17.9818),
- (-67.1841, 17.9465),
- (-67.2424, 18.3744),
- (-67.1006, 18.5206),
- (-66.2824, 18.5147),
- (-77.5696, 18.4905),
- (-76.8966, 18.4008),
- (-76.3653, 18.1607),
- (-76.1996, 17.8868),
- (-76.9025, 17.8682),
- (-77.2063, 17.7011),
- (-77.7660, 17.8615),
- (-78.3377, 18.2259),
- (-78.2177, 18.4545),
- (-77.7973, 18.5242),
- (-77.5696, 18.4905),
- (-82.2681, 23.1886),
- (-81.4044, 23.1172),
- (-80.6187, 23.1060),
- (-79.6795, 22.7653),
- (-79.2814, 22.3992),
- (-78.3474, 22.5121),
- (-77.9932, 22.2771),
- (-77.1464, 21.6578),
- (-76.5238, 21.2068),
- (-76.1946, 21.2205),
- (-75.5982, 21.0166),
- (-75.6710, 20.7350),
- (-74.9338, 20.6939),
- (-74.1780, 20.2846),
- (-74.2966, 20.0503),
- (-74.9615, 19.9234),
- (-75.6346, 19.8737),
- (-76.3236, 19.9528),
- (-77.7554, 19.8554),
- (-77.0851, 20.4133),
- (-77.4926, 20.6731),
- (-78.1372, 20.7399),
- (-78.4828, 21.0286),
- (-78.7198, 21.5981),
- (-79.2849, 21.5591),
- (-80.2174, 21.8273),
- (-80.5175, 22.0370),
- (-81.8209, 22.1920),
- (-82.1699, 22.3871),
- (-81.7950, 22.6369),
- (-82.7758, 22.6881),
- (-83.4944, 22.1685),
- (-83.9088, 22.1545),
- (-84.0521, 21.9105),
- (-84.5470, 21.8012),
- (-84.9749, 21.8960),
- (-84.4470, 22.2049),
- (-84.2303, 22.5657),
- (-83.7782, 22.7881),
- (-83.2675, 22.9830),
- (-82.5104, 23.0787),
- (-82.2681, 23.1886),
- (-55.6002, 51.3170),
- (-56.1340, 50.6870),
- (-56.7958, 49.8123),
- (-56.1431, 50.1501),
- (-55.4714, 49.9358),
- (-55.8224, 49.5871),
- (-54.9351, 49.3130),
- (-54.4737, 49.5566),
- (-53.4765, 49.2491),
- (-53.7860, 48.5167),
- (-53.0861, 48.6878),
- (-52.9586, 48.1571),
- (-52.6480, 47.5355),
- (-53.0691, 46.6554),
- (-53.5214, 46.6182),
- (-54.1789, 46.8070),
- (-53.9618, 47.6252),
- (-54.2404, 47.7522),
- (-55.4007, 46.8849),
- (-55.9974, 46.9197),
- (-55.2912, 47.3895),
- (-56.2507, 47.6325),
- (-57.3252, 47.5728),
- (-59.2660, 47.6033),
- (-59.4194, 47.8994),
- (-58.7965, 48.2515),
- (-59.2316, 48.5231),
- (-58.3918, 49.1255),
- (-57.3586, 50.7182),
- (-56.7386, 51.2874),
- (-55.8709, 51.6320),
- (-55.4069, 51.5882),
- (-55.6002, 51.3170),
- (-83.8826, 65.1096),
- (-82.7875, 64.7666),
- (-81.6420, 64.4551),
- (-81.5534, 63.9796),
- (-80.8173, 64.0574),
- (-80.1034, 63.7259),
- (-80.9910, 63.4112),
- (-82.5471, 63.6517),
- (-83.1087, 64.1018),
- (-84.1004, 63.5697),
- (-85.5234, 63.0523),
- (-85.8667, 63.6372),
- (-87.2219, 63.5412),
- (-86.3527, 64.0358),
- (-86.2248, 64.8229),
- (-85.8838, 65.7387),
- (-85.1613, 65.6572),
- (-84.9757, 65.2175),
- (-84.4640, 65.3717),
- (-83.8826, 65.1096),
- (-78.7706, 72.3521),
- (-77.8246, 72.7496),
- (-75.6058, 72.2436),
- (-74.2286, 71.7671),
- (-74.0991, 71.3308),
- (-72.2422, 71.5569),
- (-71.2000, 70.9200),
- (-68.7860, 70.5250),
- (-67.9149, 70.1219),
- (-66.9690, 69.1860),
- (-68.8051, 68.7201),
- (-66.4498, 68.0671),
- (-64.8623, 67.8475),
- (-63.4249, 66.9284),
- (-61.8519, 66.8621),
- (-62.1631, 66.1602),
- (-63.9184, 64.9986),
- (-65.1488, 65.4260),
- (-66.7212, 66.3880),
- (-68.0150, 66.2627),
- (-68.1412, 65.6897),
- (-67.0896, 65.1084),
- (-65.7320, 64.6484),
- (-65.3201, 64.3827),
- (-64.6694, 63.3929),
- (-65.0138, 62.6741),
- (-66.2750, 62.9450),
- (-68.7831, 63.7456),
- (-67.3696, 62.8839),
- (-66.3282, 62.2800),
- (-66.1655, 61.9308),
- (-68.8773, 62.3301),
- (-71.0234, 62.9107),
- (-72.2353, 63.3978),
- (-71.8862, 63.6799),
- (-73.3783, 64.1939),
- (-74.8344, 64.6791),
- (-74.8185, 64.3890),
- (-77.7099, 64.2295),
- (-78.5559, 64.5729),
- (-77.8972, 65.3091),
- (-76.0182, 65.3269),
- (-73.9597, 65.4547),
- (-74.2938, 65.8117),
- (-73.9449, 66.3105),
- (-72.6511, 67.2845),
- (-72.9260, 67.7269),
- (-73.3116, 68.0694),
- (-74.8433, 68.5546),
- (-76.8691, 68.8947),
- (-76.2286, 69.1477),
- (-77.2873, 69.7695),
- (-78.1686, 69.8264),
- (-78.9572, 70.1668),
- (-79.4924, 69.8718),
- (-81.3054, 69.7431),
- (-84.9447, 69.9666),
- (-87.0600, 70.2600),
- (-88.6817, 70.4107),
- (-89.5134, 70.7620),
- (-88.4677, 71.2181),
- (-89.8881, 71.2225),
- (-90.2051, 72.2350),
- (-89.4365, 73.1294),
- (-88.4082, 73.5378),
- (-85.8261, 73.8038),
- (-86.5621, 73.1574),
- (-85.7743, 72.5341),
- (-84.8501, 73.3402),
- (-82.3155, 73.7509),
- (-80.6000, 72.7165),
- (-80.7489, 72.0619),
- (-78.7706, 72.3521),
- (-94.5036, 74.1349),
- (-92.4200, 74.1000),
- (-90.5097, 73.8567),
- (-92.0039, 72.9662),
- (-93.1962, 72.7719),
- (-94.2690, 72.0245),
- (-95.4098, 72.0618),
- (-96.0337, 72.9402),
- (-96.0182, 73.4374),
- (-95.4957, 73.8624),
- (-94.5036, 74.1349),
- (-100.4383, 72.7058),
- (-101.54, 73.36),
- (-100.3564, 73.8438),
- (-99.1638, 73.6333),
- (-97.38, 73.76),
- (-97.12, 73.47),
- (-98.0535, 72.9905),
- (-96.54, 72.56),
- (-96.72, 71.66),
- (-98.3596, 71.2728),
- (-99.3228, 71.3563),
- (-100.0148, 71.7382),
- (-102.48, 72.4829),
- (-102.48, 72.83),
- (-100.4383, 72.7058),
- (-107.8194, 75.8455),
- (-106.9289, 76.0128),
- (-105.8809, 75.9693),
- (-105.7049, 75.4795),
- (-106.3134, 75.0052),
- (-109.6999, 74.8500),
- (-112.2230, 74.4169),
- (-113.7438, 74.3942),
- (-113.8713, 74.7202),
- (-111.7942, 75.1624),
- (-116.3121, 75.0434),
- (-117.7104, 75.2222),
- (-116.3460, 76.1990),
- (-115.4048, 76.4788),
- (-112.5905, 76.1413),
- (-110.8142, 75.5491),
- (-109.0671, 75.4732),
- (-110.4972, 76.4298),
- (-109.5811, 76.7941),
- (-108.5485, 76.6783),
- (-108.2114, 76.2016),
- (-107.8194, 75.8455),
- (-122.8549, 76.1165),
- (-121.1575, 76.8645),
- (-119.1039, 77.5122),
- (-117.5701, 77.4983),
- (-116.1985, 77.6452),
- (-116.3358, 76.8769),
- (-117.1060, 76.5300),
- (-118.0404, 76.4811),
- (-119.8993, 76.0532),
- (-121.4999, 75.9000),
- (-122.8549, 76.1165),
- (-121.5378, 74.4489),
- (-120.1097, 74.2413),
- (-117.5556, 74.1857),
- (-116.5844, 73.8960),
- (-115.5107, 73.4752),
- (-116.7679, 73.2229),
- (-119.22, 72.52),
- (-120.46, 71.82),
- (-120.46, 71.3836),
- (-123.0921, 70.9016),
- (-123.62, 71.34),
- (-125.9289, 71.8686),
- (-125.5, 72.2922),
- (-124.8072, 73.0225),
- (-123.94, 73.68),
- (-124.9177, 74.2927),
- (-121.5378, 74.4489),
- (-166.4677, 60.3841),
- (-165.6744, 60.2936),
- (-165.5791, 59.9099),
- (-166.1927, 59.7544),
- (-166.8483, 59.9414),
- (-167.4552, 60.2130),
- (-166.4677, 60.3841),
- (-153.2287, 57.9690),
- (-152.5647, 57.9014),
- (-152.1411, 57.5910),
- (-153.0063, 57.1158),
- (-154.0050, 56.7346),
- (-154.5164, 56.9927),
- (-154.6709, 57.4611),
- (-153.7627, 57.8165),
- (-153.2287, 57.9690),
- (-132.7100, 54.0400),
- (-131.7499, 54.1200),
- (-132.0494, 52.9846),
- (-131.1790, 52.1804),
- (-131.5778, 52.1823),
- (-132.1804, 52.6397),
- (-132.5499, 53.1000),
- (-133.0546, 53.4114),
- (-133.2396, 53.8510),
- (-133.1800, 54.1699),
- (-132.7100, 54.0400),
- (-125.4150, 49.9500),
- (-124.9207, 49.4752),
- (-123.9225, 49.0624),
- (-123.5100, 48.5100),
- (-124.0128, 48.3708),
- (-125.6550, 48.8250),
- (-125.9549, 49.1799),
- (-126.8500, 49.5300),
- (-127.0299, 49.8149),
- (-128.0593, 49.9949),
- (-128.4445, 50.5391),
- (-128.3584, 50.7706),
- (-127.3085, 50.5525),
- (-126.6950, 50.4009),
- (-125.7550, 50.2950),
- (-125.4150, 49.9500),
- (-171.7316, 63.7825),
- (-171.1144, 63.5921),
- (-170.4911, 63.6949),
- (-169.6825, 63.4311),
- (-168.6894, 63.2975),
- (-168.7719, 63.1885),
- (-169.5294, 62.9769),
- (-170.2905, 63.1944),
- (-170.6713, 63.3758),
- (-171.5530, 63.3177),
- (-171.7911, 63.4058),
- (-171.7316, 63.7825),
- (-105.4922, 79.3015),
- (-103.5292, 79.1653),
- (-100.8251, 78.8004),
- (-100.0601, 78.3247),
- (-99.6709, 77.9075),
- (-101.3039, 78.0189),
- (-102.9498, 78.3432),
- (-105.1761, 78.3803),
- (-104.2104, 78.6774),
- (-105.4195, 78.9183),
- (-105.4922, 79.3015),
- (32.9469, 35.3867),
- (33.6672, 35.3732),
- (34.5764, 35.6715),
- (33.9008, 35.2457),
- (34.0048, 34.9780),
- (32.9798, 34.5718),
- (32.4902, 34.7016),
- (32.2566, 35.1032),
- (32.8024, 35.1455),
- (32.9469, 35.3867),
- (26.2900, 35.2999),
- (26.1649, 35.0049),
- (24.7249, 34.9199),
- (24.7350, 35.0849),
- (23.5149, 35.2799),
- (23.6999, 35.7050),
- (24.2466, 35.3680),
- (25.0250, 35.4249),
- (25.7692, 35.3540),
- (25.7450, 35.1799),
- (26.2900, 35.2999),
- (49.5435, -12.4698),
- (49.8089, -12.8952),
- (50.0565, -13.5557),
- (50.2174, -14.7587),
- (50.4765, -15.2265),
- (50.3771, -15.7060),
- (50.2002, -16.0002),
- (49.8606, -15.4142),
- (49.6726, -15.7101),
- (49.8633, -16.4510),
- (49.7745, -16.8750),
- (49.4986, -17.1060),
- (49.4356, -17.9530),
- (49.0417, -19.1187),
- (48.5485, -20.4968),
- (47.9307, -22.3915),
- (47.5477, -23.7819),
- (47.0957, -24.9416),
- (46.2824, -25.1784),
- (45.4095, -25.6014),
- (44.8335, -25.3461),
- (44.0397, -24.9883),
- (43.7637, -24.4606),
- (43.6977, -23.5741),
- (43.3456, -22.7769),
- (43.2541, -22.0574),
- (43.4332, -21.3364),
- (43.8936, -21.1633),
- (43.8963, -20.8304),
- (44.3743, -20.0723),
- (44.4643, -19.4354),
- (44.2324, -18.9619),
- (44.0429, -18.3313),
- (43.9630, -17.4099),
- (44.3124, -16.8504),
- (44.4465, -16.2162),
- (44.9449, -16.1793),
- (45.5027, -15.9743),
- (45.8729, -15.7934),
- (46.3122, -15.7800),
- (46.8821, -15.2101),
- (47.7051, -14.5943),
- (48.0052, -14.0912),
- (47.8689, -13.6638),
- (48.2938, -13.7840),
- (48.8450, -13.0891),
- (48.8635, -12.4878),
- (49.1946, -12.0405),
- (49.5435, -12.4698),
- (167.2168, -15.8918),
- (167.8448, -16.4663),
- (167.5151, -16.5978),
- (167.1800, -16.1599),
- (167.2168, -15.8918),
- (166.7931, -15.6688),
- (166.6498, -15.3927),
- (166.6291, -14.6264),
- (167.1077, -14.9339),
- (167.2700, -15.7400),
- (167.0012, -15.6146),
- (166.7931, -15.6688),
- (134.2101, -6.8952),
- (134.1127, -6.1424),
- (134.2903, -5.7830),
- (134.4996, -5.4450),
- (134.7270, -5.7375),
- (134.7246, -6.2144),
- (134.2101, -6.8952),
- (-48.6606, -78.0470),
- (-48.1513, -78.0470),
- (-46.6628, -77.8314),
- (-45.1547, -78.0470),
- (-43.9208, -78.4781),
- (-43.4899, -79.0855),
- (-43.3724, -79.5166),
- (-43.3332, -80.0261),
- (-44.8805, -80.3396),
- (-46.5061, -80.5943),
- (-48.3864, -80.8294),
- (-50.4821, -81.0254),
- (-52.8519, -80.9666),
- (-54.1642, -80.6335),
- (-53.9879, -80.2220),
- (-51.8531, -79.9477),
- (-50.9913, -79.6146),
- (-50.3645, -79.1834),
- (-49.9141, -78.8112),
- (-49.3069, -78.4585),
- (-48.6606, -78.0470),
- (-66.2900, -80.2557),
- (-64.0376, -80.2948),
- (-61.8832, -80.3928),
- (-61.1389, -79.9813),
- (-60.6101, -79.6286),
- (-59.5720, -80.0401),
- (-59.8658, -80.5496),
- (-60.1596, -81.0003),
- (-62.2553, -80.8631),
- (-64.4881, -80.9219),
- (-65.7416, -80.5888),
- (-65.7416, -80.5496),
- (-66.2900, -80.2557),
- (-73.9158, -71.2693),
- (-73.2303, -71.1517),
- (-72.0747, -71.1909),
- (-71.7809, -70.6814),
- (-71.7221, -70.3091),
- (-71.7417, -69.5057),
- (-71.1738, -69.0354),
- (-70.2532, -68.8787),
- (-69.7244, -69.2510),
- (-69.4894, -69.6233),
- (-69.0585, -70.0740),
- (-68.7255, -70.5051),
- (-68.4513, -70.9558),
- (-68.3338, -71.4064),
- (-68.5101, -71.7984),
- (-68.7842, -72.1706),
- (-69.9594, -72.3078),
- (-71.0758, -72.5038),
- (-72.3881, -72.4842),
- (-71.8984, -72.0923),
- (-73.0736, -72.2294),
- (-74.1900, -72.3666),
- (-74.9538, -72.0727),
- (-75.0126, -71.6612),
- (-73.9158, -71.2693),
- (-102.3307, -71.8941),
- (-101.7039, -71.7177),
- (-100.4309, -71.8549),
- (-98.9815, -71.9333),
- (-97.8847, -72.0705),
- (-96.7879, -71.9529),
- (-96.2003, -72.5212),
- (-96.9837, -72.4428),
- (-98.1980, -72.4820),
- (-99.4320, -72.4428),
- (-100.7834, -72.5016),
- (-101.8018, -72.3056),
- (-102.3307, -71.8941),
- (-122.6217, -73.6577),
- (-122.4062, -73.3246),
- (-121.2115, -73.5009),
- (-119.9188, -73.6577),
- (-118.7241, -73.4813),
- (-119.2921, -73.8340),
- (-120.2322, -74.0888),
- (-121.6228, -74.0104),
- (-122.6217, -73.6577),
- (-127.2831, -73.4617),
- (-126.5584, -73.2462),
- (-125.5595, -73.4813),
- (-124.0318, -73.8732),
- (-124.6194, -73.8340),
- (-125.9121, -73.7361),
- (-127.2831, -73.4617),
- (165.7799, -21.0800),
- (166.5999, -21.7000),
- (167.1200, -22.1599),
- (166.7400, -22.3999),
- (166.1897, -22.1297),
- (165.4743, -21.6796),
- (164.8298, -21.1498),
- (164.1679, -20.4447),
- (164.0296, -20.1056),
- (164.4599, -20.1200),
- (165.0200, -20.4599),
- (165.4600, -20.8000),
- (165.7799, -21.0800),
- (152.6400, -3.6599),
- (153.0199, -3.9800),
- (153.1400, -4.4999),
- (152.8272, -4.7664),
- (152.6386, -4.1761),
- (152.4060, -3.7897),
- (151.9532, -3.4620),
- (151.3842, -3.0354),
- (150.6620, -2.7414),
- (150.9399, -2.5000),
- (151.4799, -2.7799),
- (151.8200, -2.9999),
- (152.2399, -3.2400),
- (152.6400, -3.6599),
- (151.3013, -5.8407),
- (150.7544, -6.0837),
- (150.2411, -6.3177),
- (149.7099, -6.3165),
- (148.8900, -6.0260),
- (148.3189, -5.7471),
- (148.4018, -5.4377),
- (149.2984, -5.5837),
- (149.8455, -5.5055),
- (149.9962, -5.0261),
- (150.1397, -5.0013),
- (150.2369, -5.5322),
- (150.8074, -5.4558),
- (151.0896, -5.1136),
- (151.6478, -4.7570),
- (151.5378, -4.1678),
- (152.1367, -4.1487),
- (152.3387, -4.3129),
- (152.3186, -4.8676),
- (151.9827, -5.4780),
- (151.4591, -5.5602),
- (151.3013, -5.8407),
- (162.1190, -10.4827),
- (162.3986, -10.8263),
- (161.7000, -10.8200),
- (161.3197, -10.2047),
- (161.9173, -10.4466),
- (162.1190, -10.4827),
- (161.6799, -9.5999),
- (161.5293, -9.7843),
- (160.7882, -8.9175),
- (160.5799, -8.3200),
- (160.9200, -8.3200),
- (161.2800, -9.1200),
- (161.6799, -9.5999),
- (160.8522, -9.8729),
- (160.4625, -9.8952),
- (159.8494, -9.7940),
- (159.6400, -9.6399),
- (159.7029, -9.2429),
- (160.3629, -9.4003),
- (160.6885, -9.6101),
- (160.8522, -9.8729),
- (159.6400, -8.0200),
- (159.8750, -8.3373),
- (159.9174, -8.5382),
- (159.1336, -8.1141),
- (158.5861, -7.7548),
- (158.2111, -7.4218),
- (158.3599, -7.3200),
- (158.8200, -7.5600),
- (159.6400, -8.0200),
- (157.1400, -7.0216),
- (157.5384, -7.3478),
- (157.3394, -7.4047),
- (156.9020, -7.1768),
- (156.4913, -6.7659),
- (156.5428, -6.5993),
- (157.1400, -7.0216),
- (154.7599, -5.3399),
- (155.0629, -5.5667),
- (155.5477, -6.2006),
- (156.0199, -6.5400),
- (155.8800, -6.8199),
- (155.5999, -6.9199),
- (155.1669, -6.5359),
- (154.7291, -5.9007),
- (154.5141, -5.1391),
- (154.6525, -5.0423),
- (154.7599, -5.3399),
- (176.8858, -40.0659),
- (176.5080, -40.6047),
- (176.0124, -41.2896),
- (175.2395, -41.6883),
- (175.0678, -41.4258),
- (174.6509, -41.2818),
- (175.2276, -40.4592),
- (174.9001, -39.9088),
- (173.8240, -39.5088),
- (173.8522, -39.1466),
- (174.5748, -38.7976),
- (174.7434, -38.0278),
- (174.6969, -37.3811),
- (174.2920, -36.7110),
- (174.3190, -36.5348),
- (173.8409, -36.1219),
- (173.0541, -35.2371),
- (172.6360, -34.5291),
- (173.0070, -34.4506),
- (173.5512, -35.0061),
- (174.3293, -35.2654),
- (174.6120, -36.1563),
- (175.3366, -37.2090),
- (175.3575, -36.5261),
- (175.8088, -36.7989),
- (175.9584, -37.5553),
- (176.7631, -37.8812),
- (177.4388, -37.9612),
- (178.0103, -37.5798),
- (178.5170, -37.6953),
- (178.2747, -38.5828),
- (177.9704, -39.1663),
- (177.2069, -39.1457),
- (176.9399, -39.4497),
- (177.0329, -39.8799),
- (176.8858, -40.0659),
- (169.6678, -43.5553),
- (170.5249, -43.0316),
- (171.1250, -42.5127),
- (171.5697, -41.7674),
- (171.9487, -41.5144),
- (172.0972, -40.9561),
- (172.7985, -40.4939),
- (173.0203, -40.9190),
- (173.2472, -41.3319),
- (173.9584, -40.9267),
- (174.2475, -41.3491),
- (174.2485, -41.7700),
- (173.8764, -42.2331),
- (173.2227, -42.9700),
- (172.7112, -43.3722),
- (173.0801, -43.8533),
- (172.3085, -43.8656),
- (171.4529, -44.2424),
- (171.1851, -44.8971),
- (170.6166, -45.9089),
- (169.8314, -46.3557),
- (169.3323, -46.6412),
- (168.4113, -46.6199),
- (167.7637, -46.2901),
- (166.6768, -46.2199),
- (166.5091, -45.8527),
- (167.0463, -45.1109),
- (168.3037, -44.1239),
- (168.9494, -43.9358),
- (169.6678, -43.5553),
- (147.6892, -40.8082),
- (148.2890, -40.8754),
- (148.3598, -42.0623),
- (148.0173, -42.4070),
- (147.9140, -43.2115),
- (147.5645, -42.9376),
- (146.8703, -43.6345),
- (146.6633, -43.5808),
- (146.0483, -43.5497),
- (145.4319, -42.6937),
- (145.2950, -42.0336),
- (144.7180, -41.1625),
- (144.7437, -40.7039),
- (145.3979, -40.7925),
- (146.3641, -41.1376),
- (146.9085, -41.0005),
- (147.6892, -40.8082),
- (126.1487, -32.2159),
- (125.0886, -32.7287),
- (124.2216, -32.9594),
- (124.0289, -33.4838),
- (123.6596, -33.8901),
- (122.8110, -33.9144),
- (122.1830, -34.0034),
- (121.2991, -33.8210),
- (120.5802, -33.9301),
- (119.8936, -33.9760),
- (119.2988, -34.5093),
- (119.0073, -34.4641),
- (118.5057, -34.7468),
- (118.0249, -35.0647),
- (117.2955, -35.0254),
- (116.6251, -35.0250),
- (115.5643, -34.3864),
- (115.0268, -34.1965),
- (115.0486, -33.6234),
- (115.5451, -33.4872),
- (115.7146, -33.2595),
- (115.6793, -32.9003),
- (115.8016, -32.2050),
- (115.6896, -31.6124),
- (115.1609, -30.6015),
- (114.9970, -30.0307),
- (115.0400, -29.4610),
- (114.6419, -28.8102),
- (114.6164, -28.5163),
- (114.1735, -28.1180),
- (114.0488, -27.3347),
- (113.4774, -26.5431),
- (113.3389, -26.1165),
- (113.7783, -26.5490),
- (113.4409, -25.6212),
- (113.9369, -25.9112),
- (114.2328, -26.2984),
- (114.2161, -25.7862),
- (113.7212, -24.9989),
- (113.6253, -24.6839),
- (113.3935, -24.3847),
- (113.5020, -23.8063),
- (113.7069, -23.5602),
- (113.8434, -23.0599),
- (113.7365, -22.4754),
- (114.1497, -21.7558),
- (114.2253, -22.5174),
- (114.6477, -21.8295),
- (115.4601, -21.4951),
- (115.9473, -21.0686),
- (116.7116, -20.7016),
- (117.1663, -20.6235),
- (117.4415, -20.7468),
- (118.2295, -20.3742),
- (118.8360, -20.2633),
- (118.9878, -20.0442),
- (119.2524, -19.9529),
- (119.8052, -19.9765),
- (120.8562, -19.6837),
- (121.3998, -19.2397),
- (121.6550, -18.7053),
- (122.2416, -18.1976),
- (122.2866, -17.7986),
- (122.3127, -17.2549),
- (123.0125, -16.4051),
- (123.4337, -17.2685),
- (123.8593, -17.0690),
- (123.5032, -16.5965),
- (123.8170, -16.1113),
- (124.2582, -16.3279),
- (124.3797, -15.5670),
- (124.9261, -15.0751),
- (125.1672, -14.6803),
- (125.6700, -14.5100),
- (125.6857, -14.2306),
- (126.1251, -14.3473),
- (126.1428, -14.0959),
- (126.5825, -13.9527),
- (127.0658, -13.8179),
- (127.8046, -14.2769),
- (128.3596, -14.8691),
- (128.9855, -14.8759),
- (129.6214, -14.9697),
- (129.4096, -14.4206),
- (129.8886, -13.6187),
- (130.3394, -13.3573),
- (130.1835, -13.1075),
- (130.6177, -12.5363),
- (131.2234, -12.1836),
- (131.7350, -12.3024),
- (132.5752, -12.1140),
- (132.5572, -11.6030),
- (131.8246, -11.2737),
- (132.3572, -11.1285),
- (133.0195, -11.3764),
- (133.5508, -11.7865),
- (134.3930, -12.0423),
- (134.6786, -11.9411),
- (135.2984, -12.2486),
- (135.8826, -11.9622),
- (136.2583, -12.0493),
- (136.4924, -11.8572),
- (136.9516, -12.3519),
- (136.6851, -12.8872),
- (136.3054, -13.2912),
- (135.9617, -13.3245),
- (136.0776, -13.7242),
- (135.7838, -14.2239),
- (135.4286, -14.7154),
- (135.5001, -14.9977),
- (136.2951, -15.5502),
- (137.0653, -15.8707),
- (137.5804, -16.2150),
- (138.3032, -16.8076),
- (138.5851, -16.8066),
- (139.1085, -17.0626),
- (139.2605, -17.3716),
- (140.2151, -17.7108),
- (140.8754, -17.3690),
- (141.0711, -16.8320),
- (141.2740, -16.3888),
- (141.3982, -15.8405),
- (141.7021, -15.0449),
- (141.5633, -14.5613),
- (141.6355, -14.2703),
- (141.5198, -13.6980),
- (141.6509, -12.9446),
- (141.8426, -12.7415),
- (141.6869, -12.4076),
- (141.9286, -11.8774),
- (142.1184, -11.3280),
- (142.1437, -11.0427),
- (142.5152, -10.6681),
- (142.7973, -11.1573),
- (142.8667, -11.7847),
- (143.1159, -11.9056),
- (143.1586, -12.3256),
- (143.5221, -12.8343),
- (143.5971, -13.4004),
- (143.5618, -13.7636),
- (143.9220, -14.5483),
- (144.5637, -14.1711),
- (144.8948, -14.5944),
- (145.3747, -14.9849),
- (145.2719, -15.4282),
- (145.4852, -16.2856),
- (145.6369, -16.7849),
- (145.8888, -16.9069),
- (146.1603, -17.7616),
- (146.0636, -18.2800),
- (146.3874, -18.9582),
- (147.4710, -19.4807),
- (148.1776, -19.9559),
- (148.8484, -20.3912),
- (148.7174, -20.6334),
- (149.2894, -21.2605),
- (149.6783, -22.3425),
- (150.0773, -22.1227),
- (150.4829, -22.5561),
- (150.7272, -22.4023),
- (150.8995, -23.4622),
- (151.6091, -24.0762),
- (152.0735, -24.4578),
- (152.8551, -25.2675),
- (153.1361, -26.0711),
- (153.1619, -26.6413),
- (153.0929, -27.2602),
- (153.5694, -28.1100),
- (153.5121, -28.9950),
- (153.3390, -29.4582),
- (153.0692, -30.3502),
- (153.0895, -30.9236),
- (152.8915, -31.6404),
- (152.4500, -32.5500),
- (151.7091, -33.0413),
- (151.3439, -33.8160),
- (151.0105, -34.3103),
- (150.7141, -35.1734),
- (150.3282, -35.6718),
- (150.0752, -36.4202),
- (149.9461, -37.1090),
- (149.9972, -37.4252),
- (149.4238, -37.7726),
- (148.3046, -37.8090),
- (147.3817, -38.2192),
- (146.9221, -38.6065),
- (146.3179, -39.0357),
- (145.4896, -38.5937),
- (144.8769, -38.4174),
- (145.0322, -37.8961),
- (144.4856, -38.0853),
- (143.6099, -38.8094),
- (142.7454, -38.5382),
- (142.1783, -38.3800),
- (141.6065, -38.3085),
- (140.6385, -38.0193),
- (139.9921, -37.4029),
- (139.8065, -36.6436),
- (139.5741, -36.1383),
- (139.0828, -35.7327),
- (138.1207, -35.6122),
- (138.4494, -35.1272),
- (138.2075, -34.3847),
- (137.7191, -35.0768),
- (136.8294, -35.2605),
- (137.3523, -34.7073),
- (137.5038, -34.1302),
- (137.8901, -33.6404),
- (137.8103, -32.9000),
- (136.9968, -33.7527),
- (136.3720, -34.0947),
- (135.9890, -34.8901),
- (135.2082, -34.4786),
- (135.2392, -33.9479),
- (134.6134, -33.2227),
- (134.0859, -32.8480),
- (134.2739, -32.6172),
- (132.9907, -32.0112),
- (132.2880, -31.9826),
- (131.3263, -31.4958),
- (129.5357, -31.5904),
- (128.2409, -31.9484),
- (127.1028, -32.2822),
- (126.1487, -32.2159),
- (81.7879, 7.5230),
- (81.6373, 6.4817),
- (81.2180, 6.1971),
- (80.3483, 5.9683),
- (79.8724, 6.7634),
- (79.6951, 8.2008),
- (80.1478, 9.8240),
- (80.8388, 9.2684),
- (81.3043, 8.5642),
- (81.7879, 7.5230),
- (129.3709, -2.8021),
- (130.4713, -3.0937),
- (130.8348, -3.8584),
- (129.9905, -3.4463),
- (129.1552, -3.3626),
- (128.5906, -3.4286),
- (127.8988, -3.3934),
- (128.1358, -2.8436),
- (129.3709, -2.8021),
- (126.8749, -3.7909),
- (126.1838, -3.6073),
- (125.9890, -3.1772),
- (127.0006, -3.1293),
- (127.2492, -3.4590),
- (126.8749, -3.7909),
- (127.9323, 2.1745),
- (128.0041, 1.6285),
- (128.5945, 1.5408),
- (128.6882, 1.1323),
- (128.6359, 0.2584),
- (128.1201, 0.3564),
- (127.9680, -0.2520),
- (128.3799, -0.7800),
- (128.1000, -0.8999),
- (127.6964, -0.2665),
- (127.3994, 1.0117),
- (127.6005, 1.8106),
- (127.9323, 2.1745),
- (122.9275, 0.8751),
- (124.0775, 0.9171),
- (125.0659, 1.6432),
- (125.2405, 1.4198),
- (124.4370, 0.4278),
- (123.6855, 0.2355),
- (122.7230, 0.4311),
- (121.0567, 0.3812),
- (120.1830, 0.2372),
- (120.0408, -0.5196),
- (120.9359, -1.4089),
- (121.4758, -0.9559),
- (123.3405, -0.6156),
- (123.2583, -1.0762),
- (122.8226, -0.9309),
- (122.3885, -1.5168),
- (121.5082, -1.9044),
- (122.4545, -3.1860),
- (122.2718, -3.5295),
- (123.1709, -4.6836),
- (123.1623, -5.3406),
- (122.6285, -5.6345),
- (122.2363, -5.2829),
- (122.7195, -4.4641),
- (121.7382, -4.8513),
- (121.4894, -4.5745),
- (121.6191, -4.1884),
- (120.8981, -3.6021),
- (120.9723, -2.6276),
- (120.3054, -2.9316),
- (120.3900, -4.0975),
- (120.4307, -5.5282),
- (119.7965, -5.6734),
- (119.3669, -5.3798),
- (119.6536, -4.4594),
- (119.4988, -3.4944),
- (119.0783, -3.4870),
- (118.7677, -2.8019),
- (119.1809, -2.1471),
- (119.3233, -1.3531),
- (119.8259, 0.1542),
- (120.0357, 0.5664),
- (120.8857, 1.3092),
- (121.6668, 1.0139),
- (122.9275, 0.8751),
- (120.2950, -10.2586),
- (118.9678, -9.5579),
- (119.9003, -9.3613),
- (120.4257, -9.6659),
- (120.7755, -9.9696),
- (120.7156, -10.2395),
- (120.2950, -10.2586),
- (121.3416, -8.5367),
- (122.0073, -8.4606),
- (122.9035, -8.0942),
- (122.7569, -8.6498),
- (121.2544, -8.9336),
- (119.9243, -8.8104),
- (119.9209, -8.4448),
- (120.7150, -8.2369),
- (121.3416, -8.5367),
- (118.2606, -8.3623),
- (118.8784, -8.2806),
- (119.1265, -8.7058),
- (117.9704, -8.9066),
- (117.2777, -9.0408),
- (116.7401, -9.0328),
- (117.0837, -8.4571),
- (117.6320, -8.4493),
- (117.9000, -8.0956),
- (118.2606, -8.3623),
- (108.4868, -6.4219),
- (108.6234, -6.7776),
- (110.5392, -6.8773),
- (110.7595, -6.4651),
- (112.6148, -6.9460),
- (112.9787, -7.5942),
- (114.4789, -7.7765),
- (115.7055, -8.3708),
- (114.5645, -8.7518),
- (113.4647, -8.3489),
- (112.5596, -8.3761),
- (111.5220, -8.3021),
- (110.5861, -8.1226),
- (109.4276, -7.7406),
- (108.6936, -7.6416),
- (108.2777, -7.7666),
- (106.4541, -7.3548),
- (106.2806, -6.9248),
- (105.3654, -6.8514),
- (106.0516, -5.8959),
- (107.2650, -5.9549),
- (108.0720, -6.3457),
- (108.4868, -6.4219),
- (104.3699, -1.0848),
- (104.5394, -1.7823),
- (104.8878, -2.3404),
- (105.6221, -2.4288),
- (106.1085, -3.0617),
- (105.8574, -4.3055),
- (105.8176, -5.8523),
- (104.7103, -5.8732),
- (103.8682, -5.0373),
- (102.5842, -4.2202),
- (102.1561, -3.6141),
- (101.3991, -2.7997),
- (100.9025, -2.0502),
- (100.1419, -0.6503),
- (99.2637, 0.1831),
- (98.9700, 1.0428),
- (98.6013, 1.8235),
- (97.6995, 2.4531),
- (97.1769, 3.3087),
- (96.4240, 3.8688),
- (95.3808, 4.9707),
- (95.2930, 5.4798),
- (95.9368, 5.4395),
- (97.4848, 5.2463),
- (98.3691, 4.2683),
- (99.1425, 3.5903),
- (99.6939, 3.1743),
- (100.6414, 2.0993),
- (101.6580, 2.0836),
- (102.4982, 1.3987),
- (103.0768, 0.5613),
- (103.8383, 0.1045),
- (103.4376, -0.7119),
- (104.0107, -1.0592),
- (104.3699, -1.0848),
- (120.8338, 12.7044),
- (120.3234, 13.4664),
- (121.1801, 13.4296),
- (121.5273, 13.0695),
- (121.2621, 12.2055),
- (120.8338, 12.7044),
- (122.5860, 9.9810),
- (122.8370, 10.2611),
- (122.9474, 10.8818),
- (123.4988, 10.9406),
- (123.3377, 10.2673),
- (124.0779, 11.2327),
- (123.9824, 10.2787),
- (123.6230, 9.9500),
- (123.3099, 9.3182),
- (122.9958, 9.0221),
- (122.3800, 9.7133),
- (122.5860, 9.9810),
- (126.3768, 8.4147),
- (126.4785, 7.7503),
- (126.5374, 7.1894),
- (126.1967, 6.2742),
- (125.8314, 7.2937),
- (125.3638, 6.7864),
- (125.6831, 6.0496),
- (125.3965, 5.5810),
- (124.2197, 6.1613),
- (123.9387, 6.8851),
- (124.2436, 7.3606),
- (123.6101, 7.8335),
- (123.2960, 7.4188),
- (122.8255, 7.4573),
- (122.0854, 6.8994),
- (121.9199, 7.1921),
- (122.3123, 8.0349),
- (122.9423, 8.3162),
- (123.4876, 8.6930),
- (123.8411, 8.2403),
- (124.6014, 8.5141),
- (124.7646, 8.9604),
- (125.4713, 8.9869),
- (125.4121, 9.7603),
- (126.2227, 9.2860),
- (126.3066, 8.7824),
- (126.3768, 8.4147),
- (109.4752, 18.1977),
- (108.6552, 18.5076),
- (108.6262, 19.3678),
- (109.1190, 19.8210),
- (110.2115, 20.1012),
- (110.7865, 20.0775),
- (111.0100, 19.6959),
- (110.5706, 19.2558),
- (110.3391, 18.6783),
- (109.4752, 18.1977),
- (121.7778, 24.3942),
- (121.1756, 22.7908),
- (120.7470, 21.9705),
- (120.2200, 22.8148),
- (120.1061, 23.5562),
- (120.6946, 24.5384),
- (121.4950, 25.2954),
- (121.9512, 24.9975),
- (121.7778, 24.3942),
- (141.8846, 39.1808),
- (140.9594, 38.1740),
- (140.9763, 37.1420),
- (140.5997, 36.3439),
- (140.7740, 35.8428),
- (140.2532, 35.1381),
- (138.9755, 34.6676),
- (137.2175, 34.6062),
- (135.7929, 33.4648),
- (135.1209, 33.8490),
- (135.0794, 34.5965),
- (133.3403, 34.3759),
- (132.1567, 33.9049),
- (130.9861, 33.8857),
- (132.0000, 33.1499),
- (131.3327, 31.4503),
- (130.6863, 31.0295),
- (130.2024, 31.4182),
- (130.4476, 32.3194),
- (129.8146, 32.6103),
- (129.4084, 33.2960),
- (130.3539, 33.6041),
- (130.8784, 34.2327),
- (131.8842, 34.7497),
- (132.6176, 35.4333),
- (134.6083, 35.7316),
- (135.6775, 35.5271),
- (136.7238, 37.3049),
- (137.3906, 36.8273),
- (138.8576, 37.8274),
- (139.4264, 38.2159),
- (140.0547, 39.4388),
- (139.8833, 40.5633),
- (140.3057, 41.1950),
- (141.3689, 41.3785),
- (141.9142, 39.9916),
- (141.8846, 39.1808),
- (144.6134, 43.9609),
- (145.3208, 44.3847),
- (145.5431, 43.2621),
- (144.0596, 42.9883),
- (143.1838, 41.9952),
- (141.6114, 42.6787),
- (141.0672, 41.5845),
- (139.9551, 41.5695),
- (139.8175, 42.5637),
- (140.3120, 43.3332),
- (141.3805, 43.3888),
- (141.6719, 44.7721),
- (141.9676, 45.5514),
- (143.1428, 44.5103),
- (143.9101, 44.1740),
- (144.6134, 43.9609),
- (8.7099, 40.8999),
- (9.2100, 41.2099),
- (9.8099, 40.5000),
- (9.6695, 39.1773),
- (9.2148, 39.2404),
- (8.8069, 38.9066),
- (8.4283, 39.1718),
- (8.3882, 40.3783),
- (8.1599, 40.9500),
- (8.7099, 40.8999),
- (8.7460, 42.6281),
- (9.3900, 43.0099),
- (9.5600, 42.1525),
- (9.2297, 41.3800),
- (8.7757, 41.5836),
- (8.5442, 42.2565),
- (8.7460, 42.6281),
- (12.3709, 56.1114),
- (12.6900, 55.6099),
- (12.0899, 54.8000),
- (11.0435, 55.3648),
- (10.9039, 55.7799),
- (12.3709, 56.1114),
- (-4.2114, 58.5508),
- (-3.0050, 58.6350),
- (-4.0738, 57.5530),
- (-3.0550, 57.6900),
- (-1.9592, 57.6847),
- (-2.2199, 56.8700),
- (-3.1190, 55.9737),
- (-2.0850, 55.9099),
- (-1.1149, 54.6249),
- (-0.4304, 54.4643),
- (0.1849, 53.3250),
- (0.4699, 52.9299),
- (1.6815, 52.7395),
- (1.5599, 52.0999),
- (1.0505, 51.8067),
- (1.4498, 51.2894),
- (0.5503, 50.7657),
- (-0.7875, 50.7749),
- (-2.4899, 50.5000),
- (-2.9562, 50.6968),
- (-3.6174, 50.2283),
- (-4.5425, 50.3418),
- (-5.2450, 49.9599),
- (-5.7765, 50.1596),
- (-4.3099, 51.2100),
- (-3.4148, 51.4260),
- (-4.9843, 51.5934),
- (-5.2672, 51.9914),
- (-4.2223, 52.3013),
- (-4.7700, 52.8400),
- (-4.5799, 53.4950),
- (-3.0920, 53.4044),
- (-2.9450, 53.9849),
- (-3.6300, 54.6150),
- (-4.8441, 54.7909),
- (-5.0825, 55.0616),
- (-4.7191, 55.5084),
- (-5.0479, 55.7839),
- (-5.5863, 55.3111),
- (-5.6449, 56.2750),
- (-6.1499, 56.7850),
- (-5.7868, 57.8188),
- (-5.0099, 58.6300),
- (-4.2114, 58.5508),
- (-14.5086, 66.4558),
- (-14.7396, 65.8087),
- (-13.6097, 65.1266),
- (-14.9098, 64.3640),
- (-17.7944, 63.6787),
- (-18.6562, 63.4963),
- (-19.9727, 63.6436),
- (-22.7629, 63.9601),
- (-21.7784, 64.4021),
- (-23.9550, 64.8911),
- (-22.1844, 65.0849),
- (-22.2274, 65.3785),
- (-24.3261, 65.6111),
- (-23.6505, 66.2625),
- (-22.1349, 66.4104),
- (-20.5762, 65.7321),
- (-19.0568, 66.2766),
- (-17.7986, 65.9938),
- (-16.1678, 66.5268),
- (-14.5086, 66.4558),
- (142.9146, 53.7045),
- (143.2608, 52.7407),
- (143.2352, 51.7566),
- (143.6480, 50.7476),
- (144.6541, 48.9763),
- (143.1739, 49.3065),
- (142.5586, 47.8615),
- (143.5334, 46.8367),
- (143.5052, 46.1379),
- (142.7477, 46.7407),
- (142.0920, 45.9667),
- (141.9069, 46.8059),
- (142.0184, 47.7801),
- (141.9044, 48.8591),
- (142.1358, 49.6151),
- (142.1799, 50.9523),
- (141.5940, 51.9354),
- (141.6825, 53.3019),
- (142.6069, 53.7621),
- (142.2097, 54.2254),
- (142.6547, 54.3658),
- (142.9146, 53.7045),
- (118.5045, 9.3163),
- (117.1742, 8.3674),
- (117.6644, 9.0668),
- (118.3869, 9.6844),
- (118.9873, 10.3762),
- (119.5114, 11.3696),
- (119.6896, 10.5542),
- (119.0294, 10.0036),
- (118.5045, 9.3163),
- (122.3369, 18.2248),
- (122.1742, 17.8102),
- (122.5156, 17.0935),
- (122.2523, 16.2624),
- (121.6627, 15.9310),
- (121.5050, 15.1248),
- (121.7288, 14.3283),
- (122.2589, 14.2182),
- (122.7012, 14.3365),
- (123.9502, 13.7821),
- (123.8551, 13.2377),
- (124.1812, 12.9975),
- (124.0774, 12.5366),
- (123.2980, 13.0275),
- (122.9286, 13.5529),
- (122.6713, 13.1858),
- (122.0346, 13.7844),
- (121.1263, 13.6366),
- (120.6286, 13.8576),
- (120.6793, 14.2710),
- (120.9918, 14.5253),
- (120.6933, 14.7566),
- (120.5641, 14.3962),
- (120.0704, 14.9708),
- (119.9209, 15.4063),
- (119.8837, 16.3637),
- (120.2864, 16.0346),
- (120.3900, 17.5990),
- (120.7158, 18.5052),
- (121.3213, 18.5040),
- (121.9376, 18.2185),
- (122.2460, 18.4789),
- (122.3369, 18.2248),
- (122.0383, 11.4158),
- (121.8835, 11.8917),
- (122.4838, 11.5822),
- (123.1202, 11.5836),
- (123.1008, 11.1659),
- (122.6377, 10.7413),
- (122.0026, 10.4410),
- (121.9673, 10.9056),
- (122.0383, 11.4158),
- (125.5025, 12.1626),
- (125.7834, 11.0461),
- (125.0118, 11.3114),
- (125.0327, 10.9758),
- (125.2774, 10.3587),
- (124.8018, 10.1346),
- (124.7601, 10.8379),
- (124.4591, 10.8899),
- (124.3025, 11.4953),
- (124.8910, 11.4155),
- (124.8779, 11.7941),
- (124.2667, 12.5577),
- (125.2271, 12.5357),
- (125.5025, 12.1626),
- (-77.3533, 8.6705),
- (-76.8366, 8.6387),
- (-76.0863, 9.3368),
- (-75.6746, 9.4432),
- (-75.6647, 9.7740),
- (-75.4804, 10.6189),
- (-74.9068, 11.0830),
- (-74.2767, 11.1020),
- (-74.1972, 11.3104),
- (-73.4147, 11.2270),
- (-72.6278, 11.7319),
- (-72.2381, 11.9555),
- (-71.7540, 12.4373),
- (-71.3998, 12.3760),
- (-71.1374, 12.1129),
- (-71.3315, 11.7762),
- (-71.3600, 11.5399),
- (-71.9470, 11.4232),
- (-71.6208, 10.9694),
- (-71.6330, 10.4464),
- (-72.0741, 9.8656),
- (-71.6956, 9.0722),
- (-71.2645, 9.1372),
- (-71.0399, 9.8599),
- (-71.3500, 10.2119),
- (-71.4006, 10.9689),
- (-70.1552, 11.3754),
- (-70.2938, 11.8468),
- (-69.9432, 12.1623),
- (-69.5843, 11.4596),
- (-68.8829, 11.4433),
- (-68.2332, 10.8857),
- (-68.1941, 10.5546),
- (-67.2962, 10.5458),
- (-66.2278, 10.6486),
- (-65.6552, 10.2007),
- (-64.8904, 10.0772),
- (-64.3294, 10.3895),
- (-64.3180, 10.6414),
- (-63.0793, 10.7017),
- (-61.8809, 10.7156),
- (-62.7301, 10.4202),
- (-62.3885, 9.9482),
- (-61.5887, 9.8730),
- (-60.8305, 9.3813),
- (-60.6712, 8.5801),
- (-60.1500, 8.6027),
- (-59.7582, 8.3670),
- (-59.1016, 7.9992),
- (-58.4829, 7.3476),
- (-58.4548, 6.8327),
- (-58.0781, 6.8090),
- (-57.5422, 6.3212),
- (-57.1474, 5.9731),
- (-55.9493, 5.7728),
- (-55.8417, 5.9531),
- (-55.0332, 6.0252),
- (-53.9580, 5.7565),
- (-53.6184, 5.6465),
- (-52.8821, 5.4098),
- (-51.8233, 4.5657),
- (-51.6577, 4.1562),
- (-51.2999, 4.1200),
- (-51.0697, 3.6503),
- (-50.5088, 1.9015),
- (-49.9740, 1.7364),
- (-49.9471, 1.0461),
- (-50.6992, 0.2229),
- (-50.3882, -0.0784),
- (-48.6205, -0.2354),
- (-48.5844, -1.2378),
- (-47.8249, -0.5816),
- (-46.5665, -0.9410),
- (-44.9057, -1.5517),
- (-44.4176, -2.1377),
- (-44.5815, -2.6913),
- (-43.4187, -2.3831),
- (-41.4726, -2.9120),
- (-39.9786, -2.8730),
- (-38.5003, -3.7006),
- (-37.2232, -4.8209),
- (-36.4529, -5.1094),
- (-35.5977, -5.1495),
- (-35.2353, -5.4649),
- (-34.8960, -6.7381),
- (-34.7299, -7.3432),
- (-35.1282, -8.9964),
- (-35.6369, -9.6492),
- (-37.0465, -11.0407),
- (-37.6836, -12.1711),
- (-38.4238, -13.0381),
- (-38.6738, -13.0576),
- (-38.9532, -13.7933),
- (-38.8822, -15.6670),
- (-39.1610, -17.2084),
- (-39.2673, -17.8677),
- (-39.5835, -18.2622),
- (-39.7608, -19.5991),
- (-40.7747, -20.9045),
- (-40.9447, -21.9373),
- (-41.7541, -22.3706),
- (-41.9882, -22.9700),
- (-43.0747, -22.9676),
- (-44.6478, -23.3519),
- (-45.3521, -23.7968),
- (-46.4720, -24.0889),
- (-47.6489, -24.8851),
- (-48.4954, -25.8770),
- (-48.6410, -26.6236),
- (-48.4747, -27.1759),
- (-48.6615, -28.1861),
- (-48.8884, -28.6741),
- (-49.5873, -29.2244),
- (-50.6968, -30.9844),
- (-51.5762, -31.7776),
- (-52.2560, -32.2453),
- (-52.7120, -33.1965),
- (-53.3736, -33.7683),
- (-53.8064, -34.3968),
- (-54.9358, -34.9526),
- (-55.6740, -34.7526),
- (-56.2152, -34.8598),
- (-57.1396, -34.4304),
- (-57.8178, -34.4625),
- (-58.4270, -33.9094),
- (-58.4954, -34.4314),
- (-57.2258, -35.2880),
- (-57.3623, -35.9773),
- (-56.7374, -36.4131),
- (-56.7882, -36.9015),
- (-57.7491, -38.1838),
- (-59.2318, -38.7201),
- (-61.2374, -38.9284),
- (-62.3359, -38.8277),
- (-62.1257, -39.4241),
- (-62.3305, -40.1725),
- (-62.1459, -40.6768),
- (-62.7458, -41.0287),
- (-63.7704, -41.1667),
- (-64.7320, -40.8026),
- (-65.1180, -41.0643),
- (-64.9785, -42.0580),
- (-64.3034, -42.3590),
- (-63.7559, -42.0436),
- (-63.4580, -42.5631),
- (-64.3788, -42.8735),
- (-65.1818, -43.4953),
- (-65.3288, -44.5013),
- (-65.5652, -45.0367),
- (-66.5099, -45.0396),
- (-67.2937, -45.5518),
- (-67.5805, -46.3017),
- (-66.5970, -47.0338),
- (-65.6410, -47.2361),
- (-65.9850, -48.1332),
- (-67.1661, -48.6973),
- (-67.8160, -49.8696),
- (-68.7287, -50.2641),
- (-69.1385, -50.7325),
- (-68.8155, -51.7711),
- (-68.1499, -52.3499),
- (-68.5715, -52.2994),
- (-69.4612, -52.2919),
- (-69.9427, -52.5379),
- (-70.8451, -52.8991),
- (-71.0063, -53.8332),
- (-71.4297, -53.8564),
- (-72.5579, -53.5314),
- (-73.7027, -52.8350),
- (-74.9467, -52.2627),
- (-75.2600, -51.6293),
- (-74.9766, -51.0433),
- (-75.4797, -50.3783),
- (-75.6080, -48.6737),
- (-75.1827, -47.7119),
- (-74.1265, -46.9392),
- (-75.6443, -46.6476),
- (-74.6921, -45.7639),
- (-74.3517, -44.1030),
- (-73.2403, -44.4549),
- (-72.7178, -42.3833),
- (-73.3888, -42.1175),
- (-73.7013, -43.3657),
- (-74.3319, -43.2249),
- (-74.0179, -41.7948),
- (-73.6770, -39.9422),
- (-73.2175, -39.2586),
- (-73.5055, -38.2828),
- (-73.5880, -37.1562),
- (-73.1667, -37.1237),
- (-72.5531, -35.5088),
- (-71.8617, -33.9090),
- (-71.4384, -32.4188),
- (-71.6687, -30.9206),
- (-71.3700, -30.0956),
- (-71.4898, -28.8614),
- (-70.9051, -27.6403),
- (-70.7249, -25.7059),
- (-70.4039, -23.6289),
- (-70.0912, -21.3933),
- (-70.1644, -19.7564),
- (-70.3725, -18.3479),
- (-71.3752, -17.7737),
- (-71.4620, -17.3634),
- (-73.4445, -16.3593),
- (-75.2378, -15.2656),
- (-76.0092, -14.6492),
- (-76.4234, -13.8231),
- (-76.2592, -13.5350),
- (-77.1061, -12.2227),
- (-78.0921, -10.3777),
- (-79.0369, -8.3865),
- (-79.4459, -7.9308),
- (-79.7605, -7.1943),
- (-80.5374, -6.5416),
- (-81.2499, -6.1368),
- (-80.9263, -5.6905),
- (-81.4109, -4.7367),
- (-81.0996, -4.0363),
- (-80.3025, -3.4048),
- (-79.7702, -2.6575),
- (-79.9865, -2.2207),
- (-80.3687, -2.6851),
- (-80.9677, -2.2469),
- (-80.7648, -1.9650),
- (-80.9336, -1.0574),
- (-80.5833, -0.9066),
- (-80.3993, -0.2837),
- (-80.0208, 0.3603),
- (-80.0906, 0.7684),
- (-79.5427, 0.9829),
- (-78.8552, 1.3809),
- (-78.9909, 1.6913),
- (-78.6178, 1.7664),
- (-78.6621, 2.2673),
- (-78.4276, 2.6295),
- (-77.9315, 2.6966),
- (-77.5104, 3.3250),
- (-77.1276, 3.8496),
- (-77.4962, 4.0876),
- (-77.3076, 4.6679),
- (-77.5332, 5.5828),
- (-77.3188, 5.8453),
- (-77.4766, 6.6911),
- (-77.8815, 7.2237),
- (-74.6625, -52.8374),
- (-73.8380, -53.0474),
- (-72.4341, -53.7153),
- (-71.1077, -54.0743),
- (-70.5917, -53.6158),
- (-70.2674, -52.9312),
- (-69.3456, -52.5182),
- (-68.6341, -52.6362),
- (-68.2500, -53.1000),
- (-67.7499, -53.8499),
- (-66.4499, -54.4500),
- (-65.0500, -54.7000),
- (-65.5000, -55.1999),
- (-66.4499, -55.2500),
- (-66.9599, -54.8968),
- (-67.2910, -55.3012),
- (-68.1486, -55.6118),
- (-68.6399, -55.5800),
- (-69.2320, -55.4990),
- (-69.9580, -55.1984),
- (-71.0056, -55.0538),
- (-72.2639, -54.4951),
- (-73.2852, -53.9575),
- (-74.6625, -52.8374),
- (44.8469, 80.5898),
- (46.7991, 80.7719),
- (48.3184, 80.7840),
- (48.5228, 80.5145),
- (49.0971, 80.7539),
- (50.0397, 80.9188),
- (51.5229, 80.6997),
- (51.1361, 80.5472),
- (49.7936, 80.4154),
- (48.8944, 80.3395),
- (48.7549, 80.1754),
- (47.5861, 80.0101),
- (46.5028, 80.2472),
- (47.0724, 80.5594),
- (44.8469, 80.5898),
- (53.5082, 73.7498),
- (55.9024, 74.6274),
- (55.6319, 75.0814),
- (57.8686, 75.6093),
- (61.1700, 76.2518),
- (64.4983, 76.4390),
- (66.2109, 76.8097),
- (68.1570, 76.9396),
- (68.8522, 76.5448),
- (68.1805, 76.2336),
- (64.6373, 75.7377),
- (61.5835, 75.2608),
- (58.4770, 74.3090),
- (56.9867, 73.3330),
- (55.4193, 72.3712),
- (55.6228, 71.5405),
- (57.5356, 70.7204),
- (56.9449, 70.6327),
- (53.6773, 70.7626),
- (53.4120, 71.2066),
- (51.6018, 71.4747),
- (51.4557, 72.0148),
- (52.4782, 72.2294),
- (52.4441, 72.7747),
- (54.4276, 73.6275),
- (53.5082, 73.7498),
- (27.4075, 80.0564),
- (25.9246, 79.5178),
- (23.0244, 79.4000),
- (20.0751, 79.5668),
- (19.8972, 79.8423),
- (18.4622, 79.8598),
- (17.3680, 80.3188),
- (20.4559, 80.5981),
- (21.9079, 80.3576),
- (22.9192, 80.6571),
- (25.4476, 80.4073),
- (27.4075, 80.0564),
- (24.7241, 77.8538),
- (22.4903, 77.4449),
- (20.7260, 77.6770),
- (21.4160, 77.9350),
- (20.8118, 78.2546),
- (22.8842, 78.4549),
- (23.2813, 78.0795),
- (24.7241, 77.8538),
- (15.1428, 79.6743),
- (15.5225, 80.0160),
- (16.9908, 80.0508),
- (18.2518, 79.7017),
- (21.5438, 78.9561),
- (19.0273, 78.5625),
- (18.4717, 77.8266),
- (17.5944, 77.6379),
- (17.1182, 76.8094),
- (15.9131, 76.7704),
- (13.7626, 77.3803),
- (14.6695, 77.7356),
- (13.1705, 78.0249),
- (11.2222, 78.8692),
- (10.4445, 79.6523),
- (13.1707, 80.0104),
- (13.7185, 79.6604),
- (15.1428, 79.6743),
- (-77.8815, 7.2237),
- (-78.2149, 7.5122),
- (-78.4291, 8.0520),
- (-78.1820, 8.3191),
- (-78.4354, 8.3877),
- (-78.6221, 8.7181),
- (-79.1203, 8.9960),
- (-79.5578, 8.9323),
- (-79.7605, 8.5845),
- (-80.1644, 8.3333),
- (-80.3826, 8.2984),
- (-80.4806, 8.0903),
- (-80.0036, 7.5475),
- (-80.2766, 7.4197),
- (-80.4211, 7.2715),
- (-80.8864, 7.2205),
- (-81.0595, 7.8179),
- (-81.1897, 7.6479),
- (-81.5195, 7.7066),
- (-81.7213, 8.1089),
- (-82.1314, 8.1753),
- (-82.3909, 8.2923),
- (-82.6054, 8.2916),
- (-82.8200, 8.2908),
- (-82.8509, 8.0738),
- (-82.9657, 8.2250),
- (-83.5084, 8.4469),
- (-83.7114, 8.6568),
- (-83.5963, 8.8304),
- (-83.6326, 9.0513),
- (-83.9098, 9.2908),
- (-84.3034, 9.4873),
- (-84.6476, 9.6155),
- (-84.7133, 9.9080),
- (-84.9756, 10.0867),
- (-84.9113, 9.7959),
- (-85.1109, 9.5570),
- (-85.3394, 9.8345),
- (-85.6607, 9.9333),
- (-85.7974, 10.1348),
- (-85.7917, 10.4393),
- (-85.6593, 10.7543),
- (-85.8005, 10.8247),
- (-85.9417, 10.8952),
- (-85.7125, 11.0884),
- (-86.0584, 11.4034),
- (-86.5258, 11.8068),
- (-86.7459, 12.1439),
- (-87.1675, 12.4582),
- (-87.6684, 12.9099),
- (-87.5574, 13.0645),
- (-87.3923, 12.9140),
- (-87.3166, 12.9846),
- (-87.4894, 13.2975),
- (-87.6412, 13.3410),
- (-87.7931, 13.3844),
- (-87.9041, 13.1490),
- (-88.4833, 13.1639),
- (-88.8432, 13.2597),
- (-89.2567, 13.4585),
- (-89.8123, 13.5206),
- (-90.0955, 13.7353),
- (-90.6086, 13.9097),
- (-91.2324, 13.9278),
- (-91.6897, 14.1262),
- (-92.2277, 14.5388),
- (-93.3594, 15.6154),
- (-93.8751, 15.9401),
- (-94.6916, 16.2009),
- (-95.2502, 16.1283),
- (-96.0533, 15.7520),
- (-96.5574, 15.6535),
- (-97.2635, 15.9170),
- (-98.0130, 16.1073),
- (-98.9476, 16.5660),
- (-99.6973, 16.7061),
- (-100.8294, 17.1710),
- (-101.6660, 17.6490),
- (-101.9185, 17.9160),
- (-102.4781, 17.9757),
- (-103.5009, 18.2922),
- (-103.9175, 18.7485),
- (-104.9920, 19.3161),
- (-105.4930, 19.9467),
- (-105.7313, 20.4341),
- (-105.3977, 20.5317),
- (-105.5006, 20.8168),
- (-105.2707, 21.0762),
- (-105.2658, 21.4221),
- (-105.6031, 21.8711),
- (-105.6934, 22.2690),
- (-106.0287, 22.7737),
- (-106.9099, 23.7678),
- (-107.9154, 24.5489),
- (-108.4019, 25.1723),
- (-109.2601, 25.5806),
- (-109.4440, 25.8248),
- (-109.2916, 26.4429),
- (-109.8014, 26.6761),
- (-110.3917, 27.1621),
- (-110.6410, 27.8598),
- (-111.1789, 27.9412),
- (-111.7596, 28.4679),
- (-112.2282, 28.9544),
- (-112.2718, 29.2668),
- (-112.8095, 30.0211),
- (-113.1638, 30.7868),
- (-113.1486, 31.1709),
- (-113.8718, 31.5676),
- (-114.2057, 31.5240),
- (-114.7764, 31.7995),
- (-114.9366, 31.3934),
- (-114.7712, 30.9136),
- (-114.6738, 30.1626),
- (-114.3309, 29.7504),
- (-113.5888, 29.0616),
- (-113.4240, 28.8261),
- (-113.2719, 28.7547),
- (-113.1400, 28.4112),
- (-112.9622, 28.4251),
- (-112.7615, 27.7802),
- (-112.4579, 27.5258),
- (-112.2449, 27.1717),
- (-111.6164, 26.6628),
- (-111.2846, 25.7325),
- (-110.9878, 25.2946),
- (-110.7100, 24.8260),
- (-110.6550, 24.2985),
- (-110.1728, 24.2655),
- (-109.7718, 23.8111),
- (-109.4091, 23.3646),
- (-109.4333, 23.1855),
- (-109.8542, 22.8182),
- (-110.0313, 22.8230),
- (-110.2950, 23.4309),
- (-110.9495, 24.0009),
- (-111.6705, 24.4844),
- (-112.1820, 24.7384),
- (-112.1489, 25.4701),
- (-112.3007, 26.0120),
- (-112.7772, 26.3219),
- (-113.4646, 26.7681),
- (-113.5967, 26.6394),
- (-113.8489, 26.9000),
- (-114.4657, 27.1420),
- (-115.0551, 27.7227),
- (-114.9822, 27.7982),
- (-114.5703, 27.7414),
- (-114.1993, 28.1150),
- (-114.1620, 28.5661),
- (-114.9318, 29.2794),
- (-115.5186, 29.5563),
- (-115.8873, 30.1807),
- (-116.2583, 30.8364),
- (-116.7215, 31.6357),
- (-117.1277, 32.5353),
- (-117.2959, 33.0462),
- (-117.944, 33.6212),
- (-118.4106, 33.7409),
- (-118.5198, 34.0277),
- (-119.081, 34.078),
- (-119.4388, 34.3484),
- (-120.3677, 34.4470),
- (-120.6228, 34.6085),
- (-120.7443, 35.1568),
- (-121.7145, 36.1615),
- (-122.5474, 37.5517),
- (-122.5119, 37.7833),
- (-122.9531, 38.1136),
- (-123.7271, 38.9516),
- (-123.8651, 39.7669),
- (-124.3980, 40.3131),
- (-124.1788, 41.1420),
- (-124.2137, 41.9996),
- (-124.5328, 42.7659),
- (-124.1421, 43.7083),
- (-124.0205, 44.6158),
- (-123.8989, 45.5234),
- (-124.0796, 46.8647),
- (-124.3956, 47.7201),
- (-124.6872, 48.1844),
- (-124.5661, 48.3797),
- (-123.12, 48.04),
- (-122.5873, 47.0959),
- (-122.3399, 47.3600),
- (-122.5000, 48.1800),
- (-122.84, 49.0),
- (-122.9742, 49.0025),
- (-124.9102, 49.9845),
- (-125.6246, 50.4165),
- (-127.4356, 50.8305),
- (-127.9927, 51.7158),
- (-127.8503, 52.3296),
- (-129.1297, 52.7553),
- (-129.3052, 53.5615),
- (-130.5149, 54.2875),
- (-130.5361, 54.8027),
- (-131.0858, 55.1789),
- (-131.9672, 55.4977),
- (-132.2500, 56.3699),
- (-133.5391, 57.1788),
- (-134.0780, 58.1230),
- (-135.0382, 58.1877),
- (-136.6280, 58.2122),
- (-137.8000, 58.4999),
- (-139.8677, 59.5377),
- (-140.8252, 59.7275),
- (-142.5744, 60.0844),
- (-143.9588, 59.9991),
- (-145.9255, 60.4586),
- (-147.1143, 60.8846),
- (-148.2243, 60.6729),
- (-148.0180, 59.9783),
- (-148.5708, 59.9141),
- (-149.7278, 59.7056),
- (-150.6082, 59.3682),
- (-151.7163, 59.1558),
- (-151.8594, 59.7449),
- (-151.4097, 60.7258),
- (-150.3469, 61.0335),
- (-150.6211, 61.2844),
- (-151.8958, 60.7271),
- (-152.5783, 60.0616),
- (-154.0191, 59.3502),
- (-153.2875, 58.8647),
- (-154.2324, 58.1463),
- (-155.3074, 57.7277),
- (-156.3083, 57.4227),
- (-156.5560, 56.9799),
- (-158.1172, 56.4636),
- (-158.4333, 55.9941),
- (-159.6033, 55.5666),
- (-160.2897, 55.6435),
- (-161.2230, 55.3647),
- (-162.2377, 55.0241),
- (-163.0694, 54.6897),
- (-164.7855, 54.4041),
- (-164.9422, 54.5722),
- (-163.8483, 55.0394),
- (-162.8700, 55.3480),
- (-161.8041, 55.8949),
- (-160.5636, 56.0080),
- (-160.0705, 56.4180),
- (-158.6844, 57.0166),
- (-158.4610, 57.2169),
- (-157.7227, 57.5700),
- (-157.5502, 58.3283),
- (-157.0416, 58.9188),
- (-158.1947, 58.6158),
- (-158.5172, 58.7877),
- (-159.0586, 58.4241),
- (-159.7116, 58.9313),
- (-159.9812, 58.5725),
- (-160.3552, 59.0711),
- (-161.3550, 58.6708),
- (-161.9688, 58.6716),
- (-162.0549, 59.2669),
- (-161.8741, 59.6336),
- (-162.5180, 59.9897),
- (-163.8183, 59.7980),
- (-164.6622, 60.2674),
- (-165.3463, 60.5074),
- (-165.3508, 61.0738),
- (-166.1213, 61.5000),
- (-165.7344, 62.0749),
- (-164.9191, 62.6330),
- (-164.5625, 63.1463),
- (-163.7533, 63.2194),
- (-163.0672, 63.0594),
- (-162.2605, 63.5419),
- (-161.5344, 63.4558),
- (-160.7725, 63.7661),
- (-160.9583, 64.2227),
- (-161.5180, 64.4027),
- (-160.7777, 64.7886),
- (-161.3919, 64.7772),
- (-162.4530, 64.5594),
- (-162.7577, 64.3386),
- (-163.5463, 64.5591),
- (-164.9608, 64.4469),
- (-166.4252, 64.6866),
- (-166.8450, 65.0888),
- (-168.1105, 65.6699),
- (-166.7052, 66.0883),
- (-164.4747, 66.5766),
- (-163.6525, 66.5766),
- (-163.7886, 66.0772),
- (-161.6777, 66.1161),
- (-162.4897, 66.7355),
- (-163.7197, 67.1163),
- (-164.4309, 67.6163),
- (-165.3902, 68.0427),
- (-166.7644, 68.3588),
- (-166.2047, 68.8830),
- (-164.4308, 68.9155),
- (-163.1686, 69.3711),
- (-162.9305, 69.8580),
- (-161.9088, 70.3333),
- (-160.9347, 70.4476),
- (-159.0391, 70.8916),
- (-158.1197, 70.8247),
- (-156.5808, 71.3577),
- (-155.0677, 71.1477),
- (-154.3441, 70.6964),
- (-153.9000, 70.8899),
- (-152.2100, 70.8299),
- (-152.2700, 70.6000),
- (-150.7399, 70.4300),
- (-149.7200, 70.5300),
- (-147.6133, 70.2140),
- (-145.6899, 70.1200),
- (-144.9200, 69.9899),
- (-143.5894, 70.1525),
- (-142.0725, 69.8519),
- (-140.9859, 69.7119),
- (-139.1205, 69.4710),
- (-137.5463, 68.9900),
- (-136.5035, 68.8980),
- (-135.6257, 69.3151),
- (-134.4146, 69.6274),
- (-132.9292, 69.5053),
- (-131.4313, 69.9445),
- (-129.7947, 70.1937),
- (-129.1077, 69.7792),
- (-128.3615, 70.0128),
- (-128.1381, 70.4838),
- (-127.4471, 70.3772),
- (-125.7563, 69.4805),
- (-124.4248, 70.1584),
- (-124.2896, 69.3996),
- (-123.0610, 69.5637),
- (-122.6834, 69.8555),
- (-121.4722, 69.7977),
- (-119.9428, 69.3778),
- (-117.6026, 69.0112),
- (-116.2264, 68.8414),
- (-115.2468, 68.9059),
- (-113.8979, 68.3989),
- (-115.3048, 67.9026),
- (-113.4972, 67.6881),
- (-110.7980, 67.8060),
- (-109.9461, 67.9810),
- (-108.8801, 67.3814),
- (-107.7923, 67.8873),
- (-108.8129, 68.3116),
- (-108.1672, 68.6539),
- (-106.9500, 68.6999),
- (-106.1500, 68.7999),
- (-105.3428, 68.5612),
- (-104.3379, 68.0180),
- (-103.2211, 68.0977),
- (-101.4543, 67.6468),
- (-99.9019, 67.8056),
- (-98.4432, 67.7816),
- (-98.5586, 68.4039),
- (-97.6694, 68.5786),
- (-96.1199, 68.2394),
- (-96.1258, 67.2933),
- (-95.4894, 68.0907),
- (-94.6849, 68.0638),
- (-94.2328, 69.0690),
- (-95.3040, 69.6857),
- (-96.4713, 70.0897),
- (-96.3911, 71.1948),
- (-95.2088, 71.9205),
- (-93.8899, 71.7601),
- (-92.8781, 71.3186),
- (-91.5196, 70.1912),
- (-92.4069, 69.6999),
- (-90.5471, 69.4976),
- (-90.5514, 68.4749),
- (-89.2151, 69.2587),
- (-88.0196, 68.6150),
- (-88.3174, 67.8733),
- (-87.3501, 67.1987),
- (-86.3060, 67.9214),
- (-85.5766, 68.7845),
- (-85.5219, 69.8820),
- (-84.1008, 69.8054),
- (-82.6225, 69.6582),
- (-81.2804, 69.1620),
- (-81.2202, 68.6656),
- (-81.9643, 68.1325),
- (-81.2592, 67.5971),
- (-81.3865, 67.1107),
- (-83.3445, 66.4115),
- (-84.7354, 66.2572),
- (-85.7694, 66.5583),
- (-86.0676, 66.0562),
- (-87.0314, 65.2129),
- (-87.3232, 64.7756),
- (-88.4829, 64.0989),
- (-89.9144, 64.0327),
- (-90.7039, 63.6101),
- (-90.7700, 62.9602),
- (-91.9334, 62.8350),
- (-93.1569, 62.0246),
- (-94.2415, 60.8986),
- (-94.6293, 60.1102),
- (-94.6846, 58.9488),
- (-93.2150, 58.7821),
- (-92.7646, 57.8457),
- (-92.2970, 57.0870),
- (-90.8976, 57.2846),
- (-89.0395, 56.8517),
- (-88.0397, 56.4716),
- (-87.3241, 55.9991),
- (-86.0712, 55.7238),
- (-85.0118, 55.3026),
- (-83.3605, 55.2448),
- (-82.2728, 55.1483),
- (-82.4362, 54.2822),
- (-82.1250, 53.2770),
- (-81.4007, 52.1578),
- (-79.9128, 51.2083),
- (-79.1430, 51.5339),
- (-78.6019, 52.5620),
- (-79.1242, 54.1414),
- (-79.8295, 54.6677),
- (-78.2287, 55.1364),
- (-77.0955, 55.8374),
- (-76.5413, 56.5342),
- (-76.6231, 57.2026),
- (-77.3022, 58.0520),
- (-78.5168, 58.8045),
- (-77.3367, 59.8526),
- (-77.7727, 60.7578),
- (-78.1068, 62.3196),
- (-77.4106, 62.5505),
- (-75.6962, 62.2783),
- (-74.6682, 62.1811),
- (-73.8398, 62.4437),
- (-72.9085, 62.1050),
- (-71.6770, 61.5253),
- (-71.3736, 61.1371),
- (-69.5904, 61.0614),
- (-69.6203, 60.2212),
- (-69.2878, 58.9573),
- (-68.3745, 58.8010),
- (-67.6497, 58.2120),
- (-66.2017, 58.7673),
- (-65.2451, 59.8707),
- (-64.5835, 60.3355),
- (-63.8047, 59.4425),
- (-62.5023, 58.1670),
- (-61.3965, 56.9674),
- (-61.7986, 56.3394),
- (-60.4685, 55.7754),
- (-59.5696, 55.2040),
- (-57.9750, 54.9454),
- (-57.3332, 54.6264),
- (-56.9368, 53.7803),
- (-56.1581, 53.6474),
- (-55.7563, 53.2703),
- (-55.6833, 52.1466),
- (-56.4091, 51.7706),
- (-57.1269, 51.4197),
- (-58.7748, 51.0642),
- (-60.0331, 50.2427),
- (-61.7236, 50.0804),
- (-63.8625, 50.2909),
- (-65.3633, 50.2981),
- (-66.3990, 50.2289),
- (-67.2363, 49.5115),
- (-68.5111, 49.0683),
- (-69.9536, 47.7448),
- (-71.1045, 46.8217),
- (-70.2552, 46.9860),
- (-68.6499, 48.2999),
- (-66.5524, 49.1330),
- (-65.0562, 49.2327),
- (-64.1709, 48.7425),
- (-65.1154, 48.0708),
- (-64.7985, 46.9929),
- (-64.4721, 46.2384),
- (-63.1732, 45.7390),
- (-61.5207, 45.8837),
- (-60.5181, 47.0079),
- (-60.4486, 46.2826),
- (-59.8028, 45.9204),
- (-61.0398, 45.2652),
- (-63.2547, 44.6701),
- (-64.2465, 44.2655),
- (-65.3640, 43.5452),
- (-66.1234, 43.6186),
- (-66.1617, 44.4651),
- (-64.4254, 45.2920),
- (-66.0260, 45.2593),
- (-67.1374, 45.1375),
- (-66.9646, 44.8097),
- (-68.0325, 44.3252),
- (-69.06, 43.98),
- (-70.1161, 43.6840),
- (-70.6454, 43.0902),
- (-70.8148, 42.8652),
- (-70.8249, 42.3349),
- (-70.4949, 41.8049),
- (-70.0800, 41.7800),
- (-70.1849, 42.1449),
- (-69.8849, 41.9228),
- (-69.9650, 41.6371),
- (-70.6399, 41.4749),
- (-71.1203, 41.4944),
- (-71.8538, 41.3199),
- (-72.2949, 41.2699),
- (-72.8764, 41.2206),
- (-73.71, 40.9311),
- (-72.2412, 41.1194),
- (-71.9450, 40.9300),
- (-73.3450, 40.6300),
- (-73.9819, 40.6280),
- (-73.9523, 40.7507),
- (-74.2567, 40.4734),
- (-73.9624, 40.4276),
- (-74.1783, 39.7092),
- (-74.9060, 38.9395),
- (-74.9804, 39.1963),
- (-75.2000, 39.2484),
- (-75.5280, 39.4984),
- (-75.3199, 38.9599),
- (-75.0718, 38.7820),
- (-75.0567, 38.4041),
- (-75.3774, 38.0155),
- (-75.9402, 37.2169),
- (-76.0312, 37.2565),
- (-75.7220, 37.9370),
- (-76.2328, 38.3192),
- (-76.35, 39.15),
- (-76.5427, 38.7176),
- (-76.3293, 38.0832),
- (-76.9899, 38.2399),
- (-76.3016, 37.9179),
- (-76.2587, 36.9664),
- (-75.9718, 36.8972),
- (-75.8680, 36.5512),
- (-75.7274, 35.5507),
- (-76.3631, 34.8085),
- (-77.3976, 34.5120),
- (-78.0549, 33.9254),
- (-78.5543, 33.8613),
- (-79.0606, 33.4939),
- (-79.2035, 33.1583),
- (-80.3013, 32.5093),
- (-80.8649, 32.0333),
- (-81.3362, 31.4404),
- (-81.4904, 30.7299),
- (-81.3137, 30.0355),
- (-80.9799, 29.1800),
- (-80.5355, 28.4721),
- (-80.5299, 28.0399),
- (-80.0565, 26.88),
- (-80.0880, 26.2057),
- (-80.1315, 25.8167),
- (-80.3810, 25.2061),
- (-80.6800, 25.0799),
- (-81.1721, 25.2012),
- (-81.3299, 25.6399),
- (-81.7099, 25.8699),
- (-82.2400, 26.7299),
- (-82.7051, 27.4950),
- (-82.8552, 27.8862),
- (-82.6500, 28.5499),
- (-82.93, 29.1),
- (-83.7095, 29.9365),
- (-84.1, 30.09),
- (-85.1088, 29.6361),
- (-85.2878, 29.6861),
- (-85.7731, 30.1526),
- (-86.3999, 30.4000),
- (-87.5303, 30.2743),
- (-88.4178, 30.3849),
- (-89.1804, 30.3159),
- (-89.5938, 30.1599),
- (-89.4137, 29.8941),
- (-89.43, 29.48864),
- (-89.2176, 29.2910),
- (-89.4082, 29.1596),
- (-89.7792, 29.3071),
- (-90.1546, 29.1174),
- (-90.8802, 29.1485),
- (-91.6267, 29.677),
- (-92.4990, 29.5523),
- (-93.2263, 29.7837),
- (-93.8484, 29.7136),
- (-94.6900, 29.4800),
- (-95.6002, 28.7386),
- (-96.5940, 28.3074),
- (-97.1400, 27.8300),
- (-97.3699, 27.3800),
- (-97.3799, 26.6899),
- (-97.3299, 26.2100),
- (-97.1400, 25.8699),
- (-97.5280, 24.9921),
- (-97.7029, 24.2723),
- (-97.7760, 22.9325),
- (-97.8723, 22.4442),
- (-97.6990, 21.8986),
- (-97.3889, 21.4110),
- (-97.1893, 20.6354),
- (-96.5255, 19.8909),
- (-96.2921, 19.3203),
- (-95.9008, 18.8280),
- (-94.8390, 18.5627),
- (-94.4257, 18.1443),
- (-93.5486, 18.4238),
- (-92.7861, 18.5248),
- (-92.0373, 18.7045),
- (-91.4079, 18.8760),
- (-90.7718, 19.2841),
- (-90.5335, 19.8674),
- (-90.4514, 20.7075),
- (-90.2786, 20.9998),
- (-89.6013, 21.2617),
- (-88.5438, 21.4936),
- (-87.6584, 21.4588),
- (-87.0518, 21.5435),
- (-86.8119, 21.3315),
- (-86.8459, 20.8498),
- (-87.3832, 20.2554),
- (-87.6210, 19.6465),
- (-87.4367, 19.4724),
- (-87.5865, 19.0401),
- (-87.8371, 18.2598),
- (-88.0906, 18.5166),
- (-88.3000, 18.4999),
- (-88.2963, 18.3532),
- (-88.1068, 18.3486),
- (-88.1234, 18.0766),
- (-88.2853, 17.6441),
- (-88.1978, 17.4894),
- (-88.3026, 17.1316),
- (-88.2395, 17.0360),
- (-88.3554, 16.5307),
- (-88.5518, 16.2654),
- (-88.7324, 16.2336),
- (-88.9306, 15.8872),
- (-88.6045, 15.7063),
- (-88.5183, 15.8553),
- (-88.1899, 15.7199),
- (-88.1211, 15.6886),
- (-87.9018, 15.8644),
- (-87.6156, 15.8787),
- (-87.5229, 15.7972),
- (-87.3677, 15.8469),
- (-86.9031, 15.7567),
- (-86.4409, 15.7828),
- (-86.1192, 15.8934),
- (-86.0019, 16.0054),
- (-85.6833, 15.9536),
- (-85.4440, 15.8857),
- (-85.1824, 15.9091),
- (-84.9837, 15.9959),
- (-84.5269, 15.8572),
- (-84.3682, 15.8351),
- (-84.0630, 15.6482),
- (-83.7739, 15.4240),
- (-83.4103, 15.2709),
- (-83.1472, 14.9958),
- (-83.2332, 14.8998),
- (-83.2841, 14.6766),
- (-83.1821, 14.3107),
- (-83.4124, 13.9700),
- (-83.5198, 13.5676),
- (-83.5522, 13.1270),
- (-83.4985, 12.8692),
- (-83.4733, 12.4190),
- (-83.6261, 12.3208),
- (-83.7196, 11.8931),
- (-83.6508, 11.6290),
- (-83.8554, 11.3733),
- (-83.8089, 11.1030),
- (-83.6556, 10.9387),
- (-83.5900, 10.7850),
- (-83.4023, 10.3954),
- (-83.0156, 9.9929),
- (-82.5461, 9.5661),
- (-82.1871, 9.2074),
- (-82.2075, 8.9955),
- (-81.8085, 8.9506),
- (-81.7141, 9.0319),
- (-81.4392, 8.7862),
- (-80.9473, 8.8585),
- (-80.5219, 9.1110),
- (-79.9145, 9.3127),
- (-79.5733, 9.6116),
- (-79.0211, 9.5529),
- (-79.0584, 9.4545),
- (-78.5008, 9.4204),
- (-78.0559, 9.2477),
- (-77.7295, 8.9468),
- (-77.3533, 8.6705),
- (-71.7123, 19.7144),
- (-71.5873, 19.8849),
- (-71.3800, 19.9049),
- (-70.8067, 19.8802),
- (-70.2143, 19.6228),
- (-69.9508, 19.6479),
- (-69.7692, 19.2932),
- (-69.2221, 19.3132),
- (-69.2543, 19.0151),
- (-68.8094, 18.9791),
- (-68.3179, 18.6121),
- (-68.6893, 18.2051),
- (-69.1649, 18.4226),
- (-69.6239, 18.3807),
- (-69.9529, 18.4283),
- (-70.1332, 18.2459),
- (-70.5171, 18.1842),
- (-70.6692, 18.4268),
- (-70.9999, 18.2833),
- (-71.4002, 17.5985),
- (-71.6576, 17.7575),
- (-71.7083, 18.0449),
- (-72.3724, 18.2149),
- (-72.8444, 18.1456),
- (-73.4545, 18.2179),
- (-73.9224, 18.0309),
- (-74.4580, 18.3425),
- (-74.3699, 18.6649),
- (-73.4495, 18.5260),
- (-72.6949, 18.4457),
- (-72.3348, 18.6684),
- (-72.7916, 19.1016),
- (-72.7841, 19.4835),
- (-73.4150, 19.6395),
- (-73.1897, 19.9156),
- (-72.5796, 19.8715),
- (-71.7123, 19.7144),
- (14.7612, 38.1438),
- (15.5203, 38.2311),
- (15.1602, 37.4440),
- (15.3098, 37.1342),
- (15.0999, 36.6199),
- (14.3352, 36.9966),
- (13.8267, 37.1045),
- (12.4310, 37.6129),
- (12.5709, 38.1263),
- (13.7411, 38.0349),
- (14.7612, 38.1438),
- (37.5391, 44.6572),
- (38.6799, 44.2799),
- (39.9550, 43.4349),
- (132.3711, 33.4636),
- (132.9243, 34.0602),
- (133.4929, 33.9446),
- (133.9041, 34.3649),
- (134.6384, 34.1492),
- (134.7663, 33.8063),
- (134.2034, 33.2011),
- (133.7929, 33.5219),
- (133.2802, 33.2895),
- (133.0148, 32.7045),
- (132.3631, 32.9893),
- (132.3711, 33.4636),
- (180.0000, 68.9636),
- (178.5999, 69.4000),
- (175.7240, 69.8772),
- (173.6439, 69.8174),
- (170.4534, 70.0970),
- (170.0082, 69.6527),
- (170.8168, 69.0136),
- (169.5776, 68.6938),
- (167.8629, 69.5687),
- (165.9403, 69.4719),
- (164.0524, 69.6682),
- (162.2790, 69.6420),
- (160.9405, 69.4372),
- (159.7086, 69.7219),
- (159.8303, 70.4532),
- (158.9977, 70.8667),
- (157.0068, 71.0314),
- (152.9688, 70.8422),
- (150.3511, 71.6064),
- (149.5000, 72.1999),
- (140.4681, 72.8494),
- (139.1479, 72.4161),
- (139.8698, 71.4878),
- (138.2340, 71.6280),
- (137.4975, 71.3476),
- (135.5619, 71.6552),
- (133.8576, 71.3864),
- (132.2535, 71.8363),
- (131.2885, 70.7869),
- (129.7159, 71.1930),
- (128.46, 71.98),
- (129.0515, 72.3987),
- (128.5912, 73.0387),
- (126.9764, 73.5654),
- (125.38, 73.56),
- (123.2577, 73.7350),
- (123.2006, 72.9712),
- (119.02, 73.12),
- (118.7763, 73.5877),
- (115.5678, 73.7528),
- (113.9688, 73.5948),
- (113.5295, 73.3350),
- (113.0195, 73.9769),
- (112.1191, 73.7877),
- (110.64, 74.04),
- (109.4, 74.18),
- (110.1512, 74.4767),
- (112.7791, 75.0318),
- (113.8853, 75.3277),
- (114.1341, 75.8476),
- (113.3315, 76.2222),
- (111.07726, 76.71),
- (108.1537, 76.7233),
- (107.2399, 76.4799),
- (106.9701, 76.9741),
- (104.7050, 77.1273),
- (106.0666, 77.3738),
- (104.3515, 77.6979),
- (101.9908, 77.2875),
- (101.0353, 76.8618),
- (100.7596, 76.4302),
- (98.9225, 76.4468),
- (96.6782, 75.9154),
- (95.8600, 76.1400),
- (93.2342, 76.0471),
- (92.9006, 75.7733),
- (90.2599, 75.6399),
- (88.3156, 75.1439),
- (87.1668, 75.1164),
- (86.0095, 74.4596),
- (86.8223, 73.9368),
- (84.6552, 73.8059),
- (82.2499, 73.8500),
- (80.5110, 73.6482),
- (80.6107, 72.5828),
- (81.5000, 71.7499),
- (79.6520, 72.3201),
- (77.5766, 72.2671),
- (75.9031, 71.8740),
- (76.3591, 71.1528),
- (75.2889, 71.3355),
- (75.6835, 72.3005),
- (75.1580, 72.8549),
- (74.6592, 72.8322),
- (74.8908, 72.1211),
- (73.1011, 71.4471),
- (74.3998, 70.6317),
- (73.6018, 69.6276),
- (73.8423, 69.0714),
- (74.9358, 68.9891),
- (74.4692, 68.3289),
- (75.052, 67.76047),
- (74.1865, 67.2842),
- (73.9209, 66.7894),
- (72.8207, 66.5326),
- (72.4230, 66.1726),
- (71.28, 66.32),
- (73.2387, 67.7404),
- (73.6678, 68.4079),
- (72.5646, 69.0208),
- (72.7918, 70.3911),
- (72.4701, 71.0901),
- (71.8481, 71.4089),
- (72.7960, 72.2200),
- (72.5875, 72.7762),
- (69.94, 73.04),
- (69.1963, 72.8433),
- (68.5400, 71.9345),
- (66.6946, 71.0289),
- (66.7249, 70.7088),
- (67.2597, 69.9287),
- (66.9300, 69.4546),
- (68.1352, 69.3564),
- (68.1644, 69.1443),
- (69.1806, 68.6156),
- (68.5121, 68.0923),
- (64.8881, 69.2348),
- (63.5040, 69.5473),
- (60.5500, 69.8500),
- (60.03, 69.52),
- (61.0778, 68.9406),
- (59.9414, 68.2784),
- (58.802, 68.88082),
- (57.3170, 68.4662),
- (55.4426, 68.4386),
- (54.7530, 68.0868),
- (53.4858, 68.2013),
- (54.4717, 68.8081),
- (53.7174, 68.8573),
- (48.1387, 67.5223),
- (47.8941, 66.8845),
- (46.3491, 66.6676),
- (45.5620, 67.0100),
- (45.5551, 67.5665),
- (46.8213, 67.6899),
- (46.2500, 68.2499),
- (43.4528, 68.5708),
- (44.1747, 67.9616),
- (43.6983, 67.3524),
- (44.5322, 66.7563),
- (43.9497, 66.0690),
- (43.0160, 66.4185),
- (42.0930, 66.4762),
- (39.7626, 65.4968),
- (40.4356, 64.7644),
- (39.5934, 64.5207),
- (37.1760, 65.1432),
- (36.5395, 64.7644),
- (37.1419, 64.3347),
- (37.0127, 63.8498),
- (36.2312, 64.1094),
- (34.9439, 64.4143),
- (34.8785, 65.4362),
- (34.8147, 65.9001),
- (33.1844, 66.6325),
- (33.9186, 66.7595),
- (38.3829, 65.9995),
- (40.0158, 66.2661),
- (41.1259, 66.7915),
- (41.0598, 67.4571),
- (40.2923, 67.9323),
- (36.5139, 69.0634),
- (33.7754, 69.3014),
- (32.1327, 69.9059),
- (31.1010, 69.5580),
- (30.0054, 70.1862),
- (31.2934, 70.4537),
- (28.1655, 71.1854),
- (26.3700, 70.9862),
- (24.5465, 71.0304),
- (23.0237, 70.2020),
- (21.3784, 70.2551),
- (19.1840, 69.8174),
- (16.4359, 68.5632),
- (14.7611, 67.8106),
- (12.3583, 65.8797),
- (10.5277, 64.4860),
- (8.5534, 63.4540),
- (5.9129, 62.6144),
- (4.9920, 61.9709),
- (5.3082, 59.6632),
- (5.6658, 58.5881),
- (7.0487, 58.0788),
- (8.3820, 58.3132),
- (10.3565, 59.4698),
- (11.0273, 58.8561),
- (11.7879, 57.4418),
- (12.6251, 56.3070),
- (12.9429, 55.3617),
- (14.1007, 55.4077),
- (14.6666, 56.2008),
- (15.8797, 56.1043),
- (16.4477, 57.0411),
- (16.8291, 58.7198),
- (17.8692, 58.9537),
- (18.7877, 60.0819),
- (17.8313, 60.6365),
- (17.1195, 61.3411),
- (17.8477, 62.7494),
- (19.7788, 63.6095),
- (21.3696, 64.4135),
- (21.2135, 65.0260),
- (22.1831, 65.7237),
- (23.9033, 66.0069),
- (25.2940, 65.5343),
- (25.3980, 65.1114),
- (24.7305, 64.9023),
- (22.4427, 63.8178),
- (21.5360, 63.1897),
- (21.0592, 62.6073),
- (21.5448, 61.7053),
- (21.3222, 60.7201),
- (22.2907, 60.3919),
- (22.8696, 59.8463),
- (24.4966, 60.0573),
- (26.2551, 60.4239),
- (28.0699, 60.5035),
- (29.1176, 60.0280),
- (27.9811, 59.4753),
- (26.9491, 59.4458),
- (25.8641, 59.6110),
- (24.6042, 59.4658),
- (23.3397, 59.1872),
- (23.4265, 58.6127),
- (24.0611, 58.2573),
- (24.4289, 58.3834),
- (24.3128, 57.7934),
- (24.1207, 57.0256),
- (23.3184, 57.0062),
- (22.5243, 57.7533),
- (21.5818, 57.4118),
- (21.0904, 56.7838),
- (21.0558, 56.0310),
- (21.2684, 55.1904),
- (19.8884, 54.8661),
- (19.6606, 54.4260),
- (18.6962, 54.4387),
- (18.6208, 54.6826),
- (17.6228, 54.8515),
- (16.3634, 54.5131),
- (14.8029, 54.0507),
- (14.1196, 53.7570),
- (13.6474, 54.0755),
- (12.5184, 54.4703),
- (11.9562, 54.1964),
- (10.9394, 54.0086),
- (10.9501, 54.3636),
- (9.9395, 54.5966),
- (9.9219, 54.9831),
- (9.6499, 55.4699),
- (10.3699, 56.1900),
- (10.6678, 56.0813),
- (10.9121, 56.4586),
- (10.3699, 56.6099),
- (10.2500, 56.8900),
- (10.5461, 57.2157),
- (10.5800, 57.7300),
- (9.7755, 57.4479),
- (9.4244, 57.1720),
- (8.5434, 57.1100),
- (8.2565, 56.8099),
- (8.0899, 56.5400),
- (8.1203, 55.5177),
- (8.5262, 54.9627),
- (8.5721, 54.3956),
- (8.8007, 54.0207),
- (8.1217, 53.5277),
- (7.9362, 53.7482),
- (7.1004, 53.6939),
- (6.9051, 53.4821),
- (6.0741, 53.5104),
- (4.7059, 53.0917),
- (3.8302, 51.6205),
- (3.3149, 51.3457),
- (2.5135, 51.1485),
- (1.6390, 50.9466),
- (1.3387, 50.1271),
- (-0.9894, 49.3473),
- (-1.9334, 49.7763),
- (-1.6165, 48.6444),
- (-3.2958, 48.9016),
- (-4.5923, 48.6841),
- (-4.4915, 47.9549),
- (-2.9632, 47.5703),
- (-2.2257, 47.0643),
- (-1.1937, 46.0149),
- (-1.3842, 44.0226),
- (-1.9013, 43.4228),
- (-3.5175, 43.4559),
- (-4.3478, 43.4034),
- (-5.4118, 43.5742),
- (-6.7544, 43.5679),
- (-7.9781, 43.7483),
- (-9.3928, 43.0266),
- (-8.9844, 42.5927),
- (-9.0348, 41.8805),
- (-8.9907, 41.5434),
- (-8.7908, 41.1843),
- (-8.7686, 40.7606),
- (-8.9773, 40.1593),
- (-9.0483, 39.7550),
- (-9.4469, 39.3920),
- (-9.5265, 38.7374),
- (-9.2874, 38.3584),
- (-8.8399, 38.2662),
- (-8.7461, 37.6513),
- (-8.8988, 36.8688),
- (-8.3828, 36.9788),
- (-7.8556, 36.8382),
- (-7.4537, 37.0977),
- (-6.5201, 36.9429),
- (-6.2366, 36.3676),
- (-5.8664, 36.0298),
- (-5.3771, 35.9468),
- (-4.9952, 36.3247),
- (-4.3689, 36.6778),
- (-3.4157, 36.6588),
- (-2.1464, 36.6741),
- (-1.4383, 37.4430),
- (-0.6833, 37.6423),
- (-0.4671, 38.2923),
- (0.1112, 38.7385),
- (-0.2787, 39.3099),
- (0.1066, 40.1239),
- (0.7213, 40.6783),
- (0.8105, 41.0147),
- (2.0918, 41.2260),
- (3.0394, 41.8921),
- (2.9859, 42.4730),
- (3.1004, 43.0752),
- (4.5569, 43.3996),
- (6.5292, 43.1288),
- (7.4351, 43.6938),
- (7.8507, 43.7671),
- (8.4285, 44.2312),
- (8.8889, 44.3663),
- (9.7024, 44.0362),
- (10.2000, 43.9200),
- (10.5119, 42.9314),
- (11.1919, 42.3554),
- (12.1066, 41.7045),
- (12.8880, 41.2530),
- (13.6279, 41.1882),
- (14.0606, 40.7863),
- (14.7032, 40.6045),
- (14.9984, 40.1729),
- (15.4136, 40.0483),
- (15.7188, 39.5440),
- (16.1093, 38.9645),
- (15.8919, 38.7509),
- (15.6879, 38.2145),
- (15.6840, 37.9088),
- (16.1009, 37.9858),
- (16.6350, 38.8435),
- (17.0528, 38.9028),
- (17.1714, 39.4246),
- (16.4487, 39.7954),
- (16.8695, 40.4422),
- (17.7383, 40.2776),
- (18.2933, 39.8107),
- (18.4802, 40.1688),
- (18.3766, 40.3556),
- (17.5191, 40.8771),
- (16.7850, 41.1796),
- (15.8893, 41.5410),
- (16.1698, 41.7402),
- (15.9261, 41.9613),
- (15.1425, 41.9551),
- (14.0298, 42.7610),
- (13.5269, 43.5877),
- (12.5892, 44.0913),
- (12.2614, 44.6004),
- (12.3838, 44.8853),
- (12.3285, 45.3817),
- (13.1416, 45.7366),
- (13.9376, 45.5910),
- (13.7150, 45.5003),
- (13.6794, 45.4841),
- (13.6569, 45.1369),
- (13.9522, 44.8021),
- (14.2587, 45.2337),
- (14.9016, 45.0760),
- (14.9203, 44.7384),
- (15.3762, 44.3179),
- (15.1744, 44.2431),
- (16.0153, 43.5072),
- (16.9300, 43.2099),
- (17.5099, 42.8499),
- (18.4500, 42.4799),
- (18.8821, 42.2815),
- (19.1624, 41.9550),
- (19.3717, 41.8775),
- (19.5400, 41.7199),
- (19.4035, 41.4095),
- (19.3190, 40.7272),
- (19.4060, 40.2507),
- (19.9600, 39.9150),
- (19.9800, 39.6949),
- (20.1500, 39.6249),
- (20.2177, 39.3402),
- (20.7300, 38.7699),
- (21.1200, 38.3103),
- (21.2950, 37.6449),
- (21.6700, 36.8449),
- (22.4900, 36.4100),
- (23.1542, 36.4225),
- (22.7749, 37.3050),
- (23.4099, 37.4099),
- (23.1150, 37.9200),
- (24.0400, 37.6550),
- (24.0250, 38.2199),
- (23.5300, 38.5100),
- (22.9730, 38.9709),
- (23.3500, 39.1900),
- (22.8497, 39.6593),
- (22.6262, 40.2565),
- (22.8139, 40.4760),
- (23.3429, 39.9609),
- (23.8999, 39.9620),
- (24.4079, 40.1249),
- (23.7148, 40.6871),
- (24.9258, 40.9470),
- (25.4476, 40.8525),
- (26.0569, 40.8241),
- (26.0433, 40.6177),
- (26.3580, 40.1519),
- (27.1923, 40.6905),
- (27.6190, 40.9998),
- (28.8064, 41.0549),
- (28.9884, 41.2999),
- (28.1155, 41.6228),
- (27.9967, 42.0073),
- (27.6738, 42.5778),
- (28.0390, 43.2931),
- (28.5580, 43.7074),
- (28.8378, 44.9138),
- (29.1416, 44.8202),
- (29.6265, 45.0353),
- (29.6032, 45.2933),
- (30.3776, 46.0324),
- (30.7487, 46.5831),
- (31.6753, 46.7062),
- (31.7441, 46.3333),
- (33.2985, 46.0805),
- (33.5881, 45.8515),
- (32.6308, 45.5191),
- (32.4541, 45.3274),
- (33.5469, 45.0347),
- (33.3264, 44.5648),
- (33.8825, 44.3614),
- (35.2399, 44.9399),
- (36.3347, 45.1132),
- (36.5299, 45.4699),
- (35.5100, 45.4099),
- (35.0207, 45.6512),
- (34.9623, 46.2731),
- (35.8236, 46.6459),
- (36.7598, 46.6987),
- (37.4251, 47.0222),
- (38.2235, 47.1021),
- (39.1212, 47.2633),
- (39.1476, 47.0447),
- (37.6737, 46.6365),
- (38.2329, 46.2408),
- (37.4031, 45.4045),
- (36.6754, 45.2446),
- (37.5391, 44.6572),
- (38.6799, 44.2799),
- (39.9550, 43.4349),
- (40.3213, 43.1286),
- (40.8754, 43.0136),
- (41.4534, 42.6451),
- (41.7031, 41.9629),
- (41.5540, 41.5356),
- (40.3734, 41.0136),
- (39.5126, 41.1027),
- (38.3476, 40.9485),
- (36.9131, 41.3353),
- (35.1677, 42.0402),
- (33.5132, 42.0189),
- (32.3479, 41.7362),
- (31.1459, 41.0876),
- (29.2400, 41.2199),
- (28.8199, 40.4600),
- (27.2800, 40.4200),
- (26.1707, 39.4636),
- (26.8047, 38.9857),
- (26.3182, 38.2081),
- (27.0487, 37.6533),
- (27.6411, 36.6588),
- (28.7329, 36.6768),
- (29.6999, 36.1443),
- (30.3910, 36.2629),
- (30.6216, 36.6778),
- (31.6995, 36.6442),
- (32.5091, 36.1075),
- (34.0268, 36.2199),
- (34.7145, 36.7955),
- (35.5509, 36.5654),
- (36.1608, 36.6506),
- (35.7820, 36.2749),
- (36.1497, 35.8215),
- (35.9050, 35.4100),
- (35.9984, 34.6449),
- (35.9795, 34.6100),
- (35.4822, 33.9054),
- (35.1260, 33.0909),
- (35.0984, 33.0805),
- (34.9554, 32.8273),
- (34.7525, 32.0729),
- (34.4881, 31.6055),
- (34.5563, 31.5488),
- (34.2654, 31.2193),
- (33.7733, 30.9674),
- (32.9939, 31.0240),
- (32.1924, 31.2603),
- (31.9604, 30.9336),
- (31.6879, 31.4296),
- (30.9769, 31.5558),
- (30.0950, 31.4734),
- (29.6834, 31.1868),
- (28.9135, 30.8700),
- (28.4504, 31.0257),
- (27.4576, 31.3212),
- (26.4953, 31.5856),
- (25.1648, 31.5691),
- (24.9211, 31.8993),
- (23.9275, 32.0166),
- (23.6091, 32.1872),
- (23.2368, 32.1914),
- (22.8957, 32.6385),
- (21.5430, 32.8432),
- (20.8545, 32.7067),
- (20.1339, 32.2381),
- (19.8203, 31.7517),
- (20.0533, 30.9857),
- (19.5740, 30.5258),
- (19.0864, 30.2663),
- (18.0211, 30.7635),
- (16.6116, 31.1821),
- (15.7139, 31.3762),
- (15.2456, 32.2650),
- (13.9186, 32.7119),
- (13.0832, 32.8788),
- (12.6633, 32.7927),
- (11.4887, 33.1369),
- (11.1085, 33.2933),
- (10.8568, 33.7687),
- (10.3396, 33.7857),
- (10.1495, 34.3307),
- (10.8078, 34.8335),
- (10.9395, 35.6989),
- (10.5932, 35.9474),
- (10.6000, 36.4100),
- (11.1000, 36.8999),
- (11.0288, 37.0921),
- (10.1806, 36.7240),
- (10.2100, 37.2300),
- (9.5099, 37.3499),
- (8.4209, 36.9464),
- (7.7370, 36.8857),
- (7.3303, 37.1183),
- (6.2618, 37.1106),
- (5.3201, 36.7165),
- (4.8157, 36.8650),
- (3.1616, 36.7839),
- (1.4669, 36.6056),
- (0.5038, 36.3012),
- (-0.1274, 35.8886),
- (-1.2086, 35.7148),
- (-2.1699, 35.1683),
- (-2.6043, 35.1790),
- (-3.6400, 35.3998),
- (-4.5910, 35.3307),
- (-5.1938, 35.7551),
- (-5.9299, 35.7599),
- (-6.2443, 35.1458),
- (-6.9125, 34.1104),
- (-7.6541, 33.6970),
- (-8.6574, 33.2402),
- (-9.3006, 32.5646),
- (-9.4347, 32.0380),
- (-9.8147, 31.1777),
- (-9.5648, 29.9335),
- (-10.3995, 29.0985),
- (-10.9009, 28.8321),
- (-11.6889, 28.1486),
- (-12.6188, 28.0381),
- (-13.1399, 27.6401),
- (-13.7738, 26.6188),
- (-14.4399, 26.2544),
- (-14.8009, 25.6362),
- (-14.8246, 25.1035),
- (-15.0893, 24.5202),
- (-15.4260, 24.3591),
- (-15.9826, 23.7233),
- (-16.3264, 23.0177),
- (-16.2619, 22.6793),
- (-16.5891, 22.1582),
- (-16.9732, 21.8857),
- (-17.0204, 21.4223),
- (-17.0634, 20.9997),
- (-16.5363, 20.5678),
- (-16.2778, 20.0925),
- (-16.3776, 19.5938),
- (-16.2568, 19.0967),
- (-16.1463, 18.1084),
- (-16.2705, 17.1669),
- (-16.5497, 16.6738),
- (-16.4630, 16.1350),
- (-16.7007, 15.6215),
- (-17.1851, 14.9194),
- (-17.6250, 14.7295),
- (-17.1261, 14.3735),
- (-16.7137, 13.5949),
- (-16.8415, 13.1513),
- (-16.6774, 12.3848),
- (-16.6138, 12.1709),
- (-16.3089, 11.9587),
- (-16.3147, 11.8065),
- (-16.0852, 11.5245),
- (-15.6641, 11.4584),
- (-15.1303, 11.0404),
- (-14.8395, 10.8765),
- (-14.6932, 10.6563),
- (-14.5796, 10.2144),
- (-14.3300, 10.0157),
- (-14.0740, 9.8861),
- (-13.6851, 9.4947),
- (-13.2465, 8.9030),
- (-13.1240, 8.1639),
- (-12.9490, 7.7986),
- (-12.4280, 7.2629),
- (-11.7081, 6.8600),
- (-11.4387, 6.7859),
- (-10.7653, 6.1407),
- (-9.9134, 5.5935),
- (-9.0047, 4.8324),
- (-7.9741, 4.3557),
- (-7.7121, 4.3645),
- (-7.5189, 4.3382),
- (-6.5287, 4.7050),
- (-5.8344, 4.9937),
- (-4.6499, 5.1682),
- (-4.0088, 5.1798),
- (-3.3110, 4.9842),
- (-2.8561, 4.9944),
- (-1.9647, 4.7104),
- (-1.0636, 5.0005),
- (-0.5076, 5.3434),
- (1.0601, 5.9288),
- (1.8652, 6.1421),
- (2.6917, 6.2588),
- (3.5741, 6.2583),
- (4.3256, 6.2706),
- (5.0335, 5.6118),
- (5.3628, 4.8879),
- (5.8981, 4.2624),
- (6.6980, 4.2405),
- (7.0825, 4.4646),
- (7.4621, 4.4121),
- (8.5002, 4.7719),
- (8.4888, 4.4956),
- (8.7449, 4.3522),
- (8.9481, 3.9041),
- (9.4043, 3.7345),
- (9.7951, 3.0734),
- (9.6491, 2.2838),
- (9.3056, 1.1609),
- (9.4928, 1.0101),
- (9.2913, 0.2686),
- (9.0484, -0.4593),
- (8.8300, -0.7790),
- (8.7979, -1.1113),
- (9.4052, -2.1443),
- (10.0661, -2.9694),
- (11.0937, -3.9788),
- (11.9149, -5.0379),
- (12.1823, -5.7899),
- (12.3224, -6.1000),
- (12.2273, -6.2944),
- (12.7282, -6.9271),
- (12.9330, -7.5965),
- (13.2364, -8.5626),
- (12.9290, -8.9590),
- (12.8753, -9.1669),
- (13.1209, -9.7668),
- (13.3873, -10.3735),
- (13.6863, -10.7310),
- (13.7387, -11.2978),
- (13.6337, -12.0386),
- (13.3129, -12.4836),
- (12.7384, -13.1379),
- (12.5000, -13.5476),
- (12.1756, -14.4491),
- (12.1235, -14.8783),
- (11.7785, -15.7938),
- (11.6400, -16.6731),
- (11.7341, -17.3018),
- (11.7949, -18.0691),
- (12.6085, -19.0453),
- (12.8268, -19.6731),
- (13.3524, -20.8728),
- (13.8686, -21.6990),
- (14.2577, -22.1112),
- (14.3857, -22.6566),
- (14.4081, -23.8530),
- (14.7432, -25.3929),
- (14.9897, -26.1173),
- (15.2104, -27.0909),
- (15.6018, -27.8212),
- (16.3449, -28.5767),
- (17.0629, -29.8759),
- (17.0644, -29.8786),
- (17.5669, -30.7257),
- (18.2217, -31.6616),
- (18.2479, -32.4291),
- (17.9251, -32.6112),
- (18.2500, -33.2814),
- (18.2444, -33.8677),
- (18.3774, -34.1365),
- (18.4246, -33.9978),
- (18.8553, -34.4443),
- (19.1932, -34.4625),
- (19.6164, -34.8191),
- (20.0712, -34.7951),
- (20.6890, -34.4171),
- (21.5427, -34.2588),
- (22.5741, -33.8640),
- (22.9881, -33.9164),
- (23.5940, -33.7944),
- (24.6778, -33.9871),
- (25.1728, -33.7968),
- (25.7806, -33.9446),
- (25.9096, -33.6670),
- (26.4194, -33.6149),
- (27.4646, -33.2269),
- (28.2197, -32.7719),
- (28.9255, -32.1720),
- (30.0557, -31.1402),
- (30.6228, -30.4237),
- (30.9017, -29.9099),
- (31.3255, -29.4019),
- (31.5210, -29.2573),
- (32.2033, -28.7524),
- (32.4621, -28.3010),
- (32.5802, -27.4701),
- (32.8301, -26.7421),
- (32.9159, -26.2158),
- (32.6603, -26.1485),
- (32.5746, -25.7273),
- (33.0132, -25.3575),
- (34.2158, -24.8163),
- (35.0407, -24.4783),
- (35.4587, -24.1226),
- (35.6074, -23.7065),
- (35.3717, -23.5353),
- (35.5339, -23.0707),
- (35.5625, -22.09),
- (35.3858, -22.14),
- (35.3734, -21.8408),
- (35.1761, -21.2543),
- (34.7018, -20.4970),
- (34.7863, -19.7840),
- (35.1983, -19.5528),
- (35.8964, -18.8422),
- (36.2812, -18.6596),
- (37.4111, -17.5863),
- (38.5383, -17.1010),
- (39.4525, -16.7208),
- (40.0892, -16.1007),
- (40.4772, -15.4062),
- (40.7754, -14.6917),
- (40.5996, -14.2019),
- (40.5608, -12.6391),
- (40.4372, -11.7617),
- (40.4783, -10.7654),
- (40.3165, -10.3170),
- (39.9495, -10.0984),
- (39.5357, -9.1123),
- (39.1865, -8.4855),
- (39.2520, -8.0078),
- (39.1946, -7.7039),
- (39.4699, -7.0999),
- (39.4400, -6.8399),
- (38.7997, -6.4756),
- (38.7405, -5.9089),
- (39.2022, -4.6767),
- (39.6049, -4.3465),
- (39.8000, -3.6811),
- (40.1211, -3.2776),
- (40.2630, -2.5730),
- (40.6378, -2.4997),
- (40.8847, -2.0825),
- (41.5851, -1.6832),
- (41.8109, -1.4464),
- (42.0415, -0.9191),
- (43.1359, 0.2921),
- (44.0681, 1.0528),
- (45.5639, 2.0457),
- (46.5647, 2.8552),
- (47.7407, 4.2194),
- (48.5945, 5.3391),
- (49.4527, 6.8046),
- (50.0709, 8.0817),
- (50.5523, 9.1987),
- (50.8341, 10.2797),
- (51.0452, 10.6409),
- (51.0415, 11.1665),
- (51.1338, 11.7481),
- (51.1112, 12.0246),
- (50.7320, 12.0219),
- (50.2587, 11.6795),
- (49.7286, 11.5789),
- (49.2677, 11.4303),
- (48.9482, 11.4106),
- (48.3787, 11.3754),
- (48.0215, 11.1930),
- (47.5256, 11.1272),
- (46.6454, 10.8165),
- (45.5569, 10.6980),
- (44.6142, 10.4422),
- (44.1178, 10.4455),
- (43.6666, 10.8641),
- (43.4706, 11.2777),
- (43.1453, 11.4620),
- (42.7158, 11.7356),
- (43.2863, 11.9749),
- (43.3178, 12.3901),
- (43.0812, 12.6996),
- (42.5895, 13.0004),
- (42.2768, 13.3439),
- (41.7349, 13.9210),
- (41.1792, 14.4910),
- (39.8142, 15.4356),
- (39.2661, 15.9227),
- (38.9906, 16.8406),
- (38.4100, 17.9983),
- (37.8627, 18.3678),
- (37.4817, 18.6140),
- (37.1147, 19.8079),
- (36.9694, 20.8374),
- (37.1887, 21.0188),
- (36.86623, 22.0),
- (36.6907, 22.2048),
- (35.5259, 23.1024),
- (35.4937, 23.7523),
- (35.6924, 23.9267),
- (34.7950, 25.0337),
- (34.4738, 25.5985),
- (34.1045, 26.1422),
- (33.3487, 27.6998),
- (32.7348, 28.7052),
- (32.3204, 29.7604),
- (32.4232, 29.8510),
- (33.1367, 28.4176),
- (33.5881, 27.9713),
- (33.9213, 27.6487),
- (34.1545, 27.8233),
- (34.4265, 28.3439),
- (34.6417, 29.0994),
- (34.9226, 29.5013),
- (34.9560, 29.3565),
- (34.8322, 28.9574),
- (34.7877, 28.6074),
- (34.6323, 28.0585),
- (35.1301, 28.0633),
- (35.6401, 27.3765),
- (36.2491, 26.5701),
- (36.6396, 25.8262),
- (36.9316, 25.6029),
- (37.2094, 25.0845),
- (37.1548, 24.8584),
- (37.4836, 24.2854),
- (38.0238, 24.0786),
- (38.4927, 23.6884),
- (39.0663, 22.5796),
- (39.0236, 21.9868),
- (39.1393, 21.2919),
- (39.8016, 20.3388),
- (40.2476, 20.1746),
- (40.9393, 19.4864),
- (41.2213, 18.6715),
- (41.7543, 17.8330),
- (42.2708, 17.4747),
- (42.3479, 17.0758),
- (42.6495, 16.7746),
- (42.7793, 16.3478),
- (42.8236, 15.9117),
- (42.7024, 15.7188),
- (42.8050, 15.2619),
- (42.6048, 15.2133),
- (42.8922, 14.8022),
- (43.0879, 14.0626),
- (43.2514, 13.7675),
- (43.2228, 13.2209),
- (43.4829, 12.6368),
- (44.1751, 12.5859),
- (44.4945, 12.7216),
- (44.9895, 12.6995),
- (45.1443, 12.9539),
- (45.4064, 13.0269),
- (45.6250, 13.2909),
- (45.8775, 13.3477),
- (46.7170, 13.3996),
- (47.3544, 13.5922),
- (47.9389, 14.0072),
- (48.2389, 13.9480),
- (48.6792, 14.0032),
- (49.5745, 14.7087),
- (51.1725, 15.1752),
- (52.1681, 15.5974),
- (52.1917, 15.9384),
- (52.3852, 16.3824),
- (53.1085, 16.6510),
- (53.5705, 16.7076),
- (54.2392, 17.0449),
- (54.7910, 16.9506),
- (55.2749, 17.2283),
- (55.2699, 17.6323),
- (55.6614, 17.8841),
- (56.2835, 17.8760),
- (56.5121, 18.0871),
- (56.6096, 18.5742),
- (57.2342, 18.9479),
- (57.6943, 18.9447),
- (57.7887, 19.0675),
- (57.6657, 19.7360),
- (57.8263, 20.2430),
- (58.0343, 20.4814),
- (58.4879, 20.4289),
- (58.8611, 21.1140),
- (59.2824, 21.4338),
- (59.4421, 21.7145),
- (59.8061, 22.3105),
- (59.8080, 22.5336),
- (59.4500, 22.6602),
- (59.1805, 22.9923),
- (58.7292, 23.5656),
- (58.1369, 23.7479),
- (57.4034, 23.8785),
- (56.8451, 24.2416),
- (56.3968, 24.9247),
- (56.2610, 25.7146),
- (56.3914, 25.8959),
- (56.4856, 26.3091),
- (56.3620, 26.3959),
- (56.0708, 26.0554),
- (55.4390, 25.4391),
- (54.6930, 24.7978),
- (54.0080, 24.1217),
- (53.4040, 24.1513),
- (52.5770, 24.1774),
- (51.7943, 24.0198),
- (51.7574, 24.2940),
- (51.5795, 24.2454),
- (51.3896, 24.6273),
- (51.6067, 25.2156),
- (51.5890, 25.8011),
- (51.2864, 26.1145),
- (51.0133, 26.0069),
- (50.7439, 25.4824),
- (50.8101, 24.7547),
- (50.6605, 24.9998),
- (50.5273, 25.3278),
- (50.2398, 25.6080),
- (50.1133, 25.9439),
- (50.2129, 26.2770),
- (50.1524, 26.6896),
- (49.4709, 27.1099),
- (49.2995, 27.4612),
- (48.8075, 27.6896),
- (48.4160, 28.5520),
- (48.0939, 29.3062),
- (48.1831, 29.5344),
- (47.9745, 29.9758),
- (48.5679, 29.9267),
- (48.9413, 30.3170),
- (49.5768, 29.9857),
- (50.1150, 30.1477),
- (50.8529, 28.8145),
- (51.5207, 27.8656),
- (52.4835, 27.5808),
- (53.4930, 26.8123),
- (54.7150, 26.4806),
- (55.7237, 26.9646),
- (56.4921, 27.1433),
- (56.9707, 26.9661),
- (57.3972, 25.7399),
- (58.5257, 25.6099),
- (59.6161, 25.3801),
- (61.4973, 25.0782),
- (62.9057, 25.2184),
- (64.5304, 25.2370),
- (66.3728, 25.4251),
- (67.1454, 24.6636),
- (67.4436, 23.9448),
- (68.1766, 23.6919),
- (69.3495, 22.8431),
- (69.6449, 22.4507),
- (69.1641, 22.0892),
- (70.4704, 20.8773),
- (71.1752, 20.7574),
- (72.6305, 21.3560),
- (72.8244, 20.4195),
- (72.8209, 19.2082),
- (73.1199, 17.9285),
- (73.5341, 15.9906),
- (74.4438, 14.6172),
- (74.6167, 13.9925),
- (74.8648, 12.7419),
- (75.3961, 11.7812),
- (75.7464, 11.3082),
- (76.1300, 10.2996),
- (76.5929, 8.8992),
- (77.5398, 7.9655),
- (77.9411, 8.2529),
- (78.2779, 8.9330),
- (79.1897, 9.2165),
- (78.8853, 9.5461),
- (79.3405, 10.3088),
- (79.8579, 10.3572),
- (79.8625, 12.0562),
- (80.2862, 13.0062),
- (80.2332, 13.8357),
- (80.0250, 15.1364),
- (80.3248, 15.8991),
- (80.7919, 15.9519),
- (81.6927, 16.3102),
- (82.1912, 16.5566),
- (82.1927, 17.0166),
- (83.1892, 17.6712),
- (83.9410, 18.3020),
- (85.0602, 19.4785),
- (86.4993, 20.1516),
- (87.0331, 20.7433),
- (86.9757, 21.4955),
- (88.2084, 21.7031),
- (88.8887, 21.6905),
- (89.0319, 22.0557),
- (89.4188, 21.9661),
- (89.7020, 21.8571),
- (89.8474, 22.0391),
- (90.2729, 21.8363),
- (90.5869, 22.3927),
- (90.4960, 22.8050),
- (91.4170, 22.7650),
- (91.8348, 22.1829),
- (92.0252, 21.7015),
- (92.0828, 21.1921),
- (92.3685, 20.6708),
- (93.0782, 19.8551),
- (93.6632, 19.7269),
- (93.5409, 19.3664),
- (94.3248, 18.2135),
- (94.5334, 17.2772),
- (94.1888, 16.0379),
- (94.8084, 15.8034),
- (95.3693, 15.7143),
- (96.5057, 16.4272),
- (97.1645, 16.9287),
- (97.5970, 16.1005),
- (97.7777, 14.8372),
- (98.1036, 13.6404),
- (98.5095, 13.1223),
- (98.4283, 12.0329),
- (98.7645, 11.4412),
- (98.4571, 10.6752),
- (98.5535, 9.9329),
- (98.2591, 8.9739),
- (98.1500, 8.3500),
- (98.3396, 7.7945),
- (98.5037, 8.3823),
- (98.9882, 7.9079),
- (99.5196, 7.3434),
- (99.6906, 6.8482),
- (100.0857, 6.4644),
- (100.3062, 6.0405),
- (100.1967, 5.3124),
- (100.5574, 4.7672),
- (100.6954, 3.9391),
- (101.2735, 3.2702),
- (101.3906, 2.7608),
- (102.5736, 1.9671),
- (103.5197, 1.2263),
- (104.2288, 1.2930),
- (104.2479, 1.6311),
- (103.8546, 2.5154),
- (103.5024, 2.7910),
- (103.4294, 3.3828),
- (103.3321, 3.7266),
- (103.4385, 4.1816),
- (103.3812, 4.8550),
- (102.9617, 5.5244),
- (102.3711, 6.1282),
- (102.1411, 6.2216),
- (101.6230, 6.7406),
- (101.0173, 6.8568),
- (100.4592, 7.4295),
- (100.2796, 8.2951),
- (99.8738, 9.2078),
- (99.2223, 9.2392),
- (99.1537, 9.9630),
- (99.4789, 10.8463),
- (100.0187, 12.3070),
- (100.0977, 13.4068),
- (100.9784, 13.4127),
- (100.8318, 12.6270),
- (101.6871, 12.6457),
- (102.5849, 12.1865),
- (103.0906, 11.1536),
- (103.4972, 10.6325),
- (104.3343, 10.4865),
- (105.0762, 9.9184),
- (104.7951, 9.2410),
- (105.1582, 8.5997),
- (106.4051, 9.5308),
- (107.2209, 10.3644),
- (108.3661, 11.0083),
- (109.2001, 11.6668),
- (109.3352, 13.4260),
- (108.8771, 15.2766),
- (108.2694, 16.0797),
- (107.3619, 16.6974),
- (106.4268, 18.0041),
- (105.6620, 19.0581),
- (105.8816, 19.7520),
- (106.7150, 20.6968),
- (108.0501, 21.5523),
- (108.5228, 21.7152),
- (109.8644, 21.3950),
- (109.6276, 21.0082),
- (109.8898, 20.2824),
- (110.4440, 20.3410),
- (110.5093, 20.5654),
- (110.7854, 21.3971),
- (111.8435, 21.5504),
- (113.2410, 22.0513),
- (113.8067, 22.5483),
- (114.1525, 22.2237),
- (114.7638, 22.6680),
- (115.8907, 22.7828),
- (117.2816, 23.6245),
- (118.6568, 24.5473),
- (119.5854, 25.7407),
- (120.3954, 27.0532),
- (121.1256, 28.1356),
- (121.6844, 28.2255),
- (121.9384, 29.0180),
- (122.0921, 29.8325),
- (121.5035, 30.1429),
- (121.2642, 30.6762),
- (121.8919, 30.9493),
- (121.9081, 31.6921),
- (121.2290, 32.4603),
- (120.6203, 33.3767),
- (120.2275, 34.3603),
- (119.1512, 34.9098),
- (119.6645, 35.6097),
- (120.6370, 36.1114),
- (121.1041, 36.6513),
- (122.5199, 36.9306),
- (122.3579, 37.4544),
- (121.7112, 37.4811),
- (120.8234, 37.8704),
- (119.7028, 37.1563),
- (118.9116, 37.4484),
- (118.8781, 37.8973),
- (118.0596, 38.0614),
- (117.5327, 38.7376),
- (118.0427, 39.2042),
- (119.0234, 39.2523),
- (119.6396, 39.8980),
- (120.7686, 40.5933),
- (121.6403, 40.9463),
- (122.1685, 40.4224),
- (121.3767, 39.7502),
- (121.5859, 39.3608),
- (121.0545, 38.8974),
- (122.1313, 39.1704),
- (122.8675, 39.6377),
- (124.2656, 39.9284),
- (124.7374, 39.6603),
- (125.3211, 39.5513),
- (125.3865, 39.3879),
- (125.1328, 38.8485),
- (125.2219, 38.6658),
- (124.9859, 38.5484),
- (124.7121, 38.1083),
- (124.9810, 37.9488),
- (125.2400, 37.8572),
- (125.2753, 37.6690),
- (125.5684, 37.7520),
- (125.6891, 37.9400),
- (126.1747, 37.7496),
- (126.8601, 36.8939),
- (126.1173, 36.7254),
- (126.5592, 35.6845),
- (126.3739, 34.9345),
- (126.4857, 34.3900),
- (127.3865, 34.4756),
- (128.1858, 34.8903),
- (129.0913, 35.0824),
- (129.4683, 35.6321),
- (129.4604, 36.7841),
- (129.2129, 37.4323),
- (128.3497, 38.6122),
- (127.7833, 39.0508),
- (127.3854, 39.2134),
- (127.5021, 39.3239),
- (127.5334, 39.7568),
- (127.9674, 40.0254),
- (128.6333, 40.1898),
- (129.0103, 40.4854),
- (129.1881, 40.6618),
- (129.7051, 40.8828),
- (129.6673, 41.6011),
- (129.9659, 41.9413),
- (130.4000, 42.2800),
- (130.7800, 42.2200),
- (130.9358, 42.5527),
- (132.2780, 43.2845),
- (132.9062, 42.7984),
- (133.5368, 42.8114),
- (134.8694, 43.3982),
- (135.5153, 43.9889),
- (136.8623, 45.1434),
- (138.2197, 46.3079),
- (138.5547, 46.9996),
- (140.0619, 48.4467),
- (140.5130, 50.0455),
- (140.5974, 51.2396),
- (141.3792, 52.2387),
- (141.3453, 53.0895),
- (139.9015, 54.1896),
- (138.8046, 54.2545),
- (138.1647, 53.7550),
- (137.1934, 53.9773),
- (136.7017, 54.6035),
- (135.1261, 54.7295),
- (138.9584, 57.0880),
- (142.1978, 59.0399),
- (145.4872, 59.3363),
- (148.5448, 59.1644),
- (149.7837, 59.6557),
- (151.3381, 59.5039),
- (151.2657, 58.7808),
- (152.8118, 58.8838),
- (155.0437, 59.1449),
- (154.2180, 59.7581),
- (156.7206, 61.4344),
- (159.3023, 61.7739),
- (160.1214, 60.5442),
- (162.6579, 61.6424),
- (163.2583, 62.4662),
- (164.4735, 62.5506),
- (163.6696, 61.1408),
- (161.87204, 60.343),
- (160.1506, 59.3147),
- (158.3643, 58.0557),
- (156.8103, 57.8320),
- (156.7581, 57.3647),
- (155.9144, 56.7679),
- (155.4336, 55.3810),
- (155.9918, 53.1589),
- (156.42, 51.7),
- (156.7897, 51.0110),
- (158.2311, 51.9426),
- (158.5309, 52.9586),
- (160.0217, 53.2025),
- (160.3687, 54.3443),
- (162.1174, 54.8551),
- (161.7014, 55.2856),
- (162.1263, 56.1158),
- (163.0579, 56.1592),
- (163.1919, 57.6150),
- (162.0529, 57.8391),
- (162.0173, 58.2432),
- (163.2171, 59.2110),
- (163.5392, 59.8686),
- (164.8767, 59.7316),
- (165.84, 60.16),
- (166.2949, 59.7885),
- (168.9004, 60.5735),
- (170.3308, 59.8817),
- (170.6985, 60.3361),
- (172.15, 60.95),
- (173.6801, 61.6526),
- (174.5692, 61.7691),
- (177.3643, 62.5219),
- (179.2282, 62.3041),
- (179.4863, 62.5689),
- (179.3703, 62.9826),
- (178.9082, 63.2519),
- (178.313, 64.07593),
- (177.4112, 64.6082),
- (178.7072, 64.5349),
- (180.0, 64.9797),
- (-177.5500, 68.1999),
- (-179.9999, 68.9636),
- (-179.9999, -16.0671),
- (-179.7933, -16.0208),
- (-179.9173, -16.5017),
- (-179.9999, -16.5552),
- (125.9470, -8.4320),
- (126.6447, -8.3982),
- (126.9572, -8.2733),
- (127.3359, -8.3973),
- (126.9679, -8.6682),
- (125.9258, -9.1060),
- (125.0885, -9.3931),
- (124.4359, -10.1400),
- (123.5799, -10.3599),
- (123.4599, -10.2399),
- (123.5500, -9.9000),
- (123.9800, -9.2900),
- (124.9686, -8.8927),
- (125.0862, -8.6568),
- (125.9470, -8.4320),
- (-180.0, 68.9636),
- (-177.5500, 68.1999),
- (-174.9282, 67.2058),
- (-175.0142, 66.5843),
- (-174.3398, 66.3355),
- (-174.5718, 67.0621),
- (-171.8573, 66.9130),
- (-169.8995, 65.9772),
- (-170.8910, 65.5413),
- (-172.5302, 65.4379),
- (-172.555, 64.46079),
- (-172.9553, 64.2526),
- (-173.8918, 64.2826),
- (-174.6539, 64.6312),
- (-175.9835, 64.9228),
- (-176.2071, 65.3566),
- (-177.2226, 65.5202),
- (-178.3599, 65.3905),
- (-178.9033, 65.7404),
- (-178.6861, 66.1121),
- (-179.8837, 65.8745),
- (-179.4326, 65.4041),
- (-180.0, 64.9797),
- (-180.0, 71.5157),
- (-179.8718, 71.5576),
- (-179.0243, 71.5555),
- (-177.5779, 71.2694),
- (-177.6635, 71.1327),
- (-178.6937, 70.8930),
- (-180.0, 70.8321),
- (180.0, 70.8321),
- (178.9034, 70.7811),
- (178.7253, 71.0988),
- (180.0, 71.5157),
- (180.0, -16.5552),
- (179.3641, -16.8013),
- (178.7250, -17.0120),
- (178.5968, -16.6391),
- (179.0966, -16.4339),
- (179.4135, -16.3790),
- (180.0, -16.0671),
- (-61.2, -51.85),
- (-60.0, -51.25),
- (-59.15, -51.5),
- (-58.55, -51.1),
- (-57.75, -51.55),
- (-58.05, -51.9),
- (-59.4, -52.2),
- (-59.85, -51.85),
- (-60.7, -52.3),
- (-61.2, -51.85),
- (68.935, -48.625),
- (69.58, -48.94),
- (70.525, -49.065),
- (70.56, -49.255),
- (70.28, -49.71),
- (68.745, -49.775),
- (68.72, -49.2425),
- (68.8675, -48.83),
- (68.935, -48.625),
- (178.1255, -17.5048),
- (178.3736, -17.3399),
- (178.7180, -17.6284),
- (178.5527, -18.1505),
- (177.9326, -18.2879),
- (177.3814, -18.1643),
- (177.2850, -17.7246),
- (177.6708, -17.3811),
- (178.1255, -17.5048),
- (-61.68, 10.76),
- (-61.105, 10.89),
- (-60.895, 10.855),
- (-60.935, 10.11),
- (-61.77, 10.0),
- (-61.95, 10.09),
- (-61.66, 10.365),
- (-61.68, 10.76),
- (-155.4021, 20.0797),
- (-155.2245, 19.9930),
- (-155.0622, 19.8591),
- (-154.8074, 19.5087),
- (-154.8314, 19.4532),
- (-155.2221, 19.2397),
- (-155.5421, 19.0834),
- (-155.6881, 18.9161),
- (-155.9366, 19.0593),
- (-155.9080, 19.3388),
- (-156.0734, 19.7029),
- (-156.0236, 19.8142),
- (-155.8500, 19.9772),
- (-155.9190, 20.1739),
- (-155.8610, 20.2672),
- (-155.7850, 20.2487),
- (-155.4021, 20.0797),
- (-155.9956, 20.7640),
- (-156.0792, 20.6439),
- (-156.4144, 20.5724),
- (-156.58673, 20.783),
- (-156.7016, 20.8643),
- (-156.7105, 20.9267),
- (-156.6125, 21.0124),
- (-156.2571, 20.9174),
- (-155.9956, 20.7640),
- (-156.7582, 21.1768),
- (-156.7893, 21.0687),
- (-157.3252, 21.0977),
- (-157.2502, 21.2195),
- (-156.7582, 21.1768),
- (-158.0252, 21.7169),
- (-157.9416, 21.6527),
- (-157.6528, 21.3221),
- (-157.7070, 21.2644),
- (-157.7786, 21.2772),
- (-158.1266, 21.3124),
- (-158.2538, 21.5391),
- (-158.2926, 21.5791),
- (-158.0252, 21.7169),
- (-159.3656, 22.2149),
- (-159.34512, 21.982),
- (-159.4637, 21.8829),
- (-159.8005, 22.0653),
- (-159.7487, 22.1382),
- (-159.5962, 22.2361),
- (-159.3656, 22.2149),
- (-78.1908, 25.2103),
- (-77.89, 25.17),
- (-77.54, 24.34),
- (-77.5346, 23.7597),
- (-77.78, 23.71),
- (-78.0340, 24.2861),
- (-78.4084, 24.5756),
- (-78.1908, 25.2103),
- (-78.98, 26.79),
- (-78.51, 26.87),
- (-77.85, 26.84),
- (-77.82, 26.58),
- (-78.91, 26.42),
- (-78.98, 26.79),
- (-77.79, 27.04),
- (-77.0, 26.59),
- (-77.1725, 25.8791),
- (-77.3564, 26.0073),
- (-77.34, 26.53),
- (-77.7880, 26.9251),
- (-77.79, 27.04),
- (-64.0148, 47.0360),
- (-63.6645, 46.5500),
- (-62.9393, 46.4158),
- (-62.0120, 46.4431),
- (-62.5039, 46.0333),
- (-62.8743, 45.9681),
- (-64.1428, 46.3926),
- (-64.3926, 46.7274),
- (-64.0148, 47.0360),
- (46.6820, 44.6092),
- (47.6759, 45.6414),
- (48.6454, 45.8062),
- (49.1011, 46.3993),
- (50.0340, 46.6089),
- (51.1919, 47.0487),
- (52.0420, 46.8046),
- (53.0427, 46.8530),
- (53.2208, 46.2346),
- (53.0408, 45.2590),
- (52.1673, 45.4083),
- (51.3168, 45.2459),
- (51.2785, 44.5148),
- (50.3056, 44.6098),
- (50.3391, 44.2840),
- (50.8912, 44.0310),
- (51.3424, 43.1329),
- (52.5014, 42.7922),
- (52.6921, 42.4438),
- (52.4463, 42.0271),
- (52.5024, 41.7833),
- (52.8146, 41.1353),
- (52.9167, 41.8681),
- (53.7217, 42.1231),
- (54.0083, 41.5512),
- (54.7368, 40.9510),
- (53.8581, 40.6310),
- (52.9152, 40.8765),
- (52.6939, 40.0336),
- (53.3578, 39.9752),
- (53.1010, 39.2905),
- (53.8809, 38.9520),
- (53.7355, 37.9061),
- (53.9215, 37.1989),
- (53.8257, 36.9650),
- (52.2640, 36.7004),
- (50.8423, 36.8728),
- (50.1477, 37.3745),
- (49.1996, 37.5828),
- (48.8832, 38.3202),
- (48.8565, 38.8154),
- (49.2232, 39.0492),
- (49.3952, 39.3994),
- (49.5692, 40.1761),
- (50.3928, 40.2565),
- (50.0848, 40.5261),
- (49.6189, 40.5729),
- (49.1102, 41.2822),
- (48.5843, 41.8088),
- (47.4925, 42.9865),
- (47.5909, 43.6601),
- (46.6820, 44.6092),
- (-64.5191, 49.8730),
- (-64.1732, 49.9571),
- (-62.8582, 49.7064),
- (-61.8355, 49.2885),
- (-61.8063, 49.1050),
- (-62.2931, 49.0871),
- (-63.5892, 49.4006),
- (-64.5191, 49.8730),
- (-80.3153, 62.0855),
- (-79.9293, 62.3856),
- (-79.5200, 62.3637),
- (-79.2658, 62.1586),
- (-79.6575, 61.6330),
- (-80.0995, 61.7181),
- (-80.3621, 62.0164),
- (-80.3153, 62.0855),
- (-83.9936, 62.4528),
- (-83.2504, 62.9140),
- (-81.8769, 62.9045),
- (-81.8982, 62.7108),
- (-83.0685, 62.1592),
- (-83.7746, 62.1823),
- (-83.9936, 62.4528),
- (-75.2159, 67.4442),
- (-75.8658, 67.1488),
- (-76.9868, 67.0987),
- (-77.2364, 67.5880),
- (-76.8116, 68.1485),
- (-75.8952, 68.2872),
- (-75.1145, 68.0103),
- (-75.1033, 67.5820),
- (-75.2159, 67.4442),
- (-96.5574, 69.6800),
- (-95.6476, 69.1076),
- (-96.2695, 68.7570),
- (-97.6174, 69.0600),
- (-98.4318, 68.9507),
- (-99.7974, 69.4000),
- (-98.9174, 69.7100),
- (-98.2182, 70.1435),
- (-97.1574, 69.8600),
- (-96.5574, 69.6800),
- (-106.5225, 73.0760),
- (-105.4024, 72.6725),
- (-104.7748, 71.6984),
- (-104.4647, 70.9929),
- (-102.7853, 70.4977),
- (-100.9807, 70.0243),
- (-101.0893, 69.5844),
- (-102.7311, 69.5040),
- (-102.0932, 69.1196),
- (-102.4302, 68.7528),
- (-104.24, 68.91),
- (-105.96, 69.18),
- (-107.1225, 69.1192),
- (-109.0, 68.78),
- (-111.5341, 68.6300),
- (-113.3132, 68.5355),
- (-113.8549, 69.0074),
- (-115.22, 69.28),
- (-116.1079, 69.1682),
- (-117.34, 69.96),
- (-116.6747, 70.0665),
- (-115.1311, 70.2373),
- (-113.7213, 70.1923),
- (-112.4161, 70.3663),
- (-114.3499, 70.6000),
- (-116.4868, 70.5204),
- (-117.9048, 70.5405),
- (-118.4323, 70.9092),
- (-116.1131, 71.3091),
- (-117.6556, 71.2951),
- (-119.4019, 71.5585),
- (-118.5626, 72.3078),
- (-117.8664, 72.7059),
- (-115.1890, 73.3145),
- (-114.1671, 73.1214),
- (-114.6663, 72.6527),
- (-112.4410, 72.9553),
- (-111.0503, 72.4504),
- (-109.9203, 72.9611),
- (-109.0065, 72.6333),
- (-108.1883, 71.6508),
- (-107.6859, 72.0654),
- (-108.3964, 73.0895),
- (-107.5164, 73.2359),
- (-106.5225, 73.0760),
- (-79.7758, 72.8029),
- (-80.8760, 73.3331),
- (-80.8338, 73.6931),
- (-80.3530, 73.7597),
- (-78.0644, 73.6519),
- (-76.34, 73.1026),
- (-76.2514, 72.8263),
- (-77.3144, 72.8555),
- (-78.3916, 72.8766),
- (-79.4862, 72.7422),
- (-79.7758, 72.8029),
- (139.8631, 73.3698),
- (140.8117, 73.7650),
- (142.0620, 73.8575),
- (143.4828, 73.4752),
- (143.6038, 73.2124),
- (142.0876, 73.2054),
- (140.0381, 73.3169),
- (139.8631, 73.3698),
- (148.2222, 75.3458),
- (150.7316, 75.0840),
- (149.5759, 74.6889),
- (147.9774, 74.7783),
- (146.1191, 75.1729),
- (146.3584, 75.4968),
- (148.2222, 75.3458),
- (138.8310, 76.1367),
- (141.4716, 76.0928),
- (145.0862, 75.5626),
- (144.3, 74.82),
- (140.6138, 74.8476),
- (138.9554, 74.6114),
- (136.9743, 75.2616),
- (137.5117, 75.9491),
- (138.8310, 76.1367),
- (-98.5770, 76.5886),
- (-98.5, 76.72),
- (-97.7355, 76.2565),
- (-97.7044, 75.7434),
- (-98.16, 75.0),
- (-99.8087, 74.8974),
- (-100.8836, 75.0573),
- (-100.8629, 75.6407),
- (-102.5020, 75.5638),
- (-102.5655, 76.3365),
- (-101.4897, 76.3053),
- (-99.9834, 76.6463),
- (-98.5770, 76.5886),
- (102.8378, 79.2812),
- (105.3724, 78.7133),
- (105.0754, 78.3068),
- (99.43814, 77.921),
- (101.2649, 79.2339),
- (102.0863, 79.3464),
- (102.8378, 79.2812),
- (93.7776, 81.0246),
- (95.9408, 81.2504),
- (97.8838, 80.7469),
- (100.1866, 79.7801),
- (99.9397, 78.8809),
- (97.7579, 78.7562),
- (94.9725, 79.0447),
- (93.3128, 79.4265),
- (92.5454, 80.1437),
- (91.1810, 80.3414),
- (93.7776, 81.0246),
- (-96.0164, 80.6023),
- (-95.3234, 80.9072),
- (-94.2984, 80.9772),
- (-94.7354, 81.2064),
- (-92.4098, 81.2573),
- (-91.1328, 80.7234),
- (-87.81, 80.32),
- (-87.02, 79.66),
- (-85.8143, 79.3369),
- (-87.1875, 79.0393),
- (-89.0353, 78.2872),
- (-90.8043, 78.2153),
- (-92.8766, 78.3433),
- (-93.9511, 78.7510),
- (-93.9357, 79.1137),
- (-93.1452, 79.3801),
- (-94.9739, 79.3724),
- (-96.0761, 79.7050),
- (-96.7097, 80.1577),
- (-96.0164, 80.6023),
- (-91.5870, 81.8942),
- (-90.1, 82.085),
- (-88.9322, 82.1175),
- (-86.9702, 82.2796),
- (-85.5, 82.6522),
- (-84.2600, 82.6),
- (-83.18, 82.32),
- (-82.42, 82.86),
- (-81.1, 83.02),
- (-79.3066, 83.1305),
- (-76.25, 83.1720),
- (-75.7187, 83.0640),
- (-72.8315, 83.2332),
- (-70.6657, 83.1697),
- (-68.5, 83.1063),
- (-65.8273, 83.0280),
- (-63.68, 82.9),
- (-61.85, 82.6286),
- (-61.8938, 82.3616),
- (-64.334, 81.92775),
- (-66.7534, 81.7252),
- (-67.6575, 81.5014),
- (-65.4803, 81.5065),
- (-67.84, 80.9),
- (-69.4697, 80.6168),
- (-71.18, 79.8),
- (-73.2428, 79.6341),
- (-73.88, 79.4301),
- (-76.9077, 79.3230),
- (-75.5292, 79.1976),
- (-76.2204, 79.0190),
- (-75.3934, 78.5258),
- (-76.3435, 78.1829),
- (-77.8885, 77.8999),
- (-78.3626, 77.5085),
- (-79.7595, 77.2096),
- (-79.6196, 76.9833),
- (-77.9108, 77.0220),
- (-77.8891, 76.7779),
- (-80.5612, 76.1781),
- (-83.1743, 76.4540),
- (-86.1118, 76.2990),
- (-87.6, 76.42),
- (-89.4906, 76.4723),
- (-89.6161, 76.9521),
- (-87.7673, 77.1783),
- (-88.26, 77.9),
- (-87.65, 77.9702),
- (-84.9763, 77.5387),
- (-86.34, 78.18),
- (-87.9619, 78.3718),
- (-87.1519, 78.7586),
- (-85.3786, 78.9969),
- (-85.0949, 79.3454),
- (-86.5073, 79.7362),
- (-86.9317, 80.2514),
- (-84.1984, 80.2083),
- (-83.4086, 80.1),
- (-81.8482, 80.4644),
- (-84.1, 80.58),
- (-87.5989, 80.5162),
- (-89.3666, 80.8556),
- (-90.2, 81.26),
- (-91.3678, 81.5531),
- (-91.5870, 81.8942),
- (-46.7637, 82.6279),
- (-43.4064, 83.2251),
- (-39.8975, 83.1801),
- (-38.6221, 83.5490),
- (-35.0878, 83.6451),
- (-27.1004, 83.5196),
- (-20.8453, 82.7266),
- (-22.6918, 82.3416),
- (-26.5175, 82.2976),
- (-31.9, 82.2),
- (-31.3964, 82.0215),
- (-27.8566, 82.1317),
- (-24.8444, 81.7869),
- (-22.9032, 82.0931),
- (-22.0717, 81.7344),
- (-23.1696, 81.1527),
- (-20.6236, 81.5246),
- (-15.7681, 81.9124),
- (-12.7701, 81.7188),
- (-12.2085, 81.2915),
- (-16.2853, 80.5800),
- (-16.85, 80.35),
- (-20.0462, 80.1770),
- (-17.7303, 80.1291),
- (-18.9, 79.4),
- (-19.7049, 78.7512),
- (-19.6735, 77.6385),
- (-18.4728, 76.9856),
- (-20.0350, 76.9443),
- (-21.6794, 76.6279),
- (-19.8340, 76.0980),
- (-19.5989, 75.2483),
- (-20.6681, 75.1558),
- (-19.3728, 74.2956),
- (-21.5942, 74.2238),
- (-20.4345, 73.8171),
- (-20.7623, 73.4643),
- (-22.1722, 73.3095),
- (-23.5659, 73.3066),
- (-22.3131, 72.6292),
- (-22.2995, 72.1840),
- (-24.2783, 72.5978),
- (-24.7929, 72.3302),
- (-23.4429, 72.0801),
- (-22.1328, 71.4689),
- (-21.7535, 70.6636),
- (-23.53603, 70.471),
- (-24.3070, 70.8564),
- (-25.5434, 71.4309),
- (-25.2013, 70.7522),
- (-26.3627, 70.2264),
- (-23.7274, 70.1840),
- (-22.3490, 70.1294),
- (-25.0292, 69.2588),
- (-27.7473, 68.4704),
- (-30.6737, 68.1250),
- (-31.7766, 68.1207),
- (-32.8110, 67.7354),
- (-34.2019, 66.6797),
- (-36.3528, 65.9789),
- (-37.0437, 65.9376),
- (-38.3750, 65.6921),
- (-39.8122, 65.4584),
- (-40.6689, 64.8399),
- (-40.6828, 64.1390),
- (-41.1887, 63.4824),
- (-42.8193, 62.6823),
- (-42.4166, 61.9009),
- (-42.8661, 61.0740),
- (-43.3784, 60.0977),
- (-44.7875, 60.0367),
- (-46.2636, 60.8532),
- (-48.2629, 60.8584),
- (-49.2330, 61.4068),
- (-49.9003, 62.3833),
- (-51.6332, 63.6269),
- (-52.1401, 64.2784),
- (-52.2765, 65.1767),
- (-53.6616, 66.0995),
- (-53.3016, 66.8365),
- (-53.9691, 67.1889),
- (-52.9804, 68.3575),
- (-51.4753, 68.7295),
- (-51.0804, 69.1478),
- (-50.8712, 69.9291),
- (-52.0135, 69.5749),
- (-52.5579, 69.4261),
- (-53.4562, 69.2836),
- (-54.6833, 69.6100),
- (-54.7500, 70.2893),
- (-54.3588, 70.8213),
- (-53.4313, 70.8357),
- (-51.3901, 70.5697),
- (-53.1093, 71.2048),
- (-54.0042, 71.5471),
- (-55.0, 71.4065),
- (-55.8346, 71.6544),
- (-54.7181, 72.5862),
- (-55.3263, 72.9586),
- (-56.1200, 73.6497),
- (-57.3236, 74.7102),
- (-58.5967, 75.0986),
- (-58.5851, 75.5172),
- (-61.2686, 76.1023),
- (-63.3916, 76.1752),
- (-66.0642, 76.1348),
- (-68.5043, 76.0614),
- (-69.6648, 76.3797),
- (-71.4025, 77.0085),
- (-68.7767, 77.3231),
- (-66.7639, 77.3759),
- (-71.0429, 77.6359),
- (-73.297, 78.04419),
- (-73.1593, 78.4327),
- (-69.3734, 78.9138),
- (-65.7107, 79.3943),
- (-65.3239, 79.7581),
- (-68.0229, 80.1172),
- (-67.1512, 80.5158),
- (-63.6892, 81.2139),
- (-62.2344, 81.3211),
- (-62.6511, 81.7704),
- (-60.2824, 82.0336),
- (-57.2074, 82.1907),
- (-54.1344, 82.1996),
- (-53.0432, 81.8883),
- (-50.3906, 82.4388),
- (-48.0038, 82.0648),
- (-46.5998, 81.9859),
- (-44.523, 81.6607),
- (-46.9007, 82.1997),
- (-46.7637, 82.6279),
- (-106.6, 73.6),
- (-105.26, 73.64),
- (-104.5, 73.42),
- (-105.38, 72.76),
- (-106.94, 73.46),
- (-106.6, 73.6),
- (-180.0, -84.71338),
- (-179.9424, -84.7214),
- (-179.0586, -84.1394),
- (-177.2567, -84.4529),
- (-176.0846, -84.0992),
- (-175.8298, -84.1179),
- (-174.3825, -84.5343),
- (-173.1165, -84.1179),
- (-172.8891, -84.0610),
- (-169.9512, -83.8846),
- (-168.9999, -84.1179),
- (-168.5301, -84.2373),
- (-167.0220, -84.5704),
- (-164.1821, -84.8252),
- (-161.9297, -85.1387),
- (-158.0713, -85.3739),
- (-155.1922, -85.0995),
- (-150.9420, -85.2955),
- (-148.5330, -85.6090),
- (-145.8889, -85.3151),
- (-143.1077, -85.0407),
- (-142.8922, -84.5704),
- (-146.8290, -84.5312),
- (-150.0607, -84.2961),
- (-150.9029, -83.9042),
- (-153.5862, -83.6886),
- (-153.4099, -83.2380),
- (-153.0377, -82.8265),
- (-152.6656, -82.4541),
- (-152.8615, -82.0426),
- (-154.5262, -81.7683),
- (-155.2901, -81.4156),
- (-156.8374, -81.1021),
- (-154.4087, -81.1609),
- (-152.0976, -81.0041),
- (-150.6482, -81.3373),
- (-148.8659, -81.0433),
- (-147.2207, -80.6710),
- (-146.4177, -80.3379),
- (-146.7702, -79.9264),
- (-148.0629, -79.6520),
- (-149.5319, -79.3582),
- (-151.5884, -79.2993),
- (-153.3903, -79.1622),
- (-155.3293, -79.0642),
- (-155.9756, -78.6919),
- (-157.2683, -78.3784),
- (-158.0517, -78.0256),
- (-158.3651, -76.8892),
- (-157.8754, -76.9872),
- (-156.9745, -77.3007),
- (-155.3293, -77.2027),
- (-153.7428, -77.0655),
- (-152.9202, -77.4966),
- (-151.3337, -77.3987),
- (-150.0019, -77.1831),
- (-148.7484, -76.9088),
- (-147.6124, -76.5757),
- (-146.1044, -76.4777),
- (-146.1435, -76.1054),
- (-146.4960, -75.7331),
- (-146.2023, -75.3804),
- (-144.9096, -75.2040),
- (-144.3220, -75.5371),
- (-142.7943, -75.3412),
- (-141.6387, -75.0864),
- (-140.2090, -75.0668),
- (-138.8575, -74.9689),
- (-137.5062, -74.7337),
- (-136.4289, -74.5182),
- (-135.2145, -74.3026),
- (-134.4311, -74.3614),
- (-133.7456, -74.4398),
- (-132.2571, -74.3026),
- (-130.9253, -74.4790),
- (-129.5542, -74.4594),
- (-128.2420, -74.3222),
- (-126.8906, -74.4202),
- (-125.4020, -74.5182),
- (-124.0114, -74.4790),
- (-122.5621, -74.4986),
- (-121.0736, -74.5182),
- (-119.7025, -74.4790),
- (-118.6841, -74.1850),
- (-117.4698, -74.0283),
- (-116.2163, -74.2438),
- (-115.0215, -74.0675),
- (-113.9443, -73.7148),
- (-113.2979, -74.0283),
- (-112.9454, -74.3810),
- (-112.2990, -74.7141),
- (-111.2610, -74.4202),
- (-110.0663, -74.7925),
- (-108.7149, -74.9101),
- (-107.5593, -75.1844),
- (-106.1491, -75.1256),
- (-104.8760, -74.9493),
- (-103.3679, -74.9884),
- (-102.0165, -75.1256),
- (-100.6455, -75.3020),
- (-100.1166, -74.8709),
- (-100.7630, -74.5378),
- (-101.2527, -74.1850),
- (-102.5453, -74.1067),
- (-103.1133, -73.7344),
- (-103.3287, -73.3620),
- (-103.6812, -72.6175),
- (-102.9174, -72.7546),
- (-101.6052, -72.8134),
- (-100.3125, -72.7546),
- (-99.1373, -72.9114),
- (-98.1188, -73.2053),
- (-97.6880, -73.5580),
- (-96.3365, -73.6168),
- (-95.0439, -73.4797),
- (-93.6729, -73.2837),
- (-92.4390, -73.1661),
- (-91.4205, -73.4013),
- (-90.0887, -73.3229),
- (-89.2269, -72.5587),
- (-88.4239, -73.0093),
- (-87.2683, -73.1857),
- (-86.0148, -73.0877),
- (-85.1922, -73.4797),
- (-83.8799, -73.5188),
- (-82.6656, -73.6364),
- (-81.4709, -73.8519),
- (-80.6874, -73.4797),
- (-80.2957, -73.1269),
- (-79.2968, -73.5188),
- (-77.9258, -73.4208),
- (-76.9073, -73.6364),
- (-76.2218, -73.9695),
- (-74.8900, -73.8716),
- (-73.8520, -73.6560),
- (-72.8335, -73.4013),
- (-71.6192, -73.2641),
- (-70.2090, -73.1465),
- (-68.9359, -73.0093),
- (-67.9566, -72.7938),
- (-67.3690, -72.4803),
- (-67.1340, -72.0492),
- (-67.2515, -71.6377),
- (-67.5649, -71.2458),
- (-67.9174, -70.8539),
- (-68.2308, -70.4620),
- (-68.4854, -70.1093),
- (-68.5442, -69.7173),
- (-68.4462, -69.3255),
- (-67.9762, -68.9532),
- (-67.5844, -68.5417),
- (-67.4278, -68.1498),
- (-67.6236, -67.7187),
- (-67.7411, -67.3268),
- (-67.2515, -66.8761),
- (-66.7031, -66.5822),
- (-66.0568, -66.2099),
- (-65.3713, -65.8963),
- (-64.5682, -65.6025),
- (-64.1765, -65.1714),
- (-63.6281, -64.8970),
- (-63.0013, -64.6423),
- (-62.0416, -64.5835),
- (-61.4149, -64.2700),
- (-60.7098, -64.0740),
- (-59.8872, -63.9565),
- (-59.1625, -63.7017),
- (-58.5945, -63.3882),
- (-57.8111, -63.2706),
- (-57.2235, -63.5254),
- (-57.5957, -63.8585),
- (-58.6141, -64.1524),
- (-59.0450, -64.3680),
- (-59.7893, -64.2112),
- (-60.6119, -64.3092),
- (-61.2974, -64.5443),
- (-62.0221, -64.7990),
- (-62.5117, -65.0930),
- (-62.6488, -65.4849),
- (-62.5901, -65.8572),
- (-62.1200, -66.1903),
- (-62.8055, -66.4255),
- (-63.7456, -66.5038),
- (-64.2941, -66.8370),
- (-64.8816, -67.1504),
- (-65.5084, -67.5816),
- (-65.6650, -67.9538),
- (-65.3125, -68.3653),
- (-64.7837, -68.6789),
- (-63.9611, -68.9139),
- (-63.1972, -69.2275),
- (-62.7859, -69.6194),
- (-62.5705, -69.9917),
- (-62.2767, -70.3836),
- (-61.8066, -70.7167),
- (-61.5129, -71.0890),
- (-61.3758, -72.0100),
- (-61.0819, -72.3823),
- (-61.0036, -72.7742),
- (-60.6902, -73.1661),
- (-60.8273, -73.6952),
- (-61.3758, -74.1067),
- (-61.9633, -74.4398),
- (-63.2952, -74.5769),
- (-63.7456, -74.9297),
- (-64.3528, -75.2628),
- (-65.8609, -75.6351),
- (-67.1928, -75.7919),
- (-68.4462, -76.0074),
- (-69.7977, -76.2229),
- (-70.6007, -76.6344),
- (-72.2067, -76.6736),
- (-73.9695, -76.6344),
- (-75.5559, -76.7128),
- (-77.2403, -76.7128),
- (-76.9269, -77.1048),
- (-75.3992, -77.2810),
- (-74.2828, -77.5554),
- (-73.6561, -77.9081),
- (-74.7725, -78.2216),
- (-76.4961, -78.1236),
- (-77.9258, -78.3784),
- (-77.9846, -78.7899),
- (-78.0237, -79.1818),
- (-76.8486, -79.5149),
- (-76.6332, -79.8872),
- (-75.3600, -80.2595),
- (-73.2448, -80.4163),
- (-71.4429, -80.6906),
- (-70.0131, -81.0041),
- (-68.1916, -81.3176),
- (-65.7042, -81.4744),
- (-63.2560, -81.7487),
- (-61.5520, -82.0426),
- (-59.6914, -82.3758),
- (-58.7121, -82.8461),
- (-58.2224, -83.2184),
- (-57.0081, -82.8656),
- (-55.3628, -82.5717),
- (-53.6197, -82.2582),
- (-51.5436, -82.0035),
- (-49.7613, -81.7291),
- (-47.2739, -81.7095),
- (-44.8257, -81.8467),
- (-42.8083, -82.0819),
- (-42.1620, -81.6508),
- (-40.7714, -81.3568),
- (-38.2448, -81.3373),
- (-36.2666, -81.1217),
- (-34.3863, -80.9061),
- (-32.3102, -80.7690),
- (-30.0970, -80.5926),
- (-28.5498, -80.3379),
- (-29.2549, -79.9851),
- (-29.6858, -79.6325),
- (-29.6858, -79.2602),
- (-31.6248, -79.2993),
- (-33.6813, -79.4561),
- (-35.6399, -79.4561),
- (-35.9141, -79.0838),
- (-35.7770, -78.3392),
- (-35.3265, -78.1236),
- (-33.8967, -77.8885),
- (-32.2123, -77.6534),
- (-30.9980, -77.3595),
- (-29.7837, -77.0655),
- (-28.8827, -76.6736),
- (-27.5117, -76.4973),
- (-26.1603, -76.3601),
- (-25.4748, -76.2818),
- (-23.9275, -76.2425),
- (-22.4585, -76.1054),
- (-21.2246, -75.9094),
- (-20.0103, -75.6743),
- (-18.9135, -75.4392),
- (-17.5229, -75.1256),
- (-16.6415, -74.7925),
- (-15.7014, -74.4986),
- (-15.4077, -74.1067),
- (-16.4653, -73.8716),
- (-16.1127, -73.4601),
- (-15.4468, -73.1465),
- (-14.4088, -72.9505),
- (-13.3119, -72.7154),
- (-12.2935, -72.4019),
- (-11.5100, -72.0100),
- (-11.0204, -71.5397),
- (-10.2957, -71.2654),
- (-9.1010, -71.3242),
- (-8.6113, -71.6573),
- (-7.4166, -71.6965),
- (-7.3774, -71.3242),
- (-6.8682, -70.9323),
- (-5.7909, -71.0302),
- (-5.5363, -71.4026),
- (-4.3416, -71.4613),
- (-3.0489, -71.2850),
- (-1.7954, -71.1674),
- (-0.6594, -71.2262),
- (-0.2286, -71.6377),
- (0.8681, -71.3046),
- (1.8866, -71.1282),
- (3.0226, -70.9911),
- (4.1390, -70.8539),
- (5.1575, -70.6187),
- (6.2739, -70.4620),
- (7.1357, -70.2465),
- (7.7428, -69.8937),
- (8.4871, -70.1485),
- (9.5251, -70.0113),
- (10.2498, -70.4816),
- (10.8178, -70.8343),
- (11.9538, -70.6383),
- (12.4042, -70.2465),
- (13.4227, -69.9721),
- (14.7349, -70.0309),
- (15.1267, -70.4032),
- (15.9493, -70.0309),
- (17.0265, -69.9133),
- (18.2017, -69.8741),
- (19.2593, -69.8937),
- (20.3757, -70.0113),
- (21.4529, -70.0701),
- (21.9230, -70.4032),
- (22.5694, -70.6971),
- (23.6661, -70.5208),
- (24.8413, -70.4816),
- (25.9773, -70.4816),
- (27.0937, -70.4620),
- (28.0925, -70.3248),
- (29.1502, -70.2072),
- (30.0315, -69.9329),
- (30.9717, -69.7566),
- (31.9901, -69.6586),
- (32.7540, -69.3842),
- (33.3024, -68.8356),
- (33.8704, -68.5025),
- (34.9084, -68.6592),
- (35.3002, -69.0120),
- (36.1620, -69.2471),
- (37.2000, -69.1687),
- (37.9051, -69.5214),
- (38.6494, -69.7762),
- (39.6678, -69.5410),
- (40.0204, -69.1099),
- (40.9213, -68.9336),
- (41.9594, -68.6005),
- (42.9387, -68.4633),
- (44.1138, -68.2674),
- (44.8972, -68.0518),
- (45.7199, -67.8167),
- (46.5033, -67.6011),
- (47.4434, -67.7187),
- (48.3444, -67.3660),
- (48.9907, -67.0917),
- (49.9308, -67.1113),
- (50.7534, -66.8761),
- (50.9493, -66.5234),
- (51.7915, -66.2491),
- (52.6141, -66.0531),
- (53.6130, -65.8963),
- (54.5335, -65.8180),
- (55.4149, -65.8768),
- (56.3550, -65.9747),
- (57.1580, -66.2491),
- (57.2559, -66.6802),
- (58.1373, -67.0133),
- (58.7445, -67.2876),
- (59.9393, -67.4052),
- (60.6052, -67.6795),
- (61.4278, -67.9538),
- (62.3874, -68.0126),
- (63.1904, -67.8167),
- (64.0523, -67.4052),
- (64.9924, -67.6207),
- (65.9717, -67.7383),
- (66.9118, -67.8559),
- (67.8911, -67.9343),
- (68.8900, -67.9343),
- (69.7126, -68.9727),
- (69.6734, -69.2275),
- (69.5559, -69.6782),
- (68.5962, -69.9329),
- (67.8127, -70.3052),
- (67.9498, -70.6971),
- (69.0663, -70.6775),
- (68.9291, -71.0694),
- (68.4199, -71.4417),
- (67.9498, -71.8532),
- (68.7137, -72.1668),
- (69.8693, -72.2647),
- (71.0248, -72.0884),
- (71.5732, -71.6965),
- (71.9062, -71.3242),
- (72.4546, -71.0107),
- (73.0814, -70.7167),
- (73.3360, -70.3640),
- (73.8648, -69.8741),
- (74.4915, -69.7762),
- (75.6275, -69.7370),
- (76.6264, -69.6194),
- (77.6449, -69.4626),
- (78.1345, -69.0707),
- (78.4283, -68.6984),
- (79.1138, -68.3262),
- (80.0931, -68.0715),
- (80.9353, -67.8755),
- (81.4837, -67.5423),
- (82.0517, -67.3660),
- (82.7764, -67.2092),
- (83.7753, -67.3072),
- (84.6762, -67.2092),
- (85.6555, -67.0917),
- (86.7523, -67.1504),
- (87.4770, -66.8761),
- (87.9862, -66.2099),
- (88.3584, -66.4842),
- (88.8284, -66.9545),
- (89.6706, -67.1504),
- (90.6303, -67.2288),
- (91.5900, -67.1113),
- (92.6085, -67.1896),
- (93.5486, -67.2092),
- (94.1754, -67.1113),
- (95.0175, -67.1701),
- (95.7814, -67.3856),
- (96.6823, -67.2485),
- (97.7596, -67.2485),
- (98.6802, -67.1113),
- (99.7181, -67.2485),
- (100.3841, -66.9153),
- (100.8933, -66.5822),
- (101.5788, -66.3078),
- (102.8324, -65.5632),
- (103.4786, -65.7004),
- (104.2425, -65.9747),
- (104.9084, -66.3275),
- (106.1815, -66.9349),
- (107.1608, -66.9545),
- (108.0813, -66.9545),
- (109.1586, -66.8370),
- (110.2358, -66.6998),
- (111.0584, -66.4255),
- (111.7439, -66.1315),
- (112.8603, -66.0923),
- (113.6046, -65.8768),
- (114.3880, -66.0727),
- (114.8973, -66.3862),
- (115.6023, -66.6998),
- (116.6991, -66.6606),
- (117.3847, -66.9153),
- (118.5794, -67.1701),
- (119.8329, -67.2680),
- (120.8709, -67.1896),
- (121.6544, -66.8761),
- (122.3203, -66.5626),
- (123.2212, -66.4842),
- (124.1222, -66.6214),
- (125.1602, -66.7193),
- (126.1003, -66.5626),
- (127.0014, -66.5626),
- (127.8827, -66.6606),
- (128.8032, -66.7586),
- (129.7042, -66.5822),
- (130.7814, -66.4255),
- (131.7999, -66.3862),
- (132.9358, -66.3862),
- (133.8564, -66.2883),
- (134.7573, -66.2099),
- (135.0315, -65.7200),
- (135.0707, -65.3085),
- (135.6974, -65.5828),
- (135.8738, -66.0335),
- (136.2067, -66.4450),
- (136.6180, -66.7781),
- (137.4602, -66.9545),
- (138.5962, -66.8957),
- (139.9084, -66.8761),
- (140.8094, -66.8173),
- (142.1216, -66.8173),
- (143.0618, -66.7977),
- (144.3740, -66.8370),
- (145.4904, -66.9153),
- (146.1955, -67.2288),
- (145.9996, -67.6011),
- (146.6460, -67.8951),
- (147.7232, -68.1302),
- (148.8396, -68.3850),
- (150.1323, -68.5612),
- (151.4837, -68.7181),
- (152.5022, -68.8748),
- (153.6381, -68.8945),
- (154.2845, -68.5612),
- (155.1658, -68.8356),
- (155.9297, -69.1492),
- (156.8111, -69.3842),
- (158.0255, -69.4822),
- (159.1810, -69.5998),
- (159.6706, -69.9917),
- (160.8066, -70.2268),
- (161.5704, -70.5796),
- (162.6868, -70.7363),
- (163.8424, -70.7167),
- (164.9196, -70.7755),
- (166.1144, -70.7559),
- (167.3090, -70.8343),
- (168.4256, -70.9714),
- (169.4635, -71.2066),
- (170.5016, -71.4026),
- (171.2067, -71.6965),
- (171.0892, -72.0884),
- (170.5604, -72.4411),
- (170.1099, -72.8918),
- (169.7573, -73.2445),
- (169.2873, -73.6560),
- (167.9751, -73.8128),
- (167.3874, -74.1654),
- (166.0948, -74.3810),
- (165.6443, -74.7729),
- (164.9588, -75.1452),
- (164.2341, -75.4588),
- (163.8227, -75.8703),
- (163.5682, -76.2425),
- (163.4702, -76.6933),
- (163.4898, -77.0655),
- (164.0578, -77.4574),
- (164.2733, -77.8297),
- (164.7434, -78.1825),
- (166.6041, -78.3196),
- (166.9957, -78.7507),
- (165.1938, -78.9074),
- (163.6662, -79.1230),
- (161.7663, -79.1622),
- (160.9241, -79.7304),
- (160.7478, -80.2007),
- (160.3169, -80.5730),
- (159.7882, -80.9453),
- (161.1200, -81.2785),
- (161.6292, -81.6900),
- (162.4909, -82.0622),
- (163.7053, -82.3954),
- (165.0959, -82.7089),
- (166.6041, -83.0224),
- (168.8956, -83.3359),
- (169.4047, -83.8258),
- (172.2839, -84.0414),
- (172.4770, -84.1179),
- (173.2240, -84.4137),
- (175.9856, -84.1589),
- (178.2772, -84.4725),
- (180.0, -84.71338),
-];
-
-pub static WORLD_LOW_RESOLUTION: [(f64, f64); 1166] = [
- (-92.32, 48.24),
- (-88.13, 48.92),
- (-83.11, 46.27),
- (-81.66, 44.76),
- (-82.09, 42.29),
- (-77.10, 44.00),
- (-69.95, 46.92),
- (-65.92, 45.32),
- (-66.37, 44.25),
- (-61.22, 45.43),
- (-64.94, 47.34),
- (-64.12, 48.52),
- (-70.68, 47.02),
- (-67.24, 49.33),
- (-59.82, 50.48),
- (-56.14, 52.46),
- (-59.07, 53.58),
- (-58.26, 54.21),
- (-60.69, 55.33),
- (-61.97, 57.41),
- (-64.35, 59.49),
- (-67.29, 58.15),
- (-69.89, 59.91),
- (-71.31, 61.45),
- (-78.22, 61.97),
- (-77.28, 59.53),
- (-77.09, 55.88),
- (-79.06, 51.68),
- (-82.23, 52.70),
- (-86.75, 55.72),
- (-92.17, 56.86),
- (-95.61, 58.82),
- (-92.66, 62.02),
- (-90.65, 63.24),
- (-95.96, 64.12),
- (-89.88, 63.98),
- (-89.30, 65.22),
- (-86.86, 66.12),
- (-84.54, 66.88),
- (-82.30, 67.76),
- (-83.10, 69.68),
- (-86.05, 67.98),
- (-88.18, 68.20),
- (-91.00, 68.82),
- (-91.72, 69.69),
- (-93.15, 71.09),
- (-96.58, 71.05),
- (-93.35, 69.52),
- (-94.23, 68.25),
- (-95.96, 66.73),
- (-98.83, 68.27),
- (-102.45, 67.69),
- (-108.34, 68.43),
- (-105.83, 68.05),
- (-108.15, 66.60),
- (-111.15, 67.63),
- (-114.10, 68.23),
- (-120.92, 69.44),
- (-124.32, 69.26),
- (-128.76, 70.50),
- (-131.86, 69.19),
- (-131.15, 69.79),
- (-135.81, 69.13),
- (-140.19, 69.37),
- (-141.20, 69.58),
- (-141.21, 69.56),
- (-142.49, 69.83),
- (-148.09, 70.26),
- (-154.37, 70.96),
- (-159.53, 70.38),
- (-166.64, 68.25),
- (-161.56, 66.55),
- (-162.99, 65.97),
- (-168.23, 65.49),
- (-161.12, 64.49),
- (-165.29, 62.57),
- (-164.58, 60.06),
- (-162.06, 58.36),
- (-157.85, 58.12),
- (-162.34, 55.06),
- (-156.52, 57.11),
- (-153.53, 59.32),
- (-149.18, 60.81),
- (-149.90, 59.50),
- (-146.54, 60.36),
- (-139.98, 59.73),
- (-137.12, 58.28),
- (-136.01, 59.12),
- (-133.84, 57.12),
- (-131.46, 55.98),
- (-132.08, 57.20),
- (-140.37, 60.25),
- (-141.21, 60.16),
- (-133.38, 58.93),
- (-130.88, 54.83),
- (-128.86, 53.90),
- (-126.58, 52.12),
- (-127.08, 50.80),
- (-124.42, 49.66),
- (-122.56, 48.91),
- (-122.44, 48.92),
- (-124.42, 47.18),
- (-124.52, 42.48),
- (-123.09, 38.45),
- (-121.73, 36.62),
- (-117.60, 33.34),
- (-117.28, 32.64),
- (-117.29, 32.48),
- (-114.75, 27.80),
- (-112.53, 24.80),
- (-110.55, 24.07),
- (-114.23, 29.59),
- (-112.58, 29.99),
- (-109.57, 25.94),
- (-105.61, 21.94),
- (-102.09, 17.87),
- (-95.75, 15.94),
- (-92.21, 14.97),
- (-92.22, 14.71),
- (-86.74, 12.06),
- (-83.03, 8.65),
- (-79.93, 8.74),
- (-77.00, 7.82),
- (-81.99, 8.97),
- (-83.92, 12.70),
- (-86.33, 15.80),
- (-88.40, 15.92),
- (-88.45, 17.42),
- (-87.01, 21.33),
- (-91.65, 18.72),
- (-96.96, 20.37),
- (-97.65, 25.67),
- (-97.62, 25.82),
- (-95.62, 28.84),
- (-90.77, 29.03),
- (-87.33, 30.22),
- (-82.69, 28.15),
- (-80.16, 26.66),
- (-80.74, 32.31),
- (-76.89, 35.43),
- (-76.47, 38.21),
- (-75.66, 37.67),
- (-71.31, 41.76),
- (-69.44, 44.17),
- (-67.69, 47.03),
- (-73.18, 45.14),
- (-79.26, 43.28),
- (-82.84, 42.59),
- (-83.49, 45.32),
- (-86.36, 43.65),
- (-87.75, 43.42),
- (-86.01, 45.96),
- (-87.00, 46.59),
- (-91.39, 46.79),
- (-90.05, 47.96),
- (-152.62, 58.41),
- (-152.60, 58.40),
- (-153.30, 57.80),
- (-152.40, 57.48),
- (-153.32, 57.79),
- (-166.96, 53.96),
- (-167.01, 53.95),
- (-168.36, 53.50),
- (-168.19, 53.36),
- (-170.73, 52.68),
- (-170.60, 52.55),
- (-174.47, 51.94),
- (-174.47, 51.92),
- (-176.58, 51.71),
- (-176.64, 51.73),
- (-177.55, 51.76),
- (-177.41, 51.63),
- (-178.27, 51.75),
- (177.35, 51.80),
- (177.33, 51.76),
- (172.44, 53.00),
- (172.55, 53.03),
- (-123.40, 48.33),
- (-128.00, 50.84),
- (-123.50, 48.34),
- (-132.49, 52.88),
- (-132.44, 52.91),
- (-132.64, 53.02),
- (-131.97, 53.71),
- (-132.63, 53.02),
- (-55.36, 51.56),
- (-54.66, 49.52),
- (-53.65, 47.48),
- (-52.98, 46.31),
- (-56.12, 46.84),
- (-58.47, 47.57),
- (-57.61, 50.38),
- (-55.39, 51.53),
- (-61.37, 49.01),
- (-61.80, 49.29),
- (-61.38, 49.03),
- (-63.01, 46.71),
- (-64.42, 46.61),
- (-63.04, 46.68),
- (-60.14, 46.48),
- (-60.14, 46.50),
- (-71.97, 41.11),
- (-71.97, 41.15),
- (-80.79, 27.03),
- (-81.01, 26.99),
- (-113.01, 42.09),
- (-113.10, 42.01),
- (-155.74, 20.02),
- (-155.73, 19.98),
- (-156.51, 20.78),
- (-156.51, 20.78),
- (-157.12, 21.21),
- (-157.08, 20.95),
- (-157.87, 21.42),
- (-159.53, 22.07),
- (-117.44, 66.46),
- (-119.59, 65.24),
- (-123.95, 65.03),
- (-123.69, 66.44),
- (-119.21, 66.22),
- (-117.44, 66.44),
- (-120.71, 64.03),
- (-114.91, 62.30),
- (-109.07, 62.72),
- (-112.62, 61.19),
- (-118.68, 61.19),
- (-117.01, 61.17),
- (-115.97, 62.56),
- (-119.46, 64.00),
- (-120.59, 63.94),
- (-112.31, 58.46),
- (-108.90, 59.44),
- (-104.14, 58.90),
- (-102.56, 56.72),
- (-101.82, 58.73),
- (-104.65, 58.91),
- (-111.00, 58.51),
- (-112.35, 58.62),
- (-98.74, 50.09),
- (-99.75, 52.24),
- (-99.62, 51.47),
- (-98.82, 50.39),
- (-97.02, 50.21),
- (-97.50, 54.02),
- (-98.69, 52.93),
- (-97.19, 51.09),
- (-96.98, 50.20),
- (-95.34, 49.04),
- (-92.32, 50.34),
- (-94.14, 49.47),
- (-95.36, 48.82),
- (-80.39, 56.16),
- (-79.22, 55.94),
- (-80.34, 56.08),
- (-103.56, 58.60),
- (-103.60, 58.58),
- (-101.82, 58.03),
- (-102.33, 58.10),
- (-101.77, 58.06),
- (-101.88, 55.79),
- (-97.92, 57.15),
- (-101.22, 55.85),
- (-101.88, 55.74),
- (-77.61, 6.80),
- (-78.70, 0.97),
- (-80.75, -4.47),
- (-76.19, -14.57),
- (-70.44, -18.75),
- (-70.68, -26.15),
- (-71.44, -32.03),
- (-73.38, -37.27),
- (-73.06, -42.11),
- (-73.17, -46.09),
- (-73.52, -48.05),
- (-73.67, -51.56),
- (-71.06, -53.88),
- (-69.14, -50.77),
- (-67.51, -46.59),
- (-63.49, -42.80),
- (-62.14, -40.16),
- (-57.12, -36.71),
- (-53.17, -34.15),
- (-51.26, -32.02),
- (-48.16, -25.48),
- (-40.73, -22.32),
- (-38.88, -15.24),
- (-34.60, -7.81),
- (-41.95, -3.42),
- (-48.02, -1.84),
- (-48.44, -1.57),
- (-50.81, 0.00),
- (-54.47, 5.39),
- (-60.59, 8.32),
- (-64.19, 9.88),
- (-70.78, 10.64),
- (-70.97, 11.89),
- (-76.26, 8.76),
- (-77.61, 6.80),
- (-69.14, -52.79),
- (-66.16, -55.08),
- (-70.01, -54.88),
- (-70.55, -53.85),
- (-59.29, -51.58),
- (-59.35, -51.54),
- (-58.65, -51.55),
- (-58.55, -51.56),
- (-84.39, 21.44),
- (-73.90, 19.73),
- (-79.27, 21.18),
- (-83.74, 21.80),
- (-84.32, 21.42),
- (-66.96, 17.95),
- (-67.05, 17.89),
- (-77.88, 17.22),
- (-78.06, 16.98),
- (-74.47, 18.08),
- (-69.88, 18.99),
- (-71.10, 17.76),
- (-74.45, 17.86),
- (-85.28, 73.74),
- (-85.79, 70.96),
- (-85.13, 71.94),
- (-84.74, 72.96),
- (-80.61, 73.10),
- (-78.45, 72.20),
- (-75.44, 72.55),
- (-73.89, 71.98),
- (-72.56, 71.04),
- (-71.49, 70.57),
- (-69.78, 70.29),
- (-68.12, 69.71),
- (-65.91, 69.19),
- (-66.92, 68.39),
- (-64.08, 67.68),
- (-62.50, 66.68),
- (-63.07, 65.33),
- (-66.11, 66.08),
- (-67.48, 65.41),
- (-64.05, 63.15),
- (-66.58, 63.26),
- (-69.04, 62.33),
- (-72.22, 63.77),
- (-76.88, 64.17),
- (-73.25, 65.54),
- (-70.09, 66.64),
- (-72.05, 67.44),
- (-76.32, 68.36),
- (-78.34, 70.17),
- (-82.12, 69.71),
- (-87.64, 70.12),
- (-89.68, 71.43),
- (-85.28, 73.74),
- (-80.90, 76.10),
- (-84.21, 76.28),
- (-88.94, 76.38),
- (-85.47, 77.40),
- (-85.43, 77.93),
- (-87.01, 78.54),
- (-83.17, 78.94),
- (-84.87, 79.93),
- (-81.33, 79.82),
- (-76.27, 80.92),
- (-82.88, 80.62),
- (-82.58, 81.16),
- (-86.51, 81.05),
- (-89.36, 81.21),
- (-90.45, 81.38),
- (-89.28, 81.86),
- (-87.21, 82.30),
- (-80.51, 82.05),
- (-80.16, 82.55),
- (-77.83, 82.86),
- (-75.51, 83.05),
- (-71.18, 82.90),
- (-65.10, 82.78),
- (-63.34, 81.80),
- (-68.26, 81.26),
- (-69.46, 80.34),
- (-71.05, 79.82),
- (-74.40, 79.46),
- (-75.42, 79.03),
- (-75.48, 78.92),
- (-76.01, 78.20),
- (-80.66, 77.28),
- (-78.07, 76.98),
- (-80.90, 76.13),
- (-92.86, 74.13),
- (-92.50, 72.70),
- (-94.89, 73.16),
- (-92.96, 74.14),
- (-94.80, 76.95),
- (-89.68, 76.04),
- (-88.52, 75.40),
- (-82.36, 75.67),
- (-79.39, 74.65),
- (-86.15, 74.22),
- (-91.70, 74.94),
- (-95.60, 76.91),
- (-94.87, 76.96),
- (-99.96, 73.74),
- (-97.89, 72.90),
- (-98.28, 71.13),
- (-102.04, 72.92),
- (-101.34, 73.14),
- (-99.69, 73.59),
- (-107.58, 73.25),
- (-104.59, 71.02),
- (-101.71, 69.56),
- (-104.07, 68.62),
- (-106.61, 69.12),
- (-114.09, 69.05),
- (-113.89, 70.12),
- (-115.88, 70.32),
- (-116.10, 71.32),
- (-117.45, 72.48),
- (-113.53, 72.44),
- (-109.84, 72.24),
- (-106.62, 71.71),
- (-107.43, 73.04),
- (-120.96, 74.29),
- (-118.37, 72.53),
- (-123.06, 71.18),
- (-123.40, 73.77),
- (-120.93, 74.27),
- (-108.83, 76.74),
- (-106.25, 75.54),
- (-107.08, 74.78),
- (-112.99, 74.16),
- (-112.28, 74.99),
- (-116.04, 75.33),
- (-115.27, 76.20),
- (-110.95, 75.56),
- (-109.77, 76.31),
- (-108.82, 76.70),
- (-115.70, 77.46),
- (-118.10, 76.30),
- (-121.13, 76.37),
- (-116.04, 77.28),
- (-110.01, 77.86),
- (-112.36, 77.68),
- (-109.96, 77.86),
- (-109.60, 78.48),
- (-112.20, 78.01),
- (-109.60, 78.48),
- (-97.87, 76.61),
- (-99.21, 75.31),
- (-100.86, 75.60),
- (-99.40, 76.26),
- (-97.79, 76.60),
- (-94.72, 75.53),
- (-94.66, 75.52),
- (-104.10, 79.01),
- (-99.19, 77.54),
- (-103.22, 78.08),
- (-104.30, 78.95),
- (-93.74, 77.52),
- (-93.74, 77.52),
- (-96.88, 78.50),
- (-96.91, 77.77),
- (-96.94, 78.48),
- (-84.69, 65.84),
- (-81.58, 63.87),
- (-85.00, 62.96),
- (-84.63, 65.71),
- (-81.84, 62.75),
- (-82.01, 62.63),
- (-79.88, 62.12),
- (-79.88, 62.12),
- (-43.53, 59.89),
- (-45.29, 60.67),
- (-47.91, 60.83),
- (-49.90, 62.41),
- (-50.71, 64.42),
- (-51.39, 64.94),
- (-52.96, 66.09),
- (-53.62, 67.19),
- (-53.51, 67.51),
- (-51.84, 68.65),
- (-52.19, 70.00),
- (-51.85, 71.03),
- (-55.41, 71.41),
- (-54.63, 72.97),
- (-56.98, 74.70),
- (-61.95, 76.09),
- (-66.38, 75.83),
- (-71.13, 77.00),
- (-66.81, 77.60),
- (-70.78, 77.78),
- (-64.96, 79.70),
- (-63.38, 81.16),
- (-56.89, 82.17),
- (-48.18, 82.15),
- (-42.08, 82.74),
- (-38.02, 83.54),
- (-23.96, 82.94),
- (-25.97, 81.97),
- (-25.99, 80.64),
- (-13.57, 80.97),
- (-16.60, 80.16),
- (-19.82, 78.82),
- (-18.80, 77.54),
- (-21.98, 76.46),
- (-20.69, 75.12),
- (-21.78, 74.40),
- (-24.10, 73.69),
- (-26.54, 73.08),
- (-24.63, 72.69),
- (-21.84, 71.69),
- (-24.62, 71.24),
- (-27.16, 70.89),
- (-27.21, 70.00),
- (-24.10, 69.35),
- (-28.35, 68.43),
- (-32.48, 68.56),
- (-35.26, 66.26),
- (-37.90, 65.90),
- (-40.04, 65.00),
- (-40.49, 64.04),
- (-42.01, 63.14),
- (-42.88, 61.15),
- (-43.09, 60.07),
- (-43.56, 59.90),
- (-16.26, 66.41),
- (-15.32, 64.29),
- (-20.14, 63.47),
- (-21.76, 64.21),
- (-21.33, 64.97),
- (-23.04, 65.62),
- (-21.76, 66.26),
- (-18.77, 66.12),
- (-16.23, 66.35),
- (0.56, 51.47),
- (-1.71, 54.94),
- (-3.41, 57.52),
- (-5.42, 58.14),
- (-5.77, 55.59),
- (-3.48, 54.82),
- (-4.68, 52.88),
- (-2.68, 51.58),
- (-3.80, 50.08),
- (1.26, 51.14),
- (0.65, 51.41),
- (-7.17, 54.91),
- (-9.97, 53.47),
- (-8.52, 51.76),
- (-5.69, 54.79),
- (-7.34, 55.25),
- (-1.33, 60.66),
- (-1.17, 60.38),
- (-6.18, 58.44),
- (-6.09, 58.36),
- (-6.47, 57.58),
- (-6.33, 57.54),
- (-7.30, 57.54),
- (-7.46, 57.05),
- (-6.54, 56.94),
- (-6.00, 55.94),
- (-5.09, 55.55),
- (-4.44, 54.38),
- (-4.30, 54.19),
- (-8.08, 71.02),
- (-8.21, 70.86),
- (16.92, 79.52),
- (22.26, 78.46),
- (16.86, 76.41),
- (16.00, 77.39),
- (16.03, 77.92),
- (16.81, 79.50),
- (14.71, 79.40),
- (16.05, 79.12),
- (14.02, 77.80),
- (13.56, 78.46),
- (12.63, 79.26),
- (14.68, 79.40),
- (22.01, 78.24),
- (21.86, 78.23),
- (21.54, 77.75),
- (23.88, 77.26),
- (21.53, 77.67),
- (22.79, 77.79),
- (23.50, 79.97),
- (28.24, 79.54),
- (20.85, 78.94),
- (19.00, 79.34),
- (21.05, 79.88),
- (23.41, 79.96),
- (46.98, 80.23),
- (43.13, 79.97),
- (47.18, 80.22),
- (50.43, 80.19),
- (50.55, 79.88),
- (47.77, 79.86),
- (50.45, 80.14),
- (61.79, 80.18),
- (61.79, 80.18),
- (65.08, 80.69),
- (64.27, 80.59),
- (65.13, 80.68),
- (-5.13, 35.66),
- (4.06, 36.63),
- (10.40, 37.12),
- (11.36, 33.61),
- (20.10, 30.10),
- (23.49, 32.17),
- (31.65, 30.80),
- (35.76, 23.74),
- (39.75, 14.82),
- (42.93, 11.34),
- (51.52, 11.45),
- (49.82, 6.99),
- (43.13, -0.62),
- (39.15, -7.58),
- (40.37, -13.20),
- (37.74, -18.17),
- (35.33, -22.71),
- (32.84, -28.15),
- (26.50, -34.39),
- (19.55, -35.51),
- (17.50, -30.88),
- (12.24, -18.75),
- (13.89, -12.81),
- (12.05, -5.55),
- (9.67, 0.14),
- (7.19, 3.79),
- (1.74, 5.39),
- (-4.77, 4.59),
- (-12.00, 6.75),
- (-15.54, 10.98),
- (-16.33, 15.50),
- (-16.10, 22.29),
- (-12.90, 27.12),
- (-9.52, 31.09),
- (-5.41, 35.58),
- (33.71, 0.00),
- (33.48, -3.42),
- (33.34, -0.20),
- (33.71, 0.00),
- (49.30, -12.50),
- (49.28, -18.79),
- (43.95, -25.50),
- (44.37, -20.08),
- (46.34, -16.31),
- (47.91, -14.08),
- (49.30, -12.50),
- (178.88, 69.10),
- (181.20, 68.42),
- (183.52, 67.78),
- (188.87, 66.38),
- (186.54, 64.74),
- (182.87, 65.63),
- (180.13, 65.14),
- (179.48, 64.88),
- (178.20, 64.29),
- (177.46, 62.62),
- (170.42, 60.17),
- (164.48, 59.89),
- (162.92, 57.34),
- (161.82, 54.88),
- (156.42, 51.09),
- (156.40, 57.76),
- (163.79, 61.73),
- (159.90, 60.73),
- (156.81, 61.68),
- (153.83, 59.10),
- (148.57, 59.46),
- (140.77, 58.39),
- (137.10, 54.07),
- (140.72, 52.43),
- (138.77, 47.30),
- (129.92, 42.04),
- (128.33, 38.46),
- (126.15, 35.18),
- (125.12, 39.08),
- (121.62, 40.15),
- (117.58, 38.21),
- (121.77, 36.90),
- (120.73, 32.65),
- (121.28, 30.25),
- (118.83, 24.93),
- (112.69, 21.81),
- (108.53, 21.73),
- (107.55, 16.34),
- (107.32, 10.45),
- (104.39, 10.37),
- (100.01, 13.52),
- (100.26, 8.30),
- (103.22, 1.56),
- (98.21, 9.17),
- (97.66, 15.36),
- (94.21, 17.79),
- (90.05, 21.74),
- (90.06, 21.03),
- (82.06, 15.95),
- (80.05, 11.72),
- (76.41, 8.60),
- (72.79, 17.43),
- (72.02, 20.00),
- (68.98, 21.99),
- (64.62, 24.41),
- (57.83, 24.77),
- (53.11, 26.20),
- (49.67, 29.41),
- (50.96, 25.15),
- (54.33, 23.44),
- (59.03, 22.57),
- (57.87, 18.86),
- (52.95, 15.74),
- (47.26, 12.96),
- (42.75, 14.68),
- (39.93, 19.61),
- (36.92, 25.78),
- (33.30, 28.46),
- (32.60, 30.63),
- (32.18, 30.58),
- (36.08, 35.03),
- (32.53, 36.17),
- (27.77, 36.94),
- (26.51, 39.18),
- (31.54, 40.82),
- (38.53, 40.48),
- (40.35, 43.17),
- (39.88, 46.45),
- (35.18, 44.99),
- (33.50, 44.96),
- (30.24, 45.14),
- (28.70, 41.48),
- (26.55, 39.84),
- (23.62, 39.67),
- (23.80, 37.34),
- (21.90, 36.92),
- (18.79, 42.02),
- (14.52, 44.31),
- (14.58, 42.25),
- (18.32, 39.57),
- (16.05, 39.35),
- (11.52, 42.36),
- (6.87, 43.08),
- (2.80, 41.09),
- (-1.11, 37.14),
- (-6.24, 36.70),
- (-8.67, 39.57),
- (-6.51, 43.13),
- (-0.84, 45.55),
- (-3.93, 48.40),
- (0.48, 49.09),
- (4.20, 51.29),
- (6.44, 52.92),
- (8.42, 55.94),
- (11.72, 55.49),
- (11.73, 53.66),
- (16.78, 54.14),
- (21.40, 56.32),
- (24.67, 57.20),
- (28.94, 59.18),
- (24.16, 59.52),
- (22.07, 62.66),
- (23.76, 65.35),
- (18.70, 62.54),
- (19.11, 59.67),
- (18.40, 58.54),
- (15.34, 55.73),
- (11.74, 58.08),
- (8.37, 57.68),
- (5.80, 59.20),
- (7.38, 60.86),
- (7.51, 61.86),
- (9.62, 62.99),
- (13.37, 65.46),
- (15.46, 67.12),
- (18.54, 68.62),
- (22.32, 69.64),
- (24.77, 70.17),
- (25.93, 69.79),
- (28.56, 70.46),
- (29.75, 69.76),
- (33.83, 69.11),
- (41.90, 66.85),
- (35.14, 66.25),
- (33.30, 66.07),
- (35.46, 64.15),
- (37.68, 64.03),
- (41.71, 64.09),
- (44.80, 65.58),
- (44.87, 68.16),
- (45.92, 66.83),
- (51.79, 67.85),
- (53.70, 67.89),
- (59.68, 68.09),
- (65.07, 69.08),
- (68.56, 69.19),
- (68.38, 70.97),
- (73.03, 71.62),
- (73.80, 68.29),
- (69.42, 66.45),
- (73.43, 66.36),
- (77.51, 68.36),
- (80.74, 66.74),
- (75.27, 68.67),
- (75.11, 71.80),
- (78.62, 70.56),
- (78.43, 71.90),
- (82.72, 71.23),
- (84.25, 70.03),
- (81.40, 72.76),
- (86.50, 74.01),
- (87.68, 74.78),
- (90.25, 75.23),
- (89.68, 75.57),
- (95.12, 75.95),
- (99.69, 76.09),
- (104.10, 77.52),
- (106.34, 76.40),
- (112.99, 75.60),
- (107.88, 73.72),
- (110.43, 73.71),
- (113.34, 73.37),
- (123.10, 73.28),
- (128.94, 73.02),
- (126.10, 72.24),
- (130.53, 70.86),
- (135.49, 71.51),
- (139.60, 72.23),
- (146.04, 72.39),
- (146.92, 72.21),
- (150.77, 71.28),
- (159.92, 70.14),
- (167.68, 69.63),
- (170.20, 69.99),
- (178.88, 69.10),
- (68.33, 76.71),
- (66.03, 75.62),
- (59.10, 74.11),
- (54.92, 73.03),
- (56.67, 74.10),
- (58.56, 75.09),
- (63.86, 75.87),
- (68.19, 76.70),
- (53.04, 72.57),
- (58.29, 70.39),
- (55.03, 70.78),
- (53.44, 72.26),
- (53.63, 72.61),
- (52.22, 46.50),
- (51.73, 44.73),
- (52.56, 41.80),
- (53.43, 40.40),
- (54.22, 37.86),
- (49.04, 38.45),
- (48.17, 42.76),
- (49.33, 45.64),
- (52.22, 46.50),
- (62.32, 46.32),
- (60.32, 43.06),
- (59.57, 45.58),
- (61.94, 46.33),
- (79.55, 46.12),
- (74.30, 44.44),
- (78.62, 45.79),
- (79.66, 46.07),
- (76.81, 41.96),
- (76.73, 41.86),
- (35.15, 35.15),
- (34.61, 34.84),
- (35.18, 35.17),
- (23.84, 35.33),
- (24.30, 34.91),
- (24.09, 35.39),
- (15.54, 37.89),
- (13.47, 37.89),
- (15.54, 37.89),
- (9.56, 40.95),
- (8.46, 39.99),
- (9.12, 40.69),
- (9.72, 42.60),
- (9.54, 42.35),
- (80.60, 8.95),
- (79.73, 5.96),
- (80.10, 8.30),
- (11.04, 57.44),
- (10.67, 57.25),
- (-77.92, 24.67),
- (-77.98, 24.22),
- (-77.61, 23.62),
- (-77.18, 23.64),
- (-75.55, 24.13),
- (-75.41, 24.31),
- (-91.40, -0.17),
- (-91.52, -0.26),
- (-60.25, 46.68),
- (-60.71, 46.33),
- (-63.89, 49.47),
- (-63.45, 49.43),
- (142.53, -10.60),
- (145.62, -16.34),
- (149.79, -22.09),
- (153.21, -26.82),
- (150.52, -35.19),
- (145.60, -38.53),
- (140.13, -37.69),
- (137.34, -34.77),
- (135.76, -34.56),
- (131.50, -31.34),
- (121.72, -33.65),
- (115.62, -33.25),
- (114.09, -26.01),
- (114.88, -21.27),
- (122.34, -18.13),
- (125.32, -14.53),
- (128.39, -14.90),
- (132.35, -11.42),
- (136.16, -12.43),
- (138.07, -16.45),
- (142.25, -10.78),
- (144.72, -40.68),
- (148.32, -42.14),
- (145.57, -42.77),
- (146.47, -41.19),
- (172.86, -34.23),
- (176.10, -37.52),
- (177.06, -39.49),
- (174.77, -38.03),
- (172.83, -34.27),
- (172.36, -40.53),
- (172.92, -43.81),
- (168.41, -46.13),
- (170.26, -43.21),
- (173.69, -40.94),
- (150.74, -10.18),
- (143.04, -8.26),
- (138.48, -6.97),
- (131.95, -2.94),
- (130.91, -1.35),
- (134.38, -2.64),
- (141.24, -2.62),
- (148.19, -8.15),
- (150.75, -10.27),
- (117.24, 7.01),
- (117.90, 0.76),
- (113.89, -3.50),
- (109.44, -0.82),
- (113.13, 3.38),
- (117.24, 7.01),
- (95.31, 5.75),
- (102.32, 1.40),
- (106.03, -2.98),
- (101.46, -2.81),
- (95.20, 5.73),
- (140.91, 41.53),
- (140.79, 35.75),
- (136.82, 34.56),
- (133.56, 34.72),
- (132.49, 35.41),
- (136.73, 37.20),
- (139.82, 40.00),
- (140.68, 41.43),
- (133.71, 34.30),
- (131.41, 31.58),
- (129.38, 33.10),
- (133.90, 34.37),
- (141.89, 45.50),
- (144.12, 42.92),
- (140.30, 41.64),
- (141.53, 45.30),
- (141.89, 45.53),
- (142.57, 54.36),
- (143.64, 49.19),
- (141.99, 45.88),
- (141.92, 50.85),
- (142.60, 54.34),
- (121.92, 25.48),
- (120.53, 24.70),
- (121.70, 25.51),
- (110.81, 20.07),
- (109.20, 19.66),
- (110.81, 20.07),
- (106.51, -6.16),
- (114.15, -7.72),
- (108.71, -7.89),
- (106.51, -6.16),
- (164.27, -20.01),
- (164.16, -20.27),
- (178.61, -17.04),
- (178.61, -17.04),
- (179.45, -16.43),
- (179.35, -16.43),
- (-172.55, -13.39),
- (-172.61, -13.78),
- (122.26, 18.67),
- (123.05, 13.86),
- (120.73, 13.80),
- (120.43, 16.43),
- (121.72, 18.40),
- (125.34, 9.79),
- #[allow(clippy::approx_constant)]
- (125.56, 6.28),
- (122.38, 7.00),
- (125.10, 9.38),
- (119.64, 11.35),
- (118.81, 10.16),
- (119.59, 10.86),
- (119.64, 11.35),
- (-179.87, 65.14),
- (-177.13, 65.63),
- (-173.46, 64.74),
- (-171.13, 66.38),
- (-176.48, 67.78),
- (-178.80, 68.42),
- (101.96, 79.08),
- (101.31, 77.86),
- (101.22, 79.04),
- (94.29, 79.29),
- (95.31, 78.68),
- (100.02, 79.43),
- (97.26, 79.62),
- (95.44, 79.65),
- (95.46, 80.62),
- (92.39, 79.66),
- (95.07, 80.54),
- (138.54, 76.05),
- (144.93, 75.45),
- (140.30, 74.99),
- (137.27, 75.44),
- (138.29, 75.98),
- (146.08, 75.29),
- (147.75, 74.73),
- (145.85, 75.06),
- (141.44, 73.88),
- (141.48, 73.84),
- (0.01, -71.68),
- (6.57, -70.57),
- (15.04, -70.44),
- (25.10, -70.75),
- (33.37, -69.10),
- (38.46, -69.77),
- (42.85, -68.16),
- (46.59, -67.23),
- (49.35, -66.96),
- (52.90, -65.97),
- (58.46, -67.20),
- (63.60, -67.58),
- (70.63, -68.41),
- (69.24, -70.36),
- (76.20, -69.44),
- (88.08, -66.64),
- (94.98, -66.52),
- (101.53, -66.09),
- (111.31, -65.91),
- (118.64, -66.87),
- (126.24, -66.24),
- (133.09, -66.18),
- (139.85, -66.72),
- (146.86, -67.96),
- (153.65, -68.82),
- (159.94, -69.57),
- (164.10, -70.67),
- (170.19, -71.94),
- (165.68, -74.64),
- (163.82, -77.60),
- (162.10, -78.95),
- (166.72, -82.84),
- (175.58, -83.86),
- (-178.56, -84.37),
- (-147.96, -85.40),
- (-152.96, -81.12),
- (-153.95, -79.50),
- (-151.24, -77.48),
- (-146.74, -76.44),
- (-137.68, -75.16),
- (-131.63, -74.63),
- (-123.05, -74.41),
- (-114.76, -73.97),
- (-111.91, -75.41),
- (-105.05, -74.77),
- (-100.90, -74.21),
- (-101.04, -73.18),
- (-100.28, -73.06),
- (-93.06, -73.33),
- (-85.40, -73.18),
- (-79.82, -73.04),
- (-78.21, -72.52),
- (-71.90, -73.41),
- (-67.51, -71.10),
- (-67.57, -68.92),
- (-66.65, -66.83),
- (-64.30, -65.28),
- (-59.14, -63.74),
- (-59.58, -64.37),
- (-62.50, -65.94),
- (-62.48, -66.66),
- (-65.64, -68.02),
- (-63.85, -69.07),
- (-61.69, -70.87),
- (-60.89, -72.71),
- (-61.07, -74.30),
- (-63.33, -75.88),
- (-76.05, -77.06),
- (-83.04, -77.12),
- (-74.30, -80.83),
- (-56.40, -82.14),
- (-42.46, -81.65),
- (-31.60, -80.17),
- (-34.01, -79.20),
- (-32.48, -77.28),
- (-26.28, -76.18),
- (-17.18, -73.45),
- (-11.20, -72.01),
- (-8.67, -71.98),
- (-5.45, -71.45),
- (-0.82, -71.74),
- (0.07, -71.68),
- (164.65, -77.89),
- (170.95, -77.37),
- (179.67, -78.25),
- (-178.74, -78.24),
- (-165.76, -78.47),
- (-158.42, -77.73),
- (-58.98, -64.63),
- (-60.99, -68.62),
- (-61.02, -71.70),
- (-62.01, -74.94),
- (-52.00, -77.07),
- (-42.23, -77.80),
- (-36.22, -78.03),
- (-35.03, -77.81),
- (-26.13, -75.54),
- (-19.35, -73.04),
- (-12.16, -71.86),
- (-6.15, -70.65),
- (-0.57, -69.14),
- (4.93, -70.25),
- (10.91, -69.99),
- (16.52, -69.87),
- (25.41, -70.22),
- (32.13, -69.29),
- (33.62, -69.58),
- (70.56, -68.53),
- (73.91, -69.51),
- (81.42, -67.87),
- (84.67, -66.41),
- (89.07, -66.73),
- (-135.79, -74.67),
- (-124.34, -73.22),
- (-116.65, -74.08),
- (-109.93, -74.64),
- (-105.36, -74.56),
- (-105.83, -74.77),
- (-69.30, -70.06),
- (-71.33, -72.68),
- (-71.42, -71.85),
- (-75.10, -71.46),
- (-71.79, -70.55),
- (-70.34, -69.26),
- (-69.34, -70.13),
- (-49.20, -77.83),
- (-44.59, -78.79),
- (-44.14, -80.13),
- (-59.04, -79.95),
- (-49.28, -77.84),
- (-48.24, -77.81),
- (-58.13, -80.12),
- (-63.25, -80.20),
- (-58.32, -80.12),
- (-163.64, -78.74),
- (-161.20, -79.93),
- (-163.62, -78.74),
- (66.82, 66.82),
- (66.82, 66.82),
-];
diff --git a/src/ratatui/widgets/chart.rs b/src/ratatui/widgets/chart.rs
deleted file mode 100644
index a57a392b..00000000
--- a/src/ratatui/widgets/chart.rs
+++ /dev/null
@@ -1,660 +0,0 @@
-use std::{borrow::Cow, cmp::max};
-
-use unicode_width::UnicodeWidthStr;
-
-use crate::ratatui::layout::Alignment;
-use crate::ratatui::{
- buffer::Buffer,
- layout::{Constraint, Rect},
- style::{Color, Style},
- symbols,
- text::{Span, Spans},
- widgets::{
- canvas::{Canvas, Line, Points},
- Block, Borders, Widget,
- },
-};
-
-/// An X or Y axis for the chart widget
-#[derive(Debug, Clone)]
-pub struct Axis<'a> {
- /// Title displayed next to axis end
- title: Option<Spans<'a>>,
- /// Bounds for the axis (all data points outside these limits will not be represented)
- bounds: [f64; 2],
- /// A list of labels to put to the left or below the axis
- labels: Option<Vec<Span<'a>>>,
- /// The style used to draw the axis itself
- style: Style,
- /// The alignment of the labels of the Axis
- labels_alignment: Alignment,
-}
-
-impl<'a> Default for Axis<'a> {
- fn default() -> Axis<'a> {
- Axis {
- title: None,
- bounds: [0.0, 0.0],
- labels: None,
- style: Default::default(),
- labels_alignment: Alignment::Left,
- }
- }
-}
-
-impl<'a> Axis<'a> {
- pub fn title<T>(mut self, title: T) -> Axis<'a>
- where
- T: Into<Spans<'a>>,
- {
- self.title = Some(title.into());
- self
- }
-
- #[deprecated(
- since = "0.10.0",
- note = "You should use styling capabilities of `text::Spans` given as argument of the `title` method to apply styling to the title."
- )]
- pub fn title_style(mut self, style: Style) -> Axis<'a> {
- if let Some(t) = self.title {
- let title = String::from(t);
- self.title = Some(Spans::from(Span::styled(title, style)));
- }
- self
- }
-
- pub fn bounds(mut self, bounds: [f64; 2]) -> Axis<'a> {
- self.bounds = bounds;
- self
- }
-
- pub fn labels(mut self, labels: Vec<Span<'a>>) -> Axis<'a> {
- self.labels = Some(labels);
- self
- }
-
- pub fn style(mut self, style: Style) -> Axis<'a> {
- self.style = style;
- self
- }
-
- /// Defines the alignment of the labels of the axis.
- /// The alignment behaves differently based on the axis:
- /// - Y-Axis: The labels are aligned within the area on the left of the axis
- /// - X-Axis: The first X-axis label is aligned relative to the Y-axis
- pub fn labels_alignment(mut self, alignment: Alignment) -> Axis<'a> {
- self.labels_alignment = alignment;
- self
- }
-}
-
-/// Used to determine which style of graphing to use
-#[derive(Debug, Clone, Copy)]
-pub enum GraphType {
- /// Draw each point
- Scatter,
- /// Draw each point and lines between each point using the same marker
- Line,
-}
-
-/// A group of data points
-#[derive(Debug, Clone)]
-pub struct Dataset<'a> {
- /// Name of the dataset (used in the legend if shown)
- name: Cow<'a, str>,
- /// A reference to the actual data
- data: &'a [(f64, f64)],
- /// Symbol used for each points of this dataset
- marker: symbols::Marker,
- /// Determines graph type used for drawing points
- graph_type: GraphType,
- /// Style used to plot this dataset
- style: Style,
-}
-
-impl<'a> Default for Dataset<'a> {
- fn default() -> Dataset<'a> {
- Dataset {
- name: Cow::from(""),
- data: &[],
- marker: symbols::Marker::Dot,
- graph_type: GraphType::Scatter,
- style: Style::default(),
- }
- }
-}
-
-impl<'a> Dataset<'a> {
- pub fn name<S>(mut self, name: S) -> Dataset<'a>
- where
- S: Into<Cow<'a, str>>,
- {
- self.name = name.into();
- self
- }
-
- pub fn data(mut self, data: &'a [(f64, f64)]) -> Dataset<'a> {
- self.data = data;
- self
- }
-
- pub fn marker(mut self, marker: symbols::Marker) -> Dataset<'a> {
- self.marker = marker;
- self
- }
-
- pub fn graph_type(mut self, graph_type: GraphType) -> Dataset<'a> {
- self.graph_type = graph_type;
- self
- }
-
- pub fn style(mut self, style: Style) -> Dataset<'a> {
- self.style = style;
- self
- }
-}
-
-/// A container that holds all the infos about where to display each elements of the chart (axis,
-/// labels, legend, ...).
-#[derive(Debug, Clone, PartialEq, Default)]
-struct ChartLayout {
- /// Location of the title of the x axis
- title_x: Option<(u16, u16)>,
- /// Location of the title of the y axis
- title_y: Option<(u16, u16)>,
- /// Location of the first label of the x axis
- label_x: Option<u16>,
- /// Location of the first label of the y axis
- label_y: Option<u16>,
- /// Y coordinate of the horizontal axis
- axis_x: Option<u16>,
- /// X coordinate of the vertical axis
- axis_y: Option<u16>,
- /// Area of the legend
- legend_area: Option<Rect>,
- /// Area of the graph
- graph_area: Rect,
-}
-
-/// A widget to plot one or more dataset in a cartesian coordinate system
-///
-/// # Examples
-///
-/// ```
-/// # use ratatui::symbols;
-/// # use ratatui::widgets::{Block, Borders, Chart, Axis, Dataset, GraphType};
-/// # use ratatui::style::{Style, Color};
-/// # use ratatui::text::Span;
-/// let datasets = vec![
-/// Dataset::default()
-/// .name("data1")
-/// .marker(symbols::Marker::Dot)
-/// .graph_type(GraphType::Scatter)
-/// .style(Style::default().fg(Color::Cyan))
-/// .data(&[(0.0, 5.0), (1.0, 6.0), (1.5, 6.434)]),
-/// Dataset::default()
-/// .name("data2")
-/// .marker(symbols::Marker::Braille)
-/// .graph_type(GraphType::Line)
-/// .style(Style::default().fg(Color::Magenta))
-/// .data(&[(4.0, 5.0), (5.0, 8.0), (7.66, 13.5)]),
-/// ];
-/// Chart::new(datasets)
-/// .block(Block::default().title("Chart"))
-/// .x_axis(Axis::default()
-/// .title(Span::styled("X Axis", Style::default().fg(Color::Red)))
-/// .style(Style::default().fg(Color::White))
-/// .bounds([0.0, 10.0])
-/// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect()))
-/// .y_axis(Axis::default()
-/// .title(Span::styled("Y Axis", Style::default().fg(Color::Red)))
-/// .style(Style::default().fg(Color::White))
-/// .bounds([0.0, 10.0])
-/// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect()));
-/// ```
-#[derive(Debug, Clone)]
-pub struct Chart<'a> {
- /// A block to display around the widget eventually
- block: Option<Block<'a>>,
- /// The horizontal axis
- x_axis: Axis<'a>,
- /// The vertical axis
- y_axis: Axis<'a>,
- /// A reference to the datasets
- datasets: Vec<Dataset<'a>>,
- /// The widget base style
- style: Style,
- /// Constraints used to determine whether the legend should be shown or not
- hidden_legend_constraints: (Constraint, Constraint),
-}
-
-impl<'a> Chart<'a> {
- pub fn new(datasets: Vec<Dataset<'a>>) -> Chart<'a> {
- Chart {
- block: None,
- x_axis: Axis::default(),
- y_axis: Axis::default(),
- style: Default::default(),
- datasets,
- hidden_legend_constraints: (Constraint::Ratio(1, 4), Constraint::Ratio(1, 4)),
- }
- }
-
- pub fn block(mut self, block: Block<'a>) -> Chart<'a> {
- self.block = Some(block);
- self
- }
-
- pub fn style(mut self, style: Style) -> Chart<'a> {
- self.style = style;
- self
- }
-
- pub fn x_axis(mut self, axis: Axis<'a>) -> Chart<'a> {
- self.x_axis = axis;
- self
- }
-
- pub fn y_axis(mut self, axis: Axis<'a>) -> Chart<'a> {
- self.y_axis = axis;
- self
- }
-
- /// Set the constraints used to determine whether the legend should be shown or not.
- ///
- /// # Examples
- ///
- /// ```
- /// # use ratatui::widgets::Chart;
- /// # use ratatui::layout::Constraint;
- /// let constraints = (
- /// Constraint::Ratio(1, 3),
- /// Constraint::Ratio(1, 4)
- /// );
- /// // Hide the legend when either its width is greater than 33% of the total widget width
- /// // or if its height is greater than 25% of the total widget height.
- /// let _chart: Chart = Chart::new(vec![])
- /// .hidden_legend_constraints(constraints);
- /// ```
- pub fn hidden_legend_constraints(mut self, constraints: (Constraint, Constraint)) -> Chart<'a> {
- self.hidden_legend_constraints = constraints;
- self
- }
-
- /// Compute the internal layout of the chart given the area. If the area is too small some
- /// elements may be automatically hidden
- fn layout(&self, area: Rect) -> ChartLayout {
- let mut layout = ChartLayout::default();
- if area.height == 0 || area.width == 0 {
- return layout;
- }
- let mut x = area.left();
- let mut y = area.bottom() - 1;
-
- if self.x_axis.labels.is_some() && y > area.top() {
- layout.label_x = Some(y);
- y -= 1;
- }
-
- layout.label_y = self.y_axis.labels.as_ref().and(Some(x));
- x += self.max_width_of_labels_left_of_y_axis(area, self.y_axis.labels.is_some());
-
- if self.x_axis.labels.is_some() && y > area.top() {
- layout.axis_x = Some(y);
- y -= 1;
- }
-
- if self.y_axis.labels.is_some() && x + 1 < area.right() {
- layout.axis_y = Some(x);
- x += 1;
- }
-
- if x < area.right() && y > 1 {
- layout.graph_area = Rect::new(x, area.top(), area.right() - x, y - area.top() + 1);
- }
-
- if let Some(ref title) = self.x_axis.title {
- let w = title.width() as u16;
- if w < layout.graph_area.width && layout.graph_area.height > 2 {
- layout.title_x = Some((x + layout.graph_area.width - w, y));
- }
- }
-
- if let Some(ref title) = self.y_axis.title {
- let w = title.width() as u16;
- if w + 1 < layout.graph_area.width && layout.graph_area.height > 2 {
- layout.title_y = Some((x, area.top()));
- }
- }
-
- if let Some(inner_width) = self.datasets.iter().map(|d| d.name.width() as u16).max() {
- let legend_width = inner_width + 2;
- let legend_height = self.datasets.len() as u16 + 2;
- let max_legend_width = self
- .hidden_legend_constraints
- .0
- .apply(layout.graph_area.width);
- let max_legend_height = self
- .hidden_legend_constraints
- .1
- .apply(layout.graph_area.height);
- if inner_width > 0
- && legend_width < max_legend_width
- && legend_height < max_legend_height
- {
- layout.legend_area = Some(Rect::new(
- layout.graph_area.right() - legend_width,
- layout.graph_area.top(),
- legend_width,
- legend_height,
- ));
- }
- }
- layout
- }
-
- fn max_width_of_labels_left_of_y_axis(&self, area: Rect, has_y_axis: bool) -> u16 {
- let mut max_width = self
- .y_axis
- .labels
- .as_ref()
- .map(|l| l.iter().map(Span::width).max().unwrap_or_default() as u16)
- .unwrap_or_default();
-
- if let Some(first_x_label) = self.x_axis.labels.as_ref().and_then(|labels| labels.get(0)) {
- let first_label_width = first_x_label.content.width() as u16;
- let width_left_of_y_axis = match self.x_axis.labels_alignment {
- Alignment::Left => {
- // The last character of the label should be below the Y-Axis when it exists, not on its left
- let y_axis_offset = if has_y_axis { 1 } else { 0 };
- first_label_width.saturating_sub(y_axis_offset)
- }
- Alignment::Center => first_label_width / 2,
- Alignment::Right => 0,
- };
- max_width = max(max_width, width_left_of_y_axis);
- }
- // labels of y axis and first label of x axis can take at most 1/3rd of the total width
- max_width.min(area.width / 3)
- }
-
- fn render_x_labels(
- &mut self,
- buf: &mut Buffer,
- layout: &ChartLayout,
- chart_area: Rect,
- graph_area: Rect,
- ) {
- let y = match layout.label_x {
- Some(y) => y,
- None => return,
- };
- let labels = self.x_axis.labels.as_ref().unwrap();
- let labels_len = labels.len() as u16;
- if labels_len < 2 {
- return;
- }
-
- let width_between_ticks = graph_area.width / labels_len;
-
- let label_area = self.first_x_label_area(
- y,
- labels.first().unwrap().width() as u16,
- width_between_ticks,
- chart_area,
- graph_area,
- );
-
- let label_alignment = match self.x_axis.labels_alignment {
- Alignment::Left => Alignment::Right,
- Alignment::Center => Alignment::Center,
- Alignment::Right => Alignment::Left,
- };
-
- Self::render_label(buf, labels.first().unwrap(), label_area, label_alignment);
-
- for (i, label) in labels[1..labels.len() - 1].iter().enumerate() {
- // We add 1 to x (and width-1 below) to leave at least one space before each intermediate labels
- let x = graph_area.left() + (i + 1) as u16 * width_between_ticks + 1;
- let label_area = Rect::new(x, y, width_between_ticks.saturating_sub(1), 1);
-
- Self::render_label(buf, label, label_area, Alignment::Center);
- }
-
- let x = graph_area.right() - width_between_ticks;
- let label_area = Rect::new(x, y, width_between_ticks, 1);
- // The last label should be aligned Right to be at the edge of the graph area
- Self::render_label(buf, labels.last().unwrap(), label_area, Alignment::Right);
- }
-
- fn first_x_label_area(
- &self,
- y: u16,
- label_width: u16,
- max_width_after_y_axis: u16,
- chart_area: Rect,
- graph_area: Rect,
- ) -> Rect {
- let (min_x, max_x) = match self.x_axis.labels_alignment {
- Alignment::Left => (chart_area.left(), graph_area.left()),
- Alignment::Center => (
- chart_area.left(),
- graph_area.left() + max_width_after_y_axis.min(label_width),
- ),
- Alignment::Right => (
- graph_area.left().saturating_sub(1),
- graph_area.left() + max_width_after_y_axis,
- ),
- };
-
- Rect::new(min_x, y, max_x - min_x, 1)
- }
-
- fn render_label(buf: &mut Buffer, label: &Span, label_area: Rect, alignment: Alignment) {
- let label_width = label.width() as u16;
- let bounded_label_width = label_area.width.min(label_width);
-
- let x = match alignment {
- Alignment::Left => label_area.left(),
- Alignment::Center => label_area.left() + label_area.width / 2 - bounded_label_width / 2,
- Alignment::Right => label_area.right() - bounded_label_width,
- };
-
- buf.set_span(x, label_area.top(), label, bounded_label_width);
- }
-
- fn render_y_labels(
- &mut self,
- buf: &mut Buffer,
- layout: &ChartLayout,
- chart_area: Rect,
- graph_area: Rect,
- ) {
- let x = match layout.label_y {
- Some(x) => x,
- None => return,
- };
- let labels = self.y_axis.labels.as_ref().unwrap();
- let labels_len = labels.len() as u16;
- for (i, label) in labels.iter().enumerate() {
- let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1);
- if dy < graph_area.bottom() {
- let label_area = Rect::new(
- x,
- graph_area.bottom().saturating_sub(1) - dy,
- (graph_area.left() - chart_area.left()).saturating_sub(1),
- 1,
- );
- Self::render_label(buf, label, label_area, self.y_axis.labels_alignment);
- }
- }
- }
-}
-
-impl<'a> Widget for Chart<'a> {
- fn render(mut self, area: Rect, buf: &mut Buffer) {
- if area.area() == 0 {
- return;
- }
- buf.set_style(area, self.style);
- // Sample the style of the entire widget. This sample will be used to reset the style of
- // the cells that are part of the components put on top of the grah area (i.e legend and
- // axis names).
- let original_style = buf.get(area.left(), area.top()).style();
-
- let chart_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- let layout = self.layout(chart_area);
- let graph_area = layout.graph_area;
- if graph_area.width < 1 || graph_area.height < 1 {
- return;
- }
-
- self.render_x_labels(buf, &layout, chart_area, graph_area);
- self.render_y_labels(buf, &layout, chart_area, graph_area);
-
- if let Some(y) = layout.axis_x {
- for x in graph_area.left()..graph_area.right() {
- buf.get_mut(x, y)
- .set_symbol(symbols::line::HORIZONTAL)
- .set_style(self.x_axis.style);
- }
- }
-
- if let Some(x) = layout.axis_y {
- for y in graph_area.top()..graph_area.bottom() {
- buf.get_mut(x, y)
- .set_symbol(symbols::line::VERTICAL)
- .set_style(self.y_axis.style);
- }
- }
-
- if let Some(y) = layout.axis_x {
- if let Some(x) = layout.axis_y {
- buf.get_mut(x, y)
- .set_symbol(symbols::line::BOTTOM_LEFT)
- .set_style(self.x_axis.style);
- }
- }
-
- for dataset in &self.datasets {
- Canvas::default()
- .background_color(self.style.bg.unwrap_or(Color::Reset))
- .x_bounds(self.x_axis.bounds)
- .y_bounds(self.y_axis.bounds)
- .marker(dataset.marker)
- .paint(|ctx| {
- ctx.draw(&Points {
- coords: dataset.data,
- color: dataset.style.fg.unwrap_or(Color::Reset),
- });
- if let GraphType::Line = dataset.graph_type {
- for data in dataset.data.windows(2) {
- ctx.draw(&Line {
- x1: data[0].0,
- y1: data[0].1,
- x2: data[1].0,
- y2: data[1].1,
- color: dataset.style.fg.unwrap_or(Color::Reset),
- })
- }
- }
- })
- .render(graph_area, buf);
- }
-
- if let Some(legend_area) = layout.legend_area {
- buf.set_style(legend_area, original_style);
- Block::default()
- .borders(Borders::ALL)
- .render(legend_area, buf);
- for (i, dataset) in self.datasets.iter().enumerate() {
- buf.set_string(
- legend_area.x + 1,
- legend_area.y + 1 + i as u16,
- &dataset.name,
- dataset.style,
- );
- }
- }
-
- if let Some((x, y)) = layout.title_x {
- let title = self.x_axis.title.unwrap();
- let width = graph_area.right().saturating_sub(x);
- buf.set_style(
- Rect {
- x,
- y,
- width,
- height: 1,
- },
- original_style,
- );
- buf.set_spans(x, y, &title, width);
- }
-
- if let Some((x, y)) = layout.title_y {
- let title = self.y_axis.title.unwrap();
- let width = graph_area.right().saturating_sub(x);
- buf.set_style(
- Rect {
- x,
- y,
- width,
- height: 1,
- },
- original_style,
- );
- buf.set_spans(x, y, &title, width);
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- struct LegendTestCase {
- chart_area: Rect,
- hidden_legend_constraints: (Constraint, Constraint),
- legend_area: Option<Rect>,
- }
-
- #[test]
- fn it_should_hide_the_legend() {
- let data = [(0.0, 5.0), (1.0, 6.0), (3.0, 7.0)];
- let cases = [
- LegendTestCase {
- chart_area: Rect::new(0, 0, 100, 100),
- hidden_legend_constraints: (Constraint::Ratio(1, 4), Constraint::Ratio(1, 4)),
- legend_area: Some(Rect::new(88, 0, 12, 12)),
- },
- LegendTestCase {
- chart_area: Rect::new(0, 0, 100, 100),
- hidden_legend_constraints: (Constraint::Ratio(1, 10), Constraint::Ratio(1, 4)),
- legend_area: None,
- },
- ];
- for case in &cases {
- let datasets = (0..10)
- .map(|i| {
- let name = format!("Dataset #{}", i);
- Dataset::default().name(name).data(&data)
- })
- .collect::<Vec<_>>();
- let chart = Chart::new(datasets)
- .x_axis(Axis::default().title("X axis"))
- .y_axis(Axis::default().title("Y axis"))
- .hidden_legend_constraints(case.hidden_legend_constraints);
- let layout = chart.layout(case.chart_area);
- assert_eq!(layout.legend_area, case.legend_area);
- }
- }
-}
diff --git a/src/ratatui/widgets/clear.rs b/src/ratatui/widgets/clear.rs
deleted file mode 100644
index a7c8d34b..00000000
--- a/src/ratatui/widgets/clear.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-use crate::ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
-
-/// A widget to clear/reset a certain area to allow overdrawing (e.g. for popups).
-///
-/// This widget **cannot be used to clear the terminal on the first render** as `ratatui` assumes the
-/// render area is empty. Use [`crate::Terminal::clear`] instead.
-///
-/// # Examples
-///
-/// ```
-/// # use ratatui::widgets::{Clear, Block, Borders};
-/// # use ratatui::layout::Rect;
-/// # use ratatui::Frame;
-/// # use ratatui::backend::Backend;
-/// fn draw_on_clear<B: Backend>(f: &mut Frame<B>, area: Rect) {
-/// let block = Block::default().title("Block").borders(Borders::ALL);
-/// f.render_widget(Clear, area); // <- this will clear/reset the area first
-/// f.render_widget(block, area); // now render the block widget
-/// }
-/// ```
-///
-/// # Popup Example
-///
-/// For a more complete example how to utilize `Clear` to realize popups see
-/// the example `examples/popup.rs`
-#[derive(Debug, Clone)]
-pub struct Clear;
-
-impl Widget for Clear {
- fn render(self, area: Rect, buf: &mut Buffer) {
- for x in area.left()..area.right() {
- for y in area.top()..area.bottom() {
- buf.get_mut(x, y).reset();
- }
- }
- }
-}
diff --git a/src/ratatui/widgets/gauge.rs b/src/ratatui/widgets/gauge.rs
deleted file mode 100644
index 6b53b975..00000000
--- a/src/ratatui/widgets/gauge.rs
+++ /dev/null
@@ -1,313 +0,0 @@
-use crate::ratatui::{
- buffer::Buffer,
- layout::Rect,
- style::{Color, Style},
- symbols,
- text::{Span, Spans},
- widgets::{Block, Widget},
-};
-
-/// A widget to display a task progress.
-///
-/// # Examples:
-///
-/// ```
-/// # use ratatui::widgets::{Widget, Gauge, Block, Borders};
-/// # use ratatui::style::{Style, Color, Modifier};
-/// Gauge::default()
-/// .block(Block::default().borders(Borders::ALL).title("Progress"))
-/// .gauge_style(Style::default().fg(Color::White).bg(Color::Black).add_modifier(Modifier::ITALIC))
-/// .percent(20);
-/// ```
-#[derive(Debug, Clone)]
-pub struct Gauge<'a> {
- block: Option<Block<'a>>,
- ratio: f64,
- label: Option<Span<'a>>,
- use_unicode: bool,
- style: Style,
- gauge_style: Style,
-}
-
-impl<'a> Default for Gauge<'a> {
- fn default() -> Gauge<'a> {
- Gauge {
- block: None,
- ratio: 0.0,
- label: None,
- use_unicode: false,
- style: Style::default(),
- gauge_style: Style::default(),
- }
- }
-}
-
-impl<'a> Gauge<'a> {
- pub fn block(mut self, block: Block<'a>) -> Gauge<'a> {
- self.block = Some(block);
- self
- }
-
- pub fn percent(mut self, percent: u16) -> Gauge<'a> {
- assert!(
- percent <= 100,
- "Percentage should be between 0 and 100 inclusively."
- );
- self.ratio = f64::from(percent) / 100.0;
- self
- }
-
- /// Sets ratio ([0.0, 1.0]) directly.
- pub fn ratio(mut self, ratio: f64) -> Gauge<'a> {
- assert!(
- (0.0..=1.0).contains(&ratio),
- "Ratio should be between 0 and 1 inclusively."
- );
- self.ratio = ratio;
- self
- }
-
- pub fn label<T>(mut self, label: T) -> Gauge<'a>
- where
- T: Into<Span<'a>>,
- {
- self.label = Some(label.into());
- self
- }
-
- pub fn style(mut self, style: Style) -> Gauge<'a> {
- self.style = style;
- self
- }
-
- pub fn gauge_style(mut self, style: Style) -> Gauge<'a> {
- self.gauge_style = style;
- self
- }
-
- pub fn use_unicode(mut self, unicode: bool) -> Gauge<'a> {
- self.use_unicode = unicode;
- self
- }
-}
-
-impl<'a> Widget for Gauge<'a> {
- fn render(mut self, area: Rect, buf: &mut Buffer) {
- buf.set_style(area, self.style);
- let gauge_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
- buf.set_style(gauge_area, self.gauge_style);
- if gauge_area.height < 1 {
- return;
- }
-
- // compute label value and its position
- // label is put at the center of the gauge_area
- let label = {
- let pct = f64::round(self.ratio * 100.0);
- self.label
- .unwrap_or_else(|| Span::from(format!("{}%", pct)))
- };
- let clamped_label_width = gauge_area.width.min(label.width() as u16);
- let label_col = gauge_area.left() + (gauge_area.width - clamped_label_width) / 2;
- let label_row = gauge_area.top() + gauge_area.height / 2;
-
- // the gauge will be filled proportionally to the ratio
- let filled_width = f64::from(gauge_area.width) * self.ratio;
- let end = if self.use_unicode {
- gauge_area.left() + filled_width.floor() as u16
- } else {
- gauge_area.left() + filled_width.round() as u16
- };
- for y in gauge_area.top()..gauge_area.bottom() {
- // render the filled area (left to end)
- for x in gauge_area.left()..end {
- // spaces are needed to apply the background styling
- buf.get_mut(x, y)
- .set_symbol(" ")
- .set_fg(self.gauge_style.bg.unwrap_or(Color::Reset))
- .set_bg(self.gauge_style.fg.unwrap_or(Color::Reset));
- }
- if self.use_unicode && self.ratio < 1.0 {
- buf.get_mut(end, y)
- .set_symbol(get_unicode_block(filled_width % 1.0));
- }
- }
- // set the span
- buf.set_span(label_col, label_row, &label, clamped_label_width);
- }
-}
-
-fn get_unicode_block<'a>(frac: f64) -> &'a str {
- match (frac * 8.0).round() as u16 {
- 1 => symbols::block::ONE_EIGHTH,
- 2 => symbols::block::ONE_QUARTER,
- 3 => symbols::block::THREE_EIGHTHS,
- 4 => symbols::block::HALF,
- 5 => symbols::block::FIVE_EIGHTHS,
- 6 => symbols::block::THREE_QUARTERS,
- 7 => symbols::block::SEVEN_EIGHTHS,
- 8 => symbols::block::FULL,
- _ => " ",
- }
-}
-
-/// A compact widget to display a task progress over a single line.
-///
-/// # Examples:
-///
-/// ```
-/// # use ratatui::widgets::{Widget, LineGauge, Block, Borders};
-/// # use ratatui::style::{Style, Color, Modifier};
-/// # use ratatui::symbols;
-/// LineGauge::default()
-/// .block(Block::default().borders(Borders::ALL).title("Progress"))
-/// .gauge_style(Style::default().fg(Color::White).bg(Color::Black).add_modifier(Modifier::BOLD))
-/// .line_set(symbols::line::THICK)
-/// .ratio(0.4);
-/// ```
-pub struct LineGauge<'a> {
- block: Option<Block<'a>>,
- ratio: f64,
- label: Option<Spans<'a>>,
- line_set: symbols::line::Set,
- style: Style,
- gauge_style: Style,
-}
-
-impl<'a> Default for LineGauge<'a> {
- fn default() -> Self {
- Self {
- block: None,
- ratio: 0.0,
- label: None,
- style: Style::default(),
- line_set: symbols::line::NORMAL,
- gauge_style: Style::default(),
- }
- }
-}
-
-impl<'a> LineGauge<'a> {
- pub fn block(mut self, block: Block<'a>) -> Self {
- self.block = Some(block);
- self
- }
-
- pub fn ratio(mut self, ratio: f64) -> Self {
- assert!(
- (0.0..=1.0).contains(&ratio),
- "Ratio should be between 0 and 1 inclusively."
- );
- self.ratio = ratio;
- self
- }
-
- pub fn line_set(mut self, set: symbols::line::Set) -> Self {
- self.line_set = set;
- self
- }
-
- pub fn label<T>(mut self, label: T) -> Self
- where
- T: Into<Spans<'a>>,
- {
- self.label = Some(label.into());
- self
- }
-
- pub fn style(mut self, style: Style) -> Self {
- self.style = style;
- self
- }
-
- pub fn gauge_style(mut self, style: Style) -> Self {
- self.gauge_style = style;
- self
- }
-}
-
-impl<'a> Widget for LineGauge<'a> {
- fn render(mut self, area: Rect, buf: &mut Buffer) {
- buf.set_style(area, self.style);
- let gauge_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- if gauge_area.height < 1 {
- return;
- }
-
- let ratio = self.ratio;
- let label = self
- .label
- .unwrap_or_else(move || Spans::from(format!("{:.0}%", ratio * 100.0)));
- let (col, row) = buf.set_spans(
- gauge_area.left(),
- gauge_area.top(),
- &label,
- gauge_area.width,
- );
- let start = col + 1;
- if start >= gauge_area.right() {
- return;
- }
-
- let end = start
- + (f64::from(gauge_area.right().saturating_sub(start)) * self.ratio).floor() as u16;
- for col in start..end {
- buf.get_mut(col, row)
- .set_symbol(self.line_set.horizontal)
- .set_style(Style {
- fg: self.gauge_style.fg,
- bg: None,
- add_modifier: self.gauge_style.add_modifier,
- sub_modifier: self.gauge_style.sub_modifier,
- });
- }
- for col in end..gauge_area.right() {
- buf.get_mut(col, row)
- .set_symbol(self.line_set.horizontal)
- .set_style(Style {
- fg: self.gauge_style.bg,
- bg: None,
- add_modifier: self.gauge_style.add_modifier,
- sub_modifier: self.gauge_style.sub_modifier,
- });
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- #[should_panic]
- fn gauge_invalid_percentage() {
- Gauge::default().percent(110);
- }
-
- #[test]
- #[should_panic]
- fn gauge_invalid_ratio_upper_bound() {
- Gauge::default().ratio(1.1);
- }
-
- #[test]
- #[should_panic]
- fn gauge_invalid_ratio_lower_bound() {
- Gauge::default().ratio(-0.5);
- }
-}
diff --git a/src/ratatui/widgets/list.rs b/src/ratatui/widgets/list.rs
deleted file mode 100644
index 9608d299..00000000
--- a/src/ratatui/widgets/list.rs
+++ /dev/null
@@ -1,268 +0,0 @@
-use crate::ratatui::{
- buffer::Buffer,
- layout::{Corner, Rect},
- style::Style,
- text::Text,
- widgets::{Block, StatefulWidget, Widget},
-};
-use unicode_width::UnicodeWidthStr;
-
-#[derive(Debug, Clone, Default)]
-pub struct ListState {
- offset: usize,
- selected: Option<usize>,
-}
-
-impl ListState {
- pub fn selected(&self) -> Option<usize> {
- self.selected
- }
-
- pub fn select(&mut self, index: Option<usize>) {
- self.selected = index;
- if index.is_none() {
- self.offset = 0;
- }
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ListItem<'a> {
- content: Text<'a>,
- style: Style,
-}
-
-impl<'a> ListItem<'a> {
- pub fn new<T>(content: T) -> ListItem<'a>
- where
- T: Into<Text<'a>>,
- {
- ListItem {
- content: content.into(),
- style: Style::default(),
- }
- }
-
- pub fn style(mut self, style: Style) -> ListItem<'a> {
- self.style = style;
- self
- }
-
- pub fn height(&self) -> usize {
- self.content.height()
- }
-
- pub fn width(&self) -> usize {
- self.content.width()
- }
-}
-
-/// A widget to display several items among which one can be selected (optional)
-///
-/// # Examples
-///
-/// ```
-/// # use ratatui::widgets::{Block, Borders, List, ListItem};
-/// # use ratatui::style::{Style, Color, Modifier};
-/// let items = [ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3")];
-/// List::new(items)
-/// .block(Block::default().title("List").borders(Borders::ALL))
-/// .style(Style::default().fg(Color::White))
-/// .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
-/// .highlight_symbol(">>");
-/// ```
-#[derive(Debug, Clone)]
-pub struct List<'a> {
- block: Option<Block<'a>>,
- items: Vec<ListItem<'a>>,
- /// Style used as a base style for the widget
- style: Style,
- start_corner: Corner,
- /// Style used to render selected item
- highlight_style: Style,
- /// Symbol in front of the selected item (Shift all items to the right)
- highlight_symbol: Option<&'a str>,
- /// Whether to repeat the highlight symbol for each line of the selected item
- repeat_highlight_symbol: bool,
-}
-
-impl<'a> List<'a> {
- pub fn new<T>(items: T) -> List<'a>
- where
- T: Into<Vec<ListItem<'a>>>,
- {
- List {
- block: None,
- style: Style::default(),
- items: items.into(),
- start_corner: Corner::TopLeft,
- highlight_style: Style::default(),
- highlight_symbol: None,
- repeat_highlight_symbol: false,
- }
- }
-
- pub fn block(mut self, block: Block<'a>) -> List<'a> {
- self.block = Some(block);
- self
- }
-
- pub fn style(mut self, style: Style) -> List<'a> {
- self.style = style;
- self
- }
-
- pub fn highlight_symbol(mut self, highlight_symbol: &'a str) -> List<'a> {
- self.highlight_symbol = Some(highlight_symbol);
- self
- }
-
- pub fn highlight_style(mut self, style: Style) -> List<'a> {
- self.highlight_style = style;
- self
- }
-
- pub fn repeat_highlight_symbol(mut self, repeat: bool) -> List<'a> {
- self.repeat_highlight_symbol = repeat;
- self
- }
-
- pub fn start_corner(mut self, corner: Corner) -> List<'a> {
- self.start_corner = corner;
- self
- }
-
- fn get_items_bounds(
- &self,
- selected: Option<usize>,
- offset: usize,
- max_height: usize,
- ) -> (usize, usize) {
- let offset = offset.min(self.items.len().saturating_sub(1));
- let mut start = offset;
- let mut end = offset;
- let mut height = 0;
- for item in self.items.iter().skip(offset) {
- if height + item.height() > max_height {
- break;
- }
- height += item.height();
- end += 1;
- }
-
- let selected = selected.unwrap_or(0).min(self.items.len() - 1);
- while selected >= end {
- height = height.saturating_add(self.items[end].height());
- end += 1;
- while height > max_height {
- height = height.saturating_sub(self.items[start].height());
- start += 1;
- }
- }
- while selected < start {
- start -= 1;
- height = height.saturating_add(self.items[start].height());
- while height > max_height {
- end -= 1;
- height = height.saturating_sub(self.items[end].height());
- }
- }
- (start, end)
- }
-}
-
-impl<'a> StatefulWidget for List<'a> {
- type State = ListState;
-
- fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
- buf.set_style(area, self.style);
- let list_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- if list_area.width < 1 || list_area.height < 1 {
- return;
- }
-
- if self.items.is_empty() {
- return;
- }
- let list_height = list_area.height as usize;
-
- let (start, end) = self.get_items_bounds(state.selected, state.offset, list_height);
- state.offset = start;
-
- let highlight_symbol = self.highlight_symbol.unwrap_or("");
- let blank_symbol = " ".repeat(highlight_symbol.width());
-
- let mut current_height = 0;
- let has_selection = state.selected.is_some();
- for (i, item) in self
- .items
- .iter_mut()
- .enumerate()
- .skip(state.offset)
- .take(end - start)
- {
- let (x, y) = match self.start_corner {
- Corner::BottomLeft => {
- current_height += item.height() as u16;
- (list_area.left(), list_area.bottom() - current_height)
- }
- _ => {
- let pos = (list_area.left(), list_area.top() + current_height);
- current_height += item.height() as u16;
- pos
- }
- };
- let area = Rect {
- x,
- y,
- width: list_area.width,
- height: item.height() as u16,
- };
- let item_style = self.style.patch(item.style);
- buf.set_style(area, item_style);
-
- let is_selected = state.selected.map(|s| s == i).unwrap_or(false);
- for (j, line) in item.content.lines.iter().enumerate() {
- // if the item is selected, we need to display the highlight symbol:
- // - either for the first line of the item only,
- // - or for each line of the item if the appropriate option is set
- let symbol = if is_selected && (j == 0 || self.repeat_highlight_symbol) {
- highlight_symbol
- } else {
- &blank_symbol
- };
- let (elem_x, max_element_width) = if has_selection {
- let (elem_x, _) = buf.set_stringn(
- x,
- y + j as u16,
- symbol,
- list_area.width as usize,
- item_style,
- );
- (elem_x, (list_area.width - (elem_x - x)))
- } else {
- (x, list_area.width)
- };
- buf.set_spans(elem_x, y + j as u16, line, max_element_width);
- }
- if is_selected {
- buf.set_style(area, self.highlight_style);
- }
- }
- }
-}
-
-impl<'a> Widget for List<'a> {
- fn render(self, area: Rect, buf: &mut Buffer) {
- let mut state = ListState::default();
- StatefulWidget::render(self, area, buf, &mut state);
- }
-}
diff --git a/src/ratatui/widgets/mod.rs b/src/ratatui/widgets/mod.rs
deleted file mode 100644
index 5a6167b8..00000000
--- a/src/ratatui/widgets/mod.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-//! `widgets` is a collection of types that implement [`Widget`] or [`StatefulWidget`] or both.
-//!
-//! All widgets are implemented using the builder pattern and are consumable objects. They are not
-//! meant to be stored but used as *commands* to draw common figures in the UI.
-//!
-//! The available widgets are:
-//! - [`Block`]
-//! - [`Tabs`]
-//! - [`List`]
-//! - [`Table`]
-//! - [`Paragraph`]
-//! - [`Chart`]
-//! - [`BarChart`]
-//! - [`Gauge`]
-//! - [`Sparkline`]
-//! - [`Clear`]
-
-mod barchart;
-mod block;
-pub mod canvas;
-mod chart;
-mod clear;
-mod gauge;
-mod list;
-mod paragraph;
-mod reflow;
-mod sparkline;
-mod table;
-mod tabs;
-
-pub use self::barchart::BarChart;
-pub use self::block::{Block, BorderType};
-pub use self::chart::{Axis, Chart, Dataset, GraphType};
-pub use self::clear::Clear;
-pub use self::gauge::{Gauge, LineGauge};
-pub use self::list::{List, ListItem, ListState};
-pub use self::paragraph::{Paragraph, Wrap};
-pub use self::sparkline::Sparkline;
-pub use self::table::{Cell, Row, Table, TableState};
-pub use self::tabs::Tabs;
-
-use crate::ratatui::{buffer::Buffer, layout::Rect};
-use bitflags::bitflags;
-
-bitflags! {
- /// Bitflags that can be composed to set the visible borders essentially on the block widget.
- pub struct Borders: u8 {
- /// Show no border (default)
- const NONE = 0b0000;
- /// Show the top border
- const TOP = 0b0001;
- /// Show the right border
- const RIGHT = 0b0010;
- /// Show the bottom border
- const BOTTOM = 0b0100;
- /// Show the left border
- const LEFT = 0b1000;
- /// Show all borders
- const ALL = Self::TOP.bits | Self::RIGHT.bits | Self::BOTTOM.bits | Self::LEFT.bits;
- }
-}
-
-/// Base requirements for a Widget
-pub trait Widget {
- /// Draws the current state of the widget in the given buffer. That is the only method required
- /// to implement a custom widget.
- fn render(self, area: Rect, buf: &mut Buffer);
-}
-
-/// A `StatefulWidget` is a widget that can take advantage of some local state to remember things
-/// between two draw calls.
-///
-/// Most widgets can be drawn directly based on the input parameters. However, some features may
-/// require some kind of associated state to be implemented.
-///
-/// For example, the [`List`] widget can highlight the item currently selected. This can be
-/// translated in an offset, which is the number of elements to skip in order to have the selected
-/// item within the viewport currently allocated to this widget. The widget can therefore only
-/// provide the following behavior: whenever the selected item is out of the viewport scroll to a
-/// predefined position (making the selected item the last viewable item or the one in the middle
-/// for example). Nonetheless, if the widget has access to the last computed offset then it can
-/// implement a natural scrolling experience where the last offset is reused until the selected
-/// item is out of the viewport.
-///
-/// ## Examples
-///
-/// ```rust,no_run
-/// # use std::io;
-/// # use ratatui::Terminal;
-/// # use ratatui::backend::{Backend, TestBackend};
-/// # use ratatui::widgets::{Widget, List, ListItem, ListState};
-///
-/// // Let's say we have some events to display.
-/// struct Events {
-/// // `items` is the state managed by your application.
-/// items: Vec<String>,
-/// // `state` is the state that can be modified by the UI. It stores the index of the selected
-/// // item as well as the offset computed during the previous draw call (used to implement
-/// // natural scrolling).
-/// state: ListState
-/// }
-///
-/// impl Events {
-/// fn new(items: Vec<String>) -> Events {
-/// Events {
-/// items,
-/// state: ListState::default(),
-/// }
-/// }
-///
-/// pub fn set_items(&mut self, items: Vec<String>) {
-/// self.items = items;
-/// // We reset the state as the associated items have changed. This effectively reset
-/// // the selection as well as the stored offset.
-/// self.state = ListState::default();
-/// }
-///
-/// // Select the next item. This will not be reflected until the widget is drawn in the
-/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
-/// pub fn next(&mut self) {
-/// let i = match self.state.selected() {
-/// Some(i) => {
-/// if i >= self.items.len() - 1 {
-/// 0
-/// } else {
-/// i + 1
-/// }
-/// }
-/// None => 0,
-/// };
-/// self.state.select(Some(i));
-/// }
-///
-/// // Select the previous item. This will not be reflected until the widget is drawn in the
-/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
-/// pub fn previous(&mut self) {
-/// let i = match self.state.selected() {
-/// Some(i) => {
-/// if i == 0 {
-/// self.items.len() - 1
-/// } else {
-/// i - 1
-/// }
-/// }
-/// None => 0,
-/// };
-/// self.state.select(Some(i));
-/// }
-///
-/// // Unselect the currently selected item if any. The implementation of `ListState` makes
-/// // sure that the stored offset is also reset.
-/// pub fn unselect(&mut self) {
-/// self.state.select(None);
-/// }
-/// }
-///
-/// # let backend = TestBackend::new(5, 5);
-/// # let mut terminal = Terminal::new(backend).unwrap();
-///
-/// let mut events = Events::new(vec![
-/// String::from("Item 1"),
-/// String::from("Item 2")
-/// ]);
-///
-/// loop {
-/// terminal.draw(|f| {
-/// // The items managed by the application are transformed to something
-/// // that is understood by ratatui.
-/// let items: Vec<ListItem>= events.items.iter().map(|i| ListItem::new(i.as_ref())).collect();
-/// // The `List` widget is then built with those items.
-/// let list = List::new(items);
-/// // Finally the widget is rendered using the associated state. `events.state` is
-/// // effectively the only thing that we will "remember" from this draw call.
-/// f.render_stateful_widget(list, f.size(), &mut events.state);
-/// });
-///
-/// // In response to some input events or an external http request or whatever:
-/// events.next();
-/// }
-/// ```
-pub trait StatefulWidget {
- type State;
- fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
-}
diff --git a/src/ratatui/widgets/paragraph.rs b/src/ratatui/widgets/paragraph.rs
deleted file mode 100644
index 5edfe08e..00000000
--- a/src/ratatui/widgets/paragraph.rs
+++ /dev/null
@@ -1,214 +0,0 @@
-use crate::ratatui::{
- buffer::Buffer,
- layout::{Alignment, Rect},
- style::Style,
- text::{StyledGrapheme, Text},
- widgets::{
- reflow::{LineComposer, LineTruncator, WordWrapper},
- Block, Widget,
- },
-};
-use std::iter;
-use unicode_width::UnicodeWidthStr;
-
-fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
- match alignment {
- Alignment::Center => (text_area_width / 2).saturating_sub(line_width / 2),
- Alignment::Right => text_area_width.saturating_sub(line_width),
- Alignment::Left => 0,
- }
-}
-
-/// A widget to display some text.
-///
-/// # Examples
-///
-/// ```
-/// # use ratatui::text::{Text, Spans, Span};
-/// # use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
-/// # use ratatui::style::{Style, Color, Modifier};
-/// # use ratatui::layout::{Alignment};
-/// let text = vec![
-/// Spans::from(vec![
-/// Span::raw("First"),
-/// Span::styled("line",Style::default().add_modifier(Modifier::ITALIC)),
-/// Span::raw("."),
-/// ]),
-/// Spans::from(Span::styled("Second line", Style::default().fg(Color::Red))),
-/// ];
-/// Paragraph::new(text)
-/// .block(Block::default().title("Paragraph").borders(Borders::ALL))
-/// .style(Style::default().fg(Color::White).bg(Color::Black))
-/// .alignment(Alignment::Center)
-/// .wrap(Wrap { trim: true });
-/// ```
-#[derive(Debug, Clone)]
-pub struct Paragraph<'a> {
- /// A block to wrap the widget in
- block: Option<Block<'a>>,
- /// Widget style
- style: Style,
- /// How to wrap the text
- wrap: Option<Wrap>,
- /// The text to display
- text: Text<'a>,
- /// Scroll
- scroll: (u16, u16),
- /// Alignment of the text
- alignment: Alignment,
-}
-
-/// Describes how to wrap text across lines.
-///
-/// ## Examples
-///
-/// ```
-/// # use ratatui::widgets::{Paragraph, Wrap};
-/// # use ratatui::text::Text;
-/// let bullet_points = Text::from(r#"Some indented points:
-/// - First thing goes here and is long so that it wraps
-/// - Here is another point that is long enough to wrap"#);
-///
-/// // With leading spaces trimmed (window width of 30 chars):
-/// Paragraph::new(bullet_points.clone()).wrap(Wrap { trim: true });
-/// // Some indented points:
-/// // - First thing goes here and is
-/// // long so that it wraps
-/// // - Here is another point that
-/// // is long enough to wrap
-///
-/// // But without trimming, indentation is preserved:
-/// Paragraph::new(bullet_points).wrap(Wrap { trim: false });
-/// // Some indented points:
-/// // - First thing goes here
-/// // and is long so that it wraps
-/// // - Here is another point
-/// // that is long enough to wrap
-/// ```
-#[derive(Debug, Clone, Copy)]
-pub struct Wrap {
- /// Should leading whitespace be trimmed
- pub trim: bool,
-}
-
-impl<'a> Paragraph<'a> {
- pub fn new<T>(text: T) -> Paragraph<'a>
- where
- T: Into<Text<'a>>,
- {
- Paragraph {
- block: None,
- style: Default::default(),
- wrap: None,
- text: text.into(),
- scroll: (0, 0),
- alignment: Alignment::Left,
- }
- }
-
- pub fn block(mut self, block: Block<'a>) -> Paragraph<'a> {
- self.block = Some(block);
- self
- }
-
- pub fn style(mut self, style: Style) -> Paragraph<'a> {
- self.style = style;
- self
- }
-
- pub fn wrap(mut self, wrap: Wrap) -> Paragraph<'a> {
- self.wrap = Some(wrap);
- self
- }
-
- pub fn scroll(mut self, offset: (u16, u16)) -> Paragraph<'a> {
- self.scroll = offset;
- self
- }
-
- pub fn alignment(mut self, alignment: Alignment) -> Paragraph<'a> {
- self.alignment = alignment;
- self
- }
-}
-
-impl<'a> Widget for Paragraph<'a> {
- fn render(mut self, area: Rect, buf: &mut Buffer) {
- buf.set_style(area, self.style);
- let text_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- if text_area.height < 1 {
- return;
- }
-
- let style = self.style;
- let mut styled = self.text.lines.iter().flat_map(|spans| {
- spans
- .0
- .iter()
- .flat_map(|span| span.styled_graphemes(style))
- // Required given the way composers work but might be refactored out if we change
- // composers to operate on lines instead of a stream of graphemes.
- .chain(iter::once(StyledGrapheme {
- symbol: "\n",
- style: self.style,
- }))
- });
-
- let mut line_composer: Box<dyn LineComposer> = if let Some(Wrap { trim }) = self.wrap {
- Box::new(WordWrapper::new(&mut styled, text_area.width, trim))
- } else {
- let mut line_composer = Box::new(LineTruncator::new(&mut styled, text_area.width));
- if let Alignment::Left = self.alignment {
- line_composer.set_horizontal_offset(self.scroll.1);
- }
- line_composer
- };
- let mut y = 0;
- while let Some((current_line, current_line_width)) = line_composer.next_line() {
- if y >= self.scroll.0 {
- let mut x = get_line_offset(current_line_width, text_area.width, self.alignment);
- for StyledGrapheme { symbol, style } in current_line {
- let width = symbol.width();
- if width == 0 {
- continue;
- }
- buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll.0)
- .set_symbol(if symbol.is_empty() {
- // If the symbol is empty, the last char which rendered last time will
- // leave on the line. It's a quick fix.
- " "
- } else {
- symbol
- })
- .set_style(*style);
- x += width as u16;
- }
- }
- y += 1;
- if y >= text_area.height + self.scroll.0 {
- break;
- }
- }
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn zero_width_char_at_end_of_line() {
- let line = "foo\0";
- let paragraph = Paragraph::new(line);
- let mut buf = Buffer::with_lines(vec![line]);
- paragraph.render(*buf.area(), &mut buf);
- }
-}
diff --git a/src/ratatui/widgets/reflow.rs b/src/ratatui/widgets/reflow.rs
deleted file mode 100644
index 3806b33e..00000000
--- a/src/ratatui/widgets/reflow.rs
+++ /dev/null
@@ -1,534 +0,0 @@
-use crate::ratatui::text::StyledGrapheme;
-use unicode_segmentation::UnicodeSegmentation;
-use unicode_width::UnicodeWidthStr;
-
-const NBSP: &str = "\u{00a0}";
-
-/// A state machine to pack styled symbols into lines.
-/// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming
-/// iterators for that).
-pub trait LineComposer<'a> {
- fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)>;
-}
-
-/// A state machine that wraps lines on word boundaries.
-pub struct WordWrapper<'a, 'b> {
- symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
- max_line_width: u16,
- current_line: Vec<StyledGrapheme<'a>>,
- next_line: Vec<StyledGrapheme<'a>>,
- /// Removes the leading whitespace from lines
- trim: bool,
-}
-
-impl<'a, 'b> WordWrapper<'a, 'b> {
- pub fn new(
- symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
- max_line_width: u16,
- trim: bool,
- ) -> WordWrapper<'a, 'b> {
- WordWrapper {
- symbols,
- max_line_width,
- current_line: vec![],
- next_line: vec![],
- trim,
- }
- }
-}
-
-impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> {
- fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> {
- if self.max_line_width == 0 {
- return None;
- }
- std::mem::swap(&mut self.current_line, &mut self.next_line);
- self.next_line.truncate(0);
-
- let mut current_line_width = self
- .current_line
- .iter()
- .map(|StyledGrapheme { symbol, .. }| symbol.width() as u16)
- .sum();
-
- let mut symbols_to_last_word_end: usize = 0;
- let mut width_to_last_word_end: u16 = 0;
- let mut prev_whitespace = false;
- let mut symbols_exhausted = true;
- for StyledGrapheme { symbol, style } in &mut self.symbols {
- symbols_exhausted = false;
- let symbol_whitespace = symbol.chars().all(&char::is_whitespace) && symbol != NBSP;
-
- // Ignore characters wider that the total max width.
- if symbol.width() as u16 > self.max_line_width
- // Skip leading whitespace when trim is enabled.
- || self.trim && symbol_whitespace && symbol != "\n" && current_line_width == 0
- {
- continue;
- }
-
- // Break on newline and discard it.
- if symbol == "\n" {
- if prev_whitespace {
- current_line_width = width_to_last_word_end;
- self.current_line.truncate(symbols_to_last_word_end);
- }
- break;
- }
-
- // Mark the previous symbol as word end.
- if symbol_whitespace && !prev_whitespace {
- symbols_to_last_word_end = self.current_line.len();
- width_to_last_word_end = current_line_width;
- }
-
- self.current_line.push(StyledGrapheme { symbol, style });
- current_line_width += symbol.width() as u16;
-
- if current_line_width > self.max_line_width {
- // If there was no word break in the text, wrap at the end of the line.
- let (truncate_at, truncated_width) = if symbols_to_last_word_end != 0 {
- (symbols_to_last_word_end, width_to_last_word_end)
- } else {
- (self.current_line.len() - 1, self.max_line_width)
- };
-
- // Push the remainder to the next line but strip leading whitespace:
- {
- let remainder = &self.current_line[truncate_at..];
- if let Some(remainder_nonwhite) =
- remainder.iter().position(|StyledGrapheme { symbol, .. }| {
- !symbol.chars().all(&char::is_whitespace)
- })
- {
- self.next_line
- .extend_from_slice(&remainder[remainder_nonwhite..]);
- }
- }
- self.current_line.truncate(truncate_at);
- current_line_width = truncated_width;
- break;
- }
-
- prev_whitespace = symbol_whitespace;
- }
-
- // Even if the iterator is exhausted, pass the previous remainder.
- if symbols_exhausted && self.current_line.is_empty() {
- None
- } else {
- Some((&self.current_line[..], current_line_width))
- }
- }
-}
-
-/// A state machine that truncates overhanging lines.
-pub struct LineTruncator<'a, 'b> {
- symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
- max_line_width: u16,
- current_line: Vec<StyledGrapheme<'a>>,
- /// Record the offset to skip render
- horizontal_offset: u16,
-}
-
-impl<'a, 'b> LineTruncator<'a, 'b> {
- pub fn new(
- symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
- max_line_width: u16,
- ) -> LineTruncator<'a, 'b> {
- LineTruncator {
- symbols,
- max_line_width,
- horizontal_offset: 0,
- current_line: vec![],
- }
- }
-
- pub fn set_horizontal_offset(&mut self, horizontal_offset: u16) {
- self.horizontal_offset = horizontal_offset;
- }
-}
-
-impl<'a, 'b> LineComposer<'a> for LineTruncator<'a, 'b> {
- fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> {
- if self.max_line_width == 0 {
- return None;
- }
-
- self.current_line.truncate(0);
- let mut current_line_width = 0;
-
- let mut skip_rest = false;
- let mut symbols_exhausted = true;
- let mut horizontal_offset = self.horizontal_offset as usize;
- for StyledGrapheme { symbol, style } in &mut self.symbols {
- symbols_exhausted = false;
-
- // Ignore characters wider that the total max width.
- if symbol.width() as u16 > self.max_line_width {
- continue;
- }
-
- // Break on newline and discard it.
- if symbol == "\n" {
- break;
- }
-
- if current_line_width + symbol.width() as u16 > self.max_line_width {
- // Exhaust the remainder of the line.
- skip_rest = true;
- break;
- }
-
- let symbol = if horizontal_offset == 0 {
- symbol
- } else {
- let w = symbol.width();
- if w > horizontal_offset {
- let t = trim_offset(symbol, horizontal_offset);
- horizontal_offset = 0;
- t
- } else {
- horizontal_offset -= w;
- ""
- }
- };
- current_line_width += symbol.width() as u16;
- self.current_line.push(StyledGrapheme { symbol, style });
- }
-
- if skip_rest {
- for StyledGrapheme { symbol, .. } in &mut self.symbols {
- if symbol == "\n" {
- break;
- }
- }
- }
-
- if symbols_exhausted && self.current_line.is_empty() {
- None
- } else {
- Some((&self.current_line[..], current_line_width))
- }
- }
-}
-
-/// This function will return a str slice which start at specified offset.
-/// As src is a unicode str, start offset has to be calculated with each character.
-fn trim_offset(src: &str, mut offset: usize) -> &str {
- let mut start = 0;
- for c in UnicodeSegmentation::graphemes(src, true) {
- let w = c.width();
- if w <= offset {
- offset -= w;
- start += c.len();
- } else {
- break;
- }
- }
- &src[start..]
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
- use unicode_segmentation::UnicodeSegmentation;
-
- enum Composer {
- WordWrapper { trim: bool },
- LineTruncator,
- }
-
- fn run_composer(which: Composer, text: &str, text_area_width: u16) -> (Vec<String>, Vec<u16>) {
- let style = Default::default();
- let mut styled =
- UnicodeSegmentation::graphemes(text, true).map(|g| StyledGrapheme { symbol: g, style });
- let mut composer: Box<dyn LineComposer> = match which {
- Composer::WordWrapper { trim } => {
- Box::new(WordWrapper::new(&mut styled, text_area_width, trim))
- }
- Composer::LineTruncator => Box::new(LineTruncator::new(&mut styled, text_area_width)),
- };
- let mut lines = vec![];
- let mut widths = vec![];
- while let Some((styled, width)) = composer.next_line() {
- let line = styled
- .iter()
- .map(|StyledGrapheme { symbol, .. }| *symbol)
- .collect::<String>();
- assert!(width <= text_area_width);
- lines.push(line);
- widths.push(width);
- }
- (lines, widths)
- }
-
- #[test]
- fn line_composer_one_line() {
- let width = 40;
- for i in 1..width {
- let text = "a".repeat(i);
- let (word_wrapper, _) =
- run_composer(Composer::WordWrapper { trim: true }, &text, width as u16);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, &text, width as u16);
- let expected = vec![text];
- assert_eq!(word_wrapper, expected);
- assert_eq!(line_truncator, expected);
- }
- }
-
- #[test]
- fn line_composer_short_lines() {
- let width = 20;
- let text =
- "abcdefg\nhijklmno\npabcdefg\nhijklmn\nopabcdefghijk\nlmnopabcd\n\n\nefghijklmno";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
-
- let wrapped: Vec<&str> = text.split('\n').collect();
- assert_eq!(word_wrapper, wrapped);
- assert_eq!(line_truncator, wrapped);
- }
-
- #[test]
- fn line_composer_long_word() {
- let width = 20;
- let text = "abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmno";
- let (word_wrapper, _) =
- run_composer(Composer::WordWrapper { trim: true }, text, width as u16);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
-
- let wrapped = vec![
- &text[..width],
- &text[width..width * 2],
- &text[width * 2..width * 3],
- &text[width * 3..],
- ];
- assert_eq!(
- word_wrapper, wrapped,
- "WordWrapper should detect the line cannot be broken on word boundary and \
- break it at line width limit."
- );
- assert_eq!(line_truncator, vec![&text[..width]]);
- }
-
- #[test]
- fn line_composer_long_sentence() {
- let width = 20;
- let text =
- "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l m n o";
- let text_multi_space =
- "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l \
- m n o";
- let (word_wrapper_single_space, _) =
- run_composer(Composer::WordWrapper { trim: true }, text, width as u16);
- let (word_wrapper_multi_space, _) = run_composer(
- Composer::WordWrapper { trim: true },
- text_multi_space,
- width as u16,
- );
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
-
- let word_wrapped = vec![
- "abcd efghij",
- "klmnopabcd efgh",
- "ijklmnopabcdefg",
- "hijkl mnopab c d e f",
- "g h i j k l m n o",
- ];
- assert_eq!(word_wrapper_single_space, word_wrapped);
- assert_eq!(word_wrapper_multi_space, word_wrapped);
-
- assert_eq!(line_truncator, vec![&text[..width]]);
- }
-
- #[test]
- fn line_composer_zero_width() {
- let width = 0;
- let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab ";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
-
- let expected: Vec<&str> = Vec::new();
- assert_eq!(word_wrapper, expected);
- assert_eq!(line_truncator, expected);
- }
-
- #[test]
- fn line_composer_max_line_width_of_1() {
- let width = 1;
- let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab ";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
-
- let expected: Vec<&str> = UnicodeSegmentation::graphemes(text, true)
- .filter(|g| g.chars().any(|c| !c.is_whitespace()))
- .collect();
- assert_eq!(word_wrapper, expected);
- assert_eq!(line_truncator, vec!["a"]);
- }
-
- #[test]
- fn line_composer_max_line_width_of_1_double_width_characters() {
- let width = 1;
- let text = "コンピュータ上で文字を扱う場合、典型的には文字\naaaによる通信を行う場合にその\
- 両端点では、";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
- assert_eq!(word_wrapper, vec!["", "a", "a", "a"]);
- assert_eq!(line_truncator, vec!["", "a"]);
- }
-
- /// Tests WordWrapper with words some of which exceed line length and some not.
- #[test]
- fn line_composer_word_wrapper_mixed_length() {
- let width = 20;
- let text = "abcd efghij klmnopabcdefghijklmnopabcdefghijkl mnopab cdefghi j klmno";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- assert_eq!(
- word_wrapper,
- vec![
- "abcd efghij",
- "klmnopabcdefghijklmn",
- "opabcdefghijkl",
- "mnopab cdefghi j",
- "klmno",
- ]
- )
- }
-
- #[test]
- fn line_composer_double_width_chars() {
- let width = 20;
- let text = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点\
- では、";
- let (word_wrapper, word_wrapper_width) =
- run_composer(Composer::WordWrapper { trim: true }, text, width);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
- assert_eq!(line_truncator, vec!["コンピュータ上で文字"]);
- let wrapped = vec![
- "コンピュータ上で文字",
- "を扱う場合、典型的に",
- "は文字による通信を行",
- "う場合にその両端点で",
- "は、",
- ];
- assert_eq!(word_wrapper, wrapped);
- assert_eq!(word_wrapper_width, vec![width, width, width, width, 4]);
- }
-
- #[test]
- fn line_composer_leading_whitespace_removal() {
- let width = 20;
- let text = "AAAAAAAAAAAAAAAAAAAA AAA";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
- assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAAAAAAA", "AAA",]);
- assert_eq!(line_truncator, vec!["AAAAAAAAAAAAAAAAAAAA"]);
- }
-
- /// Tests truncation of leading whitespace.
- #[test]
- fn line_composer_lots_of_spaces() {
- let width = 20;
- let text = " ";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
- assert_eq!(word_wrapper, vec![""]);
- assert_eq!(line_truncator, vec![" "]);
- }
-
- /// Tests an input starting with a letter, followed by spaces - some of the behaviour is
- /// incidental.
- #[test]
- fn line_composer_char_plus_lots_of_spaces() {
- let width = 20;
- let text = "a ";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
- // What's happening below is: the first line gets consumed, trailing spaces discarded,
- // after 20 of which a word break occurs (probably shouldn't). The second line break
- // discards all whitespace. The result should probably be vec!["a"] but it doesn't matter
- // that much.
- assert_eq!(word_wrapper, vec!["a", ""]);
- assert_eq!(line_truncator, vec!["a "]);
- }
-
- #[test]
- fn line_composer_word_wrapper_double_width_chars_mixed_with_spaces() {
- let width = 20;
- // Japanese seems not to use spaces but we should break on spaces anyway... We're using it
- // to test double-width chars.
- // You are more than welcome to add word boundary detection based of alterations of
- // hiragana and katakana...
- // This happens to also be a test case for mixed width because regular spaces are single width.
- let text = "コンピュ ータ上で文字を扱う場合、 典型的には文 字による 通信を行 う場合にその両端点では、";
- let (word_wrapper, word_wrapper_width) =
- run_composer(Composer::WordWrapper { trim: true }, text, width);
- assert_eq!(
- word_wrapper,
- vec![
- "コンピュ",
- "ータ上で文字を扱う場",
- "合、 典型的には文",
- "字による 通信を行",
- "う場合にその両端点で",
- "は、",
- ]
- );
- // Odd-sized lines have a space in them.
- assert_eq!(word_wrapper_width, vec![8, 20, 17, 17, 20, 4]);
- }
-
- /// Ensure words separated by nbsp are wrapped as if they were a single one.
- #[test]
- fn line_composer_word_wrapper_nbsp() {
- let width = 20;
- let text = "AAAAAAAAAAAAAAA AAAA\u{00a0}AAA";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
- assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAA", "AAAA\u{00a0}AAA",]);
-
- // Ensure that if the character was a regular space, it would be wrapped differently.
- let text_space = text.replace('\u{00a0}', " ");
- let (word_wrapper_space, _) =
- run_composer(Composer::WordWrapper { trim: true }, &text_space, width);
- assert_eq!(word_wrapper_space, vec!["AAAAAAAAAAAAAAA AAAA", "AAA",]);
- }
-
- #[test]
- fn line_composer_word_wrapper_preserve_indentation() {
- let width = 20;
- let text = "AAAAAAAAAAAAAAAAAAAA AAA";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
- assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAAAAAAA", " AAA",]);
- }
-
- #[test]
- fn line_composer_word_wrapper_preserve_indentation_with_wrap() {
- let width = 10;
- let text = "AAA AAA AAAAA AA AAAAAA\n B\n C\n D";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
- assert_eq!(
- word_wrapper,
- vec!["AAA AAA", "AAAAA AA", "AAAAAA", " B", " C", " D"]
- );
- }
-
- #[test]
- fn line_composer_word_wrapper_preserve_indentation_lots_of_whitespace() {
- let width = 10;
- let text = " 4 Indent\n must wrap!";
- let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
- assert_eq!(
- word_wrapper,
- vec![
- " ",
- " 4",
- "Indent",
- " ",
- " must",
- "wrap!"
- ]
- );
- }
-}
diff --git a/src/ratatui/widgets/sparkline.rs b/src/ratatui/widgets/sparkline.rs
deleted file mode 100644
index a05df112..00000000
--- a/src/ratatui/widgets/sparkline.rs
+++ /dev/null
@@ -1,155 +0,0 @@
-use crate::ratatui::{
- buffer::Buffer,
- layout::Rect,
- style::Style,
- symbols,
- widgets::{Block, Widget},
-};
-use std::cmp::min;
-
-/// Widget to render a sparkline over one or more lines.
-///
-/// # Examples
-///
-/// ```
-/// # use ratatui::widgets::{Block, Borders, Sparkline};
-/// # use ratatui::style::{Style, Color};
-/// Sparkline::default()
-/// .block(Block::default().title("Sparkline").borders(Borders::ALL))
-/// .data(&[0, 2, 3, 4, 1, 4, 10])
-/// .max(5)
-/// .style(Style::default().fg(Color::Red).bg(Color::White));
-/// ```
-#[derive(Debug, Clone)]
-pub struct Sparkline<'a> {
- /// A block to wrap the widget in
- block: Option<Block<'a>>,
- /// Widget style
- style: Style,
- /// A slice of the data to display
- data: &'a [u64],
- /// The maximum value to take to compute the maximum bar height (if nothing is specified, the
- /// widget uses the max of the dataset)
- max: Option<u64>,
- /// A set of bar symbols used to represent the give data
- bar_set: symbols::bar::Set,
-}
-
-impl<'a> Default for Sparkline<'a> {
- fn default() -> Sparkline<'a> {
- Sparkline {
- block: None,
- style: Default::default(),
- data: &[],
- max: None,
- bar_set: symbols::bar::NINE_LEVELS,
- }
- }
-}
-
-impl<'a> Sparkline<'a> {
- pub fn block(mut self, block: Block<'a>) -> Sparkline<'a> {
- self.block = Some(block);
- self
- }
-
- pub fn style(mut self, style: Style) -> Sparkline<'a> {
- self.style = style;
- self
- }
-
- pub fn data(mut self, data: &'a [u64]) -> Sparkline<'a> {
- self.data = data;
- self
- }
-
- pub fn max(mut self, max: u64) -> Sparkline<'a> {
- self.max = Some(max);
- self
- }
-
- pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> Sparkline<'a> {
- self.bar_set = bar_set;
- self
- }
-}
-
-impl<'a> Widget for Sparkline<'a> {
- fn render(mut self, area: Rect, buf: &mut Buffer) {
- let spark_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- if spark_area.height < 1 {
- return;
- }
-
- let max = match self.max {
- Some(v) => v,
- None => *self.data.iter().max().unwrap_or(&1u64),
- };
- let max_index = min(spark_area.width as usize, self.data.len());
- let mut data = self
- .data
- .iter()
- .take(max_index)
- .map(|e| {
- if max != 0 {
- e * u64::from(spark_area.height) * 8 / max
- } else {
- 0
- }
- })
- .collect::<Vec<u64>>();
- for j in (0..spark_area.height).rev() {
- for (i, d) in data.iter_mut().enumerate() {
- let symbol = match *d {
- 0 => self.bar_set.empty,
- 1 => self.bar_set.one_eighth,
- 2 => self.bar_set.one_quarter,
- 3 => self.bar_set.three_eighths,
- 4 => self.bar_set.half,
- 5 => self.bar_set.five_eighths,
- 6 => self.bar_set.three_quarters,
- 7 => self.bar_set.seven_eighths,
- _ => self.bar_set.full,
- };
- buf.get_mut(spark_area.left() + i as u16, spark_area.top() + j)
- .set_symbol(symbol)
- .set_style(self.style);
-
- if *d > 8 {
- *d -= 8;
- } else {
- *d = 0;
- }
- }
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn it_does_not_panic_if_max_is_zero() {
- let widget = Sparkline::default().data(&[0, 0, 0]);
- let area = Rect::new(0, 0, 3, 1);
- let mut buffer = Buffer::empty(area);
- widget.render(area, &mut buffer);
- }
-
- #[test]
- fn it_does_not_panic_if_max_is_set_to_zero() {
- let widget = Sparkline::default().data(&[0, 1, 2]).max(0);
- let area = Rect::new(0, 0, 3, 1);
- let mut buffer = Buffer::empty(area);
- widget.render(area, &mut buffer);
- }
-}
diff --git a/src/ratatui/widgets/table.rs b/src/ratatui/widgets/table.rs
deleted file mode 100644
index d987a8d9..00000000
--- a/src/ratatui/widgets/table.rs
+++ /dev/null
@@ -1,504 +0,0 @@
-use crate::ratatui::{
- buffer::Buffer,
- layout::{Constraint, Direction, Layout, Rect},
- style::Style,
- text::Text,
- widgets::{Block, StatefulWidget, Widget},
-};
-use unicode_width::UnicodeWidthStr;
-
-/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
-///
-/// It can be created from anything that can be converted to a [`Text`].
-/// ```rust
-/// # use ratatui::widgets::Cell;
-/// # use ratatui::style::{Style, Modifier};
-/// # use ratatui::text::{Span, Spans, Text};
-/// # use std::borrow::Cow;
-/// Cell::from("simple string");
-///
-/// Cell::from(Span::from("span"));
-///
-/// Cell::from(Spans::from(vec![
-/// Span::raw("a vec of "),
-/// Span::styled("spans", Style::default().add_modifier(Modifier::BOLD))
-/// ]));
-///
-/// Cell::from(Text::from("a text"));
-///
-/// Cell::from(Text::from(Cow::Borrowed("hello")));
-/// ```
-///
-/// You can apply a [`Style`] on the entire [`Cell`] using [`Cell::style`] or rely on the styling
-/// capabilities of [`Text`].
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
-pub struct Cell<'a> {
- content: Text<'a>,
- style: Style,
-}
-
-impl<'a> Cell<'a> {
- /// Set the `Style` of this cell.
- pub fn style(mut self, style: Style) -> Self {
- self.style = style;
- self
- }
-}
-
-impl<'a, T> From<T> for Cell<'a>
-where
- T: Into<Text<'a>>,
-{
- fn from(content: T) -> Cell<'a> {
- Cell {
- content: content.into(),
- style: Style::default(),
- }
- }
-}
-
-/// Holds data to be displayed in a [`Table`] widget.
-///
-/// A [`Row`] is a collection of cells. It can be created from simple strings:
-/// ```rust
-/// # use ratatui::widgets::Row;
-/// Row::new(vec!["Cell1", "Cell2", "Cell3"]);
-/// ```
-///
-/// But if you need a bit more control over individual cells, you can explicitly create [`Cell`]s:
-/// ```rust
-/// # use ratatui::widgets::{Row, Cell};
-/// # use ratatui::style::{Style, Color};
-/// Row::new(vec![
-/// Cell::from("Cell1"),
-/// Cell::from("Cell2").style(Style::default().fg(Color::Yellow)),
-/// ]);
-/// ```
-///
-/// You can also construct a row from any type that can be converted into [`Text`]:
-/// ```rust
-/// # use std::borrow::Cow;
-/// # use ratatui::widgets::Row;
-/// Row::new(vec![
-/// Cow::Borrowed("hello"),
-/// Cow::Owned("world".to_uppercase()),
-/// ]);
-/// ```
-///
-/// By default, a row has a height of 1 but you can change this using [`Row::height`].
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
-pub struct Row<'a> {
- cells: Vec<Cell<'a>>,
- height: u16,
- style: Style,
- bottom_margin: u16,
-}
-
-impl<'a> Row<'a> {
- /// Creates a new [`Row`] from an iterator where items can be converted to a [`Cell`].
- pub fn new<T>(cells: T) -> Self
- where
- T: IntoIterator,
- T::Item: Into<Cell<'a>>,
- {
- Self {
- height: 1,
- cells: cells.into_iter().map(|c| c.into()).collect(),
- style: Style::default(),
- bottom_margin: 0,
- }
- }
-
- /// Set the fixed height of the [`Row`]. Any [`Cell`] whose content has more lines than this
- /// height will see its content truncated.
- pub fn height(mut self, height: u16) -> Self {
- self.height = height;
- self
- }
-
- /// Set the [`Style`] of the entire row. This [`Style`] can be overridden by the [`Style`] of a
- /// any individual [`Cell`] or event by their [`Text`] content.
- pub fn style(mut self, style: Style) -> Self {
- self.style = style;
- self
- }
-
- /// Set the bottom margin. By default, the bottom margin is `0`.
- pub fn bottom_margin(mut self, margin: u16) -> Self {
- self.bottom_margin = margin;
- self
- }
-
- /// Returns the total height of the row.
- fn total_height(&self) -> u16 {
- self.height.saturating_add(self.bottom_margin)
- }
-}
-
-/// A widget to display data in formatted columns.
-///
-/// It is a collection of [`Row`]s, themselves composed of [`Cell`]s:
-/// ```rust
-/// # use ratatui::widgets::{Block, Borders, Table, Row, Cell};
-/// # use ratatui::layout::Constraint;
-/// # use ratatui::style::{Style, Color, Modifier};
-/// # use ratatui::text::{Text, Spans, Span};
-/// Table::new(vec![
-/// // Row can be created from simple strings.
-/// Row::new(vec!["Row11", "Row12", "Row13"]),
-/// // You can style the entire row.
-/// Row::new(vec!["Row21", "Row22", "Row23"]).style(Style::default().fg(Color::Blue)),
-/// // If you need more control over the styling you may need to create Cells directly
-/// Row::new(vec![
-/// Cell::from("Row31"),
-/// Cell::from("Row32").style(Style::default().fg(Color::Yellow)),
-/// Cell::from(Spans::from(vec![
-/// Span::raw("Row"),
-/// Span::styled("33", Style::default().fg(Color::Green))
-/// ])),
-/// ]),
-/// // If a Row need to display some content over multiple lines, you just have to change
-/// // its height.
-/// Row::new(vec![
-/// Cell::from("Row\n41"),
-/// Cell::from("Row\n42"),
-/// Cell::from("Row\n43"),
-/// ]).height(2),
-/// ])
-/// // You can set the style of the entire Table.
-/// .style(Style::default().fg(Color::White))
-/// // It has an optional header, which is simply a Row always visible at the top.
-/// .header(
-/// Row::new(vec!["Col1", "Col2", "Col3"])
-/// .style(Style::default().fg(Color::Yellow))
-/// // If you want some space between the header and the rest of the rows, you can always
-/// // specify some margin at the bottom.
-/// .bottom_margin(1)
-/// )
-/// // As any other widget, a Table can be wrapped in a Block.
-/// .block(Block::default().title("Table"))
-/// // Columns widths are constrained in the same way as Layout...
-/// .widths(&[Constraint::Length(5), Constraint::Length(5), Constraint::Length(10)])
-/// // ...and they can be separated by a fixed spacing.
-/// .column_spacing(1)
-/// // If you wish to highlight a row in any specific way when it is selected...
-/// .highlight_style(Style::default().add_modifier(Modifier::BOLD))
-/// // ...and potentially show a symbol in front of the selection.
-/// .highlight_symbol(">>");
-/// ```
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Table<'a> {
- /// A block to wrap the widget in
- block: Option<Block<'a>>,
- /// Base style for the widget
- style: Style,
- /// Width constraints for each column
- widths: &'a [Constraint],
- /// Space between each column
- column_spacing: u16,
- /// Style used to render the selected row
- highlight_style: Style,
- /// Symbol in front of the selected rom
- highlight_symbol: Option<&'a str>,
- /// Optional header
- header: Option<Row<'a>>,
- /// Data to display in each row
- rows: Vec<Row<'a>>,
-}
-
-impl<'a> Table<'a> {
- pub fn new<T>(rows: T) -> Self
- where
- T: IntoIterator<Item = Row<'a>>,
- {
- Self {
- block: None,
- style: Style::default(),
- widths: &[],
- column_spacing: 1,
- highlight_style: Style::default(),
- highlight_symbol: None,
- header: None,
- rows: rows.into_iter().collect(),
- }
- }
-
- pub fn block(mut self, block: Block<'a>) -> Self {
- self.block = Some(block);
- self
- }
-
- pub fn header(mut self, header: Row<'a>) -> Self {
- self.header = Some(header);
- self
- }
-
- pub fn widths(mut self, widths: &'a [Constraint]) -> Self {
- let between_0_and_100 = |&w| match w {
- Constraint::Percentage(p) => p <= 100,
- _ => true,
- };
- assert!(
- widths.iter().all(between_0_and_100),
- "Percentages should be between 0 and 100 inclusively."
- );
- self.widths = widths;
- self
- }
-
- pub fn style(mut self, style: Style) -> Self {
- self.style = style;
- self
- }
-
- pub fn highlight_symbol(mut self, highlight_symbol: &'a str) -> Self {
- self.highlight_symbol = Some(highlight_symbol);
- self
- }
-
- pub fn highlight_style(mut self, highlight_style: Style) -> Self {
- self.highlight_style = highlight_style;
- self
- }
-
- pub fn column_spacing(mut self, spacing: u16) -> Self {
- self.column_spacing = spacing;
- self
- }
-
- fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> Vec<u16> {
- let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1);
- if has_selection {
- let highlight_symbol_width =
- self.highlight_symbol.map(|s| s.width() as u16).unwrap_or(0);
- constraints.push(Constraint::Length(highlight_symbol_width));
- }
- for constraint in self.widths {
- constraints.push(*constraint);
- constraints.push(Constraint::Length(self.column_spacing));
- }
- if !self.widths.is_empty() {
- constraints.pop();
- }
- let chunks = Layout::default()
- .direction(Direction::Horizontal)
- .constraints(constraints)
- .expand_to_fill(false)
- .split(Rect {
- x: 0,
- y: 0,
- width: max_width,
- height: 1,
- });
- let mut chunks = &chunks[..];
- if has_selection {
- chunks = &chunks[1..];
- }
- chunks.iter().step_by(2).map(|c| c.width).collect()
- }
-
- fn get_row_bounds(
- &self,
- selected: Option<usize>,
- offset: usize,
- max_height: u16,
- ) -> (usize, usize) {
- let offset = offset.min(self.rows.len().saturating_sub(1));
- let mut start = offset;
- let mut end = offset;
- let mut height = 0;
- for item in self.rows.iter().skip(offset) {
- if height + item.height > max_height {
- break;
- }
- height += item.total_height();
- end += 1;
- }
-
- let selected = selected.unwrap_or(0).min(self.rows.len() - 1);
- while selected >= end {
- height = height.saturating_add(self.rows[end].total_height());
- end += 1;
- while height > max_height {
- height = height.saturating_sub(self.rows[start].total_height());
- start += 1;
- }
- }
- while selected < start {
- start -= 1;
- height = height.saturating_add(self.rows[start].total_height());
- while height > max_height {
- end -= 1;
- height = height.saturating_sub(self.rows[end].total_height());
- }
- }
- (start, end)
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct TableState {
- offset: usize,
- selected: Option<usize>,
-}
-
-impl TableState {
- pub fn selected(&self) -> Option<usize> {
- self.selected
- }
-
- pub fn select(&mut self, index: Option<usize>) {
- self.selected = index;
- if index.is_none() {
- self.offset = 0;
- }
- }
-
- /// Returns a copy of the receiver's scroll offset.
- ///
- /// This is useful, for example, if you need to "synchronize" the scrolling of a `Table` and a `Paragraph`.
- pub fn offset(&self) -> usize {
- self.offset
- }
-}
-
-impl<'a> StatefulWidget for Table<'a> {
- type State = TableState;
-
- fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
- if area.area() == 0 {
- return;
- }
- buf.set_style(area, self.style);
- let table_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- let has_selection = state.selected.is_some();
- let columns_widths = self.get_columns_widths(table_area.width, has_selection);
- let highlight_symbol = self.highlight_symbol.unwrap_or("");
- let blank_symbol = " ".repeat(highlight_symbol.width());
- let mut current_height = 0;
- let mut rows_height = table_area.height;
-
- // Draw header
- if let Some(ref header) = self.header {
- let max_header_height = table_area.height.min(header.total_height());
- buf.set_style(
- Rect {
- x: table_area.left(),
- y: table_area.top(),
- width: table_area.width,
- height: table_area.height.min(header.height),
- },
- header.style,
- );
- let mut col = table_area.left();
- if has_selection {
- col += (highlight_symbol.width() as u16).min(table_area.width);
- }
- for (width, cell) in columns_widths.iter().zip(header.cells.iter()) {
- render_cell(
- buf,
- cell,
- Rect {
- x: col,
- y: table_area.top(),
- width: *width,
- height: max_header_height,
- },
- );
- col += *width + self.column_spacing;
- }
- current_height += max_header_height;
- rows_height = rows_height.saturating_sub(max_header_height);
- }
-
- // Draw rows
- if self.rows.is_empty() {
- return;
- }
- let (start, end) = self.get_row_bounds(state.selected, state.offset, rows_height);
- state.offset = start;
- for (i, table_row) in self
- .rows
- .iter_mut()
- .enumerate()
- .skip(state.offset)
- .take(end - start)
- {
- let (row, col) = (table_area.top() + current_height, table_area.left());
- current_height += table_row.total_height();
- let table_row_area = Rect {
- x: col,
- y: row,
- width: table_area.width,
- height: table_row.height,
- };
- buf.set_style(table_row_area, table_row.style);
- let is_selected = state.selected.map(|s| s == i).unwrap_or(false);
- let table_row_start_col = if has_selection {
- let symbol = if is_selected {
- highlight_symbol
- } else {
- &blank_symbol
- };
- let (col, _) =
- buf.set_stringn(col, row, symbol, table_area.width as usize, table_row.style);
- col
- } else {
- col
- };
- let mut col = table_row_start_col;
- for (width, cell) in columns_widths.iter().zip(table_row.cells.iter()) {
- render_cell(
- buf,
- cell,
- Rect {
- x: col,
- y: row,
- width: *width,
- height: table_row.height,
- },
- );
- col += *width + self.column_spacing;
- }
- if is_selected {
- buf.set_style(table_row_area, self.highlight_style);
- }
- }
- }
-}
-
-fn render_cell(buf: &mut Buffer, cell: &Cell, area: Rect) {
- buf.set_style(area, cell.style);
- for (i, spans) in cell.content.lines.iter().enumerate() {
- if i as u16 >= area.height {
- break;
- }
- buf.set_spans(area.x, area.y + i as u16, spans, area.width);
- }
-}
-
-impl<'a> Widget for Table<'a> {
- fn render(self, area: Rect, buf: &mut Buffer) {
- let mut state = TableState::default();
- StatefulWidget::render(self, area, buf, &mut state);
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- #[should_panic]
- fn table_invalid_percentages() {
- Table::new(vec![]).widths(&[Constraint::Percentage(110)]);
- }
-}
diff --git a/src/ratatui/widgets/tabs.rs b/src/ratatui/widgets/tabs.rs
deleted file mode 100644
index 9b5f5469..00000000
--- a/src/ratatui/widgets/tabs.rs
+++ /dev/null
@@ -1,129 +0,0 @@
-use crate::ratatui::{
- buffer::Buffer,
- layout::Rect,
- style::Style,
- symbols,
- text::{Span, Spans},
- widgets::{Block, Widget},
-};
-
-/// A widget to display available tabs in a multiple panels context.
-///
-/// # Examples
-///
-/// ```
-/// # use ratatui::widgets::{Block, Borders, Tabs};
-/// # use ratatui::style::{Style, Color};
-/// # use ratatui::text::{Spans};
-/// # use ratatui::symbols::{DOT};
-/// let titles = ["Tab1", "Tab2", "Tab3", "Tab4"].iter().cloned().map(Spans::from).collect();
-/// Tabs::new(titles)
-/// .block(Block::default().title("Tabs").borders(Borders::ALL))
-/// .style(Style::default().fg(Color::White))
-/// .highlight_style(Style::default().fg(Color::Yellow))
-/// .divider(DOT);
-/// ```
-#[derive(Debug, Clone)]
-pub struct Tabs<'a> {
- /// A block to wrap this widget in if necessary
- block: Option<Block<'a>>,
- /// One title for each tab
- titles: Vec<Spans<'a>>,
- /// The index of the selected tabs
- selected: usize,
- /// The style used to draw the text
- style: Style,
- /// Style to apply to the selected item
- highlight_style: Style,
- /// Tab divider
- divider: Span<'a>,
-}
-
-impl<'a> Tabs<'a> {
- pub fn new(titles: Vec<Spans<'a>>) -> Tabs<'a> {
- Tabs {
- block: None,
- titles,
- selected: 0,
- style: Default::default(),
- highlight_style: Default::default(),
- divider: Span::raw(symbols::line::VERTICAL),
- }
- }
-
- pub fn block(mut self, block: Block<'a>) -> Tabs<'a> {
- self.block = Some(block);
- self
- }
-
- pub fn select(mut self, selected: usize) -> Tabs<'a> {
- self.selected = selected;
- self
- }
-
- pub fn style(mut self, style: Style) -> Tabs<'a> {
- self.style = style;
- self
- }
-
- pub fn highlight_style(mut self, style: Style) -> Tabs<'a> {
- self.highlight_style = style;
- self
- }
-
- pub fn divider<T>(mut self, divider: T) -> Tabs<'a>
- where
- T: Into<Span<'a>>,
- {
- self.divider = divider.into();
- self
- }
-}
-
-impl<'a> Widget for Tabs<'a> {
- fn render(mut self, area: Rect, buf: &mut Buffer) {
- buf.set_style(area, self.style);
- let tabs_area = match self.block.take() {
- Some(b) => {
- let inner_area = b.inner(area);
- b.render(area, buf);
- inner_area
- }
- None => area,
- };
-
- if tabs_area.height < 1 {
- return;
- }
-
- let mut x = tabs_area.left();
- let titles_length = self.titles.len();
- for (i, title) in self.titles.into_iter().enumerate() {
- let last_title = titles_length - 1 == i;
- x = x.saturating_add(1);
- let remaining_width = tabs_area.right().saturating_sub(x);
- if remaining_width == 0 {
- break;
- }
- let pos = buf.set_spans(x, tabs_area.top(), &title, remaining_width);
- if i == self.selected {
- buf.set_style(
- Rect {
- x,
- y: tabs_area.top(),
- width: pos.0.saturating_sub(x),
- height: 1,
- },
- self.highlight_style,
- );
- }
- x = pos.0.saturating_add(1);
- let remaining_width = tabs_area.right().saturating_sub(x);
- if remaining_width == 0 || last_title {
- break;
- }
- let pos = buf.set_span(x, tabs_area.top(), &self.divider, remaining_width);
- x = pos.0;
- }
- }
-}
diff --git a/src/shell/atuin.bash b/src/shell/atuin.bash
deleted file mode 100644
index 1056dcf5..00000000
--- a/src/shell/atuin.bash
+++ /dev/null
@@ -1,34 +0,0 @@
-ATUIN_SESSION=$(atuin uuid)
-export ATUIN_SESSION
-
-_atuin_preexec() {
- local id
- id=$(atuin history start -- "$1")
- export ATUIN_HISTORY_ID="${id}"
-}
-
-_atuin_precmd() {
- local EXIT="$?"
-
- [[ -z "${ATUIN_HISTORY_ID}" ]] && return
-
- (RUST_LOG=error atuin history end --exit "${EXIT}" -- "${ATUIN_HISTORY_ID}" &) >/dev/null 2>&1
-}
-
-__atuin_history() {
- tput rmkx
- # shellcheck disable=SC2048,SC2086
- HISTORY="$(RUST_LOG=error atuin search $* -i -- "${READLINE_LINE}" 3>&1 1>&2 2>&3)"
- tput smkx
-
- READLINE_LINE=${HISTORY}
- READLINE_POINT=${#READLINE_LINE}
-}
-
-if [[ -n "${BLE_VERSION-}" ]]; then
- blehook PRECMD-+=_atuin_precmd
- blehook PREEXEC-+=_atuin_preexec
-else
- precmd_functions+=(_atuin_precmd)
- preexec_functions+=(_atuin_preexec)
-fi
diff --git a/src/shell/atuin.fish b/src/shell/atuin.fish
deleted file mode 100644
index de90b156..00000000
--- a/src/shell/atuin.fish
+++ /dev/null
@@ -1,40 +0,0 @@
-set -gx ATUIN_SESSION (atuin uuid)
-
-function _atuin_preexec --on-event fish_preexec
- if not test -n "$fish_private_mode"
- set -gx ATUIN_HISTORY_ID (atuin history start -- "$argv[1]")
- end
-end
-
-function _atuin_postexec --on-event fish_postexec
- set s $status
- if test -n "$ATUIN_HISTORY_ID"
- RUST_LOG=error atuin history end --exit $s -- $ATUIN_HISTORY_ID &>/dev/null &
- disown
- end
-end
-
-function _atuin_search
- set h (RUST_LOG=error atuin search $argv -i -- (commandline -b) 3>&1 1>&2 2>&3)
- commandline -f repaint
- if test -n "$h"
- commandline -r $h
- end
-end
-
-function _atuin_bind_up
- # Fallback to fish's builtin up-or-search if we're in search or paging mode
- if commandline --search-mode; or commandline --paging-mode
- up-or-search
- return
- end
-
- # Only invoke atuin if we're on the top line of the command
- set -l lineno (commandline --line)
- switch $lineno
- case 1
- _atuin_search --shell-up-key-binding
- case '*'
- up-or-search
- end
-end
diff --git a/src/shell/atuin.nu b/src/shell/atuin.nu
deleted file mode 100644
index d76cba91..00000000
--- a/src/shell/atuin.nu
+++ /dev/null
@@ -1,44 +0,0 @@
-# Source this in your ~/.config/nushell/config.nu
-let-env ATUIN_SESSION = (atuin uuid)
-
-# Magic token to make sure we don't record commands run by keybindings
-let ATUIN_KEYBINDING_TOKEN = $"# (random uuid)"
-
-let _atuin_pre_execution = {||
- let cmd = (commandline)
- if ($cmd | is-empty) {
- return
- }
- if not ($cmd | str starts-with $ATUIN_KEYBINDING_TOKEN) {
- let-env ATUIN_HISTORY_ID = (atuin history start -- $cmd)
- }
-}
-
-let _atuin_pre_prompt = {||
- let last_exit = $env.LAST_EXIT_CODE
- if 'ATUIN_HISTORY_ID' not-in $env {
- return
- }
- with-env { RUST_LOG: error } {
- atuin history end $'--exit=($last_exit)' -- $env.ATUIN_HISTORY_ID | null
- }
-}
-
-def _atuin_search_cmd [...flags: string] {
- [
- $ATUIN_KEYBINDING_TOKEN,
- ([
- `commandline (RUST_LOG=error run-external --redirect-stderr atuin search`,
- ($flags | append [--interactive, --] | each {|e| $'"($e)"'}),
- `(commandline) | complete | $in.stderr | str substring ..-1)`,
- ] | flatten | str join ' '),
- ] | str join "\n"
-}
-
-let-env config = (
- $env.config | upsert hooks (
- $env.config.hooks
- | upsert pre_execution ($env.config.hooks.pre_execution | append $_atuin_pre_execution)
- | upsert pre_prompt ($env.config.hooks.pre_prompt | append $_atuin_pre_prompt)
- )
-)
diff --git a/src/shell/atuin.zsh b/src/shell/atuin.zsh
deleted file mode 100644
index b0e160ff..00000000
--- a/src/shell/atuin.zsh
+++ /dev/null
@@ -1,57 +0,0 @@
-# shellcheck disable=SC2034,SC2153,SC2086,SC2155
-
-# Above line is because shellcheck doesn't support zsh, per
-# https://github.com/koalaman/shellcheck/wiki/SC1071, and the ignore: param in
-# ludeeus/action-shellcheck only supports _directories_, not _files_. So
-# instead, we manually add any error the shellcheck step finds in the file to
-# the above line ...
-
-# Source this in your ~/.zshrc
-autoload -U add-zsh-hook
-
-export ATUIN_SESSION=$(atuin uuid)
-export ATUIN_HISTORY="atuin history list"
-
-_atuin_preexec() {
- local id
- id=$(atuin history start -- "$1")
- export ATUIN_HISTORY_ID="$id"
-}
-
-_atuin_precmd() {
- local EXIT="$?"
-
- [[ -z "${ATUIN_HISTORY_ID}" ]] && return
-
- (RUST_LOG=error atuin history end --exit $EXIT -- $ATUIN_HISTORY_ID &) >/dev/null 2>&1
-}
-
-_atuin_search() {
- emulate -L zsh
- zle -I
-
- # Switch to cursor mode, then back to application
- echoti rmkx
- # swap stderr and stdout, so that the tui stuff works
- # TODO: not this
- # shellcheck disable=SC2048
- output=$(RUST_LOG=error atuin search $* -i -- $BUFFER 3>&1 1>&2 2>&3)
- echoti smkx
-
- if [[ -n $output ]]; then
- RBUFFER=""
- LBUFFER=$output
- fi
-
- zle reset-prompt
-}
-
-_atuin_up_search() {
- _atuin_search --shell-up-key-binding
-}
-
-add-zsh-hook preexec _atuin_preexec
-add-zsh-hook precmd _atuin_precmd
-
-zle -N _atuin_search_widget _atuin_search
-zle -N _atuin_up_search_widget _atuin_up_search