Terminal control/Restricted width positional input/With wrapping: Difference between revisions
(Added Kotlin) |
m (→{{header|Wren}}: Changed to Wren S/H) |
||
(20 intermediate revisions by 10 users not shown) | |||
Line 1: | Line 1: | ||
{{draft task}} |
{{draft task}} |
||
[[Category:Terminal control]] |
|||
Create a routine to obtain data entry using a specific place on the terminal screen. |
Create a routine to obtain data entry using a specific place on the terminal screen. |
||
Data entry fields should be restricted to a specific length, and the cursor should not move beyond the input length. |
|||
value entered by the user. |
|||
The routine should accept parameters for row number, column number, and input length, and should obtain a string value entered by the user. |
|||
For the purpose of this task, obtain input from the user, showing data entry at row 3, column 5, with input width restricted to a maximum of 8 characters. |
|||
For the purpose of this task, obtain input from the user, showing data entry at row '''3''', column '''5''', with input width restricted to a maximum of '''8''' characters. |
|||
Note: In this task input wrapping is allowed. If the length of input exceeds the maximum width, the left hand side of the field should disappear, allowing additional input to be obtained. However, the cursor must not move beyond the designated input length. |
|||
Note: in this task input wrapping is allowed. If the length of input exceeds the maximum width, the left hand side of the field should disappear, allowing additional input to be obtained. However, the cursor must not move beyond the designated input length. |
|||
It is permissible to use navigation keys to see input field length. |
It is permissible to use navigation keys to see input field length. |
||
Line 14: | Line 17: | ||
(See [[Terminal control/Restricted width positional input/No wrapping]] for similar input routine with no wrapping). |
(See [[Terminal control/Restricted width positional input/No wrapping]] for similar input routine with no wrapping). |
||
For a similar task using a graphical user interface, see [[Graphical User Interface/Restricted width positional input/With wrapping]]. |
For a similar task using a graphical user interface, see [[Graphical User Interface/Restricted width positional input/With wrapping]]. |
||
<br><br> |
|||
=={{header|Common Lisp}}== |
|||
[[Category:Terminal control]] |
|||
==={{header|ncurses}}=== |
|||
To interface ncurses from Lisp, the ''[https://www.cliki.net/croatoan croatoan]'' library is used. |
|||
<syntaxhighlight lang="lisp">;; Load the library from the quicklisp repository |
|||
(ql:quickload "croatoan") |
|||
(in-package :croatoan) |
|||
(defun field-input-with-wrapping (row column input-length) |
|||
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t) |
|||
(let ((field (make-instance 'field :position (list row column) :width input-length :max-buffer-length 30 :window scr))) |
|||
(setf (style field) |
|||
(list :background (list :simple-char #\.))) |
|||
(bind field #\newline 'accept) |
|||
(edit field) |
|||
(clear scr) |
|||
(refresh scr) |
|||
;; return the value of the field as a string |
|||
(value field)))) |
|||
;; call the routine |
|||
(field-input-with-wrapping 2 4 8)</syntaxhighlight> |
|||
=={{header|FreeBASIC}}== |
|||
<syntaxhighlight lang="freebasic">Function getInput(fila As Integer, columna As Integer, ancho As Integer) As String |
|||
Locate fila, columna, 0 |
|||
Dim As String KBD, cadena = "" |
|||
Dim As Integer ini |
|||
Do |
|||
Do: KBD = Inkey: Loop Until KBD <> "" |
|||
If KBD = Chr(8) Then 'backspace |
|||
cadena = Left(cadena, Len(cadena) - 1) |
|||
Print !"\b "; |
|||
Else |
|||
cadena &= KBD |
|||
End If |
|||
ini = Iif(Len(cadena) > ancho, Len(cadena) - ancho + 1, 1) |
|||
Locate fila, columna : Print Mid(cadena, ini, ancho); |
|||
Loop Until KBD = Chr(13) |
|||
Return cadena |
|||
End Function |
|||
Dim As String s = getInput(3, 5, 8) |
|||
Locate 23,1 : Print "You entered: "; s |
|||
Sleep</syntaxhighlight> |
|||
=={{header|Go}}== |
|||
{{works with|Windows 10}} |
|||
{{trans|Kotlin}} |
|||
<br> |
|||
This uses _getch() rather than _getwch() as only ASCII is supported. |
|||
<syntaxhighlight lang="go">package main |
|||
/* |
|||
#include <windows.h> |
|||
#include <conio.h> |
|||
*/ |
|||
import "C" |
|||
import ( |
|||
"fmt" |
|||
"os" |
|||
"os/exec" |
|||
) |
|||
var conOut = C.GetStdHandle(C.STD_OUTPUT_HANDLE) |
|||
func setCursor(p C.COORD) { |
|||
C.SetConsoleCursorPosition(conOut, p) |
|||
} |
|||
func cls() { |
|||
cmd := exec.Command("cmd", "/c", "cls") |
|||
cmd.Stdout = os.Stdout |
|||
cmd.Run() |
|||
} |
|||
func getInput(row, col, width int) string { |
|||
if row < 0 || row > 20 || col < 0 || width < 1 || width > 78 || col > (79 - width) { |
|||
panic("Invalid parameter(s) to getInput. Terminating program") |
|||
} |
|||
coord := C.COORD{C.short(col), C.short(row)} |
|||
setCursor(coord) |
|||
var sb []byte |
|||
wlen := 0 // length of text in editing window |
|||
full := false |
|||
loop: |
|||
for { |
|||
ch := C._getch() // gets next character, no echo |
|||
switch c := byte(ch); c { |
|||
case 3, 13: |
|||
break loop // break on Ctrl-C or enter key |
|||
case 8: |
|||
if wlen > 0 { // mimic backspace |
|||
fmt.Print("\b \b") |
|||
sb = sb[:len(sb) - 1] |
|||
wlen-- |
|||
} |
|||
if len(sb) > wlen { |
|||
coord.X = C.short(col) |
|||
coord.Y = C.short(row) |
|||
setCursor(coord) |
|||
fmt.Print(string(sb)[len(sb) - width:]) |
|||
wlen = width |
|||
} |
|||
case 0, 224: |
|||
C._getch() // consume extra character |
|||
default: |
|||
if c >= 32 && c <= 126 { // echo ascii character, ignore others |
|||
sb = append(sb, c) |
|||
if !full { |
|||
C._putch(ch) |
|||
wlen++ |
|||
} else if len(sb) > wlen { |
|||
coord.X = C.short(col) |
|||
coord.Y = C.short(row) |
|||
setCursor(coord) |
|||
fmt.Print(string(sb)[len(sb) - width:]) |
|||
} |
|||
} |
|||
} |
|||
full = wlen == width // wlen can't exceed width |
|||
} |
|||
return string(sb) |
|||
} |
|||
func main() { |
|||
cls() // clear the console |
|||
s := getInput(2, 4, 8) // Windows console row/col numbering starts at 0 |
|||
coord := C.COORD{0, 22} |
|||
setCursor(coord) |
|||
fmt.Printf("You entered '%s'\n", s) |
|||
}</syntaxhighlight> |
|||
=={{header|Julia}}== |
|||
Requires an ANSI compatible terminal and a system C library implementing _getch() for unbuffered keyboard input. |
|||
<syntaxhighlight lang="julia">getch() = UInt8(ccall(:_getch, Cint, ())) |
|||
cls() = print("\33[2J") |
|||
reposition(row, col) = print("\u001b[$row;$(col)H") |
|||
clearfromcursor() = print("\u001b[K") |
|||
function input_y_x_upto(row, col, cmax, width=cmax) |
|||
buf = "" |
|||
while (buflen = length(buf)) < cmax && !((c = getch()) in [0xff, 0x0d, 0x0a]) |
|||
reposition(row, col) |
|||
clearfromcursor() |
|||
if c == '\b' && buflen > 0 |
|||
buf = buf[1:end-1] |
|||
else |
|||
buf = buf * Char(c) |
|||
end |
|||
print(buf[(buflen > width ? buflen - width + 1 : 1):end]) |
|||
end |
|||
return buf |
|||
end |
|||
cls() |
|||
reposition(3, 5) |
|||
s = input_y_x_upto(3, 5, 80, 8) |
|||
println("\n\n\nResult: You entered <<$s>>") |
|||
</syntaxhighlight> |
|||
=={{header|Kotlin}}== |
=={{header|Kotlin}}== |
||
{{works with|Windows 10}} |
{{works with|Windows 10}} |
||
This follows a similar approach to the Kotlin entry for the [[Terminal control/Restricted width positional input/No wrapping]] task except, of course, that the code now allows for wrapping. |
This follows a similar approach to the Kotlin entry for the [[Terminal control/Restricted width positional input/No wrapping]] task except, of course, that the code now allows for wrapping. |
||
< |
<syntaxhighlight lang="scala">// Kotlin Native v0.5 |
||
import kotlinx.cinterop.* |
import kotlinx.cinterop.* |
||
Line 89: | Line 257: | ||
} |
} |
||
println("You entered '$s'") |
println("You entered '$s'") |
||
}</ |
}</syntaxhighlight> |
||
=={{header|Nim}}== |
|||
{{trans|Julia}} |
|||
Rather than using directly escape codes, we use the standard module <code>terminal</code> which works on Posix systems and on Windows. Unfortunately, there is no function to clear from cursor to end of line, so we had to provide one, but it works only on Posix systems. |
|||
We had also to check for backspace character and for character <code>'\x7f'</code> as the backspace key gives this last value (but Control+H gives <code>'\x08'</code> i.e. ASCII backspace character). |
|||
<syntaxhighlight lang="nim">import strformat, terminal |
|||
proc eraseLineEnd() = stdout.write("\e[K") |
|||
proc inputXYUpto(row, col, cmax: int; width = cmax): string = |
|||
while result.len < cmax and not ((let c = getch(); c) in ['\xff', '\f', '\r']): |
|||
setCursorPos(row, col) |
|||
eraseLineEnd() |
|||
if c in ['\b', '\x7f'] and result.len > 0: |
|||
result.setLen(result.len - 1) |
|||
else: |
|||
result.add c |
|||
stdout.write result[(if result.len > width: result.len - width else: 0)..result.high] |
|||
eraseScreen() |
|||
setCursorPos(3, 5) |
|||
let s = inputXYUpto(3, 5, 80, 8) |
|||
echo &"\n\n\nResult: You entered <<{s}>>"</syntaxhighlight> |
|||
=={{header|Perl}}== |
|||
<syntaxhighlight lang="perl">#!/usr/bin/perl |
|||
use strict; # https://rosettacode.org/wiki/Terminal_control/Restricted_width_positional_input/With_wrapping |
|||
use warnings; |
|||
use Term::ReadKey; |
|||
sub input |
|||
{ |
|||
my ($row, $column, $length) = @_; |
|||
my ($input, $done, $start) = ( '', 0, |
|||
"\e[$row;${column}H" . ' ' x $length . "\e[$row;${column}H"); |
|||
local $| = 1; |
|||
ReadMode 'raw'; |
|||
until( $done ) |
|||
{ |
|||
print $start, substr $input, -$length; |
|||
local $_ = ReadKey 0; |
|||
if( tr/ -~// ) { $input .= $_ } # add char |
|||
elsif( tr/\cu// ) { $input = '' } # clear all |
|||
elsif( tr/\b\x7f// ) { chop $input } # delete last char |
|||
elsif( tr/\n\r\e\cc// ) { $done++ } # guess! |
|||
} |
|||
ReadMode 'restore'; |
|||
return $input; |
|||
} |
|||
print "\e[H\e[Jinput at row 3 column 5 length 8\n"; |
|||
my $in = input( 3, 5, 8 ); |
|||
print "\n\n\ninput is $in\n\n";</syntaxhighlight> |
|||
=={{header|Phix}}== |
|||
<!--<syntaxhighlight lang="phix">(notonline)--> |
|||
<span style="color: #008080;">without</span> <span style="color: #008080;">js</span> <span style="color: #000080;font-style:italic;">-- (position, wait_key)</span> |
|||
<span style="color: #008080;">function</span> <span style="color: #000000;">getInput</span><span style="color: #0000FF;">(</span><span style="color: #004080;">integer</span> <span style="color: #000000;">row</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">col</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">width</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #7060A8;">position</span><span style="color: #0000FF;">(</span><span style="color: #000000;">row</span><span style="color: #0000FF;">,</span><span style="color: #000000;">col</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">string</span> <span style="color: #000000;">s</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">""</span> |
|||
<span style="color: #008080;">while</span> <span style="color: #000000;">1</span> <span style="color: #008080;">do</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #000000;">ch</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">wait_key</span><span style="color: #0000FF;">()</span> |
|||
<span style="color: #008080;">if</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;">=</span><span style="color: #008000;">'\r'</span> <span style="color: #008080;">then</span> <span style="color: #008080;">exit</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
<span style="color: #008080;">if</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;">=</span><span style="color: #008000;">'\b'</span> <span style="color: #008080;">then</span> |
|||
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">)></span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span> |
|||
<span style="color: #7060A8;">puts</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"\b \b"</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000000;">s</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">s</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">..$-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">)>=</span><span style="color: #000000;">width</span> <span style="color: #008080;">then</span> |
|||
<span style="color: #7060A8;">position</span><span style="color: #0000FF;">(</span><span style="color: #000000;">row</span><span style="color: #0000FF;">,</span><span style="color: #000000;">col</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #7060A8;">puts</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">s</span><span style="color: #0000FF;">[-</span><span style="color: #000000;">width</span><span style="color: #0000FF;">..-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">])</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
<span style="color: #008080;">elsif</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;">>=</span><span style="color: #008000;">' '</span> <span style="color: #008080;">and</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;"><=</span><span style="color: #008000;">'~'</span> <span style="color: #008080;">then</span> |
|||
<span style="color: #000000;">s</span> <span style="color: #0000FF;">&=</span> <span style="color: #000000;">ch</span> |
|||
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">)<=</span><span style="color: #000000;">width</span> <span style="color: #008080;">then</span> |
|||
<span style="color: #7060A8;">puts</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">ch</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">else</span> |
|||
<span style="color: #7060A8;">position</span><span style="color: #0000FF;">(</span><span style="color: #000000;">row</span><span style="color: #0000FF;">,</span><span style="color: #000000;">col</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #7060A8;">puts</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">s</span><span style="color: #0000FF;">[-</span><span style="color: #000000;">width</span><span style="color: #0000FF;">..-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">])</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">while</span> |
|||
<span style="color: #008080;">return</span> <span style="color: #000000;">s</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span> |
|||
<span style="color: #7060A8;">clear_screen</span><span style="color: #0000FF;">()</span> <span style="color: #000080;font-style:italic;">-- clear the console</span> |
|||
<span style="color: #004080;">string</span> <span style="color: #000000;">s</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">getInput</span><span style="color: #0000FF;">(</span><span style="color: #000000;">3</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">5</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">8</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #7060A8;">position</span><span style="color: #0000FF;">(</span><span style="color: #000000;">23</span><span style="color: #0000FF;">,</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #7060A8;">printf</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"You entered '%s'\n"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">s</span><span style="color: #0000FF;">})</span> |
|||
<!--</syntaxhighlight>--> |
|||
=={{header|Raku}}== |
|||
(formerly Perl 6) |
|||
{{works with|Rakudo|2018.10}} |
|||
Should work with any termios compatible terminal. |
|||
All printable character keys (except Tab) work, as does backspace and enter. Ctrl-c to exit. All other keys / key-combos are ignored. |
|||
<syntaxhighlight lang="raku" line>use Term::termios; |
|||
constant $saved = Term::termios.new(fd => 1).getattr; |
|||
constant $termios = Term::termios.new(fd => 1).getattr; |
|||
# raw mode interferes with carriage returns, so |
|||
# set flags needed to emulate it manually |
|||
$termios.unset_iflags(<BRKINT ICRNL ISTRIP IXON>); |
|||
$termios.unset_lflags(<ECHO ICANON IEXTEN ISIG>); |
|||
$termios.setattr(:DRAIN); |
|||
END { |
|||
$saved.setattr(:NOW); # reset terminal to original settings |
|||
print "\e[?25h \e[H\e[J"; # clear and reset screen |
|||
} |
|||
my $row = 3; |
|||
my $column = 5; |
|||
my $field = ''; |
|||
my $spacer = ' ' x 8; |
|||
my $pointer = 0; |
|||
my ($rows,$cols) = qx/stty size/.words; # get screen size |
|||
my @screen = "\e[41m{' ' x $cols}\e[0m" xx $rows; |
|||
update($spacer); |
|||
loop { |
|||
my $key = $*IN.read(4).decode; |
|||
given $key { |
|||
when ' '..'~' { |
|||
if $pointer < 8 { |
|||
$field ~= $_; |
|||
$spacer = ' ' x 8 - $field.chars; |
|||
$pointer +=1; |
|||
update($field~$spacer) |
|||
} elsif $pointer >= 8 { |
|||
$field ~= $_; |
|||
$spacer = ''; |
|||
update($field.substr(*-8)) |
|||
} |
|||
} |
|||
when "\c[127]" { # backspace |
|||
if $pointer > 0 { |
|||
$field.=substr(0,*-1); |
|||
$spacer = ' ' x (8 - $field.chars max 0); |
|||
$pointer -= 1 if $field.chars < 8; |
|||
my $display = $field.chars < 8 ?? $field !! $field.substr(*-8); |
|||
update($display~$spacer) |
|||
} |
|||
} |
|||
when "\c[13]" { |
|||
update(' '); |
|||
print "\e[10;6H\e[1;33;41mYou entered: $field\e[0m\e[$row;{$column}H"; |
|||
$field = ''; |
|||
$pointer = 0; |
|||
} |
|||
when "\c[0003]" { exit } # Ctrl-c |
|||
default { } |
|||
} |
|||
} |
|||
sub update ($str) { |
|||
($rows,$cols) = qx/stty size/.words; |
|||
@screen = "\e[41m{' ' x $cols}\e[0m" xx $rows; |
|||
print "\e[H\e[J{@screen.join: "\n"}\e[$row;{$column}H$str\e[$row;{$column + $pointer}H"; |
|||
}</syntaxhighlight> |
|||
=={{header|REXX}}== |
|||
(Only works with: REXX/Personal) |
|||
<syntaxhighlight lang="rexx">/*REXX pgm reads text from the terminal screen from a certain row, column, and length.*/ |
|||
parse arg row col len . /*obtain optional arguments from the CL*/ |
|||
if row=='' | row=="," then row= 3 /*Not specified? Then use the default.*/ |
|||
if col=='' | col=="," then col= 5 /* " " " " " " */ |
|||
if len=='' | len=="," then len= 8 /* " " " " " " */ |
|||
parse upper version v . /*obtain the version of REXX being used*/ |
|||
if v\=='REXX/PERSONAL' then do; say /*Not correct version? Tell err msg. */ |
|||
say '***error***:' |
|||
say 'This REXX program requires Personal REXX version.' |
|||
say |
|||
exit 13 |
|||
end |
|||
$= scrread(row, col, len) |
|||
say 'data read from terminal row ' row " col " col ' length ' len " is:" |
|||
say $ |
|||
exit 0 /*stick a fork in it, we're all done. */</syntaxhighlight> |
|||
=={{header|Wren}}== |
|||
Due to a bug the ''Stdin.readByte()'' method can currently process only the first byte of a multi-byte character. The others are skipped. |
|||
<syntaxhighlight lang="wren">import "io" for Stdin, Stdout |
|||
var textAtPos = Fn.new { |text, r, c| |
|||
System.write("\e[%(r);%(c)H%(text)") |
|||
Stdout.flush() |
|||
} |
|||
var input = Fn.new { |r, c, maxWidth| |
|||
System.write("\e[2J") |
|||
textAtPos.call("", r, c) |
|||
Stdin.isRaw = true |
|||
var res = "" |
|||
var blank = " " * (maxWidth-1) |
|||
while (true) { |
|||
var byte = Stdin.readByte() |
|||
if (byte >= 32 && byte < 127) { // All printable ASCII characters |
|||
var char = String.fromByte(byte) |
|||
res = res + char |
|||
var count = res.count |
|||
if (count <= maxWidth-1) { |
|||
textAtPos.call(res, r, c) |
|||
} else { |
|||
textAtPos.call(res[-maxWidth+1..-1], r, c) |
|||
} |
|||
} else if (byte == 127 && res.count > 0) { // Backspace/delete (127 used rather than 8) |
|||
res = res[0...-1] |
|||
var count = res.count |
|||
if (count <= maxWidth-1) { |
|||
textAtPos.call(blank, r, c) |
|||
textAtPos.call(res, r, c) |
|||
} else { |
|||
textAtPos.call(res[-maxWidth+1..-1], r, c) |
|||
} |
|||
} else if (byte == 13 || byte == 10) { // Carriage return or line feed |
|||
System.print() |
|||
break |
|||
} else if (byte == 3 || byte == 4) { // Ctrl-C or Ctrl-D |
|||
Fiber.abort("\nScript aborted") |
|||
} |
|||
} |
|||
Stdin.isRaw = false |
|||
return res |
|||
} |
|||
var res = input.call(3, 5, 8) |
|||
System.print(res)</syntaxhighlight> |
|||
=={{header|Yabasic}}== |
|||
<syntaxhighlight lang="yabasic">// Rosetta Code problem: http://rosettacode.org/wiki/Restricted_width_positional_input/With_wrapping |
|||
// by Galileo, 04/2022 |
|||
clear screen |
|||
sub getInput$(r, c, long) |
|||
local text$, c$, ini, lt |
|||
c = c - 1 |
|||
r = r - 1 |
|||
print at(c, r); |
|||
do |
|||
c$ = inkey$ |
|||
if c$ = "enter" break |
|||
if c$ = "backspace" then |
|||
text$ = left$(text$, len(text$) - 1) |
|||
print "\b "; |
|||
else |
|||
text$ = text$ + c$ |
|||
end if |
|||
lt = len(text$) |
|||
if lt > long then ini = lt - long + 1 else ini = 1 end if |
|||
print at(c, r) mid$(text$, ini, long); |
|||
loop |
|||
return text$ |
|||
end sub |
|||
text$ = getInput$(3, 5, 8) |
|||
print at(1, 23) "You entered: ", text$</syntaxhighlight> |
Latest revision as of 12:18, 13 February 2024
Create a routine to obtain data entry using a specific place on the terminal screen.
Data entry fields should be restricted to a specific length, and the cursor should not move beyond the input length.
The routine should accept parameters for row number, column number, and input length, and should obtain a string value entered by the user.
For the purpose of this task, obtain input from the user, showing data entry at row 3, column 5, with input width restricted to a maximum of 8 characters.
Note: in this task input wrapping is allowed. If the length of input exceeds the maximum width, the left hand side of the field should disappear, allowing additional input to be obtained. However, the cursor must not move beyond the designated input length.
It is permissible to use navigation keys to see input field length.
(See Terminal control/Restricted width positional input/No wrapping for similar input routine with no wrapping).
For a similar task using a graphical user interface, see Graphical User Interface/Restricted width positional input/With wrapping.
Common Lisp
ncurses
To interface ncurses from Lisp, the croatoan library is used.
;; Load the library from the quicklisp repository
(ql:quickload "croatoan")
(in-package :croatoan)
(defun field-input-with-wrapping (row column input-length)
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
(let ((field (make-instance 'field :position (list row column) :width input-length :max-buffer-length 30 :window scr)))
(setf (style field)
(list :background (list :simple-char #\.)))
(bind field #\newline 'accept)
(edit field)
(clear scr)
(refresh scr)
;; return the value of the field as a string
(value field))))
;; call the routine
(field-input-with-wrapping 2 4 8)
FreeBASIC
Function getInput(fila As Integer, columna As Integer, ancho As Integer) As String
Locate fila, columna, 0
Dim As String KBD, cadena = ""
Dim As Integer ini
Do
Do: KBD = Inkey: Loop Until KBD <> ""
If KBD = Chr(8) Then 'backspace
cadena = Left(cadena, Len(cadena) - 1)
Print !"\b ";
Else
cadena &= KBD
End If
ini = Iif(Len(cadena) > ancho, Len(cadena) - ancho + 1, 1)
Locate fila, columna : Print Mid(cadena, ini, ancho);
Loop Until KBD = Chr(13)
Return cadena
End Function
Dim As String s = getInput(3, 5, 8)
Locate 23,1 : Print "You entered: "; s
Sleep
Go
This uses _getch() rather than _getwch() as only ASCII is supported.
package main
/*
#include <windows.h>
#include <conio.h>
*/
import "C"
import (
"fmt"
"os"
"os/exec"
)
var conOut = C.GetStdHandle(C.STD_OUTPUT_HANDLE)
func setCursor(p C.COORD) {
C.SetConsoleCursorPosition(conOut, p)
}
func cls() {
cmd := exec.Command("cmd", "/c", "cls")
cmd.Stdout = os.Stdout
cmd.Run()
}
func getInput(row, col, width int) string {
if row < 0 || row > 20 || col < 0 || width < 1 || width > 78 || col > (79 - width) {
panic("Invalid parameter(s) to getInput. Terminating program")
}
coord := C.COORD{C.short(col), C.short(row)}
setCursor(coord)
var sb []byte
wlen := 0 // length of text in editing window
full := false
loop:
for {
ch := C._getch() // gets next character, no echo
switch c := byte(ch); c {
case 3, 13:
break loop // break on Ctrl-C or enter key
case 8:
if wlen > 0 { // mimic backspace
fmt.Print("\b \b")
sb = sb[:len(sb) - 1]
wlen--
}
if len(sb) > wlen {
coord.X = C.short(col)
coord.Y = C.short(row)
setCursor(coord)
fmt.Print(string(sb)[len(sb) - width:])
wlen = width
}
case 0, 224:
C._getch() // consume extra character
default:
if c >= 32 && c <= 126 { // echo ascii character, ignore others
sb = append(sb, c)
if !full {
C._putch(ch)
wlen++
} else if len(sb) > wlen {
coord.X = C.short(col)
coord.Y = C.short(row)
setCursor(coord)
fmt.Print(string(sb)[len(sb) - width:])
}
}
}
full = wlen == width // wlen can't exceed width
}
return string(sb)
}
func main() {
cls() // clear the console
s := getInput(2, 4, 8) // Windows console row/col numbering starts at 0
coord := C.COORD{0, 22}
setCursor(coord)
fmt.Printf("You entered '%s'\n", s)
}
Julia
Requires an ANSI compatible terminal and a system C library implementing _getch() for unbuffered keyboard input.
getch() = UInt8(ccall(:_getch, Cint, ()))
cls() = print("\33[2J")
reposition(row, col) = print("\u001b[$row;$(col)H")
clearfromcursor() = print("\u001b[K")
function input_y_x_upto(row, col, cmax, width=cmax)
buf = ""
while (buflen = length(buf)) < cmax && !((c = getch()) in [0xff, 0x0d, 0x0a])
reposition(row, col)
clearfromcursor()
if c == '\b' && buflen > 0
buf = buf[1:end-1]
else
buf = buf * Char(c)
end
print(buf[(buflen > width ? buflen - width + 1 : 1):end])
end
return buf
end
cls()
reposition(3, 5)
s = input_y_x_upto(3, 5, 80, 8)
println("\n\n\nResult: You entered <<$s>>")
Kotlin
This follows a similar approach to the Kotlin entry for the Terminal control/Restricted width positional input/No wrapping task except, of course, that the code now allows for wrapping.
// Kotlin Native v0.5
import kotlinx.cinterop.*
import platform.windows.*
import platform.posix.*
val ascii = 32..126
val conOut = GetStdHandle(STD_OUTPUT_HANDLE)!!
fun setCursor(p: COORD) = SetConsoleCursorPosition(conOut, p.readValue())
fun getInput(row: Short, col: Short, width: Int): String {
require(row in 0..20 && col in 0..(79 - width) && width in 1..78) { "Invalid parameter(s)" }
val coord = nativeHeap.alloc<COORD>().apply { X = col; Y = row }
setCursor(coord)
val sb = StringBuilder(width)
var wlen = 0 // length of text in editing window
var full = false
loop@ while (true) {
val ch = _getwch() // gets next character, no echo
when (ch.toInt()) {
3, 13 -> break@loop // break on Ctrl-C or enter key
8 -> { // mimic backspace
if (wlen > 0) {
print("\b \b")
sb.length--
wlen--
}
if (sb.length > wlen) {
coord.apply { X = col; Y = row }
setCursor(coord)
print(sb.toString().takeLast(width))
wlen = width
}
}
0, 224 -> _getwch() // consume extra character
in ascii -> { // echo ascii character
sb.append(ch.toChar())
if (!full) {
_putwch(ch)
wlen++
}
else if (sb.length > wlen) {
coord.apply { X = col; Y = row }
setCursor(coord)
print(sb.toString().takeLast(width))
}
}
else -> {} // igore other characters
}
full = wlen == width // wlen can't exceed width
}
nativeHeap.free(coord)
return sb.toString()
}
fun main(args: Array<String>) {
system("cls") // clear the console
val s = getInput(2, 4, 8) // Windows console row/col numbering starts at 0
memScoped {
val coord = alloc<COORD>().apply { X = 0 ; Y = 22 }
setCursor(coord)
}
println("You entered '$s'")
}
Nim
Rather than using directly escape codes, we use the standard module terminal
which works on Posix systems and on Windows. Unfortunately, there is no function to clear from cursor to end of line, so we had to provide one, but it works only on Posix systems.
We had also to check for backspace character and for character '\x7f'
as the backspace key gives this last value (but Control+H gives '\x08'
i.e. ASCII backspace character).
import strformat, terminal
proc eraseLineEnd() = stdout.write("\e[K")
proc inputXYUpto(row, col, cmax: int; width = cmax): string =
while result.len < cmax and not ((let c = getch(); c) in ['\xff', '\f', '\r']):
setCursorPos(row, col)
eraseLineEnd()
if c in ['\b', '\x7f'] and result.len > 0:
result.setLen(result.len - 1)
else:
result.add c
stdout.write result[(if result.len > width: result.len - width else: 0)..result.high]
eraseScreen()
setCursorPos(3, 5)
let s = inputXYUpto(3, 5, 80, 8)
echo &"\n\n\nResult: You entered <<{s}>>"
Perl
#!/usr/bin/perl
use strict; # https://rosettacode.org/wiki/Terminal_control/Restricted_width_positional_input/With_wrapping
use warnings;
use Term::ReadKey;
sub input
{
my ($row, $column, $length) = @_;
my ($input, $done, $start) = ( '', 0,
"\e[$row;${column}H" . ' ' x $length . "\e[$row;${column}H");
local $| = 1;
ReadMode 'raw';
until( $done )
{
print $start, substr $input, -$length;
local $_ = ReadKey 0;
if( tr/ -~// ) { $input .= $_ } # add char
elsif( tr/\cu// ) { $input = '' } # clear all
elsif( tr/\b\x7f// ) { chop $input } # delete last char
elsif( tr/\n\r\e\cc// ) { $done++ } # guess!
}
ReadMode 'restore';
return $input;
}
print "\e[H\e[Jinput at row 3 column 5 length 8\n";
my $in = input( 3, 5, 8 );
print "\n\n\ninput is $in\n\n";
Phix
without js -- (position, wait_key) function getInput(integer row, col, width) position(row,col) string s = "" while 1 do integer ch = wait_key() if ch='\r' then exit end if if ch='\b' then if length(s)>0 then puts(1,"\b \b") s = s[1..$-1] end if if length(s)>=width then position(row,col) puts(1,s[-width..-1]) end if elsif ch>=' ' and ch<='~' then s &= ch if length(s)<=width then puts(1,ch) else position(row,col) puts(1,s[-width..-1]) end if end if end while return s end function clear_screen() -- clear the console string s = getInput(3, 5, 8) position(23,1) printf(1,"You entered '%s'\n",{s})
Raku
(formerly Perl 6)
Should work with any termios compatible terminal.
All printable character keys (except Tab) work, as does backspace and enter. Ctrl-c to exit. All other keys / key-combos are ignored.
use Term::termios;
constant $saved = Term::termios.new(fd => 1).getattr;
constant $termios = Term::termios.new(fd => 1).getattr;
# raw mode interferes with carriage returns, so
# set flags needed to emulate it manually
$termios.unset_iflags(<BRKINT ICRNL ISTRIP IXON>);
$termios.unset_lflags(<ECHO ICANON IEXTEN ISIG>);
$termios.setattr(:DRAIN);
END {
$saved.setattr(:NOW); # reset terminal to original settings
print "\e[?25h \e[H\e[J"; # clear and reset screen
}
my $row = 3;
my $column = 5;
my $field = '';
my $spacer = ' ' x 8;
my $pointer = 0;
my ($rows,$cols) = qx/stty size/.words; # get screen size
my @screen = "\e[41m{' ' x $cols}\e[0m" xx $rows;
update($spacer);
loop {
my $key = $*IN.read(4).decode;
given $key {
when ' '..'~' {
if $pointer < 8 {
$field ~= $_;
$spacer = ' ' x 8 - $field.chars;
$pointer +=1;
update($field~$spacer)
} elsif $pointer >= 8 {
$field ~= $_;
$spacer = '';
update($field.substr(*-8))
}
}
when "\c[127]" { # backspace
if $pointer > 0 {
$field.=substr(0,*-1);
$spacer = ' ' x (8 - $field.chars max 0);
$pointer -= 1 if $field.chars < 8;
my $display = $field.chars < 8 ?? $field !! $field.substr(*-8);
update($display~$spacer)
}
}
when "\c[13]" {
update(' ');
print "\e[10;6H\e[1;33;41mYou entered: $field\e[0m\e[$row;{$column}H";
$field = '';
$pointer = 0;
}
when "\c[0003]" { exit } # Ctrl-c
default { }
}
}
sub update ($str) {
($rows,$cols) = qx/stty size/.words;
@screen = "\e[41m{' ' x $cols}\e[0m" xx $rows;
print "\e[H\e[J{@screen.join: "\n"}\e[$row;{$column}H$str\e[$row;{$column + $pointer}H";
}
REXX
(Only works with: REXX/Personal)
/*REXX pgm reads text from the terminal screen from a certain row, column, and length.*/
parse arg row col len . /*obtain optional arguments from the CL*/
if row=='' | row=="," then row= 3 /*Not specified? Then use the default.*/
if col=='' | col=="," then col= 5 /* " " " " " " */
if len=='' | len=="," then len= 8 /* " " " " " " */
parse upper version v . /*obtain the version of REXX being used*/
if v\=='REXX/PERSONAL' then do; say /*Not correct version? Tell err msg. */
say '***error***:'
say 'This REXX program requires Personal REXX version.'
say
exit 13
end
$= scrread(row, col, len)
say 'data read from terminal row ' row " col " col ' length ' len " is:"
say $
exit 0 /*stick a fork in it, we're all done. */
Wren
Due to a bug the Stdin.readByte() method can currently process only the first byte of a multi-byte character. The others are skipped.
import "io" for Stdin, Stdout
var textAtPos = Fn.new { |text, r, c|
System.write("\e[%(r);%(c)H%(text)")
Stdout.flush()
}
var input = Fn.new { |r, c, maxWidth|
System.write("\e[2J")
textAtPos.call("", r, c)
Stdin.isRaw = true
var res = ""
var blank = " " * (maxWidth-1)
while (true) {
var byte = Stdin.readByte()
if (byte >= 32 && byte < 127) { // All printable ASCII characters
var char = String.fromByte(byte)
res = res + char
var count = res.count
if (count <= maxWidth-1) {
textAtPos.call(res, r, c)
} else {
textAtPos.call(res[-maxWidth+1..-1], r, c)
}
} else if (byte == 127 && res.count > 0) { // Backspace/delete (127 used rather than 8)
res = res[0...-1]
var count = res.count
if (count <= maxWidth-1) {
textAtPos.call(blank, r, c)
textAtPos.call(res, r, c)
} else {
textAtPos.call(res[-maxWidth+1..-1], r, c)
}
} else if (byte == 13 || byte == 10) { // Carriage return or line feed
System.print()
break
} else if (byte == 3 || byte == 4) { // Ctrl-C or Ctrl-D
Fiber.abort("\nScript aborted")
}
}
Stdin.isRaw = false
return res
}
var res = input.call(3, 5, 8)
System.print(res)
Yabasic
// Rosetta Code problem: http://rosettacode.org/wiki/Restricted_width_positional_input/With_wrapping
// by Galileo, 04/2022
clear screen
sub getInput$(r, c, long)
local text$, c$, ini, lt
c = c - 1
r = r - 1
print at(c, r);
do
c$ = inkey$
if c$ = "enter" break
if c$ = "backspace" then
text$ = left$(text$, len(text$) - 1)
print "\b ";
else
text$ = text$ + c$
end if
lt = len(text$)
if lt > long then ini = lt - long + 1 else ini = 1 end if
print at(c, r) mid$(text$, ini, long);
loop
return text$
end sub
text$ = getInput$(3, 5, 8)
print at(1, 23) "You entered: ", text$