Skip to content
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

Implement transaction countdown timer using ANSI escape codes #333

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

PlasmaIntec
Copy link
Contributor

Issue #, if available:
#203

Description of changes:
This is a bit of a hacky change until I figure out how to make asynchronous calls to draw prompts using the rustylines, or use another readline implementation altogether.

This solution relies on the customer's terminal handling the ESC7 and ESC8 ANSI escape codes as Save Cursor Position and Restore Cursor Position, respectively. This is supported in VT100 Mode, which is apparently supported by the xterm terminal emulator.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Copy link
Contributor

@marcbowes marcbowes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for submitting this. I love the feature. I think we need to dig a bit more into the implementation (as you say in the overview) to really make sure we're comfortable with the way we do IO. rustyline sort of assumes it's the only IO system..

const REFRESH_INTERVAL: u64 = 100;

pub struct Timer {
bus: Bus<String>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should try do this without a new dependency. Each new dependency needs to carry its weight. So, we're gaining a new feature but is it that much better than what the stdlib or tokio offer? For example, tokio already has synchronization primitives (https://docs.rs/tokio/0.2.3/tokio/sync/index.html) that might be good enough here.

Adding dependencies is not evil, but they do increase compile times (more to link, fresh builds are slower) and require maintenance as new versions come out or there are security issues (maybe in them, or what they drag in too).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the concern about adding new dependencies. However, bus provides a zero buffer broadcast function that is unavailable in tokio (the most similar function would be watch, but this stores the latest value, which results in a bug when stop_timer is called and there are zero subscribers to the bus, then start_timer is called)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about notify? Or, if we use a task, we could cancel it?


pub fn run_timer(&mut self) {
let mut recv = self.bus.add_rx();
thread::spawn(move || {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure about the thread here. Is there a way to do this with async instead, such that we have concurrency but not parallelism? I think the way you wrote this is race-safe (given the move to top left + restore), but it'd probably make more sense to have this be part of the overall IO loop. This probably comes back to your question - can we do this with rustyline or switch to some other library that has animations/feedback as a feature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, rustyline does not provide an async interface. I'm currently looking into implementing this, however.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we'd simply store the start time of the transaction in a variable somewhere and then define a render function that computed the remaining time based on now-start.

I'm not sure that the linked issue is relevant here. The missing piece is some sort of render hook.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should be responsible for the render function? Could you clarify on including this render process in to overall IO loop? Would the ideal solution not spawn any threads?

src/transaction.rs Outdated Show resolved Hide resolved
src/timer.rs Outdated Show resolved Hide resolved
use bus::Bus;
use std::io::{self, Write};

const SAVE: &str = "\x1b7";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need something like termcap to figure out if we're able to do this at all. Otherwise we might just wreck the user's console on cmd.exe or similar.

src/transaction.rs Outdated Show resolved Hide resolved
@marcbowes
Copy link
Contributor

What happens when the screen is already full? Does the timer overwrite previous input/output?

@PlasmaIntec
Copy link
Contributor Author

Yes- the current implementation uses the erase line ANSI Escape Code const ERASE_LINE: &str = "\x1b[2K"; on the first line in the terminal.

@marcbowes
Copy link
Contributor

It seems to me the thing we're trying to warn customers about is that the query they're about to input could fail. And so the UI feedback should be close to the input. Having it far away (worse case, top left while the cursor is bottom right) may make it hard to spot or intuit what it means. And, having it override previous results is annoying. One use-case for the CLI is to do a bunch of queries and then copy the interaction to something (like a github issue). So I think this needs to be unobtrusive as a design requirement. What are your thoughts?

@PlasmaIntec
Copy link
Contributor Author

I agree. Ideally, the * in the prompt should be replaceable with a countdown timer.

I wonder if the timer should be tightly coupled with the transaction lifecycle, though. If the timer ends prematurely, should the transaction automatically be aborted, bringing the user back to the default blue prompt?

@marcbowes
Copy link
Contributor

I agree. Ideally, the * in the prompt should be replaceable with a countdown timer.

That sounds reasonable.

I wonder if the timer should be tightly coupled with the transaction lifecycle, though. If the timer ends prematurely, should the transaction automatically be aborted, bringing the user back to the default blue prompt?

I don't think so.

@PlasmaIntec
Copy link
Contributor Author

I implemented a readline library which accomplishes the live timer using tokio's async/await functionality. However, my implementation uses an entirely separate approach than the one rustyline uses, which would make integration rather difficult. How do you think we should proceed?

@marcbowes
Copy link
Contributor

I implemented a readline library which accomplishes the live timer using tokio's async/await functionality. However, my implementation uses an entirely separate approach than the one rustyline uses, which would make integration rather difficult. How do you think we should proceed?

I think it's reasonable to try cram this feature in within the confines of rustyline, then as a separate item figure out if rustyline is the right longterm bet.

The idea of the prompt having a "timer space" in it that we are allowed to fiddle with is pretty reasonable. It provides a decent opt-in/out experience.

@guyilin-amazon guyilin-amazon marked this pull request as draft July 11, 2022 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants