crossterm-rs / crossterm Goto Github PK
View Code? Open in Web Editor NEWCross platform terminal library rust
License: MIT License
Cross platform terminal library rust
License: MIT License
The library contains an bin project. This will be removed in crossterm v0.2.1.
I've been experiencing a a few bugs related to character deletion on input::read_line()
.
I believe this issues to be deeply related to the fact that I'm using a portuguese-configured keyboard, which may map keys in a slightly different fashion. Also, I haven't tested this in any other platform besides Windows 10.
There are three main problems:
When I press the backspace
key the cursor will move to the left accordingly, but the last character of the displayed text will not be remove. If I then type another key, this new character will take it's place.
Also if I press the backspace
key and then press enter
, read_line()
will return the text as if I hadn't pressed backspace
in the first place.
If I keep pressing backspace
until I get to the start of the line, crossterm will keep moving the cursor back over previously printed text.
If I press any of the arrow keys, instead of moving the cursor, crossterm will count that as an ร
character.
Thank you for the great library. I like it ๐
I am not sure if close issue 40 fixes the trouble but version 0.6 is not published yet.
OS: Window 7 Home 64 bit
Rust: 1.30 GNU-MinGW
Crossterm: 0.5.3
I started using your library a few weeks ago. It was 0.5.1. It worked smoothly. Today I ran cargo update and my application started panicking at line let term = crossterm::terminal::terminal();
.
It shows:
thread 'main' panicked at 'called Result::unwrap()
on an Err
value: Os { code: 87, kind: Other, message: "The parameter is incorrect." }', libcore\result.rs:1009:5
I rolled back to 0.5.1. Should I wait for 0.6? Or it is a new bug and it needs some investigation?
When making changes it would be handy to restore to the original state after the current process ends.
The introductory documentation shown in crate.io and the REAMDE.md is great!
It should be part of the lending page of the documentation generated by cargo doc
.
I am learning how to do documentation, but would like to submit a PR with a solution if that's okay.
I got an interesting problem first of the error message: cannot borrow
a as mutable because it is also borrowed as immutable
.
Simplefied sittuation
First of some code
struct A
{
b: B
}
impl A
{
fn immutable(&self) -> C
{
// some function that does not mutate A but returns a new instance to some type with a refrence to `B` in `A`
C { b: &self.b }
}
fn mutable(&mut self)
{
// some function that mutates A
}
}
struct B;
struct C<'b>
{
b: &'b B
}
fn main()
{
let mut a = A { b: B{} };
let b = a.immutable();
a.mutable();
}
I get the point of this error message and why it is thrown. I have an immutable reference to C
wilts has a immutable reference to B
when I call mutable()
b: B
in structA
will be mutable and this is a dangerous problem because when we mutate b: B
the immutable reference we have in C
could not be valid anymore. That is why rust helps here.
The code above is a simple demonstration of the code architecture I currently have. I could solve this error by just doing the following:
fn main()
{
let mut a = A { b: B{} };
{
let b = a.immutable();
}
a.mutable();
}
But I find this an ugly solution. Because I am working on a library I will not push the user to add own scopes around function calls to get it to work properly. Is there a better way with managing the lifetime of the reference to B
in C
?
Application to Crossterm
The types in the examples could be mapped to the following types of crossterm.
A: Crossterm
B: ScreenManager
C: TerminalCursor, TerminalColor, Terminal
The reason why this error occurs is that TerminalCursor, TerminalColor, Terminal has a reference to an ScreenManager
stored in Crossterm
. Because I have a type with an immutable reference to SceenManager I cannot create another mutable borrow when calling: to_alternate_screen()
on the Crossterm
type. This would cause the reference to be invalid stored in TerminalCursor and lucky (or maybe not) rust comes into play and prevents this behavior.
I want to make the font size smaller, what should I do?
I didn't find the information in the documentation and examples.
Currently, there are somethings what could be done better according to raw mode.
I can't explain what it is because there are playing a lot of factors a role and that story will be longer than me fixing it.
However, this is just a tracking issue.
https://github.com/TimonPost/crossterm/blob/master/examples/Crossterm%200.3.0/crossterm_type/mod.rs
You can see the git diff "-" symbol at the start of each line
Hi! is it possible to handle more than one terminals in parallel.
I want to use alternate one while main is recieving some data constantly and when i drop alternate i see whats happening in main?
P.S.: Or i want too much from terminal? :)
I am working on an alternate screen feature for my cross-platform terminal manipulating crate.
Situation
When I want to write something to console I can use the following code to write to the current standard output. This works on both UNIX and Windows systems:
write!(::std::fmt::io::stdout(), "Some text").
When I switch to the alternate screen, I don't want to write to the current standard output but to the alternate screen. This works differently depending on which platform you are on. The basics are the same: I need to store the handle somewhere globally so that I could put the stored output handle
in alternate screen mode and write all my output, commands and actions to that stored output handle
.
When I am in alternate mode, my code writes to the alternate screen and when in main screen modes my code writes to the main screen.
Unix
For UNIX systems, I can use ANSI escape codes to switch to the alternate screen and back. I store the ::std::io::stdout()
somewhere and all my UNIX code uses that handle for access to the terminal output
. When in alternate screen mode, all the write
s I do are done on the alternate screen and when on the main screen all the write
s are done on the main screen.
Windows
For Windows systems, I can use WinAPI to switch to the alternate screen buffer. I'll create a new screen buffer with CreateConsoleScreenBuffer
then I'll use SetConsoleActiveScreenBuffer
to change the active buffer. At last, I need to store the handle gotten from CreateConsoleScreenBuffer
. Through this output handle, I can write output to the alternate screen buffer.
If I would not have used the above-described way and switched to alternate screen and just called this write!(::std::fmt::io::stdout(), "Some text")
, I would write to the main screen instead of the alternate screen on both Windows and Unix systems because stdout()
is a handle to the standard output.
The Question
The way described above works to a certain point; when I want to write to the stored handle.
For Unix I can do the following:
// (...) some logic to get the handle to the current screen.
let stored_handle: Write = ...;
write!(stored_handle, "Some text);
But for Windows I could not do this:
// (...) some logic to get the handle to the current screen for windows.
let stored_handle: HANDLE = ...;
write!(stored_handle, "Some text);
I could implement std::io::Write
for the struct where I store the stdout
so that for Windows I create my own logic for writing to the console with WinAPI. If I would do that I would be able to write to that struct like the following:
#[cfg(target_os = "windows")]
let storage = WindowsScreenManager::new();
#[cfg(not(target_os = "windows"))]
let storage = UnixScreenManager::new();
write!(storage, "Some text");
This is not ideal for my situation because I can not use the Rust string escape characters like \n \t
my string will not contain any newlines or tabs when doing it this way. Think because WinAPI does not know these formatting options. Also I don't want to manage all the writing to the console for Windows manually on my side.
I really want to use the WinAPI HANDLE
as std::io::Write
so that it can be used in the write!
macro, just like I do in UNIX. Just store the stdout()
and write to that stdout()
using the write!
macro, but storing the HANDLE
and writing to that.
I suppose that this should work since when calling println!()
or write!(stdout())
on Windows it will write the to the standard output handle of the current process. But now I want to write to the alternate handle and not only to the default handle. Or am I wrong with this?
If the above cannot be done, how would I write to the alternate screen HANDLE
without using my own implementation for writing to the Console using WinAPI?
error[E0432]: unresolved import `super::WinApiOutput`
--> src/output/test.rs:1:34
|
1 | use super::{AnsiOutput, IStdout, WinApiOutput};
| ^^^^^^^^^^^^ no `WinApiOutput` in `output`. Did you mean to use `AnsiOutput`?
License text needs to be added both in top directory and in each crate (you can use symlink for that).
Thanks in advance!
Hi,
I'm having an issue with the Windows Command line on Windows 7.
Here's the code:
let crossterm = Crossterm::new();
...
println!("Currently, only Keycloak is supported as Identity Provider. When setting the");
println!(
"IDP URL, please note that you will have to pass {} of Keycloak.\n",
crossterm
.paint("the exact path to the saml client")
.with(Color::Yellow)
);
But it gets displayed as this:
As you can see, the "the exact path to the saml client" gets put to the start of the message. It also seems like the color is cleared to "white" instead of the default gray.
Not sure about this but curious if async event loop would be better then threads similar to alacritty https://github.com/jwilm/alacritty/blob/3c672cca4b469d1e9e59db819fe459358522f6ec/src/tty/mod.rs
I do like how it is very easy to get started with termion because it just uses ansi escape sequences. Would it be possible to support something similar in crossterm?
let stdout = MouseTerminal::from(AlternateScreen::from(io::stdout().into_raw_mode().unwrap()));
write!(stdout,
"{}{}{}{}Hi{}",
cursor::Hide,
clear::All,
cursor::Goto(0,0),
color::Fg(color::Red),
cursor::Show);
stdout.flush();
For terminals that do not support it might need to add custom parser though it could be a bit slow.
I am trying to upgrade from 0.3 to 0.4 but got over 60 errors in my project due to the breaking changes. I do a lot of calls where I just want to have some part of the output colored and use the println!
macro a lot.
println!(
"\nExample:\n\n\taws --profile {} s3 ls\n",
crossterm.paint(&group.accounts[0].name).with(Color::Yellow)
);
I was trying to update the code with as little change as possible and ended up with attempting this:
println!(
"\nExample:\n\n\taws --profile {} s3 ls\n",
style(&group.accounts[0].name).with(Color::Yellow)
);
But that yields the following error
error[E0277]: `crossterm::StyledObject<&str>` doesn't implement `std::fmt::Display`
Any chance to implement Display
? I don't really know how the library works so please forgive my ignorance. If this can't be done, I'll have to pull apart all of my print statements and squeeze style()..paint(&screen)
between my print calls which definitely would be a huge pain. Maybe there's an easier way if you could guide me to one.
I'm trying to wrap an external lib to make use of Crossterm from python (or ruby). However, when considering the use of AlternateScreen, there is no way to pass the Screen from AlternateScreen into a Box because of the Drop trait.
Is there a way to do this for FFI interop?
i'm implementing a remote shell thing. to connect it to a remote pty there's a bunch of tcsetattr flags missing. cfmakeraw is not enough
is this something that could be implemented? i rather like the way crossterm resets to original state and would like to use it
extern crate crossterm;
use self::crossterm::{Crossterm, Screen, ClearType};
use std::io::Read;
use std::{thread, time};
fn main() {
let raw_mode = true;
let screen = Screen::new(raw_mode);
let crossterm: Crossterm;
{
let alternate_screen = screen.enable_alternate_modes(raw_mode).unwrap();
crossterm = Crossterm::from_screen(&alternate_screen.screen);
}
// <-- Dropping alternate_screen here
let mut input = crossterm.input().read_async().bytes();
let terminal = crossterm.terminal();
let cursor = crossterm.cursor();
cursor.hide().unwrap();
loop {
terminal.clear(ClearType::All).unwrap();
cursor.goto(1, 1).unwrap();
match input.next() {
// These user inputs are not in raw mode
Some(ipt) => {
terminal.write(format!("{:?} <- Character pressed", ipt)).unwrap();
match ipt {
// Does not return to original screen
Ok(b'q') => break,
_ => {},
};
},
_ => {}
};
thread::sleep(time::Duration::from_millis(200));
}
}
When creating a Crossterm
instance from an AlternateScreen
, dropping the AlternateScreen
breaks both raw and alternate screen mode, see example above. This gets tricky when users assume it is possible to only store the Crossterm
instance to draw to the alternate screen.
The example folder has too many folders and should be refactored a little. Like color with only one mod.rs
and cursor with only one mod.rs
file. Those mod.rs
files could just be renamed to color.rs
and cursor.rs
, this applies to more folders.
All unwraps and panics needs to be removed for next version.
The comments for cursor.save_position
and cursor.reset_position
aren't clear as to whether the position is single use?
Is it possible to use this API to make something like markers in Vim where you can set up to N markers to jump around in the screeen?
The method safe_position
should be save_position()
This is a tracking issue for the work what still needs to be done for reading input.
The rust docs are not working for version 5.0
It would be cool if termios could be removed as dependency. And that we only would have a libc dependency for Unix systems.
Hello,
i'm a relative new in rust and i'm hanging here at one simple solution to clear the terminal. Your solution use the winapi pure, or? No ansi escape sequence.
I have searched the whole internet:), and not find an solution why a cargo run accept ansi escapes codes and directly run of an .exe don't display the codes:).
Is it possible to easily strip down your crate to an simple code fragment where only cross clear the terminal?
thx
Hello,
I'm trying to compile my project for multiple platforms. Windows doesn't seem to support bold
, whereas other platforms do. I now had to remove all calls to bold
in order to fix compiler errors. My suggestion would be to instead of failing compilation for unsupported methods on different platforms allow me to have these functions as no-ops (so just do nothing with it), but maybe with some sort of warnings. What do you think?
It is handy if the terminal could be put into an raw state.
This seems to be similar to last years issue of #15 . I just had a user report this issue on Windows 10.
He is using Powershell without Administrator rights on his machine. He can't try it on cmd, only Powershell.
This code triggers the issue:
println!("\nWelcome to saml2aws-auto. Let's configure a few things to get started.");
println!("Currently, only Keycloak is supported as Identity Provider. When setting the");
println!(
"IDP URL, please note that you will have to pass {} of Keycloak.\n",
style("the exact path to the saml client")
.with(Color::Yellow)
);
it seems to be this line that fails: https://github.com/TimonPost/crossterm/blob/ad74f6b5242e6ed998bef6240b738785ea449ffe/crossterm_winapi/src/console_mode.rs#L53
The crossterm version is the latest.
If there are any additional steps I can do to get you more information please let me know, I want to help as much as I can here.
Thanks!
Hey,
one of my users is getting a weird error:
thread 'main' panicked at 'Cannot get console mode', /cargo/registry/src/github.com-1ecc6299db9ec823/crossterm-0.2.1/src/state/commands/win_commands.rs:48:21
He's on Windows 7, tried using cmd, cygwin and cmder but always gets the same error.
I saw the TerminalInput
can be used to read a char or a hole line of input. But I want to read a key event, like Up(โ) or Down(โ). Is any way to do this?
The examples of crossterm version 0.3.0 are not working. I will update this soon.
AFAIK, the windows terminal does support attributes, such as bold, but it doesn't support a lot of *nix ones.
https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
Is it a good idea to see if we can implement partial attributes?
I realize this is a big ask :)
I was playing with crossterm today, and I really like it. But I need to deal with input, not only output. Do you have any intention of adding input someday? This isn't for anything important, but I'm curious...
a quick example is if I pass in a new alternative screen and call
let ct = Crossterm::from_screen(&alternative.screen);
let cursor = ct.cursor();
cursor.hide();
When switching back -- dropping the new screen that was created just for the alternative screen; the terminal still has the cursor hidden. Is this intended behavior, or do we want to have cursor behaviors scoped to the current screen?
I would imagine something like cursor.show() and .hide() works at the terminal level, so even if we create new screens, it's not like we can only modify cursor behavior for those screens...
Edit: yep, just confirmed with the alternate_screen.rs example file and it indeed doesn't scope the cursor only to the alt screen. Haven't checked by starting a new screen from within the example; but I'm sure it's related.
terminal(&screen).clear(ClearType::All);
cursor(&screen).goto(0, 0);
screen.write_buf(b"https://www.google.com").expect("");
screen.flush_buf().expect("");
cursor(&screen).goto(4, 0);
screen.write_buf(b"FFF").expect("");
screen.flush_buf().expect("");
expecting the result to be:
httpFFF/www.google.com
actual:
httphttps://www.google.comFFF
seems like code isn't synchronous here...
There is a problem occurring where WinaApi is the default like Windows 7,8.
Here's the code:
let crossterm = Crossterm::new();
...
println!("Currently, only Keycloak is supported as Identity Provider. When setting the");
println!(
"IDP URL, please note that you will have to pass {} of Keycloak.\n",
crossterm
.paint("the exact path to the saml client")
.with(Color::Yellow)
);
But it gets displayed as this:
As you can see, the "the exact path to the saml client" gets put to the start of the message. It also seems like the color is cleared to "white" instead of the default gray.
To print an colored text in the middle of a phrase you can call into_displayable()
on a StyledObject
. But with WinApi the colored text will not be at the place you have placed the formatting {}
symbols. But instead, it will be printed at the start of the console.
Here you have some code to reproduce.
fn main()
{
let screen= Screen::default();
let crossterm = Crossterm::new(&screen);
println!(
"Some text line 1 \n \
Some test line to {} and more text.\n",
crossterm
.style("Some yellow collored text")
.with(Color::Yellow)
.into_displayable(&screen)
);
}
I don't really know what this is I think it has to be doing something with buffering of text. Like when you call into_dispayable()
your text will be written with WinApi. Maybe because of that WinApi will write first before rust writes the text to the console.
Calling the functions all get is in my opinion a bit inconvenient as you always have to include the module name if you want to use e.g. cursor and terminal.
use cross_terminal::terminal;
use cross_terminal::terminal::ClearType;
use cross_terminal::cursor;
fn main() {
let term = terminal::get();
let cur = cursor::get();
terminal.clear(ClearType::All);
}
And you cant just use cross_terminal::terminal::* for having ClearType. Because both terminal and cursor have an get()
method.
This will be fixed in crossterm v0.2.1
So apparently doing the equivalent of C's System('pause')
in Rust is a bit harder than it should be, as you can see in this discussion.
I just thought that since the focus of this crate is cross-platform terminal functionality and this is such a common thing to do (Press any key to continue...), maybe it could get implemented as a simple function like terminal::pause()
or something along this lines.
If you think pause()
is a bit too specific, terminal::read_char()
or stdin().read_char()
would be nice too.
I forgot to implement this. Currently as you can see in the code when calling the pos method
on the terminal I return an hardcoded (0,0) value for ANSI supportable terminals. I'll fix this the next release.
I am trying to implement a toy terminal app that can open up multiple files in alternate screens for editing.
It is not clear from the comments/documentation, that using the recommended
let input = crossterm::input::from_screen(&screen);
let stdin = input.read_async().bytes();
creates a new thread per call to input.read_async()
.
Do the threads end after the screen gets dropped?
This is probably an issue with tui-rs (feel free to close if it is), but in case it can be solved here I thought I'd mention it. tui-rs issue is posted here: fdehau/tui-rs#123
error[E0308]: mismatched types
--> src/main.rs:70:46
|
70 | let scr = crossterm::Crossterm::from_screen(term.backend().screen());
| ^^^^^^^^^^^^^^^^^^^^^^^ expected struct `crossterm::Screen`, found struct `crossterm::common::screen::screen::Screen`
|
= note: expected type `&crossterm::Screen`
found type `&crossterm::common::screen::screen::Screen`
I'm doing this with an attempt to access it's input handler, as it seems like just running it async doesn't seem to work (keep getting "No Characters Pressed" String error when calling next on AsyncReader).
We can change the terminal size like the following two ways:
println!("\x1B[8;{},{}t", 40, 40)
/// Get the current terminal size.
pub fn terminal_size() -> (u16, u16) {
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
let us = UnixSize {
rows: 0,
cols: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &us) };
if r == 0 {
// because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results.
(us.cols - 1, us.rows - 1)
} else {
(0, 0)
}
}
Problem
If I set the terminal size with ANSI escape code
and call directly the ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut winsize);
after it to get the new terminal dimensions, the dimensions gotten back are not the same as we have set the terminal size to and will be instead the old dimentions before resizing. However, when running the application again we'll get the new (right) dimensions from the terminal.
The weird thing is that when adding thread::sleep()
between the function calls to set and get the terminal size. We will get the new (right)dimensions back from ioctl
Steps to reproduce:
use crossterm::terminal::Terminal;
use std::{thread,time};
fn main()
{
let screen = Screen::new(false);
let terminal = Terminal::new(&screen.stdout);
// get terminal size
let (x, y) = terminal.terminal_size();
// set size to 30, 50
terminal.set_size(30,50);
// if we uncomment the line below the code will work perfectly fine and we will get the new dimensions.
// if we comment the line below the terminal dimensions gotten from terminal_size() are equal to the old dimensions.
// thread::sleep(time::Duration::from_millis(20));
// get new dimensions
let (x_new, y_new) = terminal.terminal_size();
println!("old width: {} old height: {}", x, y);
println!("new width: {} new height: {}", x_new, y_new);
}
To try the above code just comment and uncomment the code and see the result yourself.
I have found a StackOverflow post that could be useful please take a look.
I think it would be cool to expose the WinApi and libc API of cross term. Before this could be done those Api's should be a little reformated. By exposing it the user is able to directly act on the opeating system instead of doing it via crossterm.
Pressing up, down, left, right arrows, delete, insert, home, F1-10, and Alt + Shift + in read_async_demo() all returns some kind of byte sequence as captured by tty.
On Windows, however, these inputs crash the program or cause it to lock up. Is there a way to handle these inputs as well? Or be explicit in the docs regarding the limitations.
I could try to help, if it's regarding documentation, but I have limited exposure to how terminals work between posix and windows. If you could point me in the right direction, perhaps I can help later down the line.
For some keys or key sequences, like arrows, all that is needed is to call the _getwch or _getwche a second time. I think all that is needed is to remove this: https://github.com/TimonPost/crossterm/blob/master/src/modules/input/windows_input.rs#L72
or have a different kind of error detection than 0 or 0xe0...like calling _getwch() a second time with kbhit() --> https://github.com/genotrance/snip/blob/master/src/snip/key.nim#L35
All warnings should be removed when running clippy
My development environment is deepin linux, I want to test and use the crossterm tool in deepin linux.
Is there a basic set of use cases to test crossterm compatibility on a new terminal. Do I need to execute all the test cases in the code?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.