How to write a text prompt in Nim that has readline-style line editing? - input

readLine() doesn't support line editing and recalling previous commands, eg:
while true:
var name: string = readLine(stdin)
echo "Hi, ", name, "!"
Has no editing. But if I compile that and wrap it in rlwrap:
$ rlwrap read_test
It works as I hope. with editable and recallable lines, provided by the readline library.
readLineFromStdin() almost works, but doesn't support ctrl+d, it returns an empty string on ctrl+d, which is indistinguishable from a newline.
How can I do this in pure Nim? Thanks!

Ctrl+D is an EOF "signal", and thus you can catch the EOF in your input:
while not endOfFile(stdin):
var name: string = readLine(stdin)
echo "Hi, ", name, "!"
The procedure readLineFromStdin (https://github.com/nim-lang/Nim/blob/version-1-2/lib/impure/rdstdin.nim#L54) is not that complex, and you can re-write your own adding the above code to it.

While #xbello's answer is correct, if you want to use a package, we ended up using https://github.com/jangko/nim-noise, which supports C-d handling and loads of other features.

Related

How do I store the value returned by either run or shell?

Let's say I have this script:
# prog.p6
my $info = run "uname";
When I run prog.p6, I get:
$ perl6 prog.p6
Linux
Is there a way to store a stringified version of the returned value and prevent it from being output to the terminal?
There's already a similar question but it doesn't provide a specific answer.
You need to enable the stdout pipe, which otherwise defaults to $*OUT, by setting :out. So:
my $proc = run("uname", :out);
my $stdout = $proc.out;
say $stdout.slurp;
$stdout.close;
which can be shortened to:
my $proc = run("uname", :out);
say $proc.out.slurp(:close);
If you want to capture output on stderr separately than stdout you can do:
my $proc = run("uname", :out, :err);
say "[stdout] " ~ $proc.out.slurp(:close);
say "[stderr] " ~ $proc.err.slurp(:close);
or if you want to capture stdout and stderr to one pipe, then:
my $proc = run("uname", :merge);
say "[stdout and stderr] " ~ $proc.out.slurp(:close);
Finally, if you don't want to capture the output and don't want it output to the terminal:
my $proc = run("uname", :!out, :!err);
exit( $proc.exitcode );
The solution covered in this answer is concise.
This sometimes outweighs its disadvantages:
Doesn't store the result code. If you need that, use ugexe's solution instead.
Doesn't store output to stderr. If you need that, use ugexe's solution instead.
Potential vulnerability. This is explained below. Consider ugexe's solution instead.
Documentation of the features explained below starts with the quote adverb :exec.
Safest unsafe variant: q
The safest variant uses a single q:
say qx[ echo 42 ] # 42
If there's an error then the construct returns an empty string and any error message will appear on stderr.
This safest variant is analogous to a single quoted string like 'foo' passed to the shell. Single quoted strings don't interpolate so there's no vulnerability to a code injection attack.
That said, you're passing a single string to the shell which may not be the shell you're expecting so it may not parse the string as you're expecting.
Least safe unsafe variant: qq
The following line produces the same result as the q line but uses the least safe variant:
say qqx[ echo 42 ]
This double q variant is analogous to a double quoted string ("foo"). This form of string quoting does interpolate which means it is subject to a code injection attack if you include a variable in the string passed to the shell.
By default run just passes the STDOUT and STDERR to the parent process's STDOUT and STDERR.
You have to tell it to do something else.
The simplest is to just give it :out to tell it to keep STDOUT. (Short for :out(True))
my $proc = run 'uname', :out;
my $result = $proc.out.slurp(:close);
my $proc = run 'uname', :out;
for $proc.out.lines(:close) {
.say;
}
You can also effectively tell it to just send STDOUT to /dev/null with :!out. (Short for :out(False))
There are more things you can do with :out
{
my $file will leave {.close} = open :w, 'test.out';
run 'uname', :out($file); # write directly to a file
}
print slurp 'test.out'; # Linux
my $proc = run 'uname', :out;
react {
whenever $proc.out.Supply {
.print
LAST {
$proc.out.close;
done; # in case there are other whenevers
}
}
}
If you are going to do that last one, it is probably better to use Proc::Async.

Rakudo Perl 6: clear screen while using Readline module

Here's my test program:
use Readline;
shell 'clear';
my $r = Readline.new;
loop {
my $a = $r.readline("> ");
{say ''; last} if not defined $a;
$r.add-history( $a );
say $a;
}
After I enter any string, it exits with the following message:
> abc
Internal error: unhandled encoding
in method CALL-ME at /opt/rakudo-pkg/share/perl6/sources/24DD121B5B4774C04A7084827BFAD92199756E03 (NativeCall) line 587
in method readline at /home/evb/.perl6/sources/D8BAC826F02BBAA2CCDEFC8B60D90C2AF8713C3F (Readline) line 1391
in block <unit> at abc.p6 line 7
If I comment the line shell 'clear';, everything is OK.
This is a bit of a guess, but I think when you tell your shell to clear the screen, it's sending a control character or control sequence as input to the terminal emulator. Readline is reading from that same stream, and those characters end up at the beginning of your "line" when you try to read a line. Those characters aren't valid UTF-8 (the default encoding) and so can't be interpreted as a string. You'll know more if you open the text files in the stack trace and look at the relevant line numbers.
You can try calling reset-terminal or reset-line-state to see if you can get rid of that character. What I would do in a low level programming language is to do a nonblocking read of the input (without converting it into a string), but I can't find the API for that in the Perl 6 library.

perl6 Is there a way to do editable prompt input?

In bash shell, if you hit up or down arrows, the shell will show you your previous or next command that you entered, and you can edit those commands to be new shell commands.
In perl6, if you do
my $name = prompt("Enter name: ");
it will print "Enter name: " and then ask for input; is there a way to have perl6 give you a default value and then you just edit the default to be the new value. E.g.:
my $name = prompt("Your name:", "John Doe");
and it prints
Your name: John Doe
where the John Doe part is editable, and when you hit enter, the edited string is the value of $name.
https://docs.raku.org/routine/prompt does not show how to do it.
This is useful if you have to enter many long strings each of which is just a few chars different from others.
Thanks.
To get the editing part going, you could use the Linenoise module:
zef install Linenoise
(https://github.com/hoelzro/p6-linenoise)
Then, in your code, do:
use Linenoise;
sub prompt($p) {
my $l = linenoise $p;
linenoiseHistoryAdd($l);
$l
}
Then you can do your loop with prompt. Remember, basically all Perl 6 builtin functions can be overridden lexically. Now, how to fill in the original string, that I haven't figure out just yet. Perhaps the libreadline docs can help you with that.
Well by default, programs are completely unaware of their terminals.
You need your program to communicate with the terminal to do things like pre-fill an input line, and it's unreasonable to expect Perl 6 to handle something like this as part of the core language.
That said, your exact case is handled by the Readline library as long as you have a compatible terminal.
It doesn't look like the perl 6 Readline has pre-input hooks setup so you need to handle the callback and read loop yourself, unfortunately. Here's my rough attempt that does exactly what you want:
use v6;
use Readline;
sub prompt-prefill($question, $suggestion) {
my $rl = Readline.new;
my $answer;
my sub line-handler( Str $line ) {
rl_callback_handler_remove();
$answer = $line;
}
rl_callback_handler_install( "$question ", &line-handler );
$rl.insert-text($suggestion);
$rl.redisplay;
while (!$answer) {
$rl.callback-read-char();
}
return $answer;
}
my $name = prompt-prefill("What's your name?", "Bob");
say "Hi $name. Go away.";
If you are still set on using Linenoise, you might find the 'hints' feature good enough for your needs (it's used extensively by the redis-cli application if you want a demo). See the hint callback used with linenoiseSetHintsCallback in the linenoise example.c file. If that's not good enough you'll have to start digging into the guts of linenoise.
Another solution :
Use io-prompt
With that you can set a default value and even a default type:
my $a = ask( "Life, the universe and everything?", 42, type => Num );
Life, the universe and everything? [42]
Int $a = 42
You can install it with:
zef install IO::Prompt
However, if just a default value is not enough. Then it is better you use the approach Liz has suggested.

Vim script: How to easily pipe data into the cwindow

I use a custom function (currently residing in .vimrc) and not :make or another direct command line tool to compile/check my currently edited file for errors. Like this:
function! CompileMyCode(...)
set errorformat=Error:\ %m\\,\ in\ line\ %l
let l:output = "Error: bad code!, in line 9"
return l:output
endfunction
command! -nargs=* CompileMyCode :call CompileMyCode(<f-args>)
when using the new command in command mode, no error window shows up.
:CompileMyCode | cwindow
What am I doing wrong?
Edit:
I now tried the following which also does not open any cwindow.
function! CompileMyCode(...)
set errorformat=Error:\ %m\\,\ in\ line\ %l
let l:output = "Error: bad code!, in line 9"
" I tried both of the following lines separately
cexpr l:output
call setqflist([l:output])
endfunction
The proposed commands cexpr and setqflist() do not open the cwindow correctly in my example. Maybe somebody can propose a complete solution?
Edit 2:
The main problem is solved. Here is my current code:
let l:result = expand("%").'|8| errortext'
cexpr [ l:result, l:result ]
caddexpr ''
cwindow
This example respects a default error format that vim seems to support. When cexpring the actual error output and using an errorformat the cwindow seems to ignore that.
Nevertheless, I wanted stick to a default error format anyway in the output, not having to rely on a custom errorformat
Thx for your answers!
I did something similar using cexpr l:output instead of returning the string and that placed the output of the compile in the quickfix window. You can see my vim function here: http://www.zenskg.net/wordpress/?p=199
Update
Adding a blank line to the quickfix list seems to allow the cwindow to appear. For example:
function! MyCompile()
let l:output = "Error: line 1"
cexpr l:output
caddexpr ""
cwindow
endfunction
If you already have access to the error information as structured data in Vim (or can easily obtain it), you can use setqflist().

Open Module_name gives a compiler-error

I cannot compile an extremely simple ocaml program test2.ml
open Test1
print_string " Hello "
with test1.ml containing only 1 line
type program = string
And test1.ml is compiled:
bash-3.2$ ocamlc test1.ml
bash-3.2$ ls test1.*
test1.cmi test1.cmo test1.ml
Anyone know why test1.ml does not compile?? Thank you.
More info. It's quite strange because, test2.ml compiles if I comment out its first line "open ..." OR
if I comment out its 3rd line "print_string..." but they cannot coexist!
Printing the error you received would have been helpful. For the reference, it's:
File "test2.ml", line 3, characters 0-12:
Error: Syntax error
The reason for this is a bit complex. The normal syntax is for a file to be a sequence of top-level statements, such as type definitions, let (without in), module definition/opening/including and so on.
Expressions such as print_string "Hello" are never treated as top-level statements unless the meaning is completely unambiguous, which 99% of the time involves separating them from the previous and following statement with a ;;
So, you could write the following:
open Test1 ;;
print_string " Hello "
And it would work. Most of the time, though, it is preferable to keep the file clean by turning the expression into a top-level let:
open Test1
let () = print_string " Hello "
This also has the benefit of making sure that the function returns unit, which is always nice to have.