File: excallback

package info (click to toggle)
libterm-readline-gnu-perl 1.47-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,148 kB
  • sloc: perl: 2,191; makefile: 10
file content (137 lines) | stat: -rw-r--r-- 4,664 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env perl
#
# excallback: Another test harness for the readline callback interface.
#
#   Copyright (C) 2024 Hiroo Hayashi
#
# Derived from: examples/excallback.c in the GNU Readline Library
#   Author: Jeff Solomon <jsolomon@stanford.edu>

# This little examples demonstrates the alternate interface to using readline.
# In the alternate interface, the user maintains control over program flow and
# only calls readline when STDIN is readable. Using the alternate interface,
# you can do anything else while still using readline (like talking to a
# network or another program) without blocking.
#
# Specifically, this program highlights two importants features of the
# alternate interface. The first is the ability to interactively change the
# prompt, which can't be done using the regular interface since rl_prompt is
# read-only.
#
# The second feature really highlights a subtle point when using the alternate
# interface. That is, readline will not alter the terminal when inside your
# callback handler. So let's so, your callback executes a user command that
# takes a non-trivial amount of time to complete (seconds). While your
# executing the command, the user continues to type keystrokes and expects them
# to be re-echoed on the new prompt when it returns. Unfortunately, the default
# terminal configuration doesn't do this. After the prompt returns, the user
# must hit one additional keystroke and then will see all of his previous
# keystrokes. To illustrate this, compile and run this program. Type "sleep" at
# the prompt and then type "bar" before the prompt returns (you have 3
# seconds). Notice how "bar" is re-echoed on the prompt after the prompt
# returns? This is what you expect to happen. Now comment out the 4 lines below
# the line that says COMMENT LINE BELOW. Recompile and rerun the program and do
# the same thing. When the prompt returns, you should not see "bar". Now type
# "f", see how "barf" magically appears? This behavior is un-expected and not
# desired.

use strict;
use warnings;
use Term::ReadLine;
use IO::Pty;
use POSIX qw(termios_h _POSIX_VDISABLE);

my $t = new Term::ReadLine 'rlptytest';
my $a = $t->Attribs;

my $prompt = 1;
my $old_lflag;
my $old_vtime;
my $term;

# main program

sub main {
    # Adjust the terminal slightly before the handler is installed. Disable
    # canonical mode processing and set the input character time flag to be
    # non-blocking.
    $term = POSIX::Termios->new;
    if (!defined($term->getattr(fileno(STDIN)))) {
        die "tcgetattr: $!\n";
    }
    $old_lflag = $term->getlflag();
    $old_vtime = $term->getcc(VTIME);
    $term->setlflag($old_lflag & ~ICANON);
    $term->setcc(1, VTIME);

    # COMMENT LINE BELOW - see above
    if (!defined($term->setattr(fileno(STDIN), TCSANOW))) {
        die "tcsetattr: $!\n";
    }

    $t->add_defun("change-prompt", \&change_prompt, ord "\cT");
    $t->callback_handler_install(get_prompt(), \&process_line);
    while (1) {
        my $fds = '';
        vec($fds, fileno(STDIN), 1) = 1;
        if (select($fds, undef, undef, undef) < 0) {
            die "select: $!\n";
        }
        $t->callback_read_char() if (vec($fds, fileno(STDIN), 1));
    }
    exit 0;
}

sub process_line {
    my ($line) = @_;
    if (!$line) {
        printf STDERR "\n";

        # reset the old terminal setting before exiting
        $term->setlflag($old_lflag);
        $term->setcc($old_vtime, VTIME);
        if (!defined($term->setattr(fileno(STDIN), TCSANOW))) {
            die "tcsetattr: $!\n";
        }
        exit(0);
    }
    if ($line eq "sleep") {
        sleep(3);
    } else {
        print STDERR "|$line|\n";
    }
}
sub change_prompt {
    # toggle the prompt variable
    $prompt = !$prompt;
    $t->set_prompt(get_prompt());
}

# The original implementation of change_prompt before rl_set_prompt was introduced.
sub change_prompt_original {
    # toggle the prompt variable
    $prompt = !$prompt;

    # save away the current contents of the line
    my $line_buf = $a->{line_buffer};

    # install a new handler which will change the prompt and erase the current line
    $t->callback_handler_install(get_prompt(), \&process_line);

    # insert the old text on the new line
    $t->insert_text($line_buf);

    # redraw the current line - this is an undocumented function. It invokes the
    # redraw-current-line command.
    # $t->refresh_line(0, 0);
    # $t->forced_update_display();
    # $t->reset_line_state();
    $t->redisplay();
}

sub get_prompt {
    # The prompts can even be different lengths!
    return $prompt ? "Hit ctrl-t to toggle prompt> " : "Pretty cool huh?> ";
}

main();