Crate r3bl_rs_utils
source ·Expand description
Context
R3BL TUI library & suite of apps focused on developer productivity
We are working on building command line apps in Rust which have rich text user interfaces (TUI). We want to lean into the terminal as a place of productivity, and build all kinds of awesome apps for it.
-
🔮 Instead of just building one app, we are building a library to enable any kind of rich TUI development w/ a twist: taking concepts that work really well for the frontend mobile and web development world and re-imagining them for TUI & Rust.
- Taking things like React, JSX, CSS, and Redux, but making everything async (they can be run in parallel & concurrent via Tokio).
- Even the thread running the main event loop doesn’t block since it is async.
- Using proc macros to create DSLs to implement CSS & JSX.
-
🌎 We are building apps to enhance developer productivity & workflows.
- The idea here is not to rebuild tmux in Rust (separate processes mux’d onto a single terminal window). Rather it is to build a set of integrated “apps” (or “tasks”) that run in the same process that renders to one terminal window.
- Inside of this terminal window, we can implement things like “app” switching, routing, tiling layout, stacking layout, etc. so that we can manage a lot of TUI apps (which are tightly integrated) that are running in the same process, in the same window. So you can imagine that all these “app“s have shared application state (that is in a Redux store). Each “app” may also have its own Redux store.
- Here are some examples of the types of “app“s we want to build:
- multi user text editors w/ syntax highlighting
- integrations w/ github issues
- integrations w/ calendar, email, contacts APIs
These crates provides lots of useful functionality to help you build TUI (text user interface) apps, along w/ general niceties & ergonomics that all Rustaceans 🦀 can enjoy 🎉:
- Loosely coupled & fully asynchronous TUI framework to make it possible (and easy) to build sophisticated TUIs (Text User Interface apps) in Rust that are inspired by React, Redux, CSS and Flexbox.
- Thread-safe & fully asynchronous Redux crate (using Tokio to run subscribers and middleware in separate tasks). The reducer functions are run sequentially.
- Lots of declarative macros,
and procedural macros
(both function like and derive) to avoid having to write lots of boilerplate code for many
common (and complex) tasks. And even less noisy
Result
andError
types. - Non binary tree data structure inspired by memory arenas, that is thread safe and supports parallel tree walking.
- Utility functions to improve
ergonomics of commonly used patterns
in Rust programming, ranging from things like colorizing
stdout
,stderr
output to lazy value holders.
Learn more about how this library is built
🦜 Here are some articles (on developerlife.com) about how this crate is made:
- https://developerlife.com/2022/02/24/rust-non-binary-tree/
- https://developerlife.com/2022/03/12/rust-redux/
- https://developerlife.com/2022/03/30/rust-proc-macro/
🦀 You can also find all the Rust related content on developerlife.com here.
🤷♂️ Fun fact: before we built this crate, we built a library that is similar in spirit for TypeScript (for TUI apps on Node.js) called r3bl-ts-utils. We have since switched to Rust 🦀🎉.
Re-exports
pub use tree_memory_arena::*;
pub use utils::*;
Modules
- ANSI colorized text https://github.com/ogham/rust-ansi-term helper methods.
- How to log things, and simply use the logging facilities
- All the modules in the
r3bl_rs_utils_core
crate are in support of thetui
module in the “main”r3bl_rs_utils
crate. - This module contains a lot of utility functions that are meant to:
Macros
- Safely adds two unsigned numbers and returns the result. Does not panic.
- A wrapper for
pretty_assertions::assert_eq!
macro. - Similar to
assert_eq!
but automatically prints the left and right hand side variables if the assertion fails. Useful for debugging tests, since cargo would just print out the left and right values w/out providing information on what variables were being compared. - When calling this, make sure to make a corresponding call to box_end!.
- box_start_with_componentDeprecated
- box_start_with_surface_rendererDeprecated
self
has to be passed into$arg_renderer
because this macro has alet
statement that requires it to have a block. And in the block generated by the macro,self
is not available from the calling scope. - Macro to help with the boilerplate of calling Component::handle_event on a SharedComponent. This is used by route_event_to_focused_component.
- Syntactic sugar to run a conditional statement. Here’s an example.
- Creates a new ChUnit amount.
- This is a really simple macro to make it effortless to use the color console logger. It takes a single identifier as an argument, or any number of them. It simply dumps an arrow symbol, followed by the identifier (stringify’d) along with the value that it contains (using the Debug formatter). All of the output is colorized for easy readability. You can use it like this.
- Safely decrements an unsigned number. Does not panic.
- Declarative macro to surround the given block with a call to
tokio::spawn
. This is useful for spawning a task that will run in the background from a function that is NOT async. - Safely increments an unsigned number. Does not panic.
- Macro to insulate this library from changes in crossterm KeyEvent constructor & fields.
- Examples.
- Render the component in the current box (which is retrieved from the surface). This is the “normal” way to render a component, in the FlexBox that is currently being laid out.
- Render the component in the given box (which is not retrieved from the surface). This is usually to do “absolute positioned” rendering of components (like for a modal dialog box that paints on top of everything else in the window).
- Here’s an example. Refer to RenderOps for more details.
- This macro is a convenience macro for creating a RenderPipeline. It works w/ RenderOp items. It allows them to be added in sequence, and then flushed at the end.
- Example:
- Helper macro that works w/ EventPropagation. This code block commonly appears in places where an input event is processed and an EventPropagation is returned.
- Macro to make building StyledText easy.
- Macro to make building
StyledTexts
easy. - Macro to make building Stylesheet easy. This returns a CommonResult because it checks to see that all Styles that are added have an
id
. If they don’t, then an a CommonError is thrown. This is to ensure that valid styles are added to a stylesheet. Without anid
, they can’t be retrieved after they’re added here, rendering them useless. - Safely subtracts two unsigned numbers and returns the result. Does not panic.
- Wrap the given block or stmt so that it returns a Result<()>. It is just syntactic sugar that helps having to write Ok(()) repeatedly.
- Wrap the given block or stmt so that it returns a Result<$it>. It is just syntactic sugar that helps having to write Ok($it) repeatedly.
- Macros to unwrap various locks.
- Unwrap the
$option
, and ifNone
then run the$next
closure which must return a value that is set to$option
. Basically a way to compute something lazily when it (theOption
) is set toNone
. - Unwrap the
$option
, and ifNone
then run the$next
closure which must return an error. This macro must be called in a block that returns aCommonResult<T>
. - Unwrap the
$option
, and ifNone
then return the given$err_type
. Otherwise return the unwrapped$option
. This macro must be called in a block that returns aCommonResult<T>
. - Runs the
$code
block after evaluating the$eval
expression and assigning it to$id
. - Similar to
with_mut!
except that it returns the value of the$code
block.
Structs
- This is a simple animator that can be used to run a single animation task. Animators can be re-used (stopped, and restarted repeatedly).
- Represents a character unit or “ch” unit. This is a unit of measurement that is used to represent the width or height of a character in a monospace font. The terminal displaying the Rust binary build using the tui library will ultimately determine the actual width and height of a character.
- A struct to contain info we need to print with every character.
- Common error struct. Read custom error docs here.
- This map is used to cache Components that have been created and are meant to be reused between multiple renders.
- Component scope args struct that holds references.
- Please do not construct this struct directly and use new_empty instead.
- This is a shim which allows the reusable DialogEngine to be used in the context of Component and r3bl_redux::Store. The main methods here simply pass thru all their arguments to the DialogEngine.
- Please do not construct this struct directly, and use new instead.
- Things you can do with a DialogEngine.
- DialogEngine args struct that holds references.
- Stores the data for a single editor buffer. Please do not construct this struct directly and use new_empty instead.
- This is a shim which allows the reusable EditorEngine to be used in the context of Component and Store. The main methods here simply pass thru all their arguments to the EditorEngine.
- Do not create this struct directly. Please use new() instead.
- EditorEngine args struct that holds references.
- Functions that implement the editor engine.
- A box is a rectangle with a position and size. The direction of the box determines how it’s contained elements are positioned.
- Properties that are needed to create a FlexBox.
- This is a global data structure that holds state for the entire application. This is wrapped in an Arc and Mutex so that it can be accessed from anywhere as SharedGlobalData.
- Global scope args struct that holds references.
- Main error struct. https://learning-rust.github.io/docs/e7.custom_error_types.html
- Please use the LolcatBuilder to create this struct (lots of documentation is provided here). Please do not use this struct directly.
- A builder struct for the Lolcat struct. Example usage:
- Represents a grid of cells where the row/column index maps to the terminal screen. This works regardless of the size of each cell. Cells can contain emoji who’s display width is greater than one. This complicates things since a “😃” takes up 2 display widths.
- Holds a subset of the fields in FlexBox that are required by the editor and dialog engines.
- Simple pattern matcher that matches a single character at a time. It is meant to be used to perform text clipping on a single line of text, so that the syntax highlighted version is clipped the same as the plain text version.
- Represents an integer value between 0 and 100 (inclusive).
- Here is a visual representation of how position and sizing works for the layout engine.
- For ease of use, please use the render_ops! macro.
- See render_pipeline! for the documentation. Also consider using it instead of this struct directly for convenience.
- Size, defined as [height, width].
- Key is the row index, value is the selected range in that line (display col index range).
- Represents a range of characters in a line. The range is not inclusive of the item at the end index, which means that when you call clip_to_range the item at the end index will not be part of the result (this is shown in the example below). The indices are all display column indices, not logical ones.
- Here is a visual representation of how position and sizing works for the layout engine.
- Holds a single list item for a given indent level. This may contain multiple lines which are stored in the
content_lines
field. Take a look at parse_smart_list for more details. - Thread safe and async Redux store (using
tokio
). - Spans are chunks of a text that have an associated style. There are usually multiple spans in a line of text.
- Use styled_text! macro for easier construction.
- Represents a rectangular area of the terminal screen, and not necessarily the full terminal screen.
- Properties that are needed to create a Surface.
- We need a String (since we’re returning a slice of a temporary UnicodeString that is dropped by the function that creates it, not as a result of mutation).
Enums
- For RGB colors:
- Some common errors that can occur.
- This works w/ the main event loop to let it know whether it should exit or resize after an input event has occurred (and has been passed thru the input event routing system).
- By providing a conversion from InputEvent to DialogEvent it becomes easier to write event handlers that consume InputEvent and then process events in DialogComponent and DialogEngine.
- Events that can be applied to the EditorEngine to modify an EditorBuffer.
- Crossterm docs:
- This works w/ the input event routing system to provide the caller w/ information about whether an even has been consumed or not. If it has been consumed, is a render necessary.
- Gradient has to be generated before this will be anything other than GradientLengthKind::NotCalculatedYet.
- Please see KeyPress for more information about handling keyboard input.
- This is equivalent to crossterm::event::KeyEvent except that it is cleaned up semantically and impossible states are removed. It enables the tui framework to use a different backend other than crossterm in the future. Apps written using this framework use KeyPress and not crossterm::event::KeyEvent. See convert_key_event for more information on the conversion.
- Direction of the layout of the box.
- Specific types of errors.
- A Markdown document once parsed is turned into a Vec of “blocks”. A block is the top-level element of a Markdown document and rougly represents a single line of text.
- These are things that show up in a single line of Markdown text MdLineFragments. They do not include other Markdown blocks (like code blocks, lists, headings, etc).
- Crossterm docs:
- Crossterm docs:
- Crossterm docs:
Constants
- Enable or disable compositor debug logging.
- Enable or disable select, copy, paste debug logging.
- This is the global
DEBUG
const. It is possible to create local (module scoped)DEBUG
const. However, you would have to use that symbol explicitly in the relevant module, eg: - Controls input event debugging crate::EventStreamExt, and execution of render ops crate::exec_render_op! debugging output.
- Enable or disable syntax highlighting debug logging.
- More info: https://www.colorhexa.com/
- This value should be set to ParallelExecutionPolicy::Serial unless you have a very good reason otherwise.
Statics
Traits
- An app is typically a holder for ComponentRegistry. It then lays out a bunch of Components on its Surface which do all the work of rendering and input event handling. There are examples of structs that implement this train in the examples module.
- Helper trait and impl to convert std::env::Args to a
Vec<String>
after removing the first item (which is the path to the executable). - See App.
- Marker trait to “remember” which types can be printed to the console w/ color.
- Marker trait to “remember” which types can be converted to plain text.
- This marker trait is meant to be implemented by whatever state struct is being used to store the dialog buffer for this re-usable editor component. It is used in the
where
clause of the DialogComponent to ensure that the generic typeS
implements this trait, guaranteeing that it holds a single DialogBuffer. - This marker trait is meant to be implemented by whatever state struct is being used to store the editor buffer for this re-usable editor component. It is used in the
where
clause of the EditorComponent to ensure that the generic typeS
implements this trait, guaranteeing that it holds a hash map of EditorBuffers w/ key of FlexBoxId. - Public API interface to create nested & responsive layout based UIs.
- Methods that actually perform the layout and positioning.
- Marker trait to “remember” which types support pretty printing for debugging.
- This trait marks a type as being safe to mutate (interior mutability) across threads (parallel safe) and tasks (async safe). These are just convenience static methods. You can simply use the
read()
andwrite()
methods directly on theArc
reference. - This trait marks a type as being safe to share across threads (parallel safe) and tasks (async safe).
- This trait exists to allow “pseudo operator overloading”. Rust does not support operator overloading, and the method to add a single style has a different signature than the one to add a vector of styles. To get around this, the TryAdd trait is implemented for both Style and
Vec<Style>
. Then the stylesheet! macro can “pseudo overload” them.
Functions
- At a minimum, a CodeBlockLine will be 2 lines of text.
- Arguments
- Arguments
- This is just for the bold content, not the enclosing
***
. - This is just for the bold content, not the enclosing
**
. - This is for the entire checkbox span (checked).
- This is for the entire checkbox span (unchecked).
- This style is for things like
[
,]
,*
, “`”, etc. They are dimmed so that they don’t distract from the main content they are wrapping like a link or inline code block, etc. - This style is for the foreground text of the entire document. This is the default style. It is overridden by other styles like bold, italic, etc. below.
- This is just for the bold content, not the enclosing “`”.
- This is just for the bold content, not the enclosing
*
. - This is just for the link text not the enclosing
[
and]
. - This is just for the link url not the enclosing
(
and)
. - This is for the bullet or numbered bullet of a list item, not the content.
- Bg color: #ad83daFg color: black
- Fg color: #e2a1e3Bg color: #303030
- Bg color: #4f86edFg color: black
- Fg color: #4fcbd4Bg color: #444444
- This style is for any selected range in the document.
- Please take a look at try_to_set_log_level to enable or disable logging.
- Please take a look at try_to_set_log_level to enable or disable logging.
- Please take a look at try_to_set_log_level to enable or disable logging.
- Please take a look at try_to_set_log_level to enable or disable logging.
- Please take a look at try_to_set_log_level to enable or disable logging.
- Interrogate crossterm crossterm::terminal::size() to get the size of the terminal window.
- Sample inputs: One line: “
bash\npip install foobar\n
\n” No line: “\n\n
\n” Multi line: “bash\npip install foobar\npip install foobar\n
\n” No language: “\npip install foobar\n
\n” No language, no line: “\n
\n” No language, multi line: “\npip install foobar\npip install foobar\n
\n” - This matches the heading tag and text until EOL. Outputs a tuple of HeadingLevel and FragmentsInOneLine.
- Parse a markdown text FragmentsInOneLine in the input (no EOL required).
- Parse a markdown text FragmentsInOneLine in the input (no EOL required).
- Parse a single line of markdown text FragmentsInOneLine terminated by EOL.
- Public API for parsing a smart list block in markdown.
- Parse input:
@tags: tag1, tag2, tag3
.There may or may not be a newline at the end. - Checkboxes are tricky since they begin with “[” which is also used for hyperlinks and images. So some extra hint is need from the code calling this parser to let it know whether to parse a checkbox into plain text, or into a boolean.
- Checkboxes are tricky since they begin with “[” which is also used for hyperlinks and images. So some extra hint is need from the code calling this parser to let it know whether to parse a checkbox into plain text, or into a boolean.
- Parse a single chunk of markdown text (found in a single line of text) into a MdLineFragment.
- There must be at least one match. We want to match many things that are not any of our special tags, but since we have no tools available to match and consume in the negative case (without regex) we need to match against our (start) tags, then consume one char; we repeat this until we run into one of our special characters (start tags) then we return this slice.
- Parse function that generate an RgbValue struct from a valid hex color string.
- Parse input:
@title: Something
.There may or may not be a newline at the end. - This is the main parser entry point. It takes a string slice and if it can be parsed, returns a MdDocument that represents the parsed Markdown.
- First line of
input
looks like this. - Equivalent for template string literal. One way to do this using
format!
- This diagram shows what happens per line of text.
- Render plain to an offscreen buffer. This will modify the
my_offscreen_buffer
argument. For plain text it supports counting GraphemeClusterSegments. The display width of each segment is taken into account when filling the offscreen buffer. - Ensure that the Position is within the bounds of the terminal window using RenderOpsLocalData.If the Position is outside of the bounds of the window then it is clamped to the nearest edge of the window. This clamped Position is returned.This also saves the clamped Position to RenderOpsLocalData.
- Split a string by newline. The idea is that a line is some text followed by a newline. An empty line is just a newline character.
- Respect the color support of the terminal and downgrade the color if needed. This really only applies to the TuiColor::Rgb variant.
- This is the main function that the editor uses this in order to display the markdown to the user.It is responsible for converting:
- Please take a look at try_to_set_log_level to enable or disable logging.
- If you don’t call this function w/ a value other than LevelFilter::Off, then logging is DISABLED by default. It won’t matter if you call any of the other logging functions in this module.
Type Aliases
- Alias for MdDocument.
- The backing field that is used to represent a ChUnit in memory.
- Alias for List of CodeBlockLine.
- Type alias to make it easy to work with
Result
s. Works hand in hand w/ CommonError. Here’s an example. - Alias for MdLineFragments.
- Alias for List of FragmentsInOneLine.
- This corresponds to a single Markdown document, which is produced after a successful parse operation crate::parse_markdown.
- This roughly corresponds to a single line of text. Each line is made up of one or more MdLineFragment.
- A line of text is made up of multiple StyleUSSpans.
- A document is made up of multiple StyleUSSpanLines.
- Use styled_texts! macro for easier construction.
- Span are chunks of a text that have an associated style. There are usually multiple spans in a line of text.
- A line of text is made up of multiple SyntectStyleStrSpans.