[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [gin66] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/workflows/build_examples.yml",
    "content": "name: Build examples\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Check rust version\n        run: rustup show\n      - name: Build examples with termion\n        run: cargo build --examples --features termion\n      - name: Build example with crossterm\n        run: cargo build --examples --features crossterm\n      - name: Build examples with termion and custom formatter\n        run: cargo build --examples --features termion,formatter\n"
  },
  {
    "path": ".github/workflows/build_examples_latest.yml",
    "content": "name: Build examples with latest rust version\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Check rust version\n        run: rustup show\n      - name: Update rust\n        run: rustup update\n      - name: Build examples with ratatui and termion\n        run: cargo build --examples --features termion\n      - name: Build example with ratatui and crossterm\n        run: cargo build --examples --features crossterm\n"
  },
  {
    "path": ".github/workflows/cargo_test.yml",
    "content": "name: cargo test\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Check rust version\n        run: rustup show\n      - name: Run tests\n        run: |\n          cargo test\n          cargo test -F tracing-support --doc\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Documentation\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  check-docs:\n    name: Check\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install cargo-rdme\n        uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-rdme\n      - name: Check README.md is up-to-date\n        run: cargo rdme --check\n"
  },
  {
    "path": ".github/workflows/semver_checks.yml",
    "content": "name: Semver Checks\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  semver-checks:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Check semver\n        uses: obi1kenobi/cargo-semver-checks-action@v2\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n**/*.rs.bk\nCargo.lock\n.DS_Store\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [0.18.2](https://github.com/gin66/tui-logger/compare/v0.18.1...v0.18.2) - 2026-04-01\n\n### Other\n\n- switch from chrono to jiff ([#96](https://github.com/gin66/tui-logger/pull/96))\n\n## [0.18.1](https://github.com/gin66/tui-logger/compare/v0.18.0...v0.18.1) - 2026-01-19\n\n### Fixed\n\n- *(formatter)* Prevent panic on empty log message\n\n## [0.18.0](https://github.com/gin66/tui-logger/compare/v0.17.4...v0.18.0) - 2025-12-28\n\n### Other\n\n- update to ratatui v0.30 as proposed by [#93](https://github.com/gin66/tui-logger/pull/93)\n\n## [0.17.4](https://github.com/gin66/tui-logger/compare/v0.17.3...v0.17.4) - 2025-09-28\n\n### Fixed\n\n- fix lifetime warning in demo.rs\n- fix cargo clippy warnings by opencode+grok\n- fix documentation\n- fix README, which is controlled by lib.rs\n\n### Other\n\n- run cargo fmt\n- resolve lifetime warnings as proposed by compiler\n- resolve security warning for fxhash by eliminating it ([#91](https://github.com/gin66/tui-logger/pull/91))\n- Create FUNDING.yml\n\n## [0.17.3](https://github.com/gin66/tui-logger/compare/v0.17.2...v0.17.3) - 2025-05-24\n\n### Other\n\n- update Readme\n- add ai generated documentation reference\n- add attributes of the current span for tracing subscriber (#90)\n- work on Readme\n\n## [0.17.2](https://github.com/gin66/tui-logger/compare/v0.17.1...v0.17.2) - 2025-04-23\n\n### Other\n\n- update README\n- Fix invalid docs for TuiTracingSubscriverLayer (#87)\n\n## [0.17.1](https://github.com/gin66/tui-logger/compare/v0.17.0...v0.17.1) - 2025-04-13\n\n### Other\n\n- run cargo fmt\n- use env_filter for configuration by environment variable as proposed by [#86](https://github.com/gin66/tui-logger/pull/86)\n- remove stray comment line in tests\n\n## [0.17.0](https://github.com/gin66/tui-logger/compare/v0.16.0...v0.17.0) - 2025-03-11\n\n### Other\n\n- file/module_path/line are now Option, because log::Record defines them as such, too\n\n## [0.16.0](https://github.com/gin66/tui-logger/compare/v0.15.0...v0.16.0) - 2025-03-10\n\n### Other\n\n- ExtLogRecord stores for file and module_path their static strings, if available. In addition provides module_path getter\n- provide accessors for the strings\n- update DEV_NOTES\n\n## [0.15.0](https://github.com/gin66/tui-logger/compare/v0.14.5...v0.15.0) - 2025-03-08\n\n### Fixed\n\n- fix bug with page up at top\n\n### Other\n\n- lib.rs factoring complete\n- refactor TuiLoggerError out of lib.rs\n- refactor TuiLogger API out of lib.rs\n- refactor TuiLogger out of lib.rs\n- refactor LevelConfig out of lib.rs\n- refactor TuiLoggerTargetWidget out of lib.rs\n- reduce visibility of internal structures\n- add test case for log message being larger than widget\n- turn off debugging\n- add test case with standard formatter\n- add test with wrapped lines\n- disable println in lib code with render_debug switch\n- intermediate version with debug prints\n- circular buffer allows absolute indexing\n- test cases for standard widget display and scroll\n\n## [0.14.5](https://github.com/gin66/tui-logger/compare/v0.14.4...v0.14.5) - 2025-02-22\n\n### Other\n\n- update Changelog and Readme\n- Made target width also use unicode_segmentation\n- Use unicode-segmentation for splitting chars\n- Split lines safely\n- change &mut to & reference in TuiWidgetState.transition as [#83](https://github.com/gin66/tui-logger/pull/83)\n- Update mermaid diagram in README.md\n- Update lib.rs for readme change\n- Update README.md\n- Update README.md with mermaid diagram\n- rename dev_notes.md\n- cleanup files in doc\n- udpate readme\n- new demo of the widget using vhs\n- rename cargo test workflow\n- remove stray space in github action\n\n## [0.14.4](https://github.com/gin66/tui-logger/compare/v0.14.3...v0.14.4) - 2025-01-31\n\n### Fixed\n\n- fix cargo test\n\n### Other\n\n- update Readme for custom formatter example in demo\n- example demo extended to use custom formatter in one of the widgets\n- run cargo test in github actions\n\n## [0.14.3](https://github.com/gin66/tui-logger/compare/v0.14.2...v0.14.3) - 2025-01-31\n\n### Other\n\n- work on Readme and add formatter() to smartwidget\n- standard formatter appears to work as before, but using Line/Span\n- assure LogFormatter Send+Sync\n- implement formatter trait as discussed in [#77](https://github.com/gin66/tui-logger/pull/77) and [#82](https://github.com/gin66/tui-logger/pull/82)\n\n## [0.14.2](https://github.com/gin66/tui-logger/compare/v0.14.1...v0.14.2) - 2025-01-30\n\n### Fixed\n\n- fix warnings\n\n### Other\n\n- split lib.rs into several files\n- Merge pull request [#77](https://github.com/gin66/tui-logger/pull/77) from tofubert/add_style_for_file\n- Merge pull request [#78](https://github.com/gin66/tui-logger/pull/78) from andrei-ng/fix-order-of-fields-tracing-feature\n- Merge pull request [#79](https://github.com/gin66/tui-logger/pull/79) from andrei-ng/skip-printing-message-key\n\n- use env::temp_dir for demo log file target\n- do not print the 'message' key in the formatter for tracing support\n- fix formatter for tracing events\n- make comment for file logging a bit better\n- give file logging format options\n- Update CHANGELOG.md\n\n0.14.1:\n- re-export log::LevelFilter\n\n0.14.0:\n- Update version of ratatui\n\n0.13.2:\n- fix tracing support\n\n0.13.1:\n- fix missing `move_events()` on half full buffer in case hot buffer capacity was odd\n\n0.13.0:\n- `move_events()` is not published anymore, but called by a cyclic internal task.\n  This task is called by timeout (10ms) unless the hot buffer is half full.\n- `init_logger()` returns now `Result<(), TuiLoggerError>`\n\n0.12.1:\n- fix for issue #69: avoid unwrap panic by using default level\n- add `set_buffer_depth()` to modify circular buffer size\n\n0.12.0:\n- update ratatui to 0.28\n\n0.11.2:\n- update ratatui to 0.27\n\n0.11.1:\n- one line error report for demo example, if feature flag crossterm or termion not given\n- use cargo readme and test in github build\n- Fix of issue #60: panic on too small widget size\n\n0.11.0:\n- BREAKING CHANGE: TuiWidgetEvent::transition() method now takes a TuiWidgetEvent by value instead of by reference.\n- update ratatui to 0.25\n\n0.10.1:\n- update ratatui to ^0.25.0\n\n0.10.0:\n- Remove support for tui legacy crate\n- Use `Cell::set_symbol()` as performance optimization from ratatui\n- Require chrono >= 0.4.20 for avoid security vulnerability (https://rustsec.org/advisories/RUSTSEC-2020-0159.html)\n\n0.9.6:\n- update ratatui to 0.23.0\n\n0.9.5:\n- rework examples/demo to not use termion\n\n0.9.4:\n- fix breaking change in 0.9.3 as reported by issue #43\n\n0.9.3:\n- update to ratatui 0.22 and fix clippy warnings\n\n0.9.2:\n- update to ratatui 0.21\n\n0.9.1:\n- Implement Eq for TuiWidgetEvent \n- suppport `border_type()` for TuiLoggerSmartWidget\n- Disable default features of chrono to avoid import of `time` v0.1.x\n\n0.9.0:\n- support for tracing-subscriber\n- add optional ratatui support as proposed by (#32)\n- slog is NOT a default feature anymore. Enable with `slog-support`\n\n0.8.3:\n- Make `TuiWidgetState.set_default_display_level()` work for TuiLoggerWidget (#30)\n\n0.8.2:\n- Allow TuiLoggerWidget to be controlled with TuiWidgetState by calling state() builder function (#30)\n- Extend demo for an example for this TuiLoggerWidget control\n\n0.8.1:\n- Update to tui-rs 0.19 and slog to 2.7.0\n\n0.8.0:\n- Update to tui-rs 0.18\n\n0.7.1:\n- Update to tui-rs 0.17\n\n0.7.0:\n- Update rust edition in Cargo.toml to 2021\n- Fix all warnings from cargo clippy\n- new function for TuiWidgetState to set the default display level - not impacting the recording\n  ```rust\n    set_default_display_level(self, levelfilter: LevelFilter) -> TuiWidgetState\n- changed signature for TuiWidgetState function from\n  ```rust\n    set_level_for_target(&self, target: &str, levelfilter: LevelFilter) -> &TuiWidgetState\n  ```\n  to\n  ```rust\n    set_level_for_target(self, target: &str, levelfilter: LevelFilter) -> TuiWidgetState\n  ```\n\n\n0.6.6:\n- Add functions to format output of log data as discussed in [issue #19](https://github.com/gin66/tui-logger/issues/19)\n  The functions are with their default values:\n  ```\n  output_separator(':')\n  output_timestamp(Some(\"%H:%M:%S\".to_string()))\n  output_level(Some(TuiLoggerLevelOutput::Long))\n  output_target(true)\n  output_file(true)\n  output_line(true)\n  ```\n\n0.6.5:\n- Use thread safe counterparts for Rc/RefCell\n\n0.6.4:\n- Bump version up for update to tui 0.16\n\n0.6.3:\n- Removed verbose timestamp info log (issue #16)\n\n0.6.2:\n- Fix by Wuelle to avoid panic on line wrapping inside a utf8 character\n\n0.6.1:\n- Changes in README\n\n0.6.0:\n- Support Scrollback in log history with TuiWidgetEvent::PrevPageKey, NextPageKey and EscapeKey\n- log and target panes' title can be set via .title_log(String) and .title_target(String)\n\n0.5.1:\n- TuiWidgetEvent is now Debug, Clone, PartialEq, Hash\n\n0.5.0:\n- Remove dispatcher completely\n- Get rid of dependency to termion and crossterm\n- KeyCommands to be translated by the application into KeyEvents for TuiWidgetState::transition()\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"tui-logger\"\nversion = \"0.18.2\"\nauthors = [\"Jochen Kiemes <jochen@kiemes.de>\"]\nedition = \"2021\"\nlicense = \"MIT\"\ndescription = \"Logger with smart widget for the `ratatui` crate\"\ndocumentation = \"https://docs.rs/tui-logger/latest/tui_logger/\"\nrepository = \"https://github.com/gin66/tui-logger\"\nreadme = \"README.md\"\nkeywords = [\"tui\", \"log\", \"logger\", \"widget\", \"dispatcher\"]\n\n[dependencies]\nlog = \"0.4\"\njiff = \"0.2\"\nratatui = { version = \"0.30\", default-features = false}\ntracing = {version = \"0.1.40\", optional = true}\ntracing-subscriber = {version = \"0.3\", optional = true}\nlazy_static = \"1.5\"\nparking_lot = \"0.12\"\nslog = { version = \"2.7.0\", optional = true }\nunicode-segmentation = \"1.12.0\"\nenv_filter = \"0.1.3\"\n\n[dev-dependencies]\n# the crate is compatible with ratatui >=0.25.0, but the demo uses features from 0.27.0\nratatui = { version = \"0.30\", default-features = false}\nanyhow = \"1.0.91\"\nenv_logger = \"0.11.5\"\ntermion = {version = \"4.0.3\" }\ncrossterm = {version = \"0.29\"}\n\n[features]\nslog-support = [\"slog\"]\ntracing-support = [\"tracing\", \"tracing-subscriber\"]\n\n# only necessary for the demo, the crate does has no dependencies on these\n# \n# feature_crossterm_or_termion_must_be_selected to generate one line error message\n# instead of many compile error messages, if neither crossterm nor termion are selected.\nfeature_crossterm_or_termion_must_be_selected = []\ncrossterm = [\"ratatui/crossterm\", \"feature_crossterm_or_termion_must_be_selected\"]\ntermion = [\"ratatui/termion\", \"feature_crossterm_or_termion_must_be_selected\"]\nformatter = []\n\n# Docs.rs-specific configuration required to enable documentation of\n# code requiring optional features.\n[package.metadata.docs.rs]\n# Document all features\nall-features = true\n# Defines the configuration attribute `docsrs`\nrustdoc-args = [\"--cfg\", \"docsrs\"]\n\n[[example]]\nname=\"demo\"\nrequired-features=[\"feature_crossterm_or_termion_must_be_selected\"]\n"
  },
  {
    "path": "DEV_NOTES.md",
    "content": "# Release process\n\n## Prepare documentation\n\nRun first `cargo rdme` and then decide on `cargo rdme --force`\n\n## Update Changelog and Cargo.toml\n\nExecute along the lines of:\n1. `release-plz update`, then check in the versioned files.\n2. `git push`\n3. wait for github runners are completed\n4. `git tag` and `git push --tags`\n5. `cargo publish`\n\n# Update demo.gif\n\nIn `doc` folder run `vhs demo.tape`.\nThen rename `demo.gif` to current version and update Readme - currently via lib.rs\n\nThere is another `demo-short.tape`, which is used for the demo in ratatui website.\n\n# Needed tools on macos\n\n```sh\ncargo install release-plz\ncargo install cargo-rdme\nbrew install vhs\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Jochen Kiemes\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# tui-logger\n\n<!-- cargo-rdme start -->\n\n## Logger with smart widget for the `tui` and `ratatui` crate\n\n[![dependency status](https://deps.rs/repo/github/gin66/tui-logger/status.svg?service=github&nocache=0_9_1)](https://deps.rs/repo/github/gin66/tui-logger)\n![Build examples](https://github.com/gin66/tui-logger/workflows/Build%20examples/badge.svg?service=github)\n\n\n### Demo of the widget\n\n![Demo](https://github.com/gin66/tui-logger/blob/master/doc/demo_v0.14.4.gif?raw=true)\n\n### Documentation\n\n[Documentation](https://docs.rs/tui-logger/latest/tui_logger/)\n\nI have stumbled over an excellent AI-generated description of `tui-logger`, which provides surprisingly deep and (mostly) correct implementation details.\nIt would have costed me many days to write an equally good description with so many details and diagrams.\nThis docu can be found [here](https://deepwiki.com/gin66/tui-logger).\n\n### Important note for `tui`\n\nThe `tui` crate has been archived and `ratatui` has taken over.\nIn order to avoid supporting compatibility for an inactive crate,\nthe v0.9.x releases are the last to support `tui`. In case future bug fixes\nare needed, the branch `tui_legacy` has been created to track changes to 0.9.x releases.\n\nStarting with v0.10 `tui-logger` is `ratatui` only.\n\n### Features\n\n- [X] Logger implementation for the `log` crate\n- [X] Logger enable/disable detection via hash table (avoid string compare)\n- [X] Hot logger code only copies enabled log messages with timestamp into a circular buffer\n- [X] Widgets/move_message() retrieve captured log messages from hot circular buffer\n- [X] Lost message detection due to circular buffer\n- [X] Log filtering performed on log record target\n- [X] Simple Widgets to view logs and configure debuglevel per target\n- [X] Logging of enabled logs to file\n- [X] Scrollback in log history\n- [x] Title of target and log pane can be configured\n- [X] `slog` support, providing a Drain to integrate into your `slog` infrastructure\n- [X] `tracing` support\n- [X] Support to use custom formatter for log events\n- [X] Configurable by environment variables\n- [ ] Allow configuration of target dependent loglevel specifically for file logging\n- [X] Avoid duplicating of module_path and filename in every log record\n- [ ] Simultaneous modification of all targets' display/hot logging loglevel by key command\n\n### Smart Widget\n\nSmart widget consists of two widgets. Left is the target selector widget and\non the right side the logging messages view scrolling up. The target selector widget\ncan be hidden/shown during runtime via key command.\nThe key command to be provided to the TuiLoggerWidget via transition() function.\n\nThe target selector widget looks like this:\n\n![widget](https://github.com/gin66/tui-logger/blob/master/doc/example.png?raw=true)\n\nIt controls:\n\n- Capturing of log messages by the logger\n- Selection of levels for display in the logging message view\n\nThe two columns have the following meaning:\n\n- Code EWIDT: E stands for Error, W for Warn, Info, Debug and Trace.\n  + Inverted characters (EWIDT) are enabled log levels in the view\n  + Normal characters show enabled capturing of a log level per target\n  + If any of EWIDT are not shown, then the respective log level is not captured\n- Target of the log events can be defined in the log e.g. `warn!(target: \"demo\", \"Log message\");`\n\n### Smart Widget Key Commands\n```rust\n|  KEY     | ACTION\n|----------|-----------------------------------------------------------|\n| h        | Toggles target selector widget hidden/visible\n| f        | Toggle focus on the selected target only\n| UP       | Select previous target in target selector widget\n| DOWN     | Select next target in target selector widget\n| LEFT     | Reduce SHOWN (!) log messages by one level\n| RIGHT    | Increase SHOWN (!) log messages by one level\n| -        | Reduce CAPTURED (!) log messages by one level\n| +        | Increase CAPTURED (!) log messages by one level\n| PAGEUP   | Enter Page Mode and scroll approx. half page up in log history.\n| PAGEDOWN | Only in page mode: scroll 10 events down in log history.\n| ESCAPE   | Exit page mode and go back to scrolling mode\n| SPACE    | Toggles hiding of targets, which have logfilter set to off\n```\n\nThe mapping of key to action has to be done in the application. The respective TuiWidgetEvent\nhas to be provided to TuiWidgetState::transition().\n\nRemark to the page mode: The timestamp of the event at event history's bottom line is used as\nreference. This means, changing the filters in the EWIDT/focus from the target selector window\nshould work as expected without jumps in the history. The page next/forward advances as\nper visibility of the events.\n\n### Basic usage to initialize logger-system:\n```rust\n#[macro_use]\nextern crate log;\n//use tui_logger;\n\nfn main() {\n    // Early initialization of the logger\n\n    // Set max_log_level to Trace\n    tui_logger::init_logger(log::LevelFilter::Trace).unwrap();\n\n    // Set default level for unknown targets to Trace\n    tui_logger::set_default_level(log::LevelFilter::Trace);\n\n    // code....\n}\n```\n\nFor use of the widget please check examples/demo.rs\n\n### Demo\n\nRun demo using termion:\n\n```rust\ncargo run --example demo --features termion\n```\n\nRun demo with crossterm:\n\n```rust\ncargo run --example demo --features crossterm\n```\n\nRun demo using termion and simple custom formatter in bottom right log widget:\n\n```rust\ncargo run --example demo --features termion,formatter\n```\n\n### Configuration by environment variables\n\n`tui.logger` uses `env-filter` crate to support configuration by a string or an environment variable.\nThis is an opt-in by call to one of these two functions.\n```rust\npub fn set_env_filter_from_string(filterstring: &str) {}\npub fn set_env_filter_from_env(env_name: Option<&str>) {}\n```\nDefault environment variable name is `RUST_LOG`.\n\n### `slog` support\n\n`tui-logger` provides a [`TuiSlogDrain`] which implements `slog::Drain` and will route all records\nit receives to the `tui-logger` widget.\n\nEnabled by feature \"slog-support\"\n\n### `tracing-subscriber` support\n\n`tui-logger` provides a [`TuiTracingSubscriberLayer`] which implements\n`tracing_subscriber::Layer` and will collect all events\nit receives to the `tui-logger` widget\n\nEnabled by feature \"tracing-support\"\n\n### Custom filtering\n```rust\n#[macro_use]\nextern crate log;\n//use tui_logger;\nuse env_logger;\n\nfn main() {\n    // Early initialization of the logger\n    let drain = tui_logger::Drain::new();\n    // instead of tui_logger::init_logger, we use `env_logger`\n    env_logger::Builder::default()\n        .format(move |buf, record|\n            // patch the env-logger entry through our drain to the tui-logger\n            Ok(drain.log(record))\n        ).init(); // make this the global logger\n    // code....\n}\n```\n\n### Custom formatting\n\nFor experts only ! Configure along the lines:\n```rust\nuse tui_logger::LogFormatter;\n\nlet formatter = MyLogFormatter();\n\nTuiLoggerWidget::default()\n.block(Block::bordered().title(\"Filtered TuiLoggerWidget\"))\n.formatter(formatter)\n.state(&filter_state)\n.render(left, buf);\n```\nThe example demo can be invoked to use a custom formatter as example for the bottom right widget.\n\n<!-- cargo-rdme end -->\n\n### Internals\n\nFor logging there are two circular buffers in use:\n* \"hot\" buffer, which is written to during any logging macro invocation\n* main buffer, which holds events to be displayed by the widgets.\n\nThe size of the \"hot\" buffer is 1000 and can be modified by `set_hot_buffer_depth()`.\nThe size of the main buffer is 10000 and can be modified by `set_buffer_depth()`.\n\nReason for this scheme: The main buffer is locked for a while during widget updates.\nIn order to avoid blocking the log-macros, this scheme is in use.\n\nThe copy from \"hot\" buffer to main buffer is performed by a call to `move_events()`,\nwhich is done in a cyclic task, which repeats every 10 ms, or when the hot buffer is half full.\n\nIn versions <0.13 log messages may have been lost, if the widget wasn't drawn.\n\n```mermaid\nflowchart LR\n    Logging[\"Logging Macros\"] --> Capture[\"CAPTURE Filter\"] --> HotBuffer[\"Hot Buffer (1000 entries)\"]\n    \n    MoveEvents[\"move_events()\"]\n    HotBuffer --> MoveEvents\n    MoveEvents --> MainBuffer[\"Main Buffer (10000 entries)\"]\n    \n    MainBuffer --- Show1[\"SHOW Filter\"] --- Widget1[\"Widget 1\"]\n    MainBuffer --- Show2[\"SHOW Filter\"] --- Widget2[\"Widget 2\"]\n    MainBuffer --- ShowN[\"SHOW Filter\"] --- Widget3[\"Widget N\"]\n    \n    Config1[\"set_hot_buffer_depth()\"] -.-> HotBuffer\n    Config2[\"set_buffer_depth()\"] -.-> MainBuffer\n    \n    subgraph Triggers[\"Triggers\"]\n        direction TB\n        T1[\"Every 10ms\"]\n        T2[\"Hot buffer 50% full\"]\n    end\n    \n    Triggers -.-> MoveEvents\n    \n    note[\"Note: Main buffer locks during widget updates\"]\n    note -.-> MainBuffer\n```\n\n### THANKS TO\n\n* [Florian Dehau](https://github.com/fdehau) for his great crate [tui-rs](https://github.com/fdehau/tui-rs)\n* [Antoine Büsch](https://github.com/abusch) for providing the patches to tui-rs v0.3.0 and v0.6.0\n* [Adam Sypniewski](https://github.com/ajsyp) for providing the patches to tui-rs v0.6.2\n* [James aka jklong](https://github.com/jklong) for providing the patch to tui-rs v0.7\n* [icy-ux](https://github.com/icy-ux) for adding slog support and example\n* [alvinhochun](https://github.com/alvinhochun) for updating to tui 0.10 and crossterm support\n* [brooksmtownsend](https://github.com/brooksmtownsend) Patch to remove verbose timestamp info\n* [Kibouo](https://github.com/Kibouo) Patch to change Rc/Refcell to thread-safe counterparts\n* [Afonso Bordado](https://github.com/afonso360) for providing the patch to tui-rs v0.17\n* [Benjamin Kampmann](https://github.com/gnunicorn) for providing patch to tui-rs v0.18\n* [Paul Sanders](https://github.com/pms1969) for providing patch in [issue #30](https://github.com/gin66/tui-logger/issues/30)\n* [Ákos Hadnagy](https://github.com/ahadnagy) for providing patch in [#31](https://github.com/gin66/tui-logger/issues/31) for tracing-subscriber support\n* [Orhun Parmaksız](https://github.com/orhun) for providing patches in [#33](https://github.com/gin66/tui-logger/issues/33), [#34](https://github.com/gin66/tui-logger/issues/34), [#37](https://github.com/gin66/tui-logger/issues/37) and [#46](https://github.com/gin66/tui-logger/issues/46)\n* [purephantom](https://github.com/purephantom) for providing patch in [#42](https://github.com/gin66/tui-logger/issues/42) for ratatui update\n* [Badr Bouslikhin](https://github.com/badrbouslikhin) for providing patch in [#47](https://github.com/gin66/tui-logger/issues/47) for ratatui update\n* [ganthern](https://github.com/ganthern) for providing patch in [#49](https://github.com/gin66/tui-logger/issues/49) for tui support removal\n* [Linda_pp](https://github.com/rhysd) for providing patch in [#51](https://github.com/gin66/tui-logger/issues/51) for Cell:set_symbol\n* [Josh McKinney](https://github.com/joshka) for providing patch in\n[#56](https://github.com/gin66/tui-logger/issues/56) for Copy on TuiWidgetEvent and\nTuiLoggerWidget\n* [nick42d](https://github.com/nick42d) for providing patch in\n[#63](https://github.com/gin66/tui-logger/issues/63) for semver checks, [#74](https://github.com/gin66/tui-logger/pull/74) and [#87](https://github.com/gin66/tui-logger/issues/87)\n* [Tom Groenwoldt](https://github.com/tomgroenwoldt) for providing patch in [#65](https://github.com/gin66/tui-logger/issues/65) for ratatui update\n* [Kevin](https://github.com/doesnotcompete) for providing patch in [#71](https://github.com/issues/71)\n* [urizennnn](https://github.com/urizennnn) for providing patch in [#72](https://github.com/issues/72)\n* [Earthgames](https://github.com/Earthgames) for providing patch in [#84](https://github.com/issues/84) to fix panic for unicode characters\n\n### Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=gin66/tui-logger&type=Date)](https://star-history.com/#gin66/tui-logger&Date)\n\nLicense: MIT\n"
  },
  {
    "path": "bacon.toml",
    "content": "# This is a configuration file for the bacon tool\n#\n# Bacon repository: https://github.com/Canop/bacon\n# Complete help on configuration: https://dystroy.org/bacon/config/\n# You can also check bacon's own bacon.toml file\n#  as an example: https://github.com/Canop/bacon/blob/main/bacon.toml\n\ndefault_job = \"check\"\n\n[jobs.check]\ncommand = [\"cargo\", \"check\", \"--color\", \"always\"]\nneed_stdout = false\n\n# This is a helpful job to check that the demo compiles with the crossterm\n# feature enabled. This and the termion feature are mutually exclusive.\n[jobs.check-crossterm]\ncommand = [\"cargo\", \"check\", \"--all-targets\", \"--features\", \"crossterm\", \"--color\", \"always\"]\nneed_stdout = false\n\n# This is a helpful job to check that the demo compiles with the termion\n# feature enabled. This and the crossterm feature are mutually exclusive.\n[jobs.check-termion]\ncommand = [\"cargo\", \"check\", \"--all-targets\", \"--features\", \"termion\", \"--color\", \"always\"]\nneed_stdout = false\n\n[jobs.clippy]\ncommand = [\n    \"cargo\", \"clippy\",\n    \"--color\", \"always\",\n]\nneed_stdout = false\n\n[jobs.test]\ncommand = [\n    \"cargo\", \"test\", \"--libs\", \"--color\", \"always\",\n    \"--\", \"--color\", \"always\", # see https://github.com/Canop/bacon/issues/124\n]\nneed_stdout = true\n\n[jobs.doc]\ncommand = [\"cargo\", \"doc\", \"--color\", \"always\", \"--no-deps\"]\nneed_stdout = false\n\n# If the doc compiles, then it opens in your browser and bacon switches\n# to the previous job\n[jobs.doc-open]\ncommand = [\"cargo\", \"doc\", \"--color\", \"always\", \"--no-deps\", \"--open\"]\nneed_stdout = false\non_success = \"back\" # so that we don't open the browser at each change\n\n# You may define here keybindings that would be specific to\n# a project, for example a shortcut to launch a specific job.\n# Shortcuts to internal functions (scrolling, toggling, etc.)\n# should go in your personal global prefs.toml file instead.\n[keybindings]\n# alt-m = \"job:my-job\"\n1 = \"job:check-crossterm\"\n2 = \"job:check-termion\"\n"
  },
  {
    "path": "doc/demo-short.tape",
    "content": "# VHS documentation\n#\n# Output:\n#   Output <path>.gif               Create a GIF output at the given <path>\n#   Output <path>.mp4               Create an MP4 output at the given <path>\n#   Output <path>.webm              Create a WebM output at the given <path>\n#\n# Require:\n#   Require <string>                Ensure a program is on the $PATH to proceed\n#\n# Settings:\n#   Set FontSize <number>           Set the font size of the terminal\n#   Set FontFamily <string>         Set the font family of the terminal\n#   Set Height <number>             Set the height of the terminal\n#   Set Width <number>              Set the width of the terminal\n#   Set LetterSpacing <float>       Set the font letter spacing (tracking)\n#   Set LineHeight <float>          Set the font line height\n#   Set LoopOffset <float>%         Set the starting frame offset for the GIF loop\n#   Set Theme <json|string>         Set the theme of the terminal\n#   Set Padding <number>            Set the padding of the terminal\n#   Set Framerate <number>          Set the framerate of the recording\n#   Set PlaybackSpeed <float>       Set the playback speed of the recording\n#   Set MarginFill <file|#000000>   Set the file or color the margin will be filled with.\n#   Set Margin <number>             Set the size of the margin. Has no effect if MarginFill isn't set.\n#   Set BorderRadius <number>       Set terminal border radius, in pixels.\n#   Set WindowBar <string>          Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight)\n#   Set WindowBarSize <number>      Set window bar size, in pixels. Default is 40.\n#   Set TypingSpeed <time>          Set the typing speed of the terminal. Default is 50ms.\n#\n# Sleep:\n#   Sleep <time>                    Sleep for a set amount of <time> in seconds\n#\n# Type:\n#   Type[@<time>] \"<characters>\"    Type <characters> into the terminal with a\n#                                   <time> delay between each character\n#\n# Keys:\n#   Escape[@<time>] [number]        Press the Escape key\n#   Backspace[@<time>] [number]     Press the Backspace key\n#   Delete[@<time>] [number]        Press the Delete key\n#   Insert[@<time>] [number]        Press the Insert key\n#   Down[@<time>] [number]          Press the Down key\n#   Enter[@<time>] [number]         Press the Enter key\n#   Space[@<time>] [number]         Press the Space key\n#   Tab[@<time>] [number]           Press the Tab key\n#   Left[@<time>] [number]          Press the Left Arrow key\n#   Right[@<time>] [number]         Press the Right Arrow key\n#   Up[@<time>] [number]            Press the Up Arrow key\n#   Down[@<time>] [number]          Press the Down Arrow key\n#   PageUp[@<time>] [number]        Press the Page Up key\n#   PageDown[@<time>] [number]      Press the Page Down key\n#   Ctrl+<key>                      Press the Control key + <key> (e.g. Ctrl+C)\n#\n# Display:\n#   Hide                            Hide the subsequent commands from the output\n#   Show                            Show the subsequent commands in the output\n\nOutput target/demo.gif\nSet Theme \"Aardvark Blue\"\nSet Width 1600\nSet Height 1200\nSet Framerate 50\n\nHide\nType \"cargo run --example=demo --features=crossterm\" Enter\n\nSleep 2s # allows time for build\nShow\nRight 2  # set App to tracing\nDown 2   # select the crossterm target\nRight 2  # set crossterm to tracing\nSleep 1s\n\n# a screenshot for using in a more static context (e.g. ratatui.rs showcase)\nScreenshot target/demo.png \nSleep 1s\n\n# The following commented out commands are for a more full demo similar to the existing demo\n# However they produce a much larger gif and are not necessary for the showcase\n\n# Type \"f\" Sleep 2s # focus on the crossterm target\n# Up\n# Up\n# Type \"f\" Sleep 2s # unfocus\n\n# # unselect everything except error\n# Left 4 Down\n# Left 2 Down\n# Left 4 Down\n# Left 2 Down\n# Left 2 Sleep 2s\n\n# # Hide / show the selector\n# Type \"h\" Sleep 2s\n# Type \"h\" Sleep 2s\n\n# # turn off tracing capture\n# Type \"-\" Up\n# Type \"-\" Up\n# Type \"-\" Up\n# Type \"-\" Up\n# Type \"-\" Sleep 2s\n\nHide\nType \"q\"\n"
  },
  {
    "path": "doc/demo.tape",
    "content": "# VHS documentation\n#\n# Output:\n#   Output <path>.gif               Create a GIF output at the given <path>\n#   Output <path>.mp4               Create an MP4 output at the given <path>\n#   Output <path>.webm              Create a WebM output at the given <path>\n#\n# Require:\n#   Require <string>                Ensure a program is on the $PATH to proceed\n#\n# Settings:\n#   Set FontSize <number>           Set the font size of the terminal\n#   Set FontFamily <string>         Set the font family of the terminal\n#   Set Height <number>             Set the height of the terminal\n#   Set Width <number>              Set the width of the terminal\n#   Set LetterSpacing <float>       Set the font letter spacing (tracking)\n#   Set LineHeight <float>          Set the font line height\n#   Set LoopOffset <float>%         Set the starting frame offset for the GIF loop\n#   Set Theme <json|string>         Set the theme of the terminal\n#   Set Padding <number>            Set the padding of the terminal\n#   Set Framerate <number>          Set the framerate of the recording\n#   Set PlaybackSpeed <float>       Set the playback speed of the recording\n#   Set MarginFill <file|#000000>   Set the file or color the margin will be filled with.\n#   Set Margin <number>             Set the size of the margin. Has no effect if MarginFill isn't set.\n#   Set BorderRadius <number>       Set terminal border radius, in pixels.\n#   Set WindowBar <string>          Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight)\n#   Set WindowBarSize <number>      Set window bar size, in pixels. Default is 40.\n#   Set TypingSpeed <time>          Set the typing speed of the terminal. Default is 50ms.\n#\n# Sleep:\n#   Sleep <time>                    Sleep for a set amount of <time> in seconds\n#\n# Type:\n#   Type[@<time>] \"<characters>\"    Type <characters> into the terminal with a\n#                                   <time> delay between each character\n#\n# Keys:\n#   Escape[@<time>] [number]        Press the Escape key\n#   Backspace[@<time>] [number]     Press the Backspace key\n#   Delete[@<time>] [number]        Press the Delete key\n#   Insert[@<time>] [number]        Press the Insert key\n#   Down[@<time>] [number]          Press the Down key\n#   Enter[@<time>] [number]         Press the Enter key\n#   Space[@<time>] [number]         Press the Space key\n#   Tab[@<time>] [number]           Press the Tab key\n#   Left[@<time>] [number]          Press the Left Arrow key\n#   Right[@<time>] [number]         Press the Right Arrow key\n#   Up[@<time>] [number]            Press the Up Arrow key\n#   Down[@<time>] [number]          Press the Down Arrow key\n#   PageUp[@<time>] [number]        Press the Page Up key\n#   PageDown[@<time>] [number]      Press the Page Down key\n#   Ctrl+<key>                      Press the Control key + <key> (e.g. Ctrl+C)\n#\n# Display:\n#   Hide                            Hide the subsequent commands from the output\n#   Show                            Show the subsequent commands in the output\n\nOutput demo.gif\n\nRequire echo\n\nSet Shell \"bash\"\n\n# This gives terminal 50 heigth and 100 wide\nSet FontSize 12 \nSet Width 900\nSet Height 840\n\n#Type \"stty size\" Sleep 100ms Enter\n\n# Setup split screen, hide tmux bar with host name and simple prompt without user name\n# zsh does not like comment lines and do not use history, so use sh\nHide\nType \"tmux\" Enter\nSleep 100ms\nType \"tmux split-window -l 40\" Sleep 100ms Enter\nType \"/bin/sh\" Enter\nType \"export PS1='> '\" Sleep 100ms Enter\nType \"reset\" Sleep 100ms Enter\nCtrl+B\nType \":set status off\" Enter\nCtrl+B Type \"o\" Sleep 100ms\nType \"/bin/sh\" Enter\nType \"export PS1=''\" Sleep 100ms Enter\nType \"reset\" Sleep 100ms Enter\n\n# Ready to show the windows\nShow\n\n# Currently we are in the top pane\nSleep 1s\nType \"# run the demo\" Sleep 100ms Enter\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"cargo run -q --example demo --features=termion,formatter\" Enter\n\nSleep 2\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# <States> at top are four tabs\" Enter\nType \"# In the middle is a smart widget with a target selector on the left\" Enter\nType \"# In the bottom left is a filtered tui logger widget\" Enter\nType \"# Bottom right is unfiltered tui logger widget with custom formatter ... MAYDAY\" Enter\nType \"# At the bottom is a progress task\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nSleep 1\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# select the target <background-task2> with cursor down/up\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nSleep 5\nDown Sleep 1\nDown Sleep 1\nSleep 1 \n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# focus on this target with f\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nType \"f\"\nSleep 5\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# focus on the previous target with Cursor up\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nUp Sleep 1\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# Remove focus with f\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nType \"f\"\nSleep 5\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# For all targets only show Errors using Cursor Left/Down\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nUp Sleep 100ms\nLeft Sleep 100ms\nLeft Sleep 100ms\nDown Sleep 100ms\nLeft Sleep 100ms\nLeft Sleep 100ms\nDown Sleep 100ms\nLeft Sleep 100ms\nLeft Sleep 100ms\nDown Sleep 100ms\nLeft Sleep 100ms\nLeft Sleep 100ms\nDown Sleep 100ms\nLeft Sleep 100ms\nLeft Sleep 100ms\n\nSleep 5\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# Disable recording of target trace with -\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nType \"-\" Sleep 100ms\nUp\nType \"-\" Sleep 100ms\nUp\nType \"-\" Sleep 100ms\nUp\nType \"-\" Sleep 100ms\nUp\nType \"-\" Sleep 100ms\n\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# Hide/Unhide the selector pane with h\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nType \"h\"\nSleep 3 \nType \"h\"\nSleep 3\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# Switch to another Tab - independent Widget\" Enter\nType \"# Notice the disabled recording of target trace\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nTab\nSleep 5\n\nCtrl+B Type \"o\" Sleep 100ms\nType \"# Quit with q\" Enter\nCtrl+B Type \"o\" Sleep 100ms\n\nType \"q\"\nSleep 2"
  },
  {
    "path": "examples/demo.rs",
    "content": "use std::{io, sync::mpsc, thread, time};\n\nuse log::*;\nuse ratatui::{prelude::*, widgets::*};\nuse std::env;\nuse tui_logger::*;\n\n/// Choose the backend depending on the selected feature (crossterm or termion). This is a mutually\n/// exclusive feature, so only one of them can be enabled at a time.\n#[cfg(all(feature = \"crossterm\", not(feature = \"termion\")))]\nuse self::crossterm_backend::*;\n#[cfg(all(feature = \"termion\", not(feature = \"crossterm\")))]\nuse self::termion_backend::*;\n#[cfg(not(any(feature = \"crossterm\", feature = \"termion\")))]\ncompile_error!(\"One of the features 'crossterm' or 'termion' must be enabled.\");\n#[cfg(all(feature = \"crossterm\", feature = \"termion\"))]\ncompile_error!(\"Only one of the features 'crossterm' and 'termion' can be enabled.\");\n\nstruct App {\n    mode: AppMode,\n    states: Vec<TuiWidgetState>,\n    tab_names: Vec<&'static str>,\n    selected_tab: usize,\n    progress_counter: Option<u16>,\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]\nenum AppMode {\n    #[default]\n    Run,\n    Quit,\n}\n\n#[derive(Debug)]\nenum AppEvent {\n    UiEvent(Event),\n    CounterChanged(Option<u16>),\n}\n\n//// Example for simple customized formatter\nstruct MyLogFormatter {}\nimpl LogFormatter for MyLogFormatter {\n    fn min_width(&self) -> u16 {\n        4\n    }\n    fn format(&self, _width: usize, evt: &ExtLogRecord) -> Vec<Line<'_>> {\n        let mut lines = vec![];\n        match evt.level {\n            log::Level::Error => {\n                let st = Style::new().red().bold();\n                let sp = Span::styled(\"======\", st);\n                let mayday = Span::from(\" MAYDAY MAYDAY \".to_string());\n                let sp2 = Span::styled(\"======\", st);\n                lines.push(Line::from(vec![sp, mayday, sp2]).alignment(Alignment::Center));\n                lines.push(\n                    Line::from(format!(\"{}: {}\", evt.level, evt.msg()))\n                        .alignment(Alignment::Center),\n                );\n            }\n            _ => {\n                lines.push(Line::from(format!(\"{}: {}\", evt.level, evt.msg())));\n            }\n        };\n\n        match evt.level {\n            log::Level::Error => {\n                let st = Style::new().blue().bold();\n                let sp = Span::styled(\"======\", st);\n                let mayday = Span::from(\" MAYDAY SEEN ? \".to_string());\n                let sp2 = Span::styled(\"======\", st);\n                lines.push(Line::from(vec![sp, mayday, sp2]).alignment(Alignment::Center));\n            }\n            _ => {}\n        };\n        lines\n    }\n}\n\nfn main() {\n    init_logger(LevelFilter::Trace).unwrap();\n    set_default_level(LevelFilter::Trace);\n\n    let mut dir = env::temp_dir();\n    dir.push(\"tui-logger_demo.log\");\n    let file_options = TuiLoggerFile::new(dir.to_str().unwrap())\n        .output_level(Some(TuiLoggerLevelOutput::Abbreviated))\n        .output_file(false)\n        .output_separator(':');\n    set_log_file(file_options);\n    debug!(target:\"App\", \"Logging to {}\", dir.to_str().unwrap());\n    debug!(target:\"App\", \"Logging initialized\");\n\n    let mut terminal = init_terminal().unwrap();\n    terminal.clear().unwrap();\n    terminal.hide_cursor().unwrap();\n\n    App::new().start(&mut terminal).unwrap();\n\n    restore_terminal().unwrap();\n    terminal.clear().unwrap();\n}\n\nimpl App {\n    pub fn new() -> App {\n        let states = vec![\n            TuiWidgetState::new().set_default_display_level(LevelFilter::Info),\n            TuiWidgetState::new().set_default_display_level(LevelFilter::Info),\n            TuiWidgetState::new().set_default_display_level(LevelFilter::Info),\n            TuiWidgetState::new().set_default_display_level(LevelFilter::Info),\n        ];\n\n        // Adding this line had provoked the bug as described in issue #69\n        // let states = states.into_iter().map(|s| s.set_level_for_target(\"some::logger\", LevelFilter::Off)).collect();\n        let tab_names = vec![\"State 1\", \"State 2\", \"State 3\", \"State 4\"];\n        App {\n            mode: AppMode::Run,\n            states,\n            tab_names,\n            selected_tab: 0,\n            progress_counter: None,\n        }\n    }\n\n    pub fn start<B: Backend>(mut self, terminal: &mut Terminal<B>) -> Result<(), B::Error> {\n        // Use an mpsc::channel to combine stdin events with app events\n        let (tx, rx) = mpsc::channel();\n        let event_tx = tx.clone();\n        let progress_tx = tx.clone();\n\n        thread::spawn(move || input_thread(event_tx).unwrap());\n        thread::spawn(move || progress_task(progress_tx).unwrap());\n        thread::spawn(move || background_task());\n        thread::spawn(move || background_task2());\n        thread::spawn(move || heart_task());\n\n        self.run(terminal, rx)\n    }\n\n    /// Main application loop\n    fn run<B: Backend>(\n        &mut self,\n        terminal: &mut Terminal<B>,\n        rx: mpsc::Receiver<AppEvent>,\n    ) -> Result<(), B::Error> {\n        for event in rx {\n            match event {\n                AppEvent::UiEvent(event) => self.handle_ui_event(event),\n                AppEvent::CounterChanged(value) => self.update_progress_bar(event, value),\n            }\n            if self.mode == AppMode::Quit {\n                break;\n            }\n            self.draw(terminal)?;\n        }\n        Ok(())\n    }\n\n    fn update_progress_bar(&mut self, event: AppEvent, value: Option<u16>) {\n        trace!(target: \"App\", \"Updating progress bar {:?}\",event);\n        self.progress_counter = value;\n        if value.is_none() {\n            info!(target: \"App\", \"Background task finished\");\n        }\n    }\n\n    fn handle_ui_event(&mut self, event: Event) {\n        debug!(target: \"App\", \"Handling UI event: {:?}\",event);\n        let state = self.selected_state();\n\n        if let Event::Key(key) = event {\n            #[cfg(feature = \"crossterm\")]\n            let code = key.code;\n\n            #[cfg(feature = \"termion\")]\n            let code = key;\n\n            match code.into() {\n                Key::Char('q') => self.mode = AppMode::Quit,\n                Key::Char('\\t') => self.next_tab(),\n                #[cfg(feature = \"crossterm\")]\n                Key::Tab => self.next_tab(),\n                Key::Char(' ') => state.transition(TuiWidgetEvent::SpaceKey),\n                Key::Esc => state.transition(TuiWidgetEvent::EscapeKey),\n                Key::PageUp => state.transition(TuiWidgetEvent::PrevPageKey),\n                Key::PageDown => state.transition(TuiWidgetEvent::NextPageKey),\n                Key::Up => state.transition(TuiWidgetEvent::UpKey),\n                Key::Down => state.transition(TuiWidgetEvent::DownKey),\n                Key::Left => state.transition(TuiWidgetEvent::LeftKey),\n                Key::Right => state.transition(TuiWidgetEvent::RightKey),\n                Key::Char('+') => state.transition(TuiWidgetEvent::PlusKey),\n                Key::Char('-') => state.transition(TuiWidgetEvent::MinusKey),\n                Key::Char('h') => state.transition(TuiWidgetEvent::HideKey),\n                Key::Char('f') => state.transition(TuiWidgetEvent::FocusKey),\n                _ => (),\n            }\n        }\n    }\n\n    fn selected_state(&mut self) -> &mut TuiWidgetState {\n        &mut self.states[self.selected_tab]\n    }\n\n    fn next_tab(&mut self) {\n        self.selected_tab = (self.selected_tab + 1) % self.tab_names.len();\n    }\n\n    fn draw<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<(), B::Error> {\n        terminal.draw(|frame| {\n            frame.render_widget(self, frame.area());\n        })?;\n        Ok(())\n    }\n}\n\n/// A simulated task that sends a counter value to the UI ranging from 0 to 100 every second.\nfn progress_task(tx: mpsc::Sender<AppEvent>) -> anyhow::Result<()> {\n    for progress in 0..100 {\n        debug!(target:\"progress-task\", \"Send progress to UI thread. Value: {:?}\", progress);\n        tx.send(AppEvent::CounterChanged(Some(progress)))?;\n\n        trace!(target:\"progress-task\", \"Sleep one second\");\n        thread::sleep(time::Duration::from_millis(1000));\n    }\n    info!(target:\"progress-task\", \"Progress task finished\");\n    tx.send(AppEvent::CounterChanged(None))?;\n    Ok(())\n}\n\n/// A background task that logs a log entry for each log level every second.\nfn background_task() {\n    loop {\n        error!(target:\"background-task\", \"an error\");\n        warn!(target:\"background-task\", \"a warning\");\n        info!(target:\"background-task\", \"a two line info\\nsecond line\");\n        debug!(target:\"background-task\", \"a debug\");\n        trace!(target:\"background-task\", \"a trace\");\n        thread::sleep(time::Duration::from_millis(1000));\n    }\n}\n\n/// A background task for long line\nfn background_task2() {\n    loop {\n        info!(target:\"background-task2\", \"This is a very long message, which should be wrapped on smaller screen by the standard formatter with an indentation of 9 characters.\");\n        thread::sleep(time::Duration::from_millis(2000));\n    }\n}\n\n/// A background task for utf8 example\nfn heart_task() {\n    let mut line = \"♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥\".to_string();\n    loop {\n        info!(target:\"heart-task\", \"{}\", line);\n        line = format!(\".{}\", line);\n        thread::sleep(time::Duration::from_millis(1500));\n    }\n}\n\nimpl Widget for &mut App {\n    fn render(self, area: Rect, buf: &mut Buffer) {\n        let progress_height = if self.progress_counter.is_some() {\n            3\n        } else {\n            0\n        };\n        let [tabs_area, smart_area, main_area, progress_area, help_area] = Layout::vertical([\n            Constraint::Length(3),\n            Constraint::Fill(50),\n            Constraint::Fill(30),\n            Constraint::Length(progress_height),\n            Constraint::Length(3),\n        ])\n        .areas(area);\n        // show two TuiWidgetState side-by-side\n        let [left, right] = Layout::horizontal([Constraint::Fill(1); 2]).areas(main_area);\n\n        Tabs::new(self.tab_names.iter().cloned())\n            .block(Block::default().title(\"States\").borders(Borders::ALL))\n            .highlight_style(Style::default().add_modifier(Modifier::REVERSED))\n            .select(self.selected_tab)\n            .render(tabs_area, buf);\n\n        TuiLoggerSmartWidget::default()\n            .style_error(Style::default().fg(Color::Red))\n            .style_debug(Style::default().fg(Color::Green))\n            .style_warn(Style::default().fg(Color::Yellow))\n            .style_trace(Style::default().fg(Color::Magenta))\n            .style_info(Style::default().fg(Color::Cyan))\n            .output_separator(':')\n            .output_timestamp(Some(\"%H:%M:%S\".to_string()))\n            .output_level(Some(TuiLoggerLevelOutput::Abbreviated))\n            .output_target(true)\n            .output_file(true)\n            .output_line(true)\n            .state(self.selected_state())\n            .render(smart_area, buf);\n\n        // An example of filtering the log output. The left TuiLoggerWidget is filtered to only show\n        // log entries for the \"App\" target. The right TuiLoggerWidget shows all log entries.\n        let filter_state = TuiWidgetState::new()\n            .set_default_display_level(LevelFilter::Off)\n            .set_level_for_target(\"App\", LevelFilter::Debug)\n            .set_level_for_target(\"background-task\", LevelFilter::Info);\n        let mut formatter: Option<Box<dyn LogFormatter>> = None;\n        if cfg!(feature = \"formatter\") {\n            formatter = Some(Box::new(MyLogFormatter {}));\n        }\n\n        TuiLoggerWidget::default()\n            .block(Block::bordered().title(\"Filtered TuiLoggerWidget\"))\n            .output_separator('|')\n            .output_timestamp(Some(\"%F %H:%M:%S%.3f\".to_string()))\n            .output_level(Some(TuiLoggerLevelOutput::Long))\n            .output_target(false)\n            .output_file(false)\n            .output_line(false)\n            .style(Style::default().fg(Color::White))\n            .state(&filter_state)\n            .render(left, buf);\n\n        TuiLoggerWidget::default()\n            .block(Block::bordered().title(\"Unfiltered TuiLoggerWidget\"))\n            .opt_formatter(formatter)\n            .output_separator('|')\n            .output_timestamp(Some(\"%F %H:%M:%S%.3f\".to_string()))\n            .output_level(Some(TuiLoggerLevelOutput::Long))\n            .output_target(false)\n            .output_file(false)\n            .output_line(false)\n            .style(Style::default().fg(Color::White))\n            .render(right, buf);\n\n        if let Some(percent) = self.progress_counter {\n            Gauge::default()\n                .block(Block::bordered().title(\"progress-task\"))\n                .gauge_style((Color::White, Modifier::ITALIC))\n                .percent(percent)\n                .render(progress_area, buf);\n        }\n        if area.width > 40 {\n            Text::from(vec![\n                \"Q: Quit | Tab: Switch state | ↑/↓: Select target | f: Focus target\".into(),\n                \"←/→: Display level | +/-: Filter level | Space: Toggle hidden targets\".into(),\n                \"h: Hide target selector | PageUp/Down: Scroll | Esc: Cancel scroll\".into(),\n            ])\n            .style(Color::Gray)\n            .centered()\n            .render(help_area, buf);\n        }\n    }\n}\n\n/// A module for crossterm specific code\n#[cfg(feature = \"crossterm\")]\nmod crossterm_backend {\n    use super::*;\n\n    pub use crossterm::{\n        event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode as Key},\n        execute,\n        terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},\n    };\n\n    pub fn init_terminal() -> io::Result<Terminal<impl Backend>> {\n        trace!(target:\"crossterm\", \"Initializing terminal\");\n        enable_raw_mode()?;\n        execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;\n        let backend = CrosstermBackend::new(io::stdout());\n        Terminal::new(backend)\n    }\n\n    pub fn restore_terminal() -> io::Result<()> {\n        trace!(target:\"crossterm\", \"Restoring terminal\");\n        disable_raw_mode()?;\n        execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)\n    }\n\n    pub fn input_thread(tx_event: mpsc::Sender<AppEvent>) -> anyhow::Result<()> {\n        trace!(target:\"crossterm\", \"Starting input thread\");\n        while let Ok(event) = event::read() {\n            trace!(target:\"crossterm\", \"Stdin event received {:?}\", event);\n            tx_event.send(AppEvent::UiEvent(event))?;\n        }\n        Ok(())\n    }\n}\n\n/// A module for termion specific code\n#[cfg(feature = \"termion\")]\nmod termion_backend {\n    use super::*;\n    use termion::screen::IntoAlternateScreen;\n    pub use termion::{\n        event::{Event, Key},\n        input::{MouseTerminal, TermRead},\n        raw::IntoRawMode,\n    };\n\n    pub fn init_terminal() -> io::Result<Terminal<impl Backend>> {\n        trace!(target:\"termion\", \"Initializing terminal\");\n        let stdout = io::stdout().into_raw_mode()?;\n        let stdout = MouseTerminal::from(stdout);\n        let stdout = stdout.into_alternate_screen()?;\n        let backend = TermionBackend::new(stdout);\n        Terminal::new(backend)\n    }\n\n    pub fn restore_terminal() -> io::Result<()> {\n        trace!(target:\"termion\", \"Restoring terminal\");\n        Ok(())\n    }\n\n    pub fn input_thread(tx_event: mpsc::Sender<AppEvent>) -> anyhow::Result<()> {\n        trace!(target:\"termion\", \"Starting input thread\");\n        for event in io::stdin().events() {\n            let event = event?;\n            trace!(target:\"termion\", \"Stdin event received {:?}\", event);\n            tx_event.send(AppEvent::UiEvent(event))?;\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "examples/slog.rs_outdated",
    "content": "//! Demo of slog-based logging\n//!\n\nuse slog::{debug, error, info, o, trace, warn, Drain, Logger};\nuse std::cell::RefCell;\nuse std::io;\nuse std::rc::Rc;\nuse std::sync::mpsc;\nuse std::{thread, time};\n\nuse termion::event::{self, Key};\nuse termion::input::{MouseTerminal, TermRead};\nuse termion::raw::IntoRawMode;\nuse termion::screen::AlternateScreen;\n\nuse tui::backend::{Backend, TermionBackend};\nuse tui::layout::{Constraint, Direction, Layout, Rect};\nuse tui::style::{Color, Modifier, Style};\nuse tui::text::Spans;\nuse tui::widgets::{Block, Borders, Gauge, Tabs};\nuse tui::Frame;\nuse tui::Terminal;\nuse tui_logger::*;\n\nstruct App {\n    states: Vec<TuiWidgetState>,\n    dispatcher: Rc<RefCell<Dispatcher<event::Event>>>,\n    selected_tab: Rc<RefCell<usize>>,\n    opt_info_cnt: Option<u16>,\n}\n\n#[derive(Debug)]\nenum AppEvent {\n    Termion(termion::event::Event),\n    LoopCnt(Option<u16>),\n}\n\nfn demo_application(log: Logger, tx: mpsc::Sender<AppEvent>) {\n    let one_second = time::Duration::from_millis(1_000);\n    let mut lp_cnt = (1..=100).into_iter();\n    loop {\n        trace!(log, \"Sleep one second\");\n        thread::sleep(one_second);\n        trace!(log, \"Issue log entry for each level\");\n        error!(log, \"an error\");\n        warn!(log, \"a warning\");\n        trace!(log, \"a trace\");\n        debug!(log, \"a debug\");\n        info!(log, \"an info\");\n        tx.send(AppEvent::LoopCnt(lp_cnt.next())).unwrap();\n    }\n}\n\nfn main() -> std::result::Result<(), std::io::Error> {\n    let drain = tui_logger::slog_drain().fuse();\n    let log = slog::Logger::root(drain, o!());\n    info!(log, \"Start demo\");\n\n    let stdout = io::stdout().into_raw_mode().unwrap();\n    let stdout = MouseTerminal::from(stdout);\n    let stdout = AlternateScreen::from(stdout);\n    let backend = TermionBackend::new(stdout);\n    let mut terminal = Terminal::new(backend).unwrap();\n    let stdin = io::stdin();\n    terminal.clear().unwrap();\n    terminal.hide_cursor().unwrap();\n\n    // Use an mpsc::channel to combine stdin events with app events\n    let (tx, rx) = mpsc::channel();\n    let tx_event = tx.clone();\n    let log_thread = log.clone();\n    thread::spawn(move || {\n        for c in stdin.events() {\n            trace!(log_thread, \"Stdin event received {:?}\", c);\n            tx_event.send(AppEvent::Termion(c.unwrap())).unwrap();\n        }\n    });\n    let log_thread = log.clone();\n    thread::spawn(move || {\n        demo_application(log_thread, tx);\n    });\n\n    let mut app = App {\n        states: vec![],\n        dispatcher: Rc::new(RefCell::new(Dispatcher::<event::Event>::new())),\n        selected_tab: Rc::new(RefCell::new(0)),\n        opt_info_cnt: None,\n    };\n\n    let log_thread = log.clone();\n    // Here is the main loop\n    for evt in rx {\n        trace!(log_thread, \"{:?}\", evt);\n        match evt {\n            AppEvent::Termion(evt) => {\n                if !app.dispatcher.borrow_mut().dispatch(&evt) {\n                    if evt == termion::event::Event::Key(event::Key::Char('q')) {\n                        break;\n                    }\n                }\n            }\n            AppEvent::LoopCnt(opt_cnt) => {\n                app.opt_info_cnt = opt_cnt;\n            }\n        }\n        terminal.draw(|mut f| {\n            let size = f.size();\n            draw_frame(&mut f, size, &mut app);\n        })?;\n    }\n    terminal.show_cursor().unwrap();\n    terminal.clear().unwrap();\n\n    Ok(())\n}\n\nfn draw_frame<B: Backend>(t: &mut Frame<B>, size: Rect, app: &mut App) {\n    let tabs = vec![\"V1\", \"V2\", \"V3\", \"V4\"];\n    let tabs = tabs.into_iter().map(|t| Spans::from(t)).collect::<Vec<_>>();\n    let sel = *app.selected_tab.borrow();\n    let sel_tab = if sel + 1 < tabs.len() { sel + 1 } else { 0 };\n    let sel_stab = if sel > 0 { sel - 1 } else { tabs.len() - 1 };\n    let v_sel = app.selected_tab.clone();\n\n    // Switch between tabs via Tab and Shift-Tab\n    // At least on my computer the 27/91/90 equals a Shift-Tab\n    app.dispatcher.borrow_mut().clear();\n    app.dispatcher.borrow_mut().add_listener(move |evt| {\n        if &event::Event::Unsupported(vec![27, 91, 90]) == evt {\n            *v_sel.borrow_mut() = sel_stab;\n            true\n        } else if &event::Event::Key(Key::Char('\\t')) == evt {\n            *v_sel.borrow_mut() = sel_tab;\n            true\n        } else {\n            false\n        }\n    });\n    if app.states.len() <= sel {\n        app.states.push(TuiWidgetState::new());\n    }\n\n    let block = Block::default().borders(Borders::ALL);\n    t.render_widget(block, size);\n\n    let mut constraints = vec![\n        Constraint::Length(3),\n        Constraint::Percentage(50),\n        Constraint::Min(3),\n    ];\n    if app.opt_info_cnt.is_some() {\n        constraints.push(Constraint::Length(3));\n    }\n    let chunks = Layout::default()\n        .direction(Direction::Vertical)\n        .constraints(constraints)\n        .split(size);\n\n    let tabs = Tabs::new(tabs)\n        .block(Block::default().borders(Borders::ALL))\n        .highlight_style(Style::default().add_modifier(Modifier::REVERSED))\n        .select(sel);\n    t.render_widget(tabs, chunks[0]);\n\n    let tui_sm = TuiLoggerSmartWidget::default()\n        .border_style(Style::default().fg(Color::Black))\n        .style_error(Style::default().fg(Color::Red))\n        .style_debug(Style::default().fg(Color::Green))\n        .style_warn(Style::default().fg(Color::Yellow))\n        .style_trace(Style::default().fg(Color::Magenta))\n        .style_info(Style::default().fg(Color::Cyan))\n        .state(&mut app.states[sel])\n        .dispatcher(app.dispatcher.clone());\n    t.render_widget(tui_sm, chunks[1]);\n    let tui_w: TuiLoggerWidget = TuiLoggerWidget::default()\n        .block(\n            Block::default()\n                .title(\"Independent Tui Logger View\")\n                //.title_style(Style::default().fg(Color::White).bg(Color::Black))\n                .border_style(Style::default().fg(Color::White).bg(Color::Black))\n                .borders(Borders::ALL),\n        )\n        .style(Style::default().fg(Color::White).bg(Color::Black));\n    t.render_widget(tui_w, chunks[2]);\n    if let Some(percent) = app.opt_info_cnt {\n        let guage = Gauge::default()\n            .block(Block::default().borders(Borders::ALL).title(\"Progress\"))\n            .style(\n                Style::default()\n                    .fg(Color::Black)\n                    .bg(Color::White)\n                    .add_modifier(Modifier::ITALIC),\n            )\n            .percent(percent);\n        t.render_widget(guage, chunks[3]);\n    }\n}\n"
  },
  {
    "path": "src/circular.rs",
    "content": "use std::iter;\n/// CircularBuffer is used to store the last elements of an endless sequence.\n/// Oldest elements will be overwritten. The implementation focus on\n/// speed. So memory allocations are avoided.\n///\n/// Usage example:\n///```\n/// extern crate tui_logger;\n///\n/// use tui_logger::CircularBuffer;\n///\n/// let mut cb : CircularBuffer<u64> = CircularBuffer::new(5);\n/// cb.push(1);\n/// cb.push(2);\n/// cb.push(3);\n/// cb.push(4);\n/// cb.push(5);\n/// cb.push(6); // This will overwrite the first element\n///\n/// // Total elements pushed into the buffer is 6.\n/// assert_eq!(6,cb.total_elements());\n///\n/// // Thus the buffer has wrapped around.\n/// assert_eq!(true,cb.has_wrapped());\n///\n/// /// Iterate through the elements:\n/// {\n///     let mut iter = cb.iter();\n///     assert_eq!(Some(&2), iter.next());\n///     assert_eq!(Some(&3), iter.next());\n///     assert_eq!(Some(&4), iter.next());\n///     assert_eq!(Some(&5), iter.next());\n///     assert_eq!(Some(&6), iter.next());\n///     assert_eq!(None, iter.next());\n/// }\n///\n/// /// Iterate backwards through the elements:\n/// {\n///     let mut iter = cb.rev_iter();\n///     assert_eq!(Some(&6), iter.next());\n///     assert_eq!(Some(&5), iter.next());\n///     assert_eq!(Some(&4), iter.next());\n///     assert_eq!(Some(&3), iter.next());\n///     assert_eq!(Some(&2), iter.next());\n///     assert_eq!(None, iter.next());\n/// }\n///\n/// // The elements in the buffer are now:\n/// assert_eq!(vec![2,3,4,5,6],cb.take());\n///\n/// // After taking all elements, the buffer is empty.\n/// let now_empty : Vec<u64> = vec![];\n/// assert_eq!(now_empty,cb.take());\n///```\npub struct CircularBuffer<T> {\n    buffer: Vec<T>,\n    next_write_pos: usize,\n}\n#[allow(dead_code)]\nimpl<T> CircularBuffer<T> {\n    /// Create a new CircularBuffer, which can hold max_depth elements\n    pub fn new(max_depth: usize) -> CircularBuffer<T> {\n        CircularBuffer {\n            buffer: Vec::with_capacity(max_depth),\n            next_write_pos: 0,\n        }\n    }\n    /// Return the number of elements present in the buffer\n    pub fn len(&self) -> usize {\n        self.buffer.len()\n    }\n    pub fn is_empty(&self) -> bool {\n        self.buffer.is_empty()\n    }\n    pub fn capacity(&self) -> usize {\n        self.buffer.capacity()\n    }\n    /// Next free index of the buffer\n    pub fn first_index(&self) -> Option<usize> {\n        if self.next_write_pos == 0 {\n            None\n        } else if self.next_write_pos < self.buffer.capacity() {\n            Some(0)\n        } else {\n            Some(self.next_write_pos - self.buffer.capacity())\n        }\n    }\n    pub fn last_index(&self) -> Option<usize> {\n        if self.next_write_pos == 0 {\n            None\n        } else {\n            Some(self.next_write_pos - 1)\n        }\n    }\n    pub fn element_at_index(&self, index: usize) -> Option<&T> {\n        let max_depth = self.buffer.capacity();\n        if index >= self.next_write_pos {\n            return None;\n        }\n        if index + max_depth < self.next_write_pos {\n            return None;\n        }\n        Some(&self.buffer[index % max_depth])\n    }\n    /// Push a new element into the buffer.\n    /// Until the capacity is reached, elements are pushed.\n    /// Afterwards the oldest elements will be overwritten.\n    pub fn push(&mut self, elem: T) {\n        let max_depth = self.buffer.capacity();\n        if self.buffer.len() < max_depth {\n            self.buffer.push(elem);\n        } else {\n            self.buffer[self.next_write_pos % max_depth] = elem;\n        }\n        self.next_write_pos += 1;\n    }\n    /// Take out all elements from the buffer, leaving an empty buffer behind\n    pub fn take(&mut self) -> Vec<T> {\n        let mut consumed = vec![];\n        let max_depth = self.buffer.capacity();\n        if self.buffer.len() < max_depth {\n            consumed.append(&mut self.buffer);\n        } else {\n            let pos = self.next_write_pos % max_depth;\n            let mut xvec = self.buffer.split_off(pos);\n            consumed.append(&mut xvec);\n            consumed.append(&mut self.buffer)\n        }\n        self.next_write_pos = 0;\n        consumed\n    }\n    /// Total number of elements pushed into the buffer.\n    pub fn total_elements(&self) -> usize {\n        self.next_write_pos\n    }\n    /// If has_wrapped() is true, then elements have been overwritten\n    pub fn has_wrapped(&self) -> bool {\n        self.next_write_pos > self.buffer.capacity()\n    }\n    /// Return an iterator to step through all elements in the sequence,\n    /// as these have been pushed (FIFO)\n    pub fn iter(&mut self) -> iter::Chain<std::slice::Iter<'_, T>, std::slice::Iter<'_, T>> {\n        let max_depth = self.buffer.capacity();\n        if self.next_write_pos <= max_depth {\n            // If buffer is not completely filled, then just iterate through it\n            self.buffer.iter().chain(self.buffer[..0].iter())\n        } else {\n            let wrap = self.next_write_pos % max_depth;\n            let it_end = self.buffer[..wrap].iter();\n            let it_start = self.buffer[wrap..].iter();\n            it_start.chain(it_end)\n        }\n    }\n    /// Return an iterator to step through all elements in the reverse sequence,\n    /// as these have been pushed (LIFO)\n    pub fn rev_iter(\n        &mut self,\n    ) -> iter::Chain<std::iter::Rev<std::slice::Iter<'_, T>>, std::iter::Rev<std::slice::Iter<'_, T>>>\n    {\n        let max_depth = self.buffer.capacity();\n        if self.next_write_pos <= max_depth {\n            // If buffer is not completely filled, then just iterate through it\n            self.buffer\n                .iter()\n                .rev()\n                .chain(self.buffer[..0].iter().rev())\n        } else {\n            let wrap = self.next_write_pos % max_depth;\n            let it_end = self.buffer[..wrap].iter().rev();\n            let it_start = self.buffer[wrap..].iter().rev();\n            it_end.chain(it_start)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn circular_buffer() {\n        use crate::CircularBuffer;\n\n        let mut cb: CircularBuffer<u64> = CircularBuffer::new(5);\n\n        // Empty buffer\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), None);\n            assert_eq!(cb.last_index(), None);\n        }\n\n        // Push in a value\n        cb.push(1);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(0));\n            assert_eq!(cb.last_index(), Some(0));\n        }\n\n        // Push in a value\n        cb.push(2);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(0));\n            assert_eq!(cb.last_index(), Some(1));\n        }\n\n        // Push in a value\n        cb.push(3);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(0));\n            assert_eq!(cb.last_index(), Some(2));\n        }\n\n        // Push in a value\n        cb.push(4);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(0));\n            assert_eq!(cb.last_index(), Some(3));\n        }\n\n        // Push in a value\n        cb.push(5);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(0));\n            assert_eq!(cb.last_index(), Some(4));\n        }\n\n        // Push in a value\n        cb.push(6);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(1));\n            assert_eq!(cb.last_index(), Some(5));\n        }\n\n        // Push in a value\n        cb.push(7);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(2));\n            assert_eq!(cb.last_index(), Some(6));\n        }\n\n        // Push in a value\n        cb.push(8);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), Some(&8));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(3));\n            assert_eq!(cb.last_index(), Some(7));\n        }\n\n        // Push in a value\n        cb.push(9);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), Some(&8));\n            assert_eq!(cb_iter.next(), Some(&9));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(4));\n            assert_eq!(cb.last_index(), Some(8));\n        }\n\n        // Push in a value\n        cb.push(10);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), Some(&8));\n            assert_eq!(cb_iter.next(), Some(&9));\n            assert_eq!(cb_iter.next(), Some(&10));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(5));\n            assert_eq!(cb.last_index(), Some(9));\n        }\n\n        // Push in a value\n        cb.push(11);\n        {\n            let mut cb_iter = cb.iter();\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), Some(&8));\n            assert_eq!(cb_iter.next(), Some(&9));\n            assert_eq!(cb_iter.next(), Some(&10));\n            assert_eq!(cb_iter.next(), Some(&11));\n            assert_eq!(cb_iter.next(), None);\n            assert_eq!(cb.first_index(), Some(6));\n            assert_eq!(cb.last_index(), Some(10));\n            assert_eq!(cb.element_at_index(5), None);\n            assert_eq!(cb.element_at_index(6), Some(&7));\n            assert_eq!(cb.element_at_index(10), Some(&11));\n            assert_eq!(cb.element_at_index(11), None);\n        }\n    }\n    #[test]\n    fn circular_buffer_rev() {\n        use crate::CircularBuffer;\n\n        let mut cb: CircularBuffer<u64> = CircularBuffer::new(5);\n\n        // Empty buffer\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(1);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(2);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(3);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(4);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(5);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), Some(&1));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(6);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), Some(&2));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(7);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), Some(&3));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(8);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&8));\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), Some(&4));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(9);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&9));\n            assert_eq!(cb_iter.next(), Some(&8));\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), Some(&5));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(10);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&10));\n            assert_eq!(cb_iter.next(), Some(&9));\n            assert_eq!(cb_iter.next(), Some(&8));\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), Some(&6));\n            assert_eq!(cb_iter.next(), None);\n        }\n\n        // Push in a value\n        cb.push(11);\n        {\n            let mut cb_iter = cb.rev_iter();\n            assert_eq!(cb_iter.next(), Some(&11));\n            assert_eq!(cb_iter.next(), Some(&10));\n            assert_eq!(cb_iter.next(), Some(&9));\n            assert_eq!(cb_iter.next(), Some(&8));\n            assert_eq!(cb_iter.next(), Some(&7));\n            assert_eq!(cb_iter.next(), None);\n        }\n    }\n    #[test]\n    fn total_elements() {\n        use crate::CircularBuffer;\n\n        let mut cb: CircularBuffer<u64> = CircularBuffer::new(5);\n\n        assert_eq!(0, cb.total_elements());\n        for i in 1..20 {\n            cb.push(i);\n            assert_eq!(i as usize, cb.total_elements());\n        }\n    }\n    #[test]\n    fn has_wrapped() {\n        use crate::CircularBuffer;\n\n        let mut cb: CircularBuffer<u64> = CircularBuffer::new(5);\n\n        assert_eq!(0, cb.total_elements());\n        for i in 1..20 {\n            cb.push(i);\n            assert_eq!(i >= 6, cb.has_wrapped());\n        }\n    }\n    #[test]\n    fn take() {\n        use crate::CircularBuffer;\n\n        let mut cb: CircularBuffer<u64> = CircularBuffer::new(5);\n        for i in 1..5 {\n            cb.push(i);\n        }\n        assert_eq!(vec![1, 2, 3, 4], cb.take());\n\n        for i in 1..6 {\n            cb.push(i);\n        }\n        assert_eq!(vec![1, 2, 3, 4, 5], cb.take());\n\n        for i in 1..7 {\n            cb.push(i);\n        }\n        assert_eq!(vec![2, 3, 4, 5, 6], cb.take());\n\n        let mut cb: CircularBuffer<u64> = CircularBuffer::new(5);\n        for i in 1..20 {\n            cb.push(i);\n        }\n        assert_eq!(vec![15, 16, 17, 18, 19], cb.take());\n    }\n}\n"
  },
  {
    "path": "src/config/level_config.rs",
    "content": "use std::collections::hash_map::Iter;\nuse std::collections::hash_map::Keys;\nuse std::collections::HashMap;\n\nuse log::LevelFilter;\n\n/// LevelConfig stores the relation target->LevelFilter in a hash table.\n///\n/// The table supports copying from the logger system LevelConfig to\n/// a widget's LevelConfig. In order to detect changes, the generation\n/// of the hash table is compared with any previous copied table.\n/// On every change the generation is incremented.\n#[derive(Default)]\npub struct LevelConfig {\n    config: HashMap<String, LevelFilter>,\n    generation: u64,\n    origin_generation: u64,\n    default_display_level: Option<LevelFilter>,\n}\nimpl LevelConfig {\n    /// Create an empty LevelConfig.\n    pub fn new() -> LevelConfig {\n        LevelConfig {\n            config: HashMap::new(),\n            generation: 0,\n            origin_generation: 0,\n            default_display_level: None,\n        }\n    }\n    /// Set for a given target the LevelFilter in the table and update the generation.\n    pub fn set(&mut self, target: &str, level: LevelFilter) {\n        if let Some(lev) = self.config.get_mut(target) {\n            if *lev != level {\n                *lev = level;\n                self.generation += 1;\n            }\n            return;\n        }\n        self.config.insert(target.to_string(), level);\n        self.generation += 1;\n    }\n    /// Set default display level filter for new targets - independent from recording\n    pub fn set_default_display_level(&mut self, level: LevelFilter) {\n        self.default_display_level = Some(level);\n    }\n    /// Get default display level filter for new targets - independent from recording\n    pub fn get_default_display_level(&self) -> Option<LevelFilter> {\n        self.default_display_level\n    }\n    /// Retrieve an iter for all the targets stored in the hash table.\n    pub fn keys(&self) -> Keys<'_, String, LevelFilter> {\n        self.config.keys()\n    }\n    /// Get the levelfilter for a given target.\n    pub fn get(&self, target: &str) -> Option<LevelFilter> {\n        self.config.get(target).cloned()\n    }\n    /// Retrieve an iterator through all entries of the table.\n    pub fn iter(&self) -> Iter<'_, String, LevelFilter> {\n        self.config.iter()\n    }\n    /// Merge an origin LevelConfig into this one.\n    ///\n    /// The origin table defines the maximum levelfilter.\n    /// If this table has a higher levelfilter, then it will be reduced.\n    /// Unknown targets will be copied to this table.\n    pub(crate) fn merge(&mut self, origin: &LevelConfig) {\n        if self.origin_generation != origin.generation {\n            for (target, origin_levelfilter) in origin.iter() {\n                if let Some(levelfilter) = self.get(target) {\n                    if levelfilter <= *origin_levelfilter {\n                        continue;\n                    }\n                }\n                let levelfilter = self\n                    .default_display_level\n                    .map(|lvl| {\n                        if lvl > *origin_levelfilter {\n                            *origin_levelfilter\n                        } else {\n                            lvl\n                        }\n                    })\n                    .unwrap_or(*origin_levelfilter);\n                self.set(target, levelfilter);\n            }\n            self.generation = origin.generation;\n        }\n    }\n}\n"
  },
  {
    "path": "src/config/mod.rs",
    "content": "pub mod level_config;\n\npub use level_config::LevelConfig;\n"
  },
  {
    "path": "src/file.rs",
    "content": "use std::fs::{File, OpenOptions};\n\nuse crate::logger::TuiLoggerLevelOutput;\n\n/// This closely follows the options of [``TuiLoggerSmartWidget``] but is used of logging to a file.\npub struct TuiLoggerFile {\n    pub(crate) dump: File,\n    pub(crate) format_separator: char,\n    pub(crate) timestamp_fmt: Option<String>,\n    pub(crate) format_output_target: bool,\n    pub(crate) format_output_file: bool,\n    pub(crate) format_output_line: bool,\n    pub(crate) format_output_level: Option<TuiLoggerLevelOutput>,\n}\n\nimpl TuiLoggerFile {\n    pub fn new(fname: &str) -> Self {\n        TuiLoggerFile {\n            dump: OpenOptions::new()\n                .create(true)\n                .append(true)\n                .open(fname)\n                .expect(\"Failed to open dump File\"),\n            format_separator: ':',\n            timestamp_fmt: Some(\"[%Y:%m:%d %H:%M:%S]\".to_string()),\n            format_output_file: true,\n            format_output_line: true,\n            format_output_target: true,\n            format_output_level: Some(TuiLoggerLevelOutput::Long),\n        }\n    }\n    pub fn output_target(mut self, enabled: bool) -> Self {\n        self.format_output_target = enabled;\n        self\n    }\n    pub fn output_file(mut self, enabled: bool) -> Self {\n        self.format_output_file = enabled;\n        self\n    }\n    pub fn output_line(mut self, enabled: bool) -> Self {\n        self.format_output_line = enabled;\n        self\n    }\n    pub fn output_timestamp(mut self, fmt: Option<String>) -> Self {\n        self.timestamp_fmt = fmt;\n        self\n    }\n    pub fn output_separator(mut self, sep: char) -> Self {\n        self.format_separator = sep;\n        self\n    }\n    pub fn output_level(mut self, level: Option<TuiLoggerLevelOutput>) -> Self {\n        self.format_output_level = level;\n        self\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! # Logger with smart widget for the `tui` and `ratatui` crate\n//!\n//! [![dependency status](https://deps.rs/repo/github/gin66/tui-logger/status.svg?service=github&nocache=0_9_1)](https://deps.rs/repo/github/gin66/tui-logger)\n//! ![Build examples](https://github.com/gin66/tui-logger/workflows/Build%20examples/badge.svg?service=github)\n//!\n//!\n//! ## Demo of the widget\n//!\n//! ![Demo](https://github.com/gin66/tui-logger/blob/master/doc/demo_v0.14.4.gif?raw=true)\n//!\n//! ## Documentation\n//!\n//! [Documentation](https://docs.rs/tui-logger/latest/tui_logger/)\n//!\n//! I have stumbled over an excellent AI-generated description of `tui-logger`, which provides surprisingly deep and (mostly) correct implementation details.\n//! It would have costed me many days to write an equally good description with so many details and diagrams.\n//! This docu can be found [here](https://deepwiki.com/gin66/tui-logger).\n//!\n//! ## Important note for `tui`\n//!\n//! The `tui` crate has been archived and `ratatui` has taken over.\n//! In order to avoid supporting compatibility for an inactive crate,\n//! the v0.9.x releases are the last to support `tui`. In case future bug fixes\n//! are needed, the branch `tui_legacy` has been created to track changes to 0.9.x releases.\n//!\n//! Starting with v0.10 `tui-logger` is `ratatui` only.\n//!\n//! ## Features\n//!\n//! - [X] Logger implementation for the `log` crate\n//! - [X] Logger enable/disable detection via hash table (avoid string compare)\n//! - [X] Hot logger code only copies enabled log messages with timestamp into a circular buffer\n//! - [X] Widgets/move_message() retrieve captured log messages from hot circular buffer\n//! - [X] Lost message detection due to circular buffer\n//! - [X] Log filtering performed on log record target\n//! - [X] Simple Widgets to view logs and configure debuglevel per target\n//! - [X] Logging of enabled logs to file\n//! - [X] Scrollback in log history\n//! - [x] Title of target and log pane can be configured\n//! - [X] `slog` support, providing a Drain to integrate into your `slog` infrastructure\n//! - [X] `tracing` support\n//! - [X] Support to use custom formatter for log events\n//! - [X] Configurable by environment variables\n//! - [ ] Allow configuration of target dependent loglevel specifically for file logging\n//! - [X] Avoid duplicating of module_path and filename in every log record\n//! - [ ] Simultaneous modification of all targets' display/hot logging loglevel by key command\n//!\n//! ## Smart Widget\n//!\n//! Smart widget consists of two widgets. Left is the target selector widget and\n//! on the right side the logging messages view scrolling up. The target selector widget\n//! can be hidden/shown during runtime via key command.\n//! The key command to be provided to the TuiLoggerWidget via transition() function.\n//!\n//! The target selector widget looks like this:\n//!\n//! ![widget](https://github.com/gin66/tui-logger/blob/master/doc/example.png?raw=true)\n//!\n//! It controls:\n//!\n//! - Capturing of log messages by the logger\n//! - Selection of levels for display in the logging message view\n//!\n//! The two columns have the following meaning:\n//!\n//! - Code EWIDT: E stands for Error, W for Warn, Info, Debug and Trace.\n//!   + Inverted characters (EWIDT) are enabled log levels in the view\n//!   + Normal characters show enabled capturing of a log level per target\n//!   + If any of EWIDT are not shown, then the respective log level is not captured\n//! - Target of the log events can be defined in the log e.g. `warn!(target: \"demo\", \"Log message\");`\n//!\n//! ## Smart Widget Key Commands\n//! ```ignore\n//! |  KEY     | ACTION\n//! |----------|-----------------------------------------------------------|\n//! | h        | Toggles target selector widget hidden/visible\n//! | f        | Toggle focus on the selected target only\n//! | UP       | Select previous target in target selector widget\n//! | DOWN     | Select next target in target selector widget\n//! | LEFT     | Reduce SHOWN (!) log messages by one level\n//! | RIGHT    | Increase SHOWN (!) log messages by one level\n//! | -        | Reduce CAPTURED (!) log messages by one level\n//! | +        | Increase CAPTURED (!) log messages by one level\n//! | PAGEUP   | Enter Page Mode and scroll approx. half page up in log history.\n//! | PAGEDOWN | Only in page mode: scroll 10 events down in log history.\n//! | ESCAPE   | Exit page mode and go back to scrolling mode\n//! | SPACE    | Toggles hiding of targets, which have logfilter set to off\n//! ```\n//!\n//! The mapping of key to action has to be done in the application. The respective TuiWidgetEvent\n//! has to be provided to TuiWidgetState::transition().\n//!\n//! Remark to the page mode: The timestamp of the event at event history's bottom line is used as\n//! reference. This means, changing the filters in the EWIDT/focus from the target selector window\n//! should work as expected without jumps in the history. The page next/forward advances as\n//! per visibility of the events.\n//!\n//! ## Basic usage to initialize logger-system:\n//! ```rust\n//! #[macro_use]\n//! extern crate log;\n//! //use tui_logger;\n//!\n//! fn main() {\n//!     // Early initialization of the logger\n//!\n//!     // Set max_log_level to Trace\n//!     tui_logger::init_logger(log::LevelFilter::Trace).unwrap();\n//!\n//!     // Set default level for unknown targets to Trace\n//!     tui_logger::set_default_level(log::LevelFilter::Trace);\n//!\n//!     // code....\n//! }\n//! ```\n//!\n//! For use of the widget please check examples/demo.rs\n//!\n//! ## Demo\n//!\n//! Run demo using termion:\n//!\n//! ```ignore\n//! cargo run --example demo --features termion\n//! ```\n//!\n//! Run demo with crossterm:\n//!\n//! ```ignore\n//! cargo run --example demo --features crossterm\n//! ```\n//!\n//! Run demo using termion and simple custom formatter in bottom right log widget:\n//!\n//! ```ignore\n//! cargo run --example demo --features termion,formatter\n//! ```\n//!\n//! ## Configuration by environment variables\n//!\n//! `tui.logger` uses `env-filter` crate to support configuration by a string or an environment variable.\n//! This is an opt-in by call to one of these two functions.\n//! ```rust\n//! pub fn set_env_filter_from_string(filterstring: &str) {}\n//! pub fn set_env_filter_from_env(env_name: Option<&str>) {}\n//! ```\n//! Default environment variable name is `RUST_LOG`.\n//!\n//! ## `slog` support\n//!\n//! `tui-logger` provides a [`TuiSlogDrain`] which implements `slog::Drain` and will route all records\n//! it receives to the `tui-logger` widget.\n//!\n//! Enabled by feature \"slog-support\"\n//!\n//! ## `tracing-subscriber` support\n//!\n//! `tui-logger` provides a [`TuiTracingSubscriberLayer`] which implements\n//! `tracing_subscriber::Layer` and will collect all events\n//! it receives to the `tui-logger` widget\n//!\n//! Enabled by feature \"tracing-support\"\n//!\n//! ## Custom filtering\n//! ```rust\n//! #[macro_use]\n//! extern crate log;\n//! //use tui_logger;\n//! use env_logger;\n//!\n//! fn main() {\n//!     // Early initialization of the logger\n//!     let drain = tui_logger::Drain::new();\n//!     // instead of tui_logger::init_logger, we use `env_logger`\n//!     env_logger::Builder::default()\n//!         .format(move |buf, record|\n//!             // patch the env-logger entry through our drain to the tui-logger\n//!             Ok(drain.log(record))\n//!         ).init(); // make this the global logger\n//!     // code....\n//! }\n//! ```\n//!\n//! ## Custom formatting\n//!\n//! For experts only ! Configure along the lines:\n//! ```ignore\n//! use tui_logger::LogFormatter;\n//!\n//! let formatter = MyLogFormatter();\n//!\n//! TuiLoggerWidget::default()\n//! .block(Block::bordered().title(\"Filtered TuiLoggerWidget\"))\n//! .formatter(formatter)\n//! .state(&filter_state)\n//! .render(left, buf);\n//! ```\n//! The example demo can be invoked to use a custom formatter as example for the bottom right widget.\n//!\n// Enable docsrs doc_cfg - to display non-default feature documentation.\n#![cfg_attr(docsrs, feature(doc_cfg))]\n#[macro_use]\nextern crate lazy_static;\n\npub use env_filter;\n\nmod circular;\npub use crate::circular::CircularBuffer;\n\n#[cfg(feature = \"slog-support\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"slog-support\")))]\nmod slog;\n#[cfg(feature = \"slog-support\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"slog-support\")))]\npub use crate::slog::TuiSlogDrain;\n\n#[cfg(feature = \"tracing-support\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"tracing-support\")))]\nmod tracing_subscriber;\n#[cfg(feature = \"tracing-support\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"tracing-support\")))]\npub use crate::tracing_subscriber::TuiTracingSubscriberLayer;\n#[doc(no_inline)]\npub use log::LevelFilter;\n\nmod widget;\npub use widget::inner::TuiWidgetEvent;\npub use widget::inner::TuiWidgetState;\npub use widget::logformatter::LogFormatter;\npub use widget::smart::TuiLoggerSmartWidget;\npub use widget::standard::TuiLoggerWidget;\npub use widget::target::TuiLoggerTargetWidget;\n\nmod config;\npub use config::LevelConfig;\n\nmod file;\npub use file::TuiLoggerFile;\n\nmod logger;\npub use crate::logger::api::*;\npub use crate::logger::ExtLogRecord;\npub use crate::logger::TuiLoggerLevelOutput;\nuse crate::logger::*;\n"
  },
  {
    "path": "src/logger/api.rs",
    "content": "use std::thread;\n\nuse crate::logger::fast_hash::fast_str_hash;\nuse crate::CircularBuffer;\nuse crate::TuiLoggerFile;\nuse log::LevelFilter;\nuse log::Record;\nuse log::SetLoggerError;\n\nuse crate::TUI_LOGGER;\n\n// Lots of boilerplate code, so that init_logger can return two error types...\n#[derive(Debug)]\npub enum TuiLoggerError {\n    SetLoggerError(SetLoggerError),\n    ThreadError(std::io::Error),\n}\nimpl std::error::Error for TuiLoggerError {\n    fn description(&self) -> &str {\n        match self {\n            TuiLoggerError::SetLoggerError(_) => \"SetLoggerError\",\n            TuiLoggerError::ThreadError(_) => \"ThreadError\",\n        }\n    }\n    fn cause(&self) -> Option<&dyn std::error::Error> {\n        match self {\n            TuiLoggerError::SetLoggerError(_) => None,\n            TuiLoggerError::ThreadError(err) => Some(err),\n        }\n    }\n}\nimpl std::fmt::Display for TuiLoggerError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            TuiLoggerError::SetLoggerError(err) => write!(f, \"SetLoggerError({})\", err),\n            TuiLoggerError::ThreadError(err) => write!(f, \"ThreadError({})\", err),\n        }\n    }\n}\n\n/// Init the logger.\npub fn init_logger(max_level: LevelFilter) -> Result<(), TuiLoggerError> {\n    let join_handle = thread::Builder::new()\n        .name(\"tui-logger::move_events\".into())\n        .spawn(|| {\n            let duration = std::time::Duration::from_millis(10);\n            loop {\n                thread::park_timeout(duration);\n                TUI_LOGGER.move_events();\n            }\n        })\n        .map_err(TuiLoggerError::ThreadError)?;\n    TUI_LOGGER.hot_log.lock().mover_thread = Some(join_handle);\n    if cfg!(feature = \"tracing-support\") {\n        set_default_level(max_level);\n        Ok(())\n    } else {\n        log::set_max_level(max_level);\n        log::set_logger(&*TUI_LOGGER).map_err(TuiLoggerError::SetLoggerError)\n    }\n}\n\n/// Set the depth of the hot buffer in order to avoid message loss.\n/// This is effective only after a call to move_events()\npub fn set_hot_buffer_depth(depth: usize) {\n    TUI_LOGGER.inner.lock().hot_depth = depth;\n}\n\n/// Set the depth of the circular buffer in order to avoid message loss.\n/// This will delete all existing messages in the circular buffer.\npub fn set_buffer_depth(depth: usize) {\n    TUI_LOGGER.inner.lock().events = CircularBuffer::new(depth);\n}\n\n/// Define filename and log formmating options for file dumping.\npub fn set_log_file(file_options: TuiLoggerFile) {\n    TUI_LOGGER.inner.lock().dump = Some(file_options);\n}\n\n/// Set default levelfilter for unknown targets of the logger\npub fn set_default_level(levelfilter: LevelFilter) {\n    TUI_LOGGER.hot_select.lock().default = levelfilter;\n    TUI_LOGGER.inner.lock().default = levelfilter;\n}\n\n/// Remove env filter - for debugging purposes\npub fn remove_env_filter() {\n    TUI_LOGGER.hot_select.lock().filter = None;\n    TUI_LOGGER.inner.lock().filter = None;\n}\n\nfn set_env_filter(filter1: env_filter::Filter, filter2: env_filter::Filter) {\n    // Filter does not support Copy. In order to not unnecessary lock hot_select,\n    // we use a manual copy of the env filter.\n    TUI_LOGGER.hot_select.lock().filter = Some(filter1);\n    TUI_LOGGER.inner.lock().filter = Some(filter2);\n}\n\n/// Parse environment variable for env_filter\npub fn set_env_filter_from_string(filterstring: &str) {\n    let mut builder1 = env_filter::Builder::new();\n    let mut builder2 = env_filter::Builder::new();\n\n    builder1.parse(filterstring);\n    builder2.parse(filterstring);\n\n    set_env_filter(builder1.build(), builder2.build());\n}\n\n/// Parse environment variable for env_filter\npub fn set_env_filter_from_env(env_name: Option<&str>) {\n    let mut builder1 = env_filter::Builder::new();\n    let mut builder2 = env_filter::Builder::new();\n\n    // Parse a directives string from an environment variable\n    if let Ok(ref filter) = std::env::var(env_name.unwrap_or(\"RUST_LOG\")) {\n        builder1.parse(filter);\n        builder2.parse(filter);\n\n        set_env_filter(builder1.build(), builder2.build());\n    }\n}\n\n/// Set levelfilter for a specific target in the logger\npub fn set_level_for_target(target: &str, levelfilter: LevelFilter) {\n    let h = fast_str_hash(target);\n    TUI_LOGGER.inner.lock().targets.set(target, levelfilter);\n    let mut hs = TUI_LOGGER.hot_select.lock();\n    hs.hashtable.insert(h, levelfilter);\n}\n\n// Move events from the hot log to the main log\npub fn move_events() {\n    TUI_LOGGER.move_events();\n}\n\n/// A simple `Drain` to log any event directly.\n#[derive(Default)]\npub struct Drain;\n\nimpl Drain {\n    /// Create a new Drain\n    pub fn new() -> Self {\n        Drain\n    }\n    /// Log the given record to the main tui-logger\n    pub fn log(&self, record: &Record) {\n        TUI_LOGGER.raw_log(record)\n    }\n}\n"
  },
  {
    "path": "src/logger/fast_hash.rs",
    "content": "/// A very simple and fast string hash function, based on the Java String.hashCode() algorithm.\npub fn fast_str_hash(s: &str) -> u64 {\n    let mut hash: u64 = 0xdeadbeef;\n    for b in s.bytes() {\n        hash = hash.wrapping_mul(31).wrapping_add(b as u64);\n    }\n    hash\n}\n"
  },
  {
    "path": "src/logger/inner.rs",
    "content": "use crate::logger::fast_hash::fast_str_hash;\nuse crate::{CircularBuffer, LevelConfig, TuiLoggerFile};\nuse env_filter::Filter;\nuse jiff::Zoned;\nuse log::{Level, LevelFilter, Log, Metadata, Record};\nuse parking_lot::Mutex;\nuse std::collections::HashMap;\nuse std::io::Write;\nuse std::mem;\nuse std::thread;\n\n/// The TuiLoggerWidget shows the logging messages in an endless scrolling view.\n/// It is controlled by a TuiWidgetState for selected events.\n#[derive(Debug, Clone, Copy, PartialEq, Hash)]\npub enum TuiLoggerLevelOutput {\n    Abbreviated,\n    Long,\n}\n/// These are the sub-structs for the static TUI_LOGGER struct.\npub(crate) struct HotSelect {\n    pub filter: Option<Filter>,\n    pub hashtable: HashMap<u64, LevelFilter>,\n    pub default: LevelFilter,\n}\npub(crate) struct HotLog {\n    pub events: CircularBuffer<ExtLogRecord>,\n    pub mover_thread: Option<thread::JoinHandle<()>>,\n}\n\nenum StringOrStatic {\n    StaticString(&'static str),\n    IsString(String),\n}\nimpl StringOrStatic {\n    fn as_str(&self) -> &str {\n        match self {\n            Self::StaticString(s) => s,\n            Self::IsString(s) => s,\n        }\n    }\n}\n\npub struct ExtLogRecord {\n    pub timestamp: Zoned,\n    pub level: Level,\n    target: String,\n    file: Option<StringOrStatic>,\n    module_path: Option<StringOrStatic>,\n    pub line: Option<u32>,\n    msg: String,\n}\nimpl ExtLogRecord {\n    #[inline]\n    pub fn target(&self) -> &str {\n        &self.target\n    }\n    #[inline]\n    pub fn file(&self) -> Option<&str> {\n        self.file.as_ref().map(|f| f.as_str())\n    }\n    #[inline]\n    pub fn module_path(&self) -> Option<&str> {\n        self.module_path.as_ref().map(|mp| mp.as_str())\n    }\n    #[inline]\n    pub fn msg(&self) -> &str {\n        &self.msg\n    }\n    fn from(record: &Record) -> Self {\n        let file: Option<StringOrStatic> = record\n            .file_static()\n            .map(StringOrStatic::StaticString)\n            .or_else(|| {\n                record\n                    .file()\n                    .map(|s| StringOrStatic::IsString(s.to_string()))\n            });\n        let module_path: Option<StringOrStatic> = record\n            .module_path_static()\n            .map(StringOrStatic::StaticString)\n            .or_else(|| {\n                record\n                    .module_path()\n                    .map(|s| StringOrStatic::IsString(s.to_string()))\n            });\n        ExtLogRecord {\n            timestamp: Zoned::now(),\n            level: record.level(),\n            target: record.target().to_string(),\n            file,\n            module_path,\n            line: record.line(),\n            msg: format!(\"{}\", record.args()),\n        }\n    }\n    fn overrun(timestamp: Zoned, total: usize, elements: usize) -> Self {\n        ExtLogRecord {\n            timestamp,\n            level: Level::Warn,\n            target: \"TuiLogger\".to_string(),\n            file: None,\n            module_path: None,\n            line: None,\n            msg: format!(\n                \"There have been {} events lost, {} recorded out of {}\",\n                total - elements,\n                elements,\n                total\n            ),\n        }\n    }\n}\npub(crate) struct TuiLoggerInner {\n    pub hot_depth: usize,\n    pub events: CircularBuffer<ExtLogRecord>,\n    pub dump: Option<TuiLoggerFile>,\n    pub total_events: usize,\n    pub default: LevelFilter,\n    pub targets: LevelConfig,\n    pub filter: Option<Filter>,\n}\npub struct TuiLogger {\n    pub hot_select: Mutex<HotSelect>,\n    pub hot_log: Mutex<HotLog>,\n    pub inner: Mutex<TuiLoggerInner>,\n}\nimpl TuiLogger {\n    pub fn move_events(&self) {\n        // If there are no new events, then just return\n        if self.hot_log.lock().events.total_elements() == 0 {\n            return;\n        }\n        // Exchange new event buffer with the hot buffer\n        let mut received_events = {\n            let hot_depth = self.inner.lock().hot_depth;\n            let new_circular = CircularBuffer::new(hot_depth);\n            let mut hl = self.hot_log.lock();\n            mem::replace(&mut hl.events, new_circular)\n        };\n        let mut tli = self.inner.lock();\n        let total = received_events.total_elements();\n        let elements = received_events.len();\n        tli.total_events += total;\n        let mut consumed = received_events.take();\n        let mut reversed = Vec::with_capacity(consumed.len() + 1);\n        while let Some(log_entry) = consumed.pop() {\n            reversed.push(log_entry);\n        }\n        if total > elements {\n            // Too many events received, so some have been lost\n            let new_log_entry = ExtLogRecord::overrun(\n                reversed[reversed.len() - 1].timestamp.clone(),\n                total,\n                elements,\n            );\n            reversed.push(new_log_entry);\n        }\n        while let Some(log_entry) = reversed.pop() {\n            if tli.targets.get(&log_entry.target).is_none() {\n                let mut default_level = tli.default;\n                if let Some(filter) = tli.filter.as_ref() {\n                    // Let's check, what the environment filter says about this target.\n                    let metadata = log::MetadataBuilder::new()\n                        .level(log_entry.level)\n                        .target(&log_entry.target)\n                        .build();\n                    if filter.enabled(&metadata) {\n                        // There is no direct access to the levelFilter, so we have to iterate over all possible level filters.\n                        for lf in [\n                            LevelFilter::Trace,\n                            LevelFilter::Debug,\n                            LevelFilter::Info,\n                            LevelFilter::Warn,\n                            LevelFilter::Error,\n                        ] {\n                            let metadata = log::MetadataBuilder::new()\n                                .level(lf.to_level().unwrap())\n                                .target(&log_entry.target)\n                                .build();\n                            if filter.enabled(&metadata) {\n                                // Found the related level filter\n                                default_level = lf;\n                                // In order to avoid checking the directives again,\n                                // we store the level filter in the hashtable for the hot path\n                                let h = fast_str_hash(&log_entry.target);\n                                self.hot_select.lock().hashtable.insert(h, lf);\n                                break;\n                            }\n                        }\n                    }\n                }\n                tli.targets.set(&log_entry.target, default_level);\n            }\n            if let Some(ref mut file_options) = tli.dump {\n                let mut output = String::new();\n                let (lev_long, lev_abbr, with_loc) = match log_entry.level {\n                    log::Level::Error => (\"ERROR\", \"E\", true),\n                    log::Level::Warn => (\"WARN \", \"W\", true),\n                    log::Level::Info => (\"INFO \", \"I\", false),\n                    log::Level::Debug => (\"DEBUG\", \"D\", true),\n                    log::Level::Trace => (\"TRACE\", \"T\", true),\n                };\n                if let Some(fmt) = file_options.timestamp_fmt.as_ref() {\n                    output.push_str(&log_entry.timestamp.strftime(fmt).to_string());\n                    output.push(file_options.format_separator);\n                }\n                match file_options.format_output_level {\n                    None => {}\n                    Some(TuiLoggerLevelOutput::Abbreviated) => {\n                        output.push_str(lev_abbr);\n                        output.push(file_options.format_separator);\n                    }\n                    Some(TuiLoggerLevelOutput::Long) => {\n                        output.push_str(lev_long);\n                        output.push(file_options.format_separator);\n                    }\n                }\n                if file_options.format_output_target {\n                    output.push_str(&log_entry.target);\n                    output.push(file_options.format_separator);\n                }\n                if with_loc {\n                    if file_options.format_output_file {\n                        if let Some(file) = log_entry.file() {\n                            output.push_str(file);\n                            output.push(file_options.format_separator);\n                        }\n                    }\n                    if file_options.format_output_line {\n                        if let Some(line) = log_entry.line.as_ref() {\n                            output.push_str(&format!(\"{}\", line));\n                            output.push(file_options.format_separator);\n                        }\n                    }\n                }\n                output.push_str(&log_entry.msg);\n                if let Err(_e) = writeln!(file_options.dump, \"{}\", output) {\n                    // TODO: What to do in case of write error ?\n                }\n            }\n            tli.events.push(log_entry);\n        }\n    }\n}\nlazy_static! {\n    pub static ref TUI_LOGGER: TuiLogger = {\n        let hs = HotSelect {\n            filter: None,\n            hashtable: HashMap::with_capacity(1000),\n            default: LevelFilter::Info,\n        };\n        let hl = HotLog {\n            events: CircularBuffer::new(1000),\n            mover_thread: None,\n        };\n        let tli = TuiLoggerInner {\n            hot_depth: 1000,\n            events: CircularBuffer::new(10000),\n            total_events: 0,\n            dump: None,\n            default: LevelFilter::Info,\n            targets: LevelConfig::new(),\n            filter: None,\n        };\n        TuiLogger {\n            hot_select: Mutex::new(hs),\n            hot_log: Mutex::new(hl),\n            inner: Mutex::new(tli),\n        }\n    };\n}\n\nimpl Log for TuiLogger {\n    fn enabled(&self, metadata: &Metadata) -> bool {\n        let h = fast_str_hash(metadata.target());\n        let hs = self.hot_select.lock();\n        if let Some(&levelfilter) = hs.hashtable.get(&h) {\n            metadata.level() <= levelfilter\n        } else if let Some(envfilter) = hs.filter.as_ref() {\n            envfilter.enabled(metadata)\n        } else {\n            metadata.level() <= hs.default\n        }\n    }\n\n    fn log(&self, record: &Record) {\n        if self.enabled(record.metadata()) {\n            self.raw_log(record)\n        }\n    }\n\n    fn flush(&self) {}\n}\n\nimpl TuiLogger {\n    pub fn raw_log(&self, record: &Record) {\n        let log_entry = ExtLogRecord::from(record);\n        let mut events_lock = self.hot_log.lock();\n        events_lock.events.push(log_entry);\n        let need_signal = events_lock\n            .events\n            .total_elements()\n            .is_multiple_of(events_lock.events.capacity() / 2);\n        if need_signal {\n            if let Some(jh) = events_lock.mover_thread.as_ref() {\n                thread::Thread::unpark(jh.thread());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/logger/mod.rs",
    "content": "pub mod api;\nmod fast_hash;\nmod inner;\n\npub use inner::*;\n"
  },
  {
    "path": "src/slog.rs",
    "content": "//! `slog` support for `tui-logger`\n\nuse super::TUI_LOGGER;\nuse log::{self, Log, Record};\nuse slog::{self, Drain, KV};\nuse std::{fmt, io};\n\n#[cfg_attr(docsrs, doc(cfg(feature = \"slog-support\")))]\npub fn slog_drain() -> TuiSlogDrain {\n    TuiSlogDrain\n}\n\n/// Key-Separator-Value serializer\n// Copied from `slog-stdlog`\nstruct Ksv<W: io::Write> {\n    io: W,\n}\n\nimpl<W: io::Write> Ksv<W> {\n    fn new(io: W) -> Self {\n        Ksv { io }\n    }\n\n    fn into_inner(self) -> W {\n        self.io\n    }\n}\n\nimpl<W: io::Write> slog::Serializer for Ksv<W> {\n    fn emit_arguments(&mut self, key: slog::Key, val: &fmt::Arguments) -> slog::Result {\n        write!(self.io, \", {}: {}\", key, val)?;\n        Ok(())\n    }\n}\n\n// Copied from `slog-stdlog`\nstruct LazyLogString<'a> {\n    info: &'a slog::Record<'a>,\n    logger_values: &'a slog::OwnedKVList,\n}\n\nimpl<'a> LazyLogString<'a> {\n    fn new(info: &'a slog::Record, logger_values: &'a slog::OwnedKVList) -> Self {\n        LazyLogString {\n            info,\n            logger_values,\n        }\n    }\n}\n\nimpl<'a> fmt::Display for LazyLogString<'a> {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{}\", self.info.msg())?;\n\n        let io = io::Cursor::new(Vec::new());\n        let mut ser = Ksv::new(io);\n\n        self.logger_values\n            .serialize(self.info, &mut ser)\n            .map_err(|_| fmt::Error)?;\n        self.info\n            .kv()\n            .serialize(self.info, &mut ser)\n            .map_err(|_| fmt::Error)?;\n\n        let values = ser.into_inner().into_inner();\n\n        write!(f, \"{}\", String::from_utf8_lossy(&values))\n    }\n}\n\n#[allow(clippy::needless_doctest_main)]\n///  slog-compatible Drain that feeds messages to `tui-logger`.\n///\n///  ## Basic usage:\n///  ```\n///  use slog::{self, o, Drain, info};\n///  //use tui_logger;\n///  \n///  fn main() {\n///     let drain = tui_logger::slog_drain().fuse();\n///     let log = slog::Logger::root(drain, o!());\n///     info!(log, \"Logging via slog works!\");\n///\n///  }\npub struct TuiSlogDrain;\n\nimpl Drain for TuiSlogDrain {\n    type Ok = ();\n    type Err = io::Error;\n    // Copied from `slog-stdlog`\n    fn log(&self, info: &slog::Record, logger_values: &slog::OwnedKVList) -> io::Result<()> {\n        let level = match info.level() {\n            slog::Level::Critical | slog::Level::Error => log::Level::Error,\n            slog::Level::Warning => log::Level::Warn,\n            slog::Level::Info => log::Level::Info,\n            slog::Level::Debug => log::Level::Debug,\n            slog::Level::Trace => log::Level::Trace,\n        };\n\n        let mut target = info.tag();\n        if target.is_empty() {\n            target = info.module();\n        }\n\n        let lazy = LazyLogString::new(info, logger_values);\n        TUI_LOGGER.log(\n            &Record::builder()\n                .args(format_args!(\"{}\", lazy))\n                .level(level)\n                .target(target)\n                .file(Some(info.file()))\n                .line(Some(info.line()))\n                .build(),\n        );\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/tracing_subscriber.rs",
    "content": "//! `tracing-subscriber` support for `tui-logger`\n\nuse super::TUI_LOGGER;\nuse log::{self, Log, Record};\nuse std::collections::BTreeMap;\nuse std::fmt;\nuse tracing_subscriber::registry::LookupSpan;\nuse tracing_subscriber::Layer;\n\n#[derive(Default)]\nstruct ToStringVisitor<'a>(BTreeMap<&'a str, String>);\n\nimpl fmt::Display for ToStringVisitor<'_> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        self.0.iter().try_for_each(|(k, v)| -> fmt::Result {\n            if *k == \"message\" {\n                write!(f, \" {}\", v)\n            } else {\n                write!(f, \" {}: {}\", k, v)\n            }\n        })\n    }\n}\n\nimpl<'a> tracing::field::Visit for ToStringVisitor<'a> {\n    fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {\n        self.0\n            .insert(field.name(), format_args!(\"{}\", value).to_string());\n    }\n\n    fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {\n        self.0\n            .insert(field.name(), format_args!(\"{}\", value).to_string());\n    }\n\n    fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {\n        self.0\n            .insert(field.name(), format_args!(\"{}\", value).to_string());\n    }\n\n    fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {\n        self.0\n            .insert(field.name(), format_args!(\"{}\", value).to_string());\n    }\n\n    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {\n        self.0\n            .insert(field.name(), format_args!(\"{}\", value).to_string());\n    }\n\n    fn record_error(\n        &mut self,\n        field: &tracing::field::Field,\n        value: &(dyn std::error::Error + 'static),\n    ) {\n        self.0\n            .insert(field.name(), format_args!(\"{}\", value).to_string());\n    }\n\n    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {\n        self.0\n            .insert(field.name(), format_args!(\"{:?}\", value).to_string());\n    }\n}\n\n#[allow(clippy::needless_doctest_main)]\n///  tracing-subscriber-compatible layer that feeds messages to `tui-logger`.\n///\n///  ## How it works\n///  Under the hood, tui_logger still uses `log`. `tracing` events are mapped to\n///  `log` events internally (which are then fed to `tui-logger`).\n///\n///  ## Usage note\n///  As per the example below, [init_logger()] must be called prior to logging events.\n///\n///  [init_logger()]: crate::init_logger()\n///  ## Basic usage\n///  ```\n///  use tracing_subscriber::prelude::*;\n///\n///  fn main() {\n///     tracing_subscriber::registry()\n///          .with(tui_logger::TuiTracingSubscriberLayer)\n///          .init();\n///     tui_logger::init_logger(tui_logger::LevelFilter::Trace).unwrap();\n///     tracing::info!(\"Logging via tracing works!\");\n///  }\n///  ```\n\nstruct SpanAttributes {\n    attributes: String,\n}\n\npub struct TuiTracingSubscriberLayer;\n\nimpl<S> Layer<S> for TuiTracingSubscriberLayer\nwhere\n    S: tracing::Subscriber + for<'a> LookupSpan<'a>,\n{\n    fn on_new_span(\n        &self,\n        attrs: &tracing::span::Attributes<'_>,\n        id: &tracing::span::Id,\n        ctx: tracing_subscriber::layer::Context<'_, S>,\n    ) {\n        let mut visitor = ToStringVisitor::default();\n        attrs.record(&mut visitor);\n        ctx.span(id)\n            .unwrap()\n            .extensions_mut()\n            .insert(SpanAttributes {\n                attributes: format!(\"{}\", visitor),\n            });\n    }\n\n    fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {\n        let mut visitor = ToStringVisitor::default();\n        event.record(&mut visitor);\n\n        let span_attributes = ctx\n            .event_span(event)\n            .and_then(|s| {\n                s.extensions()\n                    .get::<SpanAttributes>()\n                    .map(|a| a.attributes.to_owned())\n            })\n            .unwrap_or_else(String::new);\n\n        let level = match *event.metadata().level() {\n            tracing::Level::ERROR => log::Level::Error,\n            tracing::Level::WARN => log::Level::Warn,\n            tracing::Level::INFO => log::Level::Info,\n            tracing::Level::DEBUG => log::Level::Debug,\n            tracing::Level::TRACE => log::Level::Trace,\n        };\n\n        TUI_LOGGER.log(\n            &Record::builder()\n                .args(format_args!(\"{}{}\", span_attributes, visitor))\n                .level(level)\n                .target(event.metadata().target())\n                .file(event.metadata().file())\n                .line(event.metadata().line())\n                .module_path(event.metadata().module_path())\n                .build(),\n        );\n    }\n}\n"
  },
  {
    "path": "src/widget/inner.rs",
    "content": "use std::sync::Arc;\n\nuse log::LevelFilter;\nuse parking_lot::Mutex;\n\nuse crate::{set_level_for_target, LevelConfig};\n\n#[derive(Clone, Copy, Debug)]\npub(crate) struct LinePointer {\n    pub event_index: usize, // into event buffer\n    pub subline: usize,\n}\n\n/// This struct contains the shared state of a TuiLoggerWidget and a TuiLoggerTargetWidget.\n#[derive(Default)]\npub struct TuiWidgetState {\n    inner: Arc<Mutex<TuiWidgetInnerState>>,\n}\nimpl TuiWidgetState {\n    /// Create a new TuiWidgetState\n    pub fn new() -> TuiWidgetState {\n        TuiWidgetState {\n            inner: Arc::new(Mutex::new(TuiWidgetInnerState::new())),\n        }\n    }\n    pub fn set_default_display_level(self, levelfilter: LevelFilter) -> TuiWidgetState {\n        self.inner\n            .lock()\n            .config\n            .set_default_display_level(levelfilter);\n        self\n    }\n    pub fn set_level_for_target(self, target: &str, levelfilter: LevelFilter) -> TuiWidgetState {\n        self.inner.lock().config.set(target, levelfilter);\n        self\n    }\n    pub fn transition(&self, event: TuiWidgetEvent) {\n        self.inner.lock().transition(event);\n    }\n    pub fn clone_state(&self) -> Arc<Mutex<TuiWidgetInnerState>> {\n        self.inner.clone()\n    }\n}\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]\npub enum TuiWidgetEvent {\n    SpaceKey,\n    UpKey,\n    DownKey,\n    LeftKey,\n    RightKey,\n    PlusKey,\n    MinusKey,\n    HideKey,\n    FocusKey,\n    PrevPageKey,\n    NextPageKey,\n    EscapeKey,\n}\n\n#[derive(Default)]\npub struct TuiWidgetInnerState {\n    pub(crate) config: LevelConfig,\n    pub(crate) nr_items: usize,\n    pub(crate) selected: usize,\n    pub(crate) opt_line_pointer_center: Option<LinePointer>,\n    pub(crate) opt_line_pointer_next_page: Option<LinePointer>,\n    pub(crate) opt_line_pointer_prev_page: Option<LinePointer>,\n    pub(crate) opt_selected_target: Option<String>,\n    pub(crate) opt_selected_visibility_more: Option<LevelFilter>,\n    pub(crate) opt_selected_visibility_less: Option<LevelFilter>,\n    pub(crate) opt_selected_recording_more: Option<LevelFilter>,\n    pub(crate) opt_selected_recording_less: Option<LevelFilter>,\n    pub(crate) offset: usize,\n    pub(crate) hide_off: bool,\n    pub(crate) hide_target: bool,\n    pub(crate) focus_selected: bool,\n}\nimpl TuiWidgetInnerState {\n    pub fn new() -> TuiWidgetInnerState {\n        TuiWidgetInnerState::default()\n    }\n    fn transition(&mut self, event: TuiWidgetEvent) {\n        use TuiWidgetEvent::*;\n        match event {\n            SpaceKey => {\n                self.hide_off ^= true;\n            }\n            HideKey => {\n                self.hide_target ^= true;\n            }\n            FocusKey => {\n                self.focus_selected ^= true;\n            }\n            UpKey => {\n                if !self.hide_target && self.selected > 0 {\n                    self.selected -= 1;\n                }\n            }\n            DownKey => {\n                if !self.hide_target && self.selected + 1 < self.nr_items {\n                    self.selected += 1;\n                }\n            }\n            LeftKey => {\n                if let Some(selected_target) = self.opt_selected_target.take() {\n                    if let Some(selected_visibility_less) = self.opt_selected_visibility_less.take()\n                    {\n                        self.config.set(&selected_target, selected_visibility_less);\n                    }\n                }\n            }\n            RightKey => {\n                if let Some(selected_target) = self.opt_selected_target.take() {\n                    if let Some(selected_visibility_more) = self.opt_selected_visibility_more.take()\n                    {\n                        self.config.set(&selected_target, selected_visibility_more);\n                    }\n                }\n            }\n            PlusKey => {\n                if let Some(selected_target) = self.opt_selected_target.take() {\n                    if let Some(selected_recording_more) = self.opt_selected_recording_more.take() {\n                        set_level_for_target(&selected_target, selected_recording_more);\n                    }\n                }\n            }\n            MinusKey => {\n                if let Some(selected_target) = self.opt_selected_target.take() {\n                    if let Some(selected_recording_less) = self.opt_selected_recording_less.take() {\n                        set_level_for_target(&selected_target, selected_recording_less);\n                    }\n                }\n            }\n            PrevPageKey => self.opt_line_pointer_center = self.opt_line_pointer_prev_page,\n            NextPageKey => self.opt_line_pointer_center = self.opt_line_pointer_next_page,\n            EscapeKey => self.opt_line_pointer_center = None,\n        }\n    }\n}\n"
  },
  {
    "path": "src/widget/logformatter.rs",
    "content": "use crate::ExtLogRecord;\nuse ratatui::text::Line;\n\npub trait LogFormatter: Send + Sync {\n    fn min_width(&self) -> u16;\n\n    /// This must format any event in one or more lines.\n    /// Correct wrapping in next line with/without indenting must be performed here.\n    /// The parameter width is the available line width\n    fn format(&self, width: usize, evt: &ExtLogRecord) -> Vec<Line<'_>>;\n}\n"
  },
  {
    "path": "src/widget/mod.rs",
    "content": "pub mod inner;\npub mod logformatter;\npub mod smart;\npub mod standard;\nmod standard_formatter;\npub mod target;\n"
  },
  {
    "path": "src/widget/smart.rs",
    "content": "use crate::widget::logformatter::LogFormatter;\nuse parking_lot::Mutex;\nuse std::sync::Arc;\nuse unicode_segmentation::UnicodeSegmentation;\n\nuse log::LevelFilter;\nuse ratatui::{\n    buffer::Buffer,\n    layout::{Constraint, Direction, Layout, Rect},\n    style::Style,\n    text::Line,\n    widgets::{Block, BorderType, Borders, Widget},\n};\n\nuse crate::logger::TuiLoggerLevelOutput;\nuse crate::logger::TUI_LOGGER;\nuse crate::{TuiLoggerTargetWidget, TuiWidgetState};\n\nuse super::{inner::TuiWidgetInnerState, standard::TuiLoggerWidget};\n\n/// The Smart Widget combines the TuiLoggerWidget and the TuiLoggerTargetWidget\n/// into a nice combo, where the TuiLoggerTargetWidget can be shown/hidden.\n///\n/// In the title the number of logging messages/s in the whole buffer is shown.\npub struct TuiLoggerSmartWidget<'a> {\n    title_log: Line<'a>,\n    title_target: Line<'a>,\n    style: Option<Style>,\n    border_style: Style,\n    border_type: BorderType,\n    highlight_style: Option<Style>,\n    logformatter: Option<Box<dyn LogFormatter>>,\n    style_error: Option<Style>,\n    style_warn: Option<Style>,\n    style_debug: Option<Style>,\n    style_trace: Option<Style>,\n    style_info: Option<Style>,\n    style_show: Option<Style>,\n    style_hide: Option<Style>,\n    style_off: Option<Style>,\n    format_separator: Option<char>,\n    format_timestamp: Option<Option<String>>,\n    format_output_level: Option<Option<TuiLoggerLevelOutput>>,\n    format_output_target: Option<bool>,\n    format_output_file: Option<bool>,\n    format_output_line: Option<bool>,\n    state: Arc<Mutex<TuiWidgetInnerState>>,\n}\nimpl<'a> Default for TuiLoggerSmartWidget<'a> {\n    fn default() -> Self {\n        TuiLoggerSmartWidget {\n            title_log: Line::from(\"Tui Log\"),\n            title_target: Line::from(\"Tui Target Selector\"),\n            style: None,\n            border_style: Style::default(),\n            border_type: BorderType::Plain,\n            highlight_style: None,\n            logformatter: None,\n            style_error: None,\n            style_warn: None,\n            style_debug: None,\n            style_trace: None,\n            style_info: None,\n            style_show: None,\n            style_hide: None,\n            style_off: None,\n            format_separator: None,\n            format_timestamp: None,\n            format_output_level: None,\n            format_output_target: None,\n            format_output_file: None,\n            format_output_line: None,\n            state: Arc::new(Mutex::new(TuiWidgetInnerState::new())),\n        }\n    }\n}\nimpl<'a> TuiLoggerSmartWidget<'a> {\n    pub fn highlight_style(mut self, style: Style) -> Self {\n        self.highlight_style = Some(style);\n        self\n    }\n    pub fn border_style(mut self, style: Style) -> Self {\n        self.border_style = style;\n        self\n    }\n    pub fn border_type(mut self, border_type: BorderType) -> Self {\n        self.border_type = border_type;\n        self\n    }\n    pub fn style(mut self, style: Style) -> Self {\n        self.style = Some(style);\n        self\n    }\n    pub fn style_error(mut self, style: Style) -> Self {\n        self.style_error = Some(style);\n        self\n    }\n    pub fn style_warn(mut self, style: Style) -> Self {\n        self.style_warn = Some(style);\n        self\n    }\n    pub fn style_info(mut self, style: Style) -> Self {\n        self.style_info = Some(style);\n        self\n    }\n    pub fn style_trace(mut self, style: Style) -> Self {\n        self.style_trace = Some(style);\n        self\n    }\n    pub fn style_debug(mut self, style: Style) -> Self {\n        self.style_debug = Some(style);\n        self\n    }\n    pub fn style_off(mut self, style: Style) -> Self {\n        self.style_off = Some(style);\n        self\n    }\n    pub fn style_hide(mut self, style: Style) -> Self {\n        self.style_hide = Some(style);\n        self\n    }\n    pub fn style_show(mut self, style: Style) -> Self {\n        self.style_show = Some(style);\n        self\n    }\n    /// Separator character between field.\n    /// Default is ':'\n    pub fn output_separator(mut self, sep: char) -> Self {\n        self.format_separator = Some(sep);\n        self\n    }\n    /// The format string can be defined as described in\n    /// <https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html>\n    ///\n    /// If called with None, timestamp is not included in output.\n    ///\n    /// Default is %H:%M:%S\n    pub fn output_timestamp(mut self, fmt: Option<String>) -> Self {\n        self.format_timestamp = Some(fmt);\n        self\n    }\n    /// Possible values are\n    /// - TuiLoggerLevelOutput::Long        => DEBUG/TRACE/...\n    /// - TuiLoggerLevelOutput::Abbreviated => D/T/...\n    ///\n    /// If called with None, level is not included in output.\n    ///\n    /// Default is Long\n    pub fn output_level(mut self, level: Option<TuiLoggerLevelOutput>) -> Self {\n        self.format_output_level = Some(level);\n        self\n    }\n    /// Enables output of target field of event\n    ///\n    /// Default is true\n    pub fn output_target(mut self, enabled: bool) -> Self {\n        self.format_output_target = Some(enabled);\n        self\n    }\n    /// Enables output of file field of event\n    ///\n    /// Default is true\n    pub fn output_file(mut self, enabled: bool) -> Self {\n        self.format_output_file = Some(enabled);\n        self\n    }\n    /// Enables output of line field of event\n    ///\n    /// Default is true\n    pub fn output_line(mut self, enabled: bool) -> Self {\n        self.format_output_line = Some(enabled);\n        self\n    }\n    pub fn title_target<T>(mut self, title: T) -> Self\n    where\n        T: Into<Line<'a>>,\n    {\n        self.title_target = title.into();\n        self\n    }\n    pub fn title_log<T>(mut self, title: T) -> Self\n    where\n        T: Into<Line<'a>>,\n    {\n        self.title_log = title.into();\n        self\n    }\n    pub fn state(mut self, state: &TuiWidgetState) -> Self {\n        self.state = state.clone_state();\n        self\n    }\n}\nimpl<'a> Widget for TuiLoggerSmartWidget<'a> {\n    /// Nothing to draw for combo widget\n    fn render(self, area: Rect, buf: &mut Buffer) {\n        let entries_s = {\n            let mut tui_lock = TUI_LOGGER.inner.lock();\n            let first_timestamp = tui_lock\n                .events\n                .iter()\n                .next()\n                .map(|entry| entry.timestamp.timestamp().as_millisecond());\n            let last_timestamp = tui_lock\n                .events\n                .rev_iter()\n                .next()\n                .map(|entry| entry.timestamp.timestamp().as_millisecond());\n            if let Some(first) = first_timestamp {\n                if let Some(last) = last_timestamp {\n                    let dt = last - first;\n                    if dt > 0 {\n                        tui_lock.events.len() as f64 / (dt as f64) * 1000.0\n                    } else {\n                        0.0\n                    }\n                } else {\n                    0.0\n                }\n            } else {\n                0.0\n            }\n        };\n\n        let mut title_log = self.title_log.clone();\n        title_log\n            .spans\n            .push(format!(\" [log={:.1}/s]\", entries_s).into());\n\n        let hide_target = self.state.lock().hide_target;\n        if hide_target {\n            let tui_lw = TuiLoggerWidget::default()\n                .block(\n                    Block::default()\n                        .title(title_log)\n                        .border_style(self.border_style)\n                        .border_type(self.border_type)\n                        .borders(Borders::ALL),\n                )\n                .opt_style(self.style)\n                .opt_style_error(self.style_error)\n                .opt_style_warn(self.style_warn)\n                .opt_style_info(self.style_info)\n                .opt_style_debug(self.style_debug)\n                .opt_style_trace(self.style_trace)\n                .opt_output_separator(self.format_separator)\n                .opt_output_timestamp(self.format_timestamp)\n                .opt_output_level(self.format_output_level)\n                .opt_output_target(self.format_output_target)\n                .opt_output_file(self.format_output_file)\n                .opt_output_line(self.format_output_line)\n                .inner_state(self.state);\n            tui_lw.render(area, buf);\n        } else {\n            let mut width: usize = 0;\n            {\n                let hot_targets = &TUI_LOGGER.inner.lock().targets;\n                let mut state = self.state.lock();\n                let hide_off = state.hide_off;\n                {\n                    let targets = &mut state.config;\n                    targets.merge(hot_targets);\n                    for (t, levelfilter) in targets.iter() {\n                        if hide_off && levelfilter == &LevelFilter::Off {\n                            continue;\n                        }\n                        width = width.max(t.graphemes(true).count())\n                    }\n                }\n            }\n            let chunks = Layout::default()\n                .direction(Direction::Horizontal)\n                .constraints(vec![\n                    Constraint::Length(width as u16 + 6 + 2),\n                    Constraint::Min(10),\n                ])\n                .split(area);\n            let tui_ltw = TuiLoggerTargetWidget::default()\n                .block(\n                    Block::default()\n                        .title(self.title_target)\n                        .border_style(self.border_style)\n                        .border_type(self.border_type)\n                        .borders(Borders::ALL),\n                )\n                .opt_style(self.style)\n                .opt_highlight_style(self.highlight_style)\n                .opt_style_off(self.style_off)\n                .opt_style_hide(self.style_hide)\n                .opt_style_show(self.style_show)\n                .inner_state(self.state.clone());\n            tui_ltw.render(chunks[0], buf);\n            let tui_lw = TuiLoggerWidget::default()\n                .block(\n                    Block::default()\n                        .title(title_log)\n                        .border_style(self.border_style)\n                        .border_type(self.border_type)\n                        .borders(Borders::ALL),\n                )\n                .opt_formatter(self.logformatter)\n                .opt_style(self.style)\n                .opt_style_error(self.style_error)\n                .opt_style_warn(self.style_warn)\n                .opt_style_info(self.style_info)\n                .opt_style_debug(self.style_debug)\n                .opt_style_trace(self.style_trace)\n                .opt_output_separator(self.format_separator)\n                .opt_output_timestamp(self.format_timestamp)\n                .opt_output_level(self.format_output_level)\n                .opt_output_target(self.format_output_target)\n                .opt_output_file(self.format_output_file)\n                .opt_output_line(self.format_output_line)\n                .inner_state(self.state.clone());\n            tui_lw.render(chunks[1], buf);\n        }\n    }\n}\n"
  },
  {
    "path": "src/widget/standard.rs",
    "content": "use crate::widget::logformatter::LogFormatter;\nuse crate::widget::standard_formatter::LogStandardFormatter;\nuse parking_lot::Mutex;\nuse std::sync::Arc;\n\nuse ratatui::{\n    buffer::Buffer,\n    layout::Rect,\n    style::Style,\n    text::Line,\n    widgets::{Block, Widget},\n};\n\nuse crate::widget::inner::LinePointer;\nuse crate::{CircularBuffer, ExtLogRecord, TuiLoggerLevelOutput, TuiWidgetState, TUI_LOGGER};\n\nuse super::inner::TuiWidgetInnerState;\n\npub struct TuiLoggerWidget<'b> {\n    block: Option<Block<'b>>,\n    logformatter: Option<Box<dyn LogFormatter>>,\n    /// Base style of the widget\n    style: Style,\n    /// Level based style\n    style_error: Option<Style>,\n    style_warn: Option<Style>,\n    style_debug: Option<Style>,\n    style_trace: Option<Style>,\n    style_info: Option<Style>,\n    format_separator: char,\n    format_timestamp: Option<String>,\n    format_output_level: Option<TuiLoggerLevelOutput>,\n    format_output_target: bool,\n    format_output_file: bool,\n    format_output_line: bool,\n    state: Arc<Mutex<TuiWidgetInnerState>>,\n}\nimpl<'b> Default for TuiLoggerWidget<'b> {\n    fn default() -> TuiLoggerWidget<'b> {\n        TuiLoggerWidget {\n            block: None,\n            logformatter: None,\n            style: Default::default(),\n            style_error: None,\n            style_warn: None,\n            style_debug: None,\n            style_trace: None,\n            style_info: None,\n            format_separator: ':',\n            format_timestamp: Some(\"%H:%M:%S\".to_string()),\n            format_output_level: Some(TuiLoggerLevelOutput::Long),\n            format_output_target: true,\n            format_output_file: true,\n            format_output_line: true,\n            state: Arc::new(Mutex::new(TuiWidgetInnerState::new())),\n        }\n    }\n}\nimpl<'b> TuiLoggerWidget<'b> {\n    pub fn block(mut self, block: Block<'b>) -> Self {\n        self.block = Some(block);\n        self\n    }\n    pub fn opt_formatter(mut self, formatter: Option<Box<dyn LogFormatter>>) -> Self {\n        self.logformatter = formatter;\n        self\n    }\n    pub fn formatter(mut self, formatter: Box<dyn LogFormatter>) -> Self {\n        self.logformatter = Some(formatter);\n        self\n    }\n    pub fn opt_style(mut self, style: Option<Style>) -> Self {\n        if let Some(s) = style {\n            self.style = s;\n        }\n        self\n    }\n    pub fn opt_style_error(mut self, style: Option<Style>) -> Self {\n        if style.is_some() {\n            self.style_error = style;\n        }\n        self\n    }\n    pub fn opt_style_warn(mut self, style: Option<Style>) -> Self {\n        if style.is_some() {\n            self.style_warn = style;\n        }\n        self\n    }\n    pub fn opt_style_info(mut self, style: Option<Style>) -> Self {\n        if style.is_some() {\n            self.style_info = style;\n        }\n        self\n    }\n    pub fn opt_style_trace(mut self, style: Option<Style>) -> Self {\n        if style.is_some() {\n            self.style_trace = style;\n        }\n        self\n    }\n    pub fn opt_style_debug(mut self, style: Option<Style>) -> Self {\n        if style.is_some() {\n            self.style_debug = style;\n        }\n        self\n    }\n    pub fn style(mut self, style: Style) -> Self {\n        self.style = style;\n        self\n    }\n    pub fn style_error(mut self, style: Style) -> Self {\n        self.style_error = Some(style);\n        self\n    }\n    pub fn style_warn(mut self, style: Style) -> Self {\n        self.style_warn = Some(style);\n        self\n    }\n    pub fn style_info(mut self, style: Style) -> Self {\n        self.style_info = Some(style);\n        self\n    }\n    pub fn style_trace(mut self, style: Style) -> Self {\n        self.style_trace = Some(style);\n        self\n    }\n    pub fn style_debug(mut self, style: Style) -> Self {\n        self.style_debug = Some(style);\n        self\n    }\n    pub fn opt_output_separator(mut self, opt_sep: Option<char>) -> Self {\n        if let Some(ch) = opt_sep {\n            self.format_separator = ch;\n        }\n        self\n    }\n    /// Separator character between field.\n    /// Default is ':'\n    pub fn output_separator(mut self, sep: char) -> Self {\n        self.format_separator = sep;\n        self\n    }\n    pub fn opt_output_timestamp(mut self, opt_fmt: Option<Option<String>>) -> Self {\n        if let Some(fmt) = opt_fmt {\n            self.format_timestamp = fmt;\n        }\n        self\n    }\n    /// The format string can be defined as described in\n    /// <https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html>\n    ///\n    /// If called with None, timestamp is not included in output.\n    ///\n    /// Default is %H:%M:%S\n    pub fn output_timestamp(mut self, fmt: Option<String>) -> Self {\n        self.format_timestamp = fmt;\n        self\n    }\n    pub fn opt_output_level(mut self, opt_fmt: Option<Option<TuiLoggerLevelOutput>>) -> Self {\n        if let Some(fmt) = opt_fmt {\n            self.format_output_level = fmt;\n        }\n        self\n    }\n    /// Possible values are\n    /// - TuiLoggerLevelOutput::Long        => DEBUG/TRACE/...\n    /// - TuiLoggerLevelOutput::Abbreviated => D/T/...\n    ///\n    /// If called with None, level is not included in output.\n    ///\n    /// Default is Long\n    pub fn output_level(mut self, level: Option<TuiLoggerLevelOutput>) -> Self {\n        self.format_output_level = level;\n        self\n    }\n    pub fn opt_output_target(mut self, opt_enabled: Option<bool>) -> Self {\n        if let Some(enabled) = opt_enabled {\n            self.format_output_target = enabled;\n        }\n        self\n    }\n    /// Enables output of target field of event\n    ///\n    /// Default is true\n    pub fn output_target(mut self, enabled: bool) -> Self {\n        self.format_output_target = enabled;\n        self\n    }\n    pub fn opt_output_file(mut self, opt_enabled: Option<bool>) -> Self {\n        if let Some(enabled) = opt_enabled {\n            self.format_output_file = enabled;\n        }\n        self\n    }\n    /// Enables output of file field of event\n    ///\n    /// Default is true\n    pub fn output_file(mut self, enabled: bool) -> Self {\n        self.format_output_file = enabled;\n        self\n    }\n    pub fn opt_output_line(mut self, opt_enabled: Option<bool>) -> Self {\n        if let Some(enabled) = opt_enabled {\n            self.format_output_line = enabled;\n        }\n        self\n    }\n    /// Enables output of line field of event\n    ///\n    /// Default is true\n    pub fn output_line(mut self, enabled: bool) -> Self {\n        self.format_output_line = enabled;\n        self\n    }\n    pub fn inner_state(mut self, state: Arc<Mutex<TuiWidgetInnerState>>) -> Self {\n        self.state = state;\n        self\n    }\n    pub fn state(mut self, state: &TuiWidgetState) -> Self {\n        self.state = state.clone_state();\n        self\n    }\n    fn next_event<'a>(\n        &self,\n        events: &'a CircularBuffer<ExtLogRecord>,\n        mut index: usize,\n        ignore_current: bool,\n        increment: bool,\n        state: &TuiWidgetInnerState,\n    ) -> Option<(Option<usize>, usize, &'a ExtLogRecord)> {\n        // The result is an optional next_index, the event index and the event\n        if ignore_current {\n            index = if increment {\n                index + 1\n            } else {\n                if index == 0 {\n                    return None;\n                }\n                index - 1\n            };\n        }\n        while let Some(evt) = events.element_at_index(index) {\n            let mut skip = false;\n            if let Some(level) = state\n                .config\n                .get(evt.target())\n                .or(state.config.get_default_display_level())\n            {\n                if level < evt.level {\n                    skip = true;\n                }\n            }\n            if !skip && state.focus_selected {\n                if let Some(target) = state.opt_selected_target.as_ref() {\n                    if target != evt.target() {\n                        skip = true;\n                    }\n                }\n            }\n            if skip {\n                index = if increment {\n                    index + 1\n                } else {\n                    if index == 0 {\n                        break;\n                    }\n                    index - 1\n                };\n            } else if increment {\n                return Some((Some(index + 1), index, evt));\n            } else {\n                if index == 0 {\n                    return Some((None, index, evt));\n                }\n                return Some((Some(index - 1), index, evt));\n            }\n        }\n        None\n    }\n}\nimpl<'b> Widget for TuiLoggerWidget<'b> {\n    fn render(mut self, area: Rect, buf: &mut Buffer) {\n        let render_debug = false;\n\n        let formatter = match self.logformatter.take() {\n            Some(fmt) => fmt,\n            None => {\n                let fmt = LogStandardFormatter {\n                    style: self.style,\n                    style_error: self.style_error,\n                    style_warn: self.style_warn,\n                    style_debug: self.style_debug,\n                    style_trace: self.style_trace,\n                    style_info: self.style_info,\n                    format_separator: self.format_separator,\n                    format_timestamp: self.format_timestamp.clone(),\n                    format_output_level: self.format_output_level,\n                    format_output_target: self.format_output_target,\n                    format_output_file: self.format_output_file,\n                    format_output_line: self.format_output_line,\n                };\n                Box::new(fmt)\n            }\n        };\n\n        buf.set_style(area, self.style);\n        let list_area = match self.block.take() {\n            Some(b) => {\n                let inner_area = b.inner(area);\n                b.render(area, buf);\n                inner_area\n            }\n            None => area,\n        };\n        if list_area.width < formatter.min_width() || list_area.height < 1 {\n            return;\n        }\n\n        let mut state = self.state.lock();\n        let la_height = list_area.height as usize;\n        let la_left = list_area.left();\n        let la_top = list_area.top();\n        let la_width = list_area.width as usize;\n        let mut rev_lines: Vec<(LinePointer, Line)> = vec![];\n        let mut can_scroll_up = true;\n        let mut can_scroll_down = state.opt_line_pointer_center.is_some();\n        {\n            enum Pos {\n                Top,\n                Bottom,\n                Center(usize),\n            }\n            let tui_lock = TUI_LOGGER.inner.lock();\n            // If scrolling, the opt_line_pointer_center is set.\n            // Otherwise we are following the bottom of the events\n            let opt_pos_event_index = if let Some(lp) = state.opt_line_pointer_center {\n                tui_lock.events.first_index().map(|first_index| {\n                    if first_index <= lp.event_index {\n                        (Pos::Center(lp.subline), lp.event_index)\n                    } else {\n                        (Pos::Top, first_index)\n                    }\n                })\n            } else {\n                tui_lock\n                    .events\n                    .last_index()\n                    .map(|last_index| (Pos::Bottom, last_index))\n            };\n            if let Some((pos, event_index)) = opt_pos_event_index {\n                // There are events to be shown\n                let mut lines: Vec<(usize, Vec<Line>, usize)> = Vec::new();\n                let mut from_line: isize = 0;\n                let mut to_line = 0;\n                match pos {\n                    Pos::Center(subline) => {\n                        if render_debug {\n                            println!(\"CENTER {}\", event_index);\n                        }\n                        if let Some((_, evt_index, evt)) =\n                            self.next_event(&tui_lock.events, event_index, false, true, &state)\n                        {\n                            let evt_lines = formatter.format(la_width, evt);\n                            from_line = (la_height / 2) as isize - subline as isize;\n                            to_line = la_height / 2 + (evt_lines.len() - 1) - subline;\n                            let n = evt_lines.len();\n                            lines.push((evt_index, evt_lines, n));\n                            if render_debug {\n                                println!(\"Center is {}\", evt_index);\n                            }\n                        }\n                    }\n                    Pos::Top => {\n                        can_scroll_up = false;\n                        if render_debug {\n                            println!(\"TOP\");\n                        }\n                        if let Some((_, evt_index, evt)) =\n                            self.next_event(&tui_lock.events, event_index, false, true, &state)\n                        {\n                            let evt_lines = formatter.format(la_width, evt);\n                            from_line = 0;\n                            to_line = evt_lines.len() - 1;\n                            let n = evt_lines.len();\n                            lines.push((evt_index, evt_lines, n));\n                            if render_debug {\n                                println!(\"Top is {}\", evt_index);\n                            }\n                        }\n                    }\n                    Pos::Bottom => {\n                        if render_debug {\n                            println!(\"TOP\");\n                        }\n                        if let Some((_, evt_index, evt)) =\n                            self.next_event(&tui_lock.events, event_index, false, false, &state)\n                        {\n                            let evt_lines = formatter.format(la_width, evt);\n                            to_line = la_height - 1;\n                            from_line = to_line as isize - (evt_lines.len() - 1) as isize;\n                            let n = evt_lines.len();\n                            lines.push((evt_index, evt_lines, n));\n                            if render_debug {\n                                println!(\"Bottom is {}\", evt_index);\n                            }\n                        }\n                    }\n                }\n                if !lines.is_empty() {\n                    let mut cont = true;\n                    let mut at_top = false;\n                    let mut at_bottom = false;\n                    while cont {\n                        if render_debug {\n                            println!(\"from_line {}, to_line {}\", from_line, to_line);\n                        }\n                        cont = false;\n                        if from_line > 0 {\n                            if let Some((_, evt_index, evt)) = self.next_event(\n                                &tui_lock.events,\n                                lines.first().as_ref().unwrap().0,\n                                true,\n                                false,\n                                &state,\n                            ) {\n                                let evt_lines = formatter.format(la_width, evt);\n                                from_line -= evt_lines.len() as isize;\n                                let n = evt_lines.len();\n                                lines.insert(0, (evt_index, evt_lines, n));\n                                cont = true;\n                            } else {\n                                // no more events, so adjust start\n                                at_top = true;\n                                if render_debug {\n                                    println!(\"no more events adjust start\");\n                                }\n                                to_line -= from_line as usize;\n                                from_line = 0;\n                                if render_debug {\n                                    println!(\"=> from_line {}, to_line {}\", from_line, to_line);\n                                }\n                            }\n                        }\n                        if to_line < la_height - 1 {\n                            if let Some((_, evt_index, evt)) = self.next_event(\n                                &tui_lock.events,\n                                lines.last().as_ref().unwrap().0,\n                                true,\n                                true,\n                                &state,\n                            ) {\n                                let evt_lines = formatter.format(la_width, evt);\n                                to_line += evt_lines.len();\n                                let n = evt_lines.len();\n                                lines.push((evt_index, evt_lines, n));\n                                cont = true;\n                            } else {\n                                at_bottom = true;\n                                can_scroll_down = false;\n                                if render_debug {\n                                    println!(\"no more events at end\");\n                                }\n                                // no more events\n                                if to_line != la_height - 1 {\n                                    cont = true;\n                                } else if !cont {\n                                    break;\n                                }\n                                // no more events, so adjust end\n                                from_line += (la_height - 1 - to_line) as isize;\n                                to_line = la_height - 1;\n                                if render_debug {\n                                    println!(\"=> from_line {}, to_line {}\", from_line, to_line);\n                                }\n                            }\n                        }\n                        if at_top && at_bottom {\n                            break;\n                        }\n                    }\n                    if at_top {\n                        can_scroll_up = false;\n                    }\n                    if at_bottom {\n                        can_scroll_down = false;\n                    }\n                    if render_debug {\n                        println!(\"finished: from_line {}, to_line {}\", from_line, to_line);\n                    }\n                    let mut curr: isize = to_line as isize;\n                    while let Some((evt_index, evt_lines, mut n)) = lines.pop() {\n                        for line in evt_lines.into_iter().rev() {\n                            n -= 1;\n                            if curr < 0 {\n                                break;\n                            }\n                            if curr < la_height as isize {\n                                let line_ptr = LinePointer {\n                                    event_index: evt_index,\n                                    subline: n,\n                                };\n                                rev_lines.push((line_ptr, line));\n                            }\n                            curr -= 1;\n                        }\n                    }\n                }\n            } else {\n                can_scroll_down = false;\n                can_scroll_up = false;\n            }\n        }\n\n        state.opt_line_pointer_next_page = if can_scroll_down {\n            rev_lines.first().map(|l| l.0)\n        } else {\n            None\n        };\n        state.opt_line_pointer_prev_page = if can_scroll_up {\n            rev_lines.last().map(|l| l.0)\n        } else {\n            None\n        };\n\n        if render_debug {\n            println!(\"Line pointers in buffer:\");\n            for l in rev_lines.iter().rev() {\n                println!(\"event_index {}, subline {}\", l.0.event_index, l.0.subline);\n            }\n            if state.opt_line_pointer_center.is_some() {\n                println!(\n                    \"Linepointer center: {:?}\",\n                    state.opt_line_pointer_center.unwrap()\n                );\n            }\n            if state.opt_line_pointer_next_page.is_some() {\n                println!(\n                    \"Linepointer next: {:?}\",\n                    state.opt_line_pointer_next_page.unwrap()\n                );\n            }\n            if state.opt_line_pointer_prev_page.is_some() {\n                println!(\n                    \"Linepointer prev: {:?}\",\n                    state.opt_line_pointer_prev_page.unwrap()\n                );\n            }\n        }\n\n        // This apparently ensures, that the log starts at top\n        let offset: u16 = if state.opt_line_pointer_center.is_none() {\n            0\n        } else {\n            let lines_cnt = rev_lines.len();\n            std::cmp::max(0, la_height - lines_cnt) as u16\n        };\n\n        for (i, line) in rev_lines.into_iter().rev().take(la_height).enumerate() {\n            line.1.render(\n                Rect {\n                    x: la_left,\n                    y: la_top + i as u16 + offset,\n                    width: list_area.width,\n                    height: 1,\n                },\n                buf,\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "src/widget/standard_formatter.rs",
    "content": "use crate::logger::TuiLoggerLevelOutput;\nuse crate::widget::logformatter::LogFormatter;\nuse crate::ExtLogRecord;\nuse ratatui::style::Style;\nuse ratatui::text::{Line, Span};\nuse std::borrow::Cow;\nuse unicode_segmentation::UnicodeSegmentation;\n\npub struct LogStandardFormatter {\n    /// Base style of the widget\n    pub(crate) style: Style,\n    /// Level based style\n    pub(crate) style_error: Option<Style>,\n    pub(crate) style_warn: Option<Style>,\n    pub(crate) style_debug: Option<Style>,\n    pub(crate) style_trace: Option<Style>,\n    pub(crate) style_info: Option<Style>,\n    pub(crate) format_separator: char,\n    pub(crate) format_timestamp: Option<String>,\n    pub(crate) format_output_level: Option<TuiLoggerLevelOutput>,\n    pub(crate) format_output_target: bool,\n    pub(crate) format_output_file: bool,\n    pub(crate) format_output_line: bool,\n}\n\nimpl LogStandardFormatter {\n    fn append_wrapped_line(\n        &self,\n        style: Style,\n        indent: usize,\n        lines: &mut Vec<Line>,\n        line: &str,\n        width: usize,\n        with_indent: bool,\n    ) {\n        let mut p = 0;\n        let mut wrap_len = width;\n        if with_indent {\n            wrap_len -= indent;\n        }\n        let space = \" \".repeat(indent);\n        let line_chars = line.graphemes(true).collect::<Vec<_>>();\n        while p < line_chars.len() {\n            let linelen = std::cmp::min(wrap_len, line_chars.len() - p);\n            let subline = &line_chars[p..p + linelen];\n\n            let mut spans: Vec<Span> = Vec::new();\n            if wrap_len < width {\n                // need indent\n                spans.push(Span {\n                    style,\n                    content: Cow::Owned(space.to_string()),\n                });\n            }\n            spans.push(Span {\n                style,\n                content: Cow::Owned(subline.iter().map(|x| x.to_string()).collect()),\n            });\n            let line = Line::from(spans);\n            lines.push(line);\n\n            p += linelen;\n            // following lines need to be indented\n            wrap_len = width - indent;\n        }\n    }\n}\n\nimpl LogFormatter for LogStandardFormatter {\n    fn min_width(&self) -> u16 {\n        9 + 4\n    }\n    fn format(&self, width: usize, evt: &ExtLogRecord) -> Vec<Line<'_>> {\n        let mut lines = Vec::new();\n        let mut output = String::new();\n        let (col_style, lev_long, lev_abbr, with_loc) = match evt.level {\n            log::Level::Error => (self.style_error, \"ERROR\", \"E\", true),\n            log::Level::Warn => (self.style_warn, \"WARN \", \"W\", true),\n            log::Level::Info => (self.style_info, \"INFO \", \"I\", true),\n            log::Level::Debug => (self.style_debug, \"DEBUG\", \"D\", true),\n            log::Level::Trace => (self.style_trace, \"TRACE\", \"T\", true),\n        };\n        let col_style = col_style.unwrap_or(self.style);\n        if let Some(fmt) = self.format_timestamp.as_ref() {\n            output.push_str(&evt.timestamp.strftime(fmt).to_string());\n            output.push(self.format_separator);\n        }\n        match &self.format_output_level {\n            None => {}\n            Some(TuiLoggerLevelOutput::Abbreviated) => {\n                output.push_str(lev_abbr);\n                output.push(self.format_separator);\n            }\n            Some(TuiLoggerLevelOutput::Long) => {\n                output.push_str(lev_long);\n                output.push(self.format_separator);\n            }\n        }\n        if self.format_output_target {\n            output.push_str(evt.target());\n            output.push(self.format_separator);\n        }\n        if with_loc {\n            if self.format_output_file {\n                if let Some(file) = evt.file() {\n                    output.push_str(file);\n                    output.push(self.format_separator);\n                }\n            }\n            if self.format_output_line {\n                if let Some(line) = evt.line {\n                    output.push_str(&format!(\"{}\", line));\n                    output.push(self.format_separator);\n                }\n            }\n        }\n        let mut sublines: Vec<&str> = evt.msg().lines().rev().collect();\n\n        output.push_str(sublines.pop().unwrap_or(\"\"));\n        self.append_wrapped_line(col_style, 9, &mut lines, &output, width, false);\n\n        for subline in sublines.iter().rev() {\n            self.append_wrapped_line(col_style, 9, &mut lines, subline, width, true);\n        }\n        lines\n    }\n}\n"
  },
  {
    "path": "src/widget/target.rs",
    "content": "use std::sync::Arc;\n\nuse parking_lot::Mutex;\nuse ratatui::{\n    buffer::Buffer,\n    layout::Rect,\n    style::{Modifier, Style},\n    widgets::{Block, Widget},\n};\n\nuse crate::logger::TUI_LOGGER;\nuse crate::widget::inner::TuiWidgetInnerState;\nuse crate::TuiWidgetState;\nuse log::Level;\nuse log::LevelFilter;\n\nfn advance_levelfilter(levelfilter: LevelFilter) -> (Option<LevelFilter>, Option<LevelFilter>) {\n    match levelfilter {\n        LevelFilter::Trace => (None, Some(LevelFilter::Debug)),\n        LevelFilter::Debug => (Some(LevelFilter::Trace), Some(LevelFilter::Info)),\n        LevelFilter::Info => (Some(LevelFilter::Debug), Some(LevelFilter::Warn)),\n        LevelFilter::Warn => (Some(LevelFilter::Info), Some(LevelFilter::Error)),\n        LevelFilter::Error => (Some(LevelFilter::Warn), Some(LevelFilter::Off)),\n        LevelFilter::Off => (Some(LevelFilter::Error), None),\n    }\n}\n\n/// This is the definition for the TuiLoggerTargetWidget,\n/// which allows configuration of the logger system and selection of log messages.\npub struct TuiLoggerTargetWidget<'b> {\n    block: Option<Block<'b>>,\n    /// Base style of the widget\n    style: Style,\n    style_show: Style,\n    style_hide: Style,\n    style_off: Option<Style>,\n    highlight_style: Style,\n    state: Arc<Mutex<TuiWidgetInnerState>>,\n    targets: Vec<String>,\n}\nimpl<'b> Default for TuiLoggerTargetWidget<'b> {\n    fn default() -> TuiLoggerTargetWidget<'b> {\n        TuiLoggerTargetWidget {\n            block: None,\n            style: Default::default(),\n            style_off: None,\n            style_hide: Style::default(),\n            style_show: Style::default().add_modifier(Modifier::REVERSED),\n            highlight_style: Style::default().add_modifier(Modifier::REVERSED),\n            state: Arc::new(Mutex::new(TuiWidgetInnerState::new())),\n            targets: vec![],\n        }\n    }\n}\nimpl<'b> TuiLoggerTargetWidget<'b> {\n    pub fn block(mut self, block: Block<'b>) -> TuiLoggerTargetWidget<'b> {\n        self.block = Some(block);\n        self\n    }\n    pub fn opt_style(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {\n        if let Some(s) = style {\n            self.style = s;\n        }\n        self\n    }\n    pub fn opt_style_off(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {\n        if style.is_some() {\n            self.style_off = style;\n        }\n        self\n    }\n    pub fn opt_style_hide(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {\n        if let Some(s) = style {\n            self.style_hide = s;\n        }\n        self\n    }\n    pub fn opt_style_show(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {\n        if let Some(s) = style {\n            self.style_show = s;\n        }\n        self\n    }\n    pub fn opt_highlight_style(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {\n        if let Some(s) = style {\n            self.highlight_style = s;\n        }\n        self\n    }\n    pub fn style(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {\n        self.style = style;\n        self\n    }\n    pub fn style_off(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {\n        self.style_off = Some(style);\n        self\n    }\n    pub fn style_hide(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {\n        self.style_hide = style;\n        self\n    }\n    pub fn style_show(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {\n        self.style_show = style;\n        self\n    }\n    pub fn highlight_style(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {\n        self.highlight_style = style;\n        self\n    }\n    pub(crate) fn inner_state(\n        mut self,\n        state: Arc<Mutex<TuiWidgetInnerState>>,\n    ) -> TuiLoggerTargetWidget<'b> {\n        self.state = state;\n        self\n    }\n    pub fn state(mut self, state: &TuiWidgetState) -> TuiLoggerTargetWidget<'b> {\n        self.state = state.clone_state();\n        self\n    }\n}\nimpl<'b> Widget for TuiLoggerTargetWidget<'b> {\n    fn render(mut self, area: Rect, buf: &mut Buffer) {\n        buf.set_style(area, self.style);\n        let list_area = match self.block.take() {\n            Some(b) => {\n                let inner_area = b.inner(area);\n                b.render(area, buf);\n                inner_area\n            }\n            None => area,\n        };\n        if list_area.width < 8 || list_area.height < 1 {\n            return;\n        }\n\n        let la_left = list_area.left();\n        let la_top = list_area.top();\n        let la_width = list_area.width as usize;\n\n        {\n            let inner = &TUI_LOGGER.inner.lock();\n            let hot_targets = &inner.targets;\n            let mut state = self.state.lock();\n            let hide_off = state.hide_off;\n            let offset = state.offset;\n            let focus_selected = state.focus_selected;\n            {\n                let targets = &mut state.config;\n                targets.merge(hot_targets);\n                self.targets.clear();\n                for (t, levelfilter) in targets.iter() {\n                    if hide_off && levelfilter == &LevelFilter::Off {\n                        continue;\n                    }\n                    self.targets.push(t.clone());\n                }\n                self.targets.sort();\n            }\n            state.nr_items = self.targets.len();\n            if state.selected >= state.nr_items {\n                state.selected = state.nr_items.max(1) - 1;\n            }\n            if state.selected < state.nr_items {\n                state.opt_selected_target = Some(self.targets[state.selected].clone());\n                let t = &self.targets[state.selected];\n                let (more, less) = if let Some(levelfilter) = state.config.get(t) {\n                    advance_levelfilter(levelfilter)\n                } else {\n                    (None, None)\n                };\n                state.opt_selected_visibility_less = less;\n                state.opt_selected_visibility_more = more;\n                let (more, less) = if let Some(levelfilter) = hot_targets.get(t) {\n                    advance_levelfilter(levelfilter)\n                } else {\n                    (None, None)\n                };\n                state.opt_selected_recording_less = less;\n                state.opt_selected_recording_more = more;\n            }\n            let list_height = (list_area.height as usize).min(self.targets.len());\n            let offset = if list_height > self.targets.len() {\n                0\n            } else if state.selected < state.nr_items {\n                let sel = state.selected;\n                if sel >= offset + list_height {\n                    // selected is below visible list range => make it the bottom\n                    sel - list_height + 1\n                } else if sel.min(offset) + list_height > self.targets.len() {\n                    self.targets.len() - list_height\n                } else {\n                    sel.min(offset)\n                }\n            } else {\n                0\n            };\n            state.offset = offset;\n\n            let targets = &(&state.config);\n            let default_level = inner.default;\n            for i in 0..list_height {\n                let t = &self.targets[i + offset];\n                // Comment in relation to issue #69:\n                // Widgets maintain their own list of level filters per target.\n                // These lists are not forwarded to the TUI_LOGGER, but kept widget private.\n                // Example: This widget's private list contains a target named \"not_yet\",\n                // and the application hasn't logged an entry with target \"not_yet\".\n                // If displaying the target list, then \"not_yet\" will be only present in target,\n                // but not in hot_targets. In issue #69 the problem has been, that\n                // `hot_targets.get(t).unwrap()` has caused a panic. Which is to be expected.\n                // The remedy is to use unwrap_or with default_level.\n                let hot_level_filter = hot_targets.get(t).unwrap_or(default_level);\n                let level_filter = targets.get(t).unwrap_or(default_level);\n                for (j, sym, lev) in &[\n                    (0, \"E\", Level::Error),\n                    (1, \"W\", Level::Warn),\n                    (2, \"I\", Level::Info),\n                    (3, \"D\", Level::Debug),\n                    (4, \"T\", Level::Trace),\n                ] {\n                    if let Some(cell) = buf.cell_mut((la_left + j, la_top + i as u16)) {\n                        let cell_style = if hot_level_filter >= *lev {\n                            if level_filter >= *lev {\n                                if !focus_selected || i + offset == state.selected {\n                                    self.style_show\n                                } else {\n                                    self.style_hide\n                                }\n                            } else {\n                                self.style_hide\n                            }\n                        } else if let Some(style_off) = self.style_off {\n                            style_off\n                        } else {\n                            cell.set_symbol(\" \");\n                            continue;\n                        };\n                        cell.set_style(cell_style);\n                        cell.set_symbol(sym);\n                    }\n                }\n                buf.set_stringn(la_left + 5, la_top + i as u16, \":\", la_width, self.style);\n                buf.set_stringn(\n                    la_left + 6,\n                    la_top + i as u16,\n                    t,\n                    la_width,\n                    if i + offset == state.selected {\n                        self.highlight_style\n                    } else {\n                        self.style\n                    },\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tests/empty_log.rs",
    "content": "use log::*;\nuse ratatui::{backend::TestBackend, layout::Rect, Terminal};\nuse tui_logger::*;\n\n#[test]\nfn test_panic_on_empty_log() {\n    let _ = init_logger(LevelFilter::Trace);\n    set_default_level(LevelFilter::Trace);\n\n    info!(\"\");\n    move_events();\n\n    let backend = TestBackend::new(40, 3);\n    let mut terminal = Terminal::new(backend).unwrap();\n    let res = terminal.draw(|f| {\n        let tui_logger_widget = TuiLoggerWidget::default().output_timestamp(None);\n        f.render_widget(\n            tui_logger_widget,\n            Rect {\n                x: 0,\n                y: 0,\n                width: 40,\n                height: 3,\n            },\n        );\n    });\n    assert!(res.is_ok());\n}\n"
  },
  {
    "path": "tests/envfilter.rs",
    "content": "use log::*;\nuse ratatui::{backend::TestBackend, buffer::Buffer, layout::Rect, Terminal};\nuse tui_logger::*;\n\n#[cfg(test)]\nmod tests {\n    use super::*; // Import the functions from the parent module\n\n    #[test]\n    fn test_formatter() {\n        init_logger(LevelFilter::Trace).unwrap();\n        set_default_level(LevelFilter::Off);\n\n        warn!(\"Message\"); // This is suppressed due to LevelFilter::Off\n        move_events();\n        set_env_filter_from_string(\"envfilter=info\");\n        warn!(\"Message\");\n        move_events();\n        info!(\"Message\");\n        move_events();\n        remove_env_filter(); // Ensure the level has been stored in the hot_select hashtable\n        info!(\"Message\"); // Default filter would be Off\n        move_events();\n\n        let backend = TestBackend::new(40, 8);\n        let mut terminal = Terminal::new(backend).unwrap();\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default().output_timestamp(None);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 40,\n                        height: 8,\n                    },\n                );\n            })\n            .unwrap();\n        let expected = Buffer::with_lines([\n            \"WARN :envfilter::tests:tests/envfilter.r\",\n            \"         s:17:Message                   \",\n            \"INFO :envfilter::tests:tests/envfilter.r\",\n            \"         s:19:Message                   \",\n            \"INFO :envfilter::tests:tests/envfilter.r\",\n            \"         s:22:Message                   \",\n            \"                                        \",\n            \"                                        \",\n        ]);\n        //expected.set_style(Rect::new(0, 0, 40, 2), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n    }\n}\n"
  },
  {
    "path": "tests/formatter_wrap.rs",
    "content": "use log::*;\nuse ratatui::{backend::TestBackend, buffer::Buffer, layout::Rect, Terminal};\nuse tui_logger::*;\n\n#[cfg(test)]\nmod tests {\n    use super::*; // Import the functions from the parent module\n\n    #[test]\n    fn test_formatter() {\n        init_logger(LevelFilter::Trace).unwrap();\n        set_default_level(LevelFilter::Trace);\n\n        info!(\"Message\");\n        move_events();\n\n        let backend = TestBackend::new(40, 3);\n        let mut terminal = Terminal::new(backend).unwrap();\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default().output_timestamp(None);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 40,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        let expected = Buffer::with_lines([\n            \"INFO :formatter_wrap::tests:tests/format\",\n            \"         ter_wrap.rs:14:Message         \",\n            \"                                        \",\n        ]);\n        //expected.set_style(Rect::new(0, 0, 40, 2), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n    }\n}\n"
  },
  {
    "path": "tests/scroll.rs",
    "content": "use log::*;\nuse ratatui::text::{Line, Span};\nuse ratatui::{\n    backend::TestBackend,\n    buffer::Buffer,\n    layout::Rect,\n    style::{Style, Stylize},\n    Terminal,\n};\nuse std::borrow::Cow;\nuse std::{thread, time};\nuse tui_logger::*;\n\npub struct TestFormatter {}\nimpl LogFormatter for TestFormatter {\n    fn min_width(&self) -> u16 {\n        1\n    }\n    fn format(&self, _width: usize, evt: &ExtLogRecord) -> Vec<Line<'_>> {\n        let mut lines = Vec::new();\n        let mut spans: Vec<Span> = Vec::new();\n        let style = Style::new().reversed();\n        let msg = evt.msg().lines().rev().collect::<Vec<&str>>().join(\" \");\n        spans.push(Span {\n            style,\n            content: Cow::Owned(format!(\"Hello {}\", msg)),\n        });\n        let line = Line::from(spans);\n        lines.push(line);\n        lines\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*; // Import the functions from the parent module\n\n    #[test]\n    fn test_scroll() {\n        init_logger(LevelFilter::Trace).unwrap();\n        set_default_level(LevelFilter::Trace);\n\n        let state = TuiWidgetState::new();\n\n        info!(\"0\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"1\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"2\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"3\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"4\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"5\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"6\");\n        move_events();\n\n        println!(\"Initial draw\");\n        let backend = TestBackend::new(10, 3);\n        let mut terminal = Terminal::new(backend).unwrap();\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        let mut expected = Buffer::with_lines([\"Hello 4   \", \"Hello 5   \", \"Hello 6   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 3   \", \"Hello 4   \", \"Hello 5   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 2   \", \"Hello 3   \", \"Hello 4   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 1   \", \"Hello 2   \", \"Hello 3   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 0   \", \"Hello 1   \", \"Hello 2   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up at top\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 0   \", \"Hello 1   \", \"Hello 2   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 1   \", \"Hello 2   \", \"Hello 3   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 2   \", \"Hello 3   \", \"Hello 4   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 3   \", \"Hello 4   \", \"Hello 5   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 4   \", \"Hello 5   \", \"Hello 6   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down at bottom\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 4   \", \"Hello 5   \", \"Hello 6   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n    }\n}\n"
  },
  {
    "path": "tests/scroll_long_wrap.rs",
    "content": "use log::*;\nuse ratatui::text::{Line, Span};\nuse ratatui::{\n    backend::TestBackend,\n    buffer::Buffer,\n    layout::Rect,\n    style::{Style, Stylize},\n    Terminal,\n};\nuse std::borrow::Cow;\nuse std::{thread, time};\nuse tui_logger::*;\n\npub struct TestFormatter {}\nimpl LogFormatter for TestFormatter {\n    fn min_width(&self) -> u16 {\n        1\n    }\n    fn format(&self, _width: usize, evt: &ExtLogRecord) -> Vec<Line<'_>> {\n        let mut lines = Vec::new();\n        let style = Style::new().reversed();\n        let msg = evt.msg().lines().rev().collect::<Vec<&str>>().join(\" \");\n\n        let mut spans: Vec<Span> = Vec::new();\n        spans.push(Span {\n            style,\n            content: Cow::Owned(format!(\"Hello {}\", msg)),\n        });\n        let line = Line::from(spans);\n        lines.push(line);\n\n        let mut spans: Vec<Span> = Vec::new();\n        spans.push(Span {\n            style,\n            content: Cow::Owned(format!(\" wrap {} 1\", msg)),\n        });\n        let line = Line::from(spans);\n        lines.push(line);\n\n        let mut spans: Vec<Span> = Vec::new();\n        spans.push(Span {\n            style,\n            content: Cow::Owned(format!(\" wrap {} 2\", msg)),\n        });\n        let line = Line::from(spans);\n        lines.push(line);\n\n        let mut spans: Vec<Span> = Vec::new();\n        spans.push(Span {\n            style,\n            content: Cow::Owned(format!(\" wrap {} 3\", msg)),\n        });\n        let line = Line::from(spans);\n        lines.push(line);\n\n        lines\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*; // Import the functions from the parent module\n\n    #[test]\n    fn test_scroll() {\n        init_logger(LevelFilter::Trace).unwrap();\n        set_default_level(LevelFilter::Trace);\n\n        let state = TuiWidgetState::new();\n\n        info!(\"0\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"1\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"2\");\n        thread::sleep(time::Duration::from_millis(10));\n        move_events();\n\n        println!(\"Initial draw\");\n        let backend = TestBackend::new(10, 3);\n        let mut terminal = Terminal::new(backend).unwrap();\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        let mut expected = Buffer::with_lines([\" wrap 2 1 \", \" wrap 2 2 \", \" wrap 2 3 \"]);\n        expected.set_style(Rect::new(0, 0, 9, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 2   \", \" wrap 2 1 \", \" wrap 2 2 \"]);\n        expected.set_style(Rect::new(0, 0, 7, 1), Style::new().reversed());\n        expected.set_style(Rect::new(0, 1, 9, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 1 3 \", \"Hello 2   \", \" wrap 2 1 \"]);\n        expected.set_style(Rect::new(0, 0, 9, 1), Style::new().reversed());\n        expected.set_style(Rect::new(0, 1, 7, 2), Style::new().reversed());\n        expected.set_style(Rect::new(0, 2, 9, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n    }\n}\n"
  },
  {
    "path": "tests/scroll_wrap.rs",
    "content": "use log::*;\nuse ratatui::text::{Line, Span};\nuse ratatui::{\n    backend::TestBackend,\n    buffer::Buffer,\n    layout::Rect,\n    style::{Style, Stylize},\n    Terminal,\n};\nuse std::borrow::Cow;\nuse std::{thread, time};\nuse tui_logger::*;\n\npub struct TestFormatter {}\nimpl LogFormatter for TestFormatter {\n    fn min_width(&self) -> u16 {\n        1\n    }\n    fn format(&self, _width: usize, evt: &ExtLogRecord) -> Vec<Line<'_>> {\n        let mut lines = Vec::new();\n        let style = Style::new().reversed();\n        let msg = evt.msg().lines().rev().collect::<Vec<&str>>().join(\" \");\n        let mut spans: Vec<Span> = Vec::new();\n        spans.push(Span {\n            style,\n            content: Cow::Owned(format!(\"Hello {}\", msg)),\n        });\n        let line = Line::from(spans);\n        lines.push(line);\n        let mut spans: Vec<Span> = Vec::new();\n        spans.push(Span {\n            style,\n            content: Cow::Owned(format!(\" wrap {}\", msg)),\n        });\n        let line = Line::from(spans);\n        lines.push(line);\n        lines\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*; // Import the functions from the parent module\n\n    #[test]\n    fn test_scroll() {\n        init_logger(LevelFilter::Trace).unwrap();\n        set_default_level(LevelFilter::Trace);\n\n        let state = TuiWidgetState::new();\n\n        info!(\"0\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"1\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"2\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"3\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"4\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"5\");\n        thread::sleep(time::Duration::from_millis(10));\n        info!(\"6\");\n        move_events();\n\n        println!(\"Initial draw\");\n        let backend = TestBackend::new(10, 3);\n        let mut terminal = Terminal::new(backend).unwrap();\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        let mut expected = Buffer::with_lines([\" wrap 5   \", \"Hello 6   \", \" wrap 6   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 5   \", \" wrap 5   \", \"Hello 6   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 4   \", \"Hello 5   \", \" wrap 5   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 4   \", \" wrap 4   \", \"Hello 5   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 3   \", \"Hello 4   \", \" wrap 4   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 3   \", \" wrap 3   \", \"Hello 4   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 2   \", \"Hello 3   \", \" wrap 3   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 2   \", \" wrap 2   \", \"Hello 3   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 1   \", \"Hello 2   \", \" wrap 2   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 1   \", \" wrap 1   \", \"Hello 2   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 0   \", \"Hello 1   \", \" wrap 1   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 0   \", \" wrap 0   \", \"Hello 1   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll up at top\");\n        state.transition(TuiWidgetEvent::PrevPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 0   \", \" wrap 0   \", \"Hello 1   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 0   \", \"Hello 1   \", \" wrap 1   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 1   \", \" wrap 1   \", \"Hello 2   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 1   \", \"Hello 2   \", \" wrap 2   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 2   \", \" wrap 2   \", \"Hello 3   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 2   \", \"Hello 3   \", \" wrap 3   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 3   \", \" wrap 3   \", \"Hello 4   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 3   \", \"Hello 4   \", \" wrap 4   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 4   \", \" wrap 4   \", \"Hello 5   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 4   \", \"Hello 5   \", \" wrap 5   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\"Hello 5   \", \" wrap 5   \", \"Hello 6   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 5   \", \"Hello 6   \", \" wrap 6   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n\n        println!(\"Scroll down at bottom\");\n        state.transition(TuiWidgetEvent::NextPageKey);\n\n        terminal\n            .draw(|f| {\n                let tui_logger_widget = TuiLoggerWidget::default()\n                    .formatter(Box::new(TestFormatter {}))\n                    .state(&state);\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 3,\n                    },\n                );\n            })\n            .unwrap();\n        expected = Buffer::with_lines([\" wrap 5   \", \"Hello 6   \", \" wrap 6   \"]);\n        expected.set_style(Rect::new(0, 0, 7, 3), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n    }\n}\n"
  },
  {
    "path": "tests/simple.rs",
    "content": "use log::*;\nuse ratatui::text::{Line, Span};\nuse ratatui::{\n    backend::TestBackend,\n    buffer::Buffer,\n    layout::Rect,\n    style::{Style, Stylize},\n    Terminal,\n};\nuse std::borrow::Cow;\nuse tui_logger::*;\n\npub struct TestFormatter {}\nimpl LogFormatter for TestFormatter {\n    fn min_width(&self) -> u16 {\n        1\n    }\n    fn format(&self, _width: usize, _evt: &ExtLogRecord) -> Vec<Line<'_>> {\n        let mut lines = Vec::new();\n        let mut spans: Vec<Span> = Vec::new();\n        let style = Style::new().reversed();\n        spans.push(Span {\n            style,\n            content: Cow::Owned(\"Hello\".to_string()),\n        });\n        let line = Line::from(spans);\n        lines.push(line);\n        lines\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*; // Import the functions from the parent module\n\n    #[test]\n    fn test_simple() {\n        init_logger(LevelFilter::Trace).unwrap();\n        set_default_level(LevelFilter::Trace);\n\n        info!(\"1\");\n        move_events();\n\n        let backend = TestBackend::new(10, 1);\n        let mut terminal = Terminal::new(backend).unwrap();\n        terminal\n            .draw(|f| {\n                let tui_logger_widget =\n                    TuiLoggerWidget::default().formatter(Box::new(TestFormatter {}));\n                f.render_widget(\n                    tui_logger_widget,\n                    Rect {\n                        x: 0,\n                        y: 0,\n                        width: 10,\n                        height: 1,\n                    },\n                );\n            })\n            .unwrap();\n        let mut expected = Buffer::with_lines([\"Hello     \"]);\n        expected.set_style(Rect::new(0, 0, 5, 1), Style::new().reversed());\n        terminal.backend().assert_buffer(&expected);\n    }\n}\n"
  }
]