terminal: where am I? - perl

Is there a variable or a function, which can tell me the actual position of the cursor?
#!/usr/bin/env perl
use warnings;
use 5.012;
use Term::ReadKey;
use Term::Cap;
use POSIX;
my( $col, $row ) = GetTerminalSize();
my $termios = new POSIX::Termios;
$termios->getattr;
my $ospeed = $termios->getospeed;
my $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
# some movement ...
# at which position (x/y) is the cursor now?

You could use curses instead. It has getcurx() and getcurx(). There is a CPAN module for it (and the libcurses-perl package in Debian or Ubuntu).

I don't think you can determine the cursor position using termcap.
The termutils manual says:
If you plan to use the relative cursor motion commands in an application program, you must know what the starting cursor position is. To do this, you must keep track of the cursor position and update the records each time anything is output to the terminal, including graphic characters.

Some terminals may support querying the position, as CSI 6 n. If supported, the position will be reported as CSI Pl;Pc R. For example
$ echo -e "\e[6n"; xxd
^[[4;1R
0000000: 1b5b 343b 3152 0a .[4;1R.
This reports the cursor as being at the 1st column of the 4th line (counting from 1).
However, this probably ought not be relied upon, as not very many terminals actually support this.

Printing ESC[6n at ANSI compatible terminals will give you the current cursor position as ESC[n;mR, where n is the row and m is the column
So try reading it with terminal escape characters. Something like that:
perl -e '$/ = "R";' -e 'print "\033[6n";my $x=<STDIN>;my($n, $m)=$x=~m/(\d+)\;(\d+)/;print "Current position: $m, $n\n";'

Related

How can I send clear or reset with Term::Cap?

When I output tput clear | hexdump -c I get different results if I'm on kitty or xterm. How can I use Term::Cap to generate these terminal signals on the respective terminal?
What I've tried is a direct-copy-paste from the docs with setup,
use strict;
use warnings;
use Term::Cap;
use POSIX;
my $termios = new POSIX::Termios;
$termios->getattr;
my $ospeed = $termios->getospeed;
my $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
And then I thought this should work,
$terminal->Tputs('clear', 1, *STDOUT );
But alas, it does nothing.
If I provide a different non-existent name for the term (rather than undef which defaults to $ENV{TERM}, I get)
Can't find a valid termcap file at ./test.pl line 9.
So I know it's looking up the termcap file, and finding it.
Right way
All of termcap's signal names are two letters. For clear you'll want cl
$terminal->Tputs('cl', 1, *STDOUT );
You can find the full list on man termcap, which has a full list:
ch Move cursor horizontally only to column %1
cl Clear screen and cursor home
cm Cursor move to row %1 and column %2 (on screen)
Thanks to Thomas Dickey for the answer in comments
Wrong way
Not sure what I was doing wrong, as a temporary work around I did
use constant CLEAR => do {
open( my $fh, '-|', qw(tput clear) );
scalar <$fh>;
};
This still has a spin up another process, but it worked fine. I won't accept this answer in the event anyone knows how to do this the right way.

Term::TermKey: How to enable wide mouse support?

When I run this script, the position of the mouse works up to the column 255 - then the count begins by 0. Does this mean that my terminal does not support SGR/mode 1006?
(edited due ak2's answer)
#!/usr/bin/env perl
use warnings;
use 5.12.0;
use utf8;
use Term::TermKey qw(FLAG_UTF8 FORMAT_LONGMOD FORMAT_MOUSE_POS);
my $tk = Term::TermKey->new( \*STDIN );
binmode STDOUT, ':encoding(UTF-8)' if $tk->get_flags & FLAG_UTF8;
$|++;
print "\e[?1003h";
print "\e[?1006h";
say "Quit with \"q\"";
while( 1 ) {
$tk->waitkey( my $key );
say $tk->format_key( $key, FORMAT_LONGMOD | FORMAT_MOUSE_POS );
last if $tk->format_key( $key, 0 ) eq 'q';
}
print "\e[?1006l";
print "\e[?1003l";
No.
It means you're not using the very lastest libtermkey library yet, the one that supports positions greater than column 255. Possibly because I haven't actually released it yet ;)
I'll let you know once that's up, along with the extra CSI capture support for position reporting, etc..
Also: If you have more libtermkey-specific questions, you might want to let me know more directly. E.g. you could email me to let me know you've posted a question; I don't always make a habit of searching them out. :)
Edit 2012/04/26: I've now released libtermkey 0.15 and Term::TermKey 0.14, which supports these columns above 255, along with the position report API.
Switching on mode 1006 changes the mouse event encoding, but it doesn't actually enable mouse reporting. For that, you'll need to switch on mode 1000 (click and release only), 1002 (click, release and drag), or 1003 (click, release, and any mouse movement).

Is it possible to clear the terminal with Term::ReadKey?

Is there a way to do this with the Term::ReadKey-module?
#!/usr/bin/env perl
use warnings;
use 5.012;
use Term::Screen;
say( "Hello!\n" x 5 );
sleep 2;
my $scr = Term::Screen->new();
$scr->clrscr();
I don't know why Term::ReadKey would provide such a feature or if it does. But, how about:
#!/usr/bin/env perl
use strict; use warnings;
*clrscr = $^O eq 'MSWin32'
? sub { system('cls') }
: sub { system('clear') };
print "Hello\n" for 1 .. 5;
sleep 2;
clrscr();
Not sure why you want to use Term::Readkey for clearing the screen. It definitely does not have that capability. Are you trying to use something that's part of the standard Perl installation? You can use Term::Caps which is part of the standard Perl installation. Unfortunately, it requires the Termcaps file to be on the system, and Windows doesn't have that.
use Term::Cap;
#
# Use eval to catch the error when TERM isn't defined or their is no
# Termcap file on the system.
#
my $terminal;
eval {$terminal = Term::Cap->Tgetent();};
#
# Use 'cl' to get the Screen Clearing sequence
#
if ($#) { #Most likely a Windows Terminal
system('cls'); #We really should be doing the 2 line below
# my $clear = "\e[2J"; #But, it doesn't seem to work.
# print "$clear"; #Curse You! I'll get you yet Bill Gates!
} else { #A Real Computer
my $clear = $terminal->Tputs('cl');
print "$clear";
}
print "All nice and squeeky clean!\n";
I tried printing the ANSI Escape sequence if it was a Windows Terminal, but it doesn't seem to work.
I hate doing system calls because there is a security risk . What if someone changed the cls command on you?
Term::Readkey does not provide this function directly, but usually the key combination to clear the screen in a Terminal is ^L (Control-L):
Binary
Oct
Dec
Hex
Asc
Sym
Text
00001100
14
12
c
ff
^L
Form Feed (Next Page)
So, if you want to build this in to your application using that module, you can do something like this:
use Term::ReadKey;
# Perform a normal read using getc
my $key = ReadKey( 0 );
# If ^L was pressed, clear the screen
if ( ord $key == 12 ) { print "\e[2J" }
The above example uses the raw escape sequence \e[2J which clears the entire screen. You also have the following alternatives:
Sequence
Function
\e[J
Clears from cursor until end of screen
\e[0J
Clears from cursor until end of screen
\e[1J
Clears from cursor to beginning of screen
\e[2J
Clears entire screen
\e[K
Clears from cursor to end of line
\e[0K
Clears from cursor to end of line
\e[1K
Clears from cursor to start of line
\e[2K
Clears entire line
The escape code \e refers to ASCII character number 27:
Binary
Oct
Dec
Hex
Asc
Sym
Text
00011011
33
27
1b
esc
^[
Escape
See VT100 Escape Sequences for more information.
This should even work if you're using Windows, since apparently this works there nowadays.

getting started with vim scripting with perl

I'd like to create a vim function/command to insert an XSD style timestamp. Currently, I use the following in my vimrc file:
nmap <F5> a<C-R>=strftime("%Y-%m-%dT%H:%M:%S-07:00")<CR><Esc>
I'd like to use the Perl code:
use DateTime;
use DateTime::Format::XSD;
print DateTime->now(formatter => 'DateTime::Format::XSD', time_zone => 'America/Phoenix');
But I don't know where to start. I'm aware that I can define a function that uses Perl. Example:
function PerlTest()
perl << EOF
use DateTime;
use DateTime::Format::XSD;
print DateTime->now(formatter => 'DateTime::Format::XSD', time_zone => 'America/Phoenix');
EOF
But when I changed my vimrc to the following, I didn't get what I expected:
nmap <F5> a<C-R>=PerlTest()<CR><Esc>
Could someone point me in the right direction for implementing this? This is the first time I've tried to write functions in vim. Also, I'm using vim 7.2 compiled with perl support.
First off, you'll want to take a look at :help if_perl for the general information about using Perl from within Vim.
For this specific question, I don't think the same approach of entering insert mode and evaluating an expression is the best option. It doesn't look like the language bindings have a way to return a value like that.
What you can do instead is to have the function get the current line, put the time string at the appropriate place, and set the current line again.
fun! PerlTest()
perl << EOF
use DateTime;
use DateTime::Format::XSD;
my ($row, $col) = $curwin->Cursor();
my ($line) = $curbuf->Get($row);
substr($line, $col + 1, 0,
DateTime->now(formatter => 'DateTime::Format::XSD',
time_zone => 'America/Phoenix'));
$curbuf->Set($row, $line);
EOF
endfun
Then your map would simply be nnoremap <F5> :call PerlTest()<CR>.
One issue I've noticed with the above is that it doesn't work well if the line contains characters where 1 byte != 1 column (i.e., tabs, multi-byte characters, etc.). I've played with various ways of trying to fix that, but none of them seem to work very well.
The problem is that there's no easy way to map from Vim's cursor position to a position in the string that represents the cursor's current line.
A different approach, which avoids this problem, is to just use the Perl interface to get the data and then paste the data from Vim.
fun! PerlTest()
let a_reg = getreg('a', '1')
let a_reg_type = getregtype('a')
perl << EOF
use DateTime;
use DateTime::Format::XSD;
my $date = DateTime->now(formatter => 'DateTime::Format::XSD',
time_zone => 'America/Phoenix');
VIM::Eval("setreg('a', '$date', 'c')");
EOF
normal "ap
call setreg('a', a_reg, a_reg_type)
endfun
nnoremap <F5> :call PerlTest()<CR>
If you're happy with running an external Perl script you can try this:
:map <F5> :let #a = system("perl script.pl")<cr>"ap
this runs the command perl script.pl (adjust depending on paths), captures its output in register #a and pastes it at cursor position.

How to truncate STDIN line length?

I've been parsing through some log files and I've found that some of the lines are too long to display on one line so Terminal.app kindly wraps them onto the next line. However, I've been looking for a way to truncate a line after a certain number of characters so that Terminal doesn't wrap, making it much easier to spot patterns.
I wrote a small Perl script to do this:
#!/usr/bin/perl
die("need max length\n") unless $#ARGV == 0;
while (<STDIN>)
{
$_ = substr($_, 0, $ARGV[0]);
chomp($_);
print "$_\n";
}
But I have a feeling that this functionality is probably built into some other tools (sed?) That I just don't know enough about to use for this task.
So my question sort of a reverse question: how do I truncate a line of stdin Without writing a program to do it?
Pipe output to:
cut -b 1-LIMIT
Where LIMIT is the desired line width.
Another tactic I use for viewing log files with very long lines is to pipe the file to "less -S". The -S option for less will print lines without wrapping, and you can view the hidden part of long lines by pressing the right-arrow key.
Not exactly answering the question, but if you want to stick with Perl and use a one-liner, a possibility is:
$ perl -pe's/(?<=.{25}).*//' filename
where 25 is the desired line length.
The usual way to do this would be
perl -wlne'print substr($_,0,80)'
Golfed (for 5.10):
perl -nE'say/(.{0,80})/'
(Don't think of it as programming, think of it as using a command line tool with a huge number of options.) (Yes, the python reference is intentional.)
A Korn shell solution (truncating to 70 chars - easy to parameterize though):
typeset -L70 line
while read line
do
print $line
done
You can use a tied variable that clips its contents to a fixed length:
#! /usr/bin/perl -w
use strict;
use warnings
use String::FixedLen;
tie my $str, 'String::FixedLen', 4;
while (defined($str = <>)) {
chomp;
print "$str\n";
}
This isn't exactly what you're asking for, but GNU Screen (included with OS X, if I recall correctly, and common on other *nix systems) lets you turn line wrapping on/off (C-a r and C-a C-r). That way, you can simply resize your terminal instead of piping stuff through a script.
Screen basically gives you "virtual" terminals within one toplevel terminal application.
use strict;
use warnings
use String::FixedLen;
tie my $str, 'String::FixedLen', 4;
while (defined($str = <>)) {
chomp;
print "$str\n";
}
Unless I'm missing the point, the UNIX "fold" command was designed to do exactly that:
$ cat file
the quick brown fox jumped over the lazy dog's back
$ fold -w20 file
the quick brown fox
jumped over the lazy
dog's back
$ fold -w10 file
the quick
brown fox
jumped ove
r the lazy
dog's bac
k
$ fold -s -w10 file
the quick
brown fox
jumped
over the
lazy
dog's back