Terminal control/Restricted width positional input/With wrapping: Difference between revisions

From Rosetta Code
Content added Content deleted
m (moved a category to the top of the preamble, added whitespace before the TOC.)
m (added highlighting and whitespace.)
Line 2: Line 2:
[[Category:Terminal control]]
[[Category:Terminal control]]


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.
Create a routine to obtain data entry using a specific place on the terminal screen.


The routine should accept parameters for row number, column number and input length, and should obtain a string
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 15: 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>
<br><br>



Revision as of 09:35, 18 July 2021

Terminal control/Restricted width positional input/With wrapping is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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. <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)</lang>

Go

Works with: Windows 10
Translation of: Kotlin


This uses _getch() rather than _getwch() as only ASCII is supported. <lang go>package main

/*

  1. include <windows.h>
  2. 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)

}</lang>


Julia

Requires an ANSI compatible terminal and a system C library implementing _getch() for unbuffered keyboard input. <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>>") </lang>

Kotlin

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. <lang scala>// 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'")

}</lang>

Nim

Translation of: Julia

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).

<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}>>"</lang>

Phix

<lang Phix>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})</lang>

Raku

(formerly Perl 6)

Works with: Rakudo version 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.

<lang perl6>use Term::termios;

constant $saved = Term::termios.new(fd => 1).getattr; constant $termios = Term::termios.new(fd => 1).getattr;

  1. raw mode interferes with carriage returns, so
  2. 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";

}</lang>

REXX

(Only works with:   REXX/Personal) <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. */</lang>

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. <lang ecmascript>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)</lang>