Skip to content

Commit 4dc46af

Browse files
committed
✨ Added Output Control and Cursor Movement && Refactored
1 parent 46cc87e commit 4dc46af

File tree

5 files changed

+192
-49
lines changed

5 files changed

+192
-49
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "console-utils"
3-
version = "1.3.0"
3+
version = "1.4.0"
44
edition = "2021"
55
authors = ["Nils Wrenger <[email protected]>"]
66
description = "Cli input utilities."

README.md

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,16 @@ fn main() {
9797
}
9898
```
9999

100-
## Clear Line Function
100+
## Output Control and Cursor Movement
101101

102-
Clears the current line in the console.
102+
The library also provides functions for output control and precise cursor movement:
103103

104-
This function uses ANSI escape codes to clear the entire line and move the cursor to the
105-
beginning of the line.
106-
107-
### Usage
108-
109-
```rust
110-
use console_utils::clear_line;
111-
fn main() {
112-
// Clear the current line
113-
clear_line();
114-
}
115-
```
104+
- `clear_line`
105+
- `flush`
106+
- `move_cursor_down`
107+
- `move_cursor_up`
108+
- `move_cursor_left`
109+
- `move_cursor_right`
110+
- `move_cursor_to`
116111

117112
For more detailed documentation, please refer to the [generated Rust Docs](https://docs.rs/console-utils/latest/console_utils/).

src/lib.rs

Lines changed: 146 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ where
4242
{
4343
loop {
4444
print!("{before}");
45-
io::stdout().flush().unwrap();
45+
flush();
4646

4747
let mut cli = String::new();
4848
io::stdin().read_line(&mut cli).unwrap();
@@ -109,75 +109,78 @@ pub fn select(
109109
) -> Option<Vec<bool>> {
110110
loop {
111111
let mut matrix: Vec<bool> = vec![];
112+
let mut i = 0;
112113
let stdout = Term::buffered_stdout();
113114

114-
println!("{}\n", before,);
115+
// print everything
116+
println!("{}", before,);
115117

116118
for i in options {
117119
println!("[ ] {}", i);
118120
matrix.push(false);
119121
}
120122

121-
stdout.move_cursor_up(options.len()).unwrap();
122-
stdout.flush().unwrap();
123-
let mut i = 0;
123+
// move the cursor to the first item
124+
move_cursor_up(options.len());
125+
move_cursor_right(options[i].len() + 4);
124126

127+
// input reaction loop
125128
loop {
126129
if let Ok(character) = stdout.read_key() {
127130
match character {
128131
Key::ArrowUp | Key::Char('w') => {
129132
if i > 0 {
130-
stdout.move_cursor_up(1).unwrap();
133+
move_cursor_up(1);
134+
move_cursor_left(options[i].len() + 4);
131135
i -= 1;
136+
move_cursor_right(options[i].len() + 4);
132137
}
133138
}
134139
Key::ArrowDown | Key::Char('s') => {
135140
if i < options.len() - 1 {
136-
stdout.move_cursor_down(1).unwrap();
141+
move_cursor_down(1);
142+
move_cursor_left(options[i].len() + 4);
137143
i += 1;
144+
move_cursor_right(options[i].len() + 4);
138145
}
139146
}
140147
Key::Char(' ') => {
141-
stdout.clear_line().unwrap();
148+
clear_line();
142149
if matrix[i] {
143-
stdout.write_line(&format!("[ ] {}", options[i])).unwrap();
150+
print!("[ ] {}", options[i]);
144151
matrix[i] = false;
145152
} else {
146-
stdout
147-
.write_line(&format!("[{}] {}", style("*").cyan(), options[i]))
148-
.unwrap();
153+
print!("[{}] {}", style("*").cyan(), options[i]);
149154
matrix[i] = true;
150155
}
151-
stdout.move_cursor_up(1).unwrap();
152-
stdout.flush().unwrap();
156+
flush();
153157
}
154158
Key::Enter => {
155159
break;
156160
}
157161
_ => {}
158162
}
159163
}
160-
stdout.flush().unwrap();
161164
}
162165

166+
// process input
163167
if matrix.iter().filter(|&&selected| selected).count() > 1 && !multiple {
164-
reset(stdout, "\nPlease Select only one!\n", options.len());
168+
reset("\nPlease Select only one!\n", options.len());
165169
} else if allow_empty && matrix.iter().all(|&x| !x) {
166-
reset(stdout, "", options.len());
170+
reset("", options.len());
167171
return None;
168172
} else if !matrix.iter().all(|&x| !x) {
169-
reset(stdout, "", options.len());
173+
reset("", options.len());
170174
return Some(matrix);
171175
} else {
172-
reset(stdout, "\nPlease Select any option!\n", options.len());
176+
reset("\nPlease Select any option!\n", options.len());
173177
}
174178
}
175179
}
176180

177181
// Internal function for resetting the console.
178-
fn reset(stdout: Term, mes: &str, len: usize) {
179-
stdout.move_cursor_down(len).unwrap();
180-
stdout.flush().unwrap();
182+
fn reset(mes: &str, len: usize) {
183+
move_cursor_down(len);
181184
println!("{mes}");
182185
}
183186

@@ -237,7 +240,7 @@ pub fn spinner(mut time: f64, spinner_type: SpinnerType) {
237240
while time > 0.0 {
238241
clear_line();
239242
print!("{}", frames[i]);
240-
io::stdout().flush().unwrap();
243+
flush();
241244
thread::sleep(Duration::from_secs_f64(0.075));
242245
time -= 0.075;
243246
if i < frames.len() - 1 {
@@ -250,6 +253,20 @@ pub fn spinner(mut time: f64, spinner_type: SpinnerType) {
250253
clear_line();
251254
}
252255

256+
/// Flushes the output buffer, ensuring that all content is written to the console.
257+
///
258+
/// # Example
259+
///
260+
/// ```rust
261+
/// use console_utils::flush;
262+
///
263+
/// // Flush the output buffer to ensure content is displayed immediately
264+
/// flush();
265+
/// ```
266+
pub fn flush() {
267+
io::stdout().flush().unwrap();
268+
}
269+
253270
/// Clears the current line in the console.
254271
///
255272
/// This function uses ANSI escape codes to clear the entire line and move the cursor to the
@@ -265,7 +282,111 @@ pub fn spinner(mut time: f64, spinner_type: SpinnerType) {
265282
/// ```
266283
pub fn clear_line() {
267284
print!("\r\x1b[2K");
268-
io::stdout().flush().unwrap();
285+
flush();
286+
}
287+
288+
/// Moves the cursor down by the specified number of lines.
289+
///
290+
/// # Arguments
291+
///
292+
/// * `n` - The number of lines to move the cursor down.
293+
///
294+
/// # Example
295+
///
296+
/// ```rust
297+
/// use console_utils::move_cursor_down;
298+
///
299+
/// // Move the cursor down by 2 lines
300+
/// move_cursor_down(2);
301+
/// ```
302+
pub fn move_cursor_down(n: usize) {
303+
if n > 0 {
304+
print!("\x1b[{}B", n);
305+
flush();
306+
}
307+
}
308+
309+
/// Moves the cursor up by the specified number of lines.
310+
///
311+
/// # Arguments
312+
///
313+
/// * `n` - The number of lines to move the cursor up.
314+
///
315+
/// # Example
316+
///
317+
/// ```rust
318+
/// use console_utils::move_cursor_up;
319+
///
320+
/// // Move the cursor up by 3 lines
321+
/// move_cursor_up(3);
322+
/// ```
323+
pub fn move_cursor_up(n: usize) {
324+
if n > 0 {
325+
print!("\x1b[{}A", n);
326+
flush();
327+
}
328+
}
329+
330+
/// Moves the cursor to the left by the specified number of characters.
331+
///
332+
/// # Arguments
333+
///
334+
/// * `n` - The number of characters to move the cursor to the left.
335+
///
336+
/// # Example
337+
///
338+
/// ```rust
339+
/// use console_utils::move_cursor_left;
340+
///
341+
/// // Move the cursor left by 4 characters
342+
/// move_cursor_left(4);
343+
/// ```
344+
pub fn move_cursor_left(n: usize) {
345+
if n > 0 {
346+
print!("\x1b[{}D", n);
347+
flush();
348+
}
349+
}
350+
351+
/// Moves the cursor to the right by the specified number of characters.
352+
///
353+
/// # Arguments
354+
///
355+
/// * `n` - The number of characters to move the cursor to the right.
356+
///
357+
/// # Example
358+
///
359+
/// ```rust
360+
/// use console_utils::move_cursor_right;
361+
///
362+
/// // Move the cursor right by 5 characters
363+
/// move_cursor_right(5);
364+
/// ```
365+
pub fn move_cursor_right(n: usize) {
366+
if n > 0 {
367+
print!("\x1b[{}C", n);
368+
flush();
369+
}
370+
}
371+
372+
/// Moves the cursor to the specified position on the console.
373+
///
374+
/// # Arguments
375+
///
376+
/// * `x` - The horizontal position (column) to move the cursor to.
377+
/// * `y` - The vertical position (row) to move the cursor to.
378+
///
379+
/// # Example
380+
///
381+
/// ```rust
382+
/// use console_utils::move_cursor_to;
383+
///
384+
/// // Move the cursor to column 3, row 5
385+
/// move_cursor_to(3, 5);
386+
/// ```
387+
pub fn move_cursor_to(x: usize, y: usize) {
388+
print!("\x1B[{};{}H", y + 1, x + 1);
389+
flush();
269390
}
270391

271392
/// Reveals a string gradually, printing one character at a time with a specified time interval.
@@ -288,7 +409,7 @@ pub fn clear_line() {
288409
pub fn reveal(str: &str, time_between: f64) {
289410
for i in 0..str.len() {
290411
print!("{}", str.chars().nth(i).unwrap_or(' '));
291-
io::stdout().flush().unwrap();
412+
flush();
292413
thread::sleep(Duration::from_secs_f64(time_between));
293414
}
294415
}

tests/test.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
use std::{
2-
io::{self, Write},
3-
thread,
4-
time::Duration,
5-
};
1+
use std::{thread, time::Duration};
62

73
// Import the functions to be tested from the crate root
8-
use console_utils::{clear_line, input, reveal, select, spinner, SpinnerType};
4+
use console_utils::{
5+
clear_line, flush, input, move_cursor_down, move_cursor_up, reveal, select, spinner,
6+
SpinnerType,
7+
};
98

109
#[test]
1110
#[ignore]
@@ -23,7 +22,12 @@ fn test_input() {
2322
#[ignore]
2423
fn test_select() {
2524
// Run the function with simulated input and captured output
26-
let result = select("Select an option:", &["Option 1"], false, false);
25+
let result = select(
26+
"Select an option:",
27+
&["Option 1", "Option 2", "Option 3"],
28+
false,
29+
false,
30+
);
2731

2832
// select the first option using spacebar and click enter
2933

@@ -50,12 +54,35 @@ fn test_reveal() {
5054
fn test_clear() {
5155
// Print Something.
5256
print!("Hello World");
57+
5358
// Force update the terminal
54-
io::stdout().flush().unwrap();
59+
flush();
5560

5661
// wait
5762
thread::sleep(Duration::from_secs_f64(1.0));
5863

5964
// Clear the current line.
6065
clear_line();
6166
}
67+
68+
#[test]
69+
fn test_move() {
70+
// Print Something.
71+
println!("Hello World");
72+
println!("Hello World");
73+
74+
// move
75+
move_cursor_up(2);
76+
77+
// wait
78+
thread::sleep(Duration::from_secs_f64(0.5));
79+
80+
// move
81+
move_cursor_down(1);
82+
83+
// wait
84+
thread::sleep(Duration::from_secs_f64(0.5));
85+
86+
// Clear the current line.
87+
clear_line();
88+
}

0 commit comments

Comments
 (0)