diff --git a/logger-template/README.md b/logger-template/README.md new file mode 100644 index 0000000..3b5cf10 --- /dev/null +++ b/logger-template/README.md @@ -0,0 +1,17 @@ +# Ratatui Simple template + +The simple template will create the following project structure: + +```text +src/ +├── app.rs -> holds the state and application logic +├── main.rs -> entry-point +``` + +## Design choices + +We have a small `App` struct that has a main loop that calls methods to handle events and draw the +ui. The app can be quit by pressing any of Q/Esc/Ctrl+C. + +We use [color-eyre](https://docs.rs/color-eyre/latest/color_eyre/) for simplifying any errors that +need to be reported to the console. diff --git a/logger-template/template/.github/workflows/build_examples.yml b/logger-template/template/.github/workflows/build_examples.yml new file mode 100644 index 0000000..4cb1a8d --- /dev/null +++ b/logger-template/template/.github/workflows/build_examples.yml @@ -0,0 +1,21 @@ +name: Build examples + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Check rust version + run: rustup show + - name: Build examples with ratatui and termion + run: cargo build --examples --features termion + - name: Build example with ratatui and crossterm + run: cargo build --examples --features crossterm diff --git a/logger-template/template/.github/workflows/build_examples_latest.yml b/logger-template/template/.github/workflows/build_examples_latest.yml new file mode 100644 index 0000000..c0aff04 --- /dev/null +++ b/logger-template/template/.github/workflows/build_examples_latest.yml @@ -0,0 +1,23 @@ +name: Build examples with latest rust version + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Check rust version + run: rustup show + - name: Update rust + run: rustup update + - name: Build examples with ratatui and termion + run: cargo build --examples --features termion + - name: Build example with ratatui and crossterm + run: cargo build --examples --features crossterm diff --git a/logger-template/template/.github/workflows/docs.yml b/logger-template/template/.github/workflows/docs.yml new file mode 100644 index 0000000..11b0a37 --- /dev/null +++ b/logger-template/template/.github/workflows/docs.yml @@ -0,0 +1,20 @@ +name: Documentation + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + check-docs: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install cargo-rdme + uses: taiki-e/install-action@v2 + with: + tool: cargo-rdme + - name: Check README.md is up-to-date + run: cargo rdme --check diff --git a/logger-template/template/.github/workflows/semver_checks.yml b/logger-template/template/.github/workflows/semver_checks.yml new file mode 100644 index 0000000..b0db256 --- /dev/null +++ b/logger-template/template/.github/workflows/semver_checks.yml @@ -0,0 +1,16 @@ +name: Semver Checks + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + semver-checks: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Check semver + uses: obi1kenobi/cargo-semver-checks-action@v2 diff --git a/logger-template/template/.gitignore b/logger-template/template/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/logger-template/template/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/logger-template/template/CHANGELOG.md b/logger-template/template/CHANGELOG.md new file mode 100644 index 0000000..bd120e9 --- /dev/null +++ b/logger-template/template/CHANGELOG.md @@ -0,0 +1,139 @@ +0.14.1: +- re-export log::LevelFilter + +0.14.0: +- Update version of ratatui + +0.13.2: +- fix tracing support + +0.13.1: +- fix missing `move_events()` on half full buffer in case hot buffer capacity was odd + +0.13.0: +- `move_events()` is not published anymore, but called by a cyclic internal task. + This task is called by timeout (10ms) unless the hot buffer is half full. +- `init_logger()` returns now `Result<(), TuiLoggerError>` + +0.12.1: +- fix for issue #69: avoid unwrap panic by using default level +- add `set_buffer_depth()` to modify circular buffer size + +0.12.0: +- update ratatui to 0.28 + +0.11.2: +- update ratatui to 0.27 + +0.11.1: +- one line error report for demo example, if feature flag crossterm or termion not given +- use cargo readme and test in github build +- Fix of issue #60: panic on too small widget size + +0.11.0: +- BREAKING CHANGE: TuiWidgetEvent::transition() method now takes a TuiWidgetEvent by value instead of by reference. +- update ratatui to 0.25 + +0.10.1: +- update ratatui to ^0.25.0 + +0.10.0: +- Remove support for tui legacy crate +- Use `Cell::set_symbol()` as performance optimization from ratatui +- Require chrono >= 0.4.20 for avoid security vulnerability (https://rustsec.org/advisories/RUSTSEC-2020-0159.html) + +0.9.6: +- update ratatui to 0.23.0 + +0.9.5: +- rework examples/demo to not use termion + +0.9.4: +- fix breaking change in 0.9.3 as reported by issue #43 + +0.9.3: +- update to ratatui 0.22 and fix clippy warnings + +0.9.2: +- update to ratatui 0.21 + +0.9.1: +- Implement Eq for TuiWidgetEvent +- suppport `border_type()` for TuiLoggerSmartWidget +- Disable default features of chrono to avoid import of `time` v0.1.x + +0.9.0: +- support for tracing-subscriber +- add optional ratatui support as proposed by (#32) +- slog is NOT a default feature anymore. Enable with `slog-support` + +0.8.3: +- Make `TuiWidgetState.set_default_display_level()` work for TuiLoggerWidget (#30) + +0.8.2: +- Allow TuiLoggerWidget to be controlled with TuiWidgetState by calling state() builder function (#30) +- Extend demo for an example for this TuiLoggerWidget control + +0.8.1: +- Update to tui-rs 0.19 and slog to 2.7.0 + +0.8.0: +- Update to tui-rs 0.18 + +0.7.1: +- Update to tui-rs 0.17 + +0.7.0: +- Update rust edition in Cargo.toml to 2021 +- Fix all warnings from cargo clippy +- new function for TuiWidgetState to set the default display level - not impacting the recording + ```rust + set_default_display_level(self, levelfilter: LevelFilter) -> TuiWidgetState +- changed signature for TuiWidgetState function from + ```rust + set_level_for_target(&self, target: &str, levelfilter: LevelFilter) -> &TuiWidgetState + ``` + to + ```rust + set_level_for_target(self, target: &str, levelfilter: LevelFilter) -> TuiWidgetState + ``` + + +0.6.6: +- Add functions to format output of log data as discussed in [issue #19](https://github.com/gin66/tui-logger/issues/19) + The functions are with their default values: + ``` + output_separator(':') + output_timestamp(Some("%H:%M:%S".to_string())) + output_level(Some(TuiLoggerLevelOutput::Long)) + output_target(true) + output_file(true) + output_line(true) + ``` + +0.6.5: +- Use thread safe counterparts for Rc/RefCell + +0.6.4: +- Bump version up for update to tui 0.16 + +0.6.3: +- Removed verbose timestamp info log (issue #16) + +0.6.2: +- Fix by Wuelle to avoid panic on line wrapping inside a utf8 character + +0.6.1: +- Changes in README + +0.6.0: +- Support Scrollback in log history with TuiWidgetEvent::PrevPageKey, NextPageKey and EscapeKey +- log and target panes' title can be set via .title_log(String) and .title_target(String) + +0.5.1: +- TuiWidgetEvent is now Debug, Clone, PartialEq, Hash + +0.5.0: +- Remove dispatcher completely +- Get rid of dependency to termion and crossterm +- KeyCommands to be translated by the application into KeyEvents for TuiWidgetState::transition() diff --git a/logger-template/template/Cargo.toml b/logger-template/template/Cargo.toml new file mode 100644 index 0000000..c2fa40b --- /dev/null +++ b/logger-template/template/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "tui-logger" +version = "0.14.1" +authors = ["Jochen Kiemes "] +edition = "2021" +license = "MIT" +description = "Logger with smart widget for the `ratatui` crate" +documentation = "https://docs.rs/tui-logger/latest/tui_logger/" +repository = "https://github.com/gin66/tui-logger" +readme = "README.md" +keywords = ["tui", "log", "logger", "widget", "dispatcher"] + +[dependencies] +log = "0.4" +chrono = { version = "^0.4.38", default-features = false, features = ["clock"] } +ratatui = { version = "0.29", default-features = false} +tracing = {version = "0.1.40", optional = true} +tracing-subscriber = {version = "0.3", optional = true} +lazy_static = "1.5" +fxhash = "0.2" +parking_lot = "0.12" +slog = { version = "2.7.0", optional = true } + +[dev-dependencies] +# the crate is compatible with ratatui >=0.25.0, but the demo uses features from 0.27.0 +ratatui = { version = "0.29", default-features = false} +anyhow = "1.0.91" +env_logger = "0.11.5" +termion = {version = "4.0.3" } +crossterm = {version = "0.28"} + +[features] +slog-support = ["slog"] +tracing-support = ["tracing", "tracing-subscriber"] + +# only necessary for the demo, the crate does has no dependencies on these +# +# feature_crossterm_or_termion_must_be_selected to generate one line error message +# instead of many compile error messages, if neither crossterm nor termion are selected. +feature_crossterm_or_termion_must_be_selected = [] +crossterm = ["ratatui/crossterm", "feature_crossterm_or_termion_must_be_selected"] +termion = ["ratatui/termion", "feature_crossterm_or_termion_must_be_selected"] + +# Docs.rs-specific configuration required to enable documentation of +# code requiring optional features. +[package.metadata.docs.rs] +# Document all features +all-features = true +# Defines the configuration attribute `docsrs` +rustdoc-args = ["--cfg", "docsrs"] + +[[example]] +name="demo" +required-features=["feature_crossterm_or_termion_must_be_selected"] + diff --git a/logger-template/template/LICENSE b/logger-template/template/LICENSE new file mode 100644 index 0000000..a6efc1d --- /dev/null +++ b/logger-template/template/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Jochen Kiemes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/logger-template/template/README.md b/logger-template/template/README.md new file mode 100644 index 0000000..915da11 --- /dev/null +++ b/logger-template/template/README.md @@ -0,0 +1,213 @@ +# tui-logger + +## Logger with smart widget for the `tui` and `ratatui` crate + +[![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) +![Build examples](https://github.com/gin66/tui-logger/workflows/Build%20examples/badge.svg?service=github) + + +### Demo of the widget + +![Demo](https://github.com/gin66/tui-logger/blob/master/doc/demo_v0.6.6.gif?raw=true) + +### Documentation + +[Documentation](https://docs.rs/tui-logger/latest/tui_logger/) + +### Important note for `tui` + +The `tui` crate has been archived and `ratatui` has taken over. +In order to avoid supporting compatibility for an inactive crate, +the v0.9.x releases are the last to support `tui`. In case future bug fixes +are needed, the branch `tui_legacy` has been created to track changes to 0.9.x releases. + +Starting with v0.10 `tui-logger` is `ratatui` only. + +### Features + +- [X] Logger implementation for the `log` crate +- [X] Logger enable/disable detection via hash table (avoid string compare) +- [X] Hot logger code only copies enabled log messages with timestamp into a circular buffer +- [X] Widgets/move_message() retrieve captured log messages from hot circular buffer +- [X] Lost message detection due to circular buffer +- [X] Log filtering performed on log record target +- [X] Simple Widgets to view logs and configure debuglevel per target +- [X] Logging of enabled logs to file +- [X] Scrollback in log history +- [x] Title of target and log pane can be configured +- [X] `slog` support, providing a Drain to integrate into your `slog` infrastructure +- [X] `tracing` support +- [ ] Allow configuration of target dependent loglevel specifically for file logging +- [ ] Avoid duplicating of target, module and filename in every log record +- [ ] Simultaneous modification of all targets' display/hot logging loglevel by key command + +### Smart Widget + +Smart widget consists of two widgets. Left is the target selector widget and +on the right side the logging messages view scrolling up. The target selector widget +can be hidden/shown during runtime via key command. +The key command to be provided to the TuiLoggerWidget via transition() function. + +The target selector widget looks like this: + +![widget](https://github.com/gin66/tui-logger/blob/master/doc/example.png?raw=true) + +It controls: + +- Capturing of log messages by the logger +- Selection of levels for display in the logging message view + +The two columns have the following meaning: + +- Code EWIDT: E stands for Error, W for Warn, Info, Debug and Trace. + + Inverted characters (EWIDT) are enabled log levels in the view + + Normal characters show enabled capturing of a log level per target + + If any of EWIDT are not shown, then the respective log level is not captured +- Target of the log events can be defined in the log e.g. `warn!(target: "demo", "Log message");` + +### Smart Widget Key Commands +```rust +| KEY | ACTION +|----------|-----------------------------------------------------------| +| h | Toggles target selector widget hidden/visible +| f | Toggle focus on the selected target only +| UP | Select previous target in target selector widget +| DOWN | Select next target in target selector widget +| LEFT | Reduce SHOWN (!) log messages by one level +| RIGHT | Increase SHOWN (!) log messages by one level +| - | Reduce CAPTURED (!) log messages by one level +| + | Increase CAPTURED (!) log messages by one level +| PAGEUP | Enter Page Mode and scroll approx. half page up in log history. +| PAGEDOWN | Only in page mode: scroll 10 events down in log history. +| ESCAPE | Exit page mode and go back to scrolling mode +| SPACE | Toggles hiding of targets, which have logfilter set to off +``` + +The mapping of key to action has to be done in the application. The respective TuiWidgetEvent +has to be provided to TuiWidgetState::transition(). + +Remark to the page mode: The timestamp of the event at event history's bottom line is used as +reference. This means, changing the filters in the EWIDT/focus from the target selector window +should work as expected without jumps in the history. The page next/forward advances as +per visibility of the events. + +### Basic usage to initialize logger-system: +```rust +#[macro_use] +extern crate log; +//use tui_logger; + +fn main() { + // Early initialization of the logger + + // Set max_log_level to Trace + tui_logger::init_logger(log::LevelFilter::Trace).unwrap(); + + // Set default level for unknown targets to Trace + tui_logger::set_default_level(log::LevelFilter::Trace); + + // code.... +} +``` + +For use of the widget please check examples/demo.rs + +### Demo + +Run demo using termion: + +```rust +cargo run --example demo --features termion +``` + +Run demo with crossterm: + +```rust +cargo run --example demo --features crossterm +``` + +### `slog` support + +`tui-logger` provides a [`TuiSlogDrain`] which implements `slog::Drain` and will route all records +it receives to the `tui-logger` widget. + +Enabled by feature "slog-support" + +### `tracing-subscriber` support + +`tui-logger` provides a [`TuiTracingSubscriberLayer`] which implements +`tracing_subscriber::Layer` and will collect all events +it receives to the `tui-logger` widget + +Enabled by feature "tracing-support" + +### Custom filtering +```rust +#[macro_use] +extern crate log; +//use tui_logger; +use env_logger; + +fn main() { + // Early initialization of the logger + let drain = tui_logger::Drain::new(); + // instead of tui_logger::init_logger, we use `env_logger` + env_logger::Builder::default() + .format(move |buf, record| + // patch the env-logger entry through our drain to the tui-logger + Ok(drain.log(record)) + ).init(); // make this the global logger + // code.... +} +``` + +### Internals + +For logging there are two circular buffers in use: +* "hot" buffer, which is written to during any logging macro invocation +* main buffer, which holds events to be displayed by the widgets. + +The size of the "hot" buffer is 1000 and can be modified by `set_hot_buffer_depth()`. +The size of the main buffer is 10000 and can be modified by `set_buffer_depth()`. + +Reason for this scheme: The main buffer is locked for a while during widget updates. +In order to avoid blocking the log-macros, this scheme is in use. + +The copy from "hot" buffer to main buffer is performed by a call to `move_events()`, +which is done in a cyclic task, which repeats every 10 ms, or when the hot buffer is half full. + +In versions <0.13 log messages may have been lost, if the widget wasn't drawn. + +### THANKS TO + +* [Florian Dehau](https://github.com/fdehau) for his great crate [tui-rs](https://github.com/fdehau/tui-rs) +* [Antoine Büsch](https://github.com/abusch) for providing the patches to tui-rs v0.3.0 and v0.6.0 +* [Adam Sypniewski](https://github.com/ajsyp) for providing the patches to tui-rs v0.6.2 +* [James aka jklong](https://github.com/jklong) for providing the patch to tui-rs v0.7 +* [icy-ux](https://github.com/icy-ux) for adding slog support and example +* [alvinhochun](https://github.com/alvinhochun) for updating to tui 0.10 and crossterm support +* [brooksmtownsend](https://github.com/brooksmtownsend) Patch to remove verbose timestamp info +* [Kibouo](https://github.com/Kibouo) Patch to change Rc/Refcell to thread-safe counterparts +* [Afonso Bordado](https://github.com/afonso360) for providing the patch to tui-rs v0.17 +* [Benjamin Kampmann](https://github.com/gnunicorn) for providing patch to tui-rs v0.18 +* [Paul Sanders](https://github.com/pms1969) for providing patch in [issue #30](https://github.com/gin66/tui-logger/issues/30) +* [Ákos Hadnagy](https://github.com/ahadnagy) for providing patch in [#31](https://github.com/gin66/tui-logger/issues/31) for tracing-subscriber support +* [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) +* [purephantom](https://github.com/purephantom) for providing patch in [#42](https://github.com/gin66/tui-logger/issues/42) for ratatui update +* [Badr Bouslikhin](https://github.com/badrbouslikhin) for providing patch in [#47](https://github.com/gin66/tui-logger/issues/47) for ratatui update +* [ganthern](https://github.com/ganthern) for providing patch in [#49](https://github.com/gin66/tui-logger/issues/49) for tui support removal +* [Linda_pp](https://github.com/rhysd) for providing patch in [#51](https://github.com/gin66/tui-logger/issues/51) for Cell:set_symbol +* [Josh McKinney](https://github.com/joshka) for providing patch in +[#56](https://github.com/gin66/tui-logger/issues/56) for Copy on TuiWidgetEvent and +TuiLoggerWidget +* [nick42d](https://github.com/nick42d) for providing patch in +[#63](https://github.com/gin66/tui-logger/issues/63) for semver checks and [#74](https://github.com/gin66/tui-logger/pull/74) +* [Tom Groenwoldt](https://github.com/tomgroenwoldt) for providing patch in [#65](https://github.com/gin66/tui-logger/issues/65) for ratatui update +* [Kevin](https://github.com/doesnotcompete) for providing patch in [#71](https://github.com/issues/71) +* [urizennnn](https://github.com/urizennnn) for providing patch in [#72](https://github.com/issues/72) + +### Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=gin66/tui-logger&type=Date)](https://star-history.com/#gin66/tui-logger&Date) + +License: MIT diff --git a/logger-template/template/bacon.toml b/logger-template/template/bacon.toml new file mode 100644 index 0000000..7caa3f2 --- /dev/null +++ b/logger-template/template/bacon.toml @@ -0,0 +1,58 @@ +# This is a configuration file for the bacon tool +# +# Bacon repository: https://github.com/Canop/bacon +# Complete help on configuration: https://dystroy.org/bacon/config/ +# You can also check bacon's own bacon.toml file +# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml + +default_job = "check" + +[jobs.check] +command = ["cargo", "check", "--color", "always"] +need_stdout = false + +# This is a helpful job to check that the demo compiles with the crossterm +# feature enabled. This and the termion feature are mutually exclusive. +[jobs.check-crossterm] +command = ["cargo", "check", "--all-targets", "--features", "crossterm", "--color", "always"] +need_stdout = false + +# This is a helpful job to check that the demo compiles with the termion +# feature enabled. This and the crossterm feature are mutually exclusive. +[jobs.check-termion] +command = ["cargo", "check", "--all-targets", "--features", "termion", "--color", "always"] +need_stdout = false + +[jobs.clippy] +command = [ + "cargo", "clippy", + "--color", "always", +] +need_stdout = false + +[jobs.test] +command = [ + "cargo", "test", "--libs", "--color", "always", + "--", "--color", "always", # see https://github.com/Canop/bacon/issues/124 +] +need_stdout = true + +[jobs.doc] +command = ["cargo", "doc", "--color", "always", "--no-deps"] +need_stdout = false + +# If the doc compiles, then it opens in your browser and bacon switches +# to the previous job +[jobs.doc-open] +command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"] +need_stdout = false +on_success = "back" # so that we don't open the browser at each change + +# You may define here keybindings that would be specific to +# a project, for example a shortcut to launch a specific job. +# Shortcuts to internal functions (scrolling, toggling, etc.) +# should go in your personal global prefs.toml file instead. +[keybindings] +# alt-m = "job:my-job" +1 = "job:check-crossterm" +2 = "job:check-termion" diff --git a/logger-template/template/cargo-generate.toml b/logger-template/template/cargo-generate.toml new file mode 100644 index 0000000..2589766 --- /dev/null +++ b/logger-template/template/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +cargo_generate_version = ">=0.10.0" diff --git a/logger-template/template/demo.tape b/logger-template/template/demo.tape new file mode 100644 index 0000000..e754c9c --- /dev/null +++ b/logger-template/template/demo.tape @@ -0,0 +1,105 @@ +# VHS documentation +# +# Output: +# Output .gif Create a GIF output at the given +# Output .mp4 Create an MP4 output at the given +# Output .webm Create a WebM output at the given +# +# Require: +# Require Ensure a program is on the $PATH to proceed +# +# Settings: +# Set FontSize Set the font size of the terminal +# Set FontFamily Set the font family of the terminal +# Set Height Set the height of the terminal +# Set Width Set the width of the terminal +# Set LetterSpacing Set the font letter spacing (tracking) +# Set LineHeight Set the font line height +# Set LoopOffset % Set the starting frame offset for the GIF loop +# Set Theme Set the theme of the terminal +# Set Padding Set the padding of the terminal +# Set Framerate Set the framerate of the recording +# Set PlaybackSpeed Set the playback speed of the recording +# Set MarginFill Set the file or color the margin will be filled with. +# Set Margin Set the size of the margin. Has no effect if MarginFill isn't set. +# Set BorderRadius Set terminal border radius, in pixels. +# Set WindowBar Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight) +# Set WindowBarSize Set window bar size, in pixels. Default is 40. +# Set TypingSpeed