Skip to content

feat(widget): initial implementation of stateful rendering #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
40 changes: 20 additions & 20 deletions .cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,29 @@ filter_unconventional = true
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/tui-rs-revival/ratatui/issues/${2}))"},
{ pattern = '(better safe shared layout cache)', replace = "perf(layout): ${1}" },
{ pattern = '(Clarify README.md)', replace = "docs(readme): ${1}" },
{ pattern = '(Update README.md)', replace = "docs(readme): ${1}" },
{ pattern = '(fix typos|Fix typos)', replace = "fix: ${1}" }
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/tui-rs-revival/ratatui/issues/${2}))" },
{ pattern = '(better safe shared layout cache)', replace = "perf(layout): ${1}" },
{ pattern = '(Clarify README.md)', replace = "docs(readme): ${1}" },
{ pattern = '(Update README.md)', replace = "docs(readme): ${1}" },
{ pattern = '(fix typos|Fix typos)', replace = "fix: ${1}" },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 00 -->Features"},
{ message = "^[fF]ix", group = "<!-- 01 -->Bug Fixes"},
{ message = "^refactor", group = "<!-- 02 -->Refactor"},
{ message = "^doc", group = "<!-- 03 -->Documentation"},
{ message = "^perf", group = "<!-- 04 -->Performance"},
{ message = "^style", group = "<!-- 05 -->Styling"},
{ message = "^test", group = "<!-- 06 -->Testing"},
{ message = "^chore\\(release\\): prepare for", skip = true},
{ message = "^chore\\(pr\\)", skip = true},
{ message = "^chore\\(pull\\)", skip = true},
{ message = "^chore", group = "<!-- 07 -->Miscellaneous Tasks"},
{ body = ".*security", group = "<!-- 08 -->Security"},
{ message = "^build", group = "<!-- 09 -->Build"},
{ message = "^ci", group = "<!-- 10 -->Continuous Integration"},
{ message = "^revert", group = "<!-- 11 -->Reverted Commits"},
{ message = "^feat", group = "<!-- 00 -->Features" },
{ message = "^[fF]ix", group = "<!-- 01 -->Bug Fixes" },
{ message = "^refactor", group = "<!-- 02 -->Refactor" },
{ message = "^doc", group = "<!-- 03 -->Documentation" },
{ message = "^perf", group = "<!-- 04 -->Performance" },
{ message = "^style", group = "<!-- 05 -->Styling" },
{ message = "^test", group = "<!-- 06 -->Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore", group = "<!-- 07 -->Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 08 -->Security" },
{ message = "^build", group = "<!-- 09 -->Build" },
{ message = "^ci", group = "<!-- 10 -->Continuous Integration" },
{ message = "^revert", group = "<!-- 11 -->Reverted Commits" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
Expand Down
62 changes: 31 additions & 31 deletions .deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]

# This section is considered when running `cargo deny check advisories`
Expand All @@ -48,7 +48,7 @@ notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
#"RUSTSEC-0000-0000",
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
Expand Down Expand Up @@ -76,18 +76,18 @@ unlicensed = "deny"
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"MIT",
"Apache-2.0",
"Unicode-DFS-2016",
# "WTFPL",
# "AGPL-3.0",
#"Apache-2.0 WITH LLVM-exception",
"MIT",
"Apache-2.0",
"Unicode-DFS-2016",
# "WTFPL",
# "AGPL-3.0",
#"Apache-2.0 WITH LLVM-exception",
]
# List of explicitly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
deny = [
#"Nokia",
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "allow"
Expand All @@ -111,9 +111,9 @@ confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
]

# Some crates don't have (easily) machine readable licensing information,
Expand All @@ -132,8 +132,8 @@ exceptions = [
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]

[licenses.private]
Expand All @@ -146,7 +146,7 @@ ignore = false
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
#"https://sekretz.com/registry
]

# This section is considered when running `cargo deny check bans`.
Expand All @@ -165,28 +165,28 @@ wildcards = "allow"
highlight = "all"
# List of crates that are allowed. Use with care!
allow = [
#{ name = "ansi_term", version = "=0.11.0" },
#{ name = "ansi_term", version = "=0.11.0" },
]
# List of crates to deny
deny = [
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
]
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#{ name = "ansi_term", version = "=0.11.0" },
#{ name = "ansi_term", version = "=0.11.0" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite
skip-tree = [
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
]

# This section is considered when running `cargo deny check sources`.
Expand Down
2 changes: 1 addition & 1 deletion .rustfmt-toolchain.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
[toolchain]
channel = "nightly-2023-07-01"
components = ["rustfmt"]
targets = [ ]
targets = []
2 changes: 1 addition & 1 deletion .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
edition="2021"
edition = "2021"
newline_style = "Unix"
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
Expand Down
15 changes: 14 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ categories = ["command-line-interface", "command-line-utilities"]
# "bench",
# ]

[features]
unstable = [ "input-handling"]
input-handling = [ "crossterm", "portable-pty", "bytes", "tracing" ]

[dependencies]
ratatui = "0.23.0"
vt100 = "0.15.2"
ratatui = "0.23.0"
bytes = { version = "1.4.0", optional = true}
crossterm = { version = "0.27.0", optional = true}
portable-pty = { version = "0.8.1", optional = true}
tracing = {version = "0.1.37", optional = true}

[dev-dependencies]
bytes = "1.4.0"
Expand Down Expand Up @@ -89,6 +97,11 @@ doc-scrape-examples = true
name = "nested_shell_async"
doc-scrape-examples = true

[[example]]
name = "nested_shell_stateful"
doc-scrape-examples = true
required-features = ["unstable"]

[[example]]
name = "smux"
doc-scrape-examples = true
Expand Down
2 changes: 1 addition & 1 deletion examples/nested_shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fn main() -> std::io::Result<()> {
cmd.cwd(cwd);

let size = Size {
rows: terminal.size()?.height,
rows: terminal.size()?.height - 6,
cols: terminal.size()?.width,
};

Expand Down
103 changes: 103 additions & 0 deletions examples/nested_shell_stateful.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#![allow(unused_variables)]

use std::{io, sync::mpsc::Sender};

use bytes::Bytes;
use crossterm::{
event::{DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers},
execute,
style::ResetColor,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use portable_pty::CommandBuilder;
use ratatui::{
backend::{Backend, CrosstermBackend},
layout::Alignment,
prelude::{Constraint, Direction, Layout},
style::{Color, Modifier, Style},
terminal::Terminal,
text::Span,
widgets::{Block, Borders, Paragraph},
Frame,
};
use tui_term::widget::{PseudoTerminal, PseudoTerminalState};

fn main() -> std::io::Result<()> {
let mut stdout = io::stdout();
execute!(stdout, ResetColor)?;
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;

let cwd = std::env::current_dir().unwrap();
let shell = std::env::var("SHELL").unwrap();
let mut command = CommandBuilder::new(shell);
command.cwd(cwd);

let initial_size = terminal.size()?;
let terminal_state = PseudoTerminalState::new(initial_size);

let input_sender = terminal_state.spawn(command);

run(&mut terminal, terminal_state, input_sender)?;

// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
Ok(())
}

fn run<B: Backend>(
terminal: &mut Terminal<B>,
mut state: PseudoTerminalState,
sender: Sender<Bytes>,
) -> io::Result<()> {
let excluded_events = [Event::Key(KeyEvent::new(
KeyCode::Char('c'),
KeyModifiers::CONTROL,
))];
loop {
let input_result = state.handle_input(&excluded_events, &sender);
if !input_result.handled {
match input_result.event {
Event::Key(ke)
if ke == KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL) =>
{
return Ok(());
}
_ => {}
}
} else {
terminal.draw(|f| ui(f, &mut state))?;
}
}
}

fn ui<B: Backend>(f: &mut Frame<B>, state: &mut PseudoTerminalState) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(1), Constraint::Length(1)].as_ref())
.split(f.size());

let terminal_block = Block::default()
.title(Span::styled("Terminal", Style::default().fg(Color::Green)))
.borders(Borders::ALL)
.style(Style::default().add_modifier(Modifier::BOLD));

let pseudo_term = PseudoTerminal::default().block(terminal_block);

let explanation = "Press Ctrl+C to exit".to_string();
let explanation = Paragraph::new(explanation)
.style(Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED))
.alignment(Alignment::Center);

f.render_stateful_widget(pseudo_term, chunks[0], state);
f.render_widget(explanation, chunks[1]);
}
22 changes: 19 additions & 3 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,34 @@ use ratatui::{
style::{Modifier, Style},
};

use crate::widget::PseudoTerminal;
use crate::widget::{PseudoTerminal, PseudoTerminalState};

/// Draw the [`Screen`] to the [`Buffer`],
/// area is the designated area that the consumer provides
pub fn handle(term: &PseudoTerminal, area: Rect, buf: &mut Buffer) {
pub fn handle(
term: &PseudoTerminal,
area: Rect,
buf: &mut Buffer,
state: Option<&mut PseudoTerminalState>,
) {
let reader;
let cols = area.width;
let rows = area.height;
let col_start = area.x;
let row_start = area.y;
let area_cols = area.width + area.x;
let area_rows = area.height + area.y;
let screen = term.screen();
let screen = match term.screen() {
Some(s) => s,
None => {
let Some(state) = state else {
return;
};

reader = state.parser.read().unwrap();
reader.screen()
}
};

// The [`Screen`] is made out of rows of cells
for row in 0..rows {
Expand Down
Loading