Readline interface: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|REXX}}: added whitespace.)
m (→‎{{header|REXX}}: changed/added comments and whitespace, changed indentations.)
Line 199: Line 199:
<br>The HELP (?), REDO, error checking, and abbreviations took up most of the program.
<br>The HELP (?), REDO, error checking, and abbreviations took up most of the program.
<br>"User" commands (subroutines) are identified with a leading period (.) to make it easier to understand what's what.
<br>"User" commands (subroutines) are identified with a leading period (.) to make it easier to understand what's what.
<lang rexx>/*REXX program to implement a simple "readline" shell. */
<lang rexx>/*REXX program implements a simple "readline" shell (modeled after a DOS shell). */
trace off /*suppress echoing of non-zero RC*/
trace off /*suppress echoing of non-zero retCodes*/
signal on syntax; signal on novalue /*handle REXX program errors. */
signal on syntax; signal on noValue /*handle REXX program errors. */
cmdX='ATTRIB CAL CHDIR COPY DEL DIR ECHO EDIT FC FIND KEDIT LLL',
cmdX='ATTRIB CAL CHDIR COPY DEL DIR ECHO EDIT FC FIND KEDIT LLL MEM MKDIR MORE REM REXX',
'MEM MKDIR MORE REM REXX RMDIR SET TYPE VER XCOPY'
'RMDIR SET TYPE VER XCOPY' /* ◄──── the legal/known commands. */
cls='CLS' /*define the pgm to clear screen.*/
cls= 'CLS' /*define the program to clear screen. */
@hist.='*** command not defined. ***' /*initialize the history database*/
@hist.= '*** command not defined. ***' /*initialize the history database. */
hist#=0 /*number of commands in history. */
hist#=0 /*the number of commands in the history*/
prompt='Enter command ──or── ? ──or── Quit' /*default PROMPT msg.*/
prompt='Enter command ──or── ? ──or── Quit' /*the default PROMPT message text. */
sw=linesize() /*some REXX don't have this BIF. */
sw=linesize() /*some REXX don't have this BIF. */
cls /*start with a clean slate. */
cls /*start with a clean slate (terminal). */
redoing=0 /*flag for executing naked ReDO.*/
redoing=0 /*flag for executing naked ReDO cmd. */
/* [↓] do it 'til the fat lady sings. */
do forever; if prompter() then iterate /*Nothing entered? Then go try again. */
select /* [↓] now then, let's rock n' roll. */
when wordpos(xxxF,cmdX)\==0 then call .cmdX
when xxxF=='EXIT' | xxxF="QUIT" then leave /*da fat lady is done singing*/
when xxxF=='HISTORY' then call .history
when xxxF=='HELP' then call .help
when xxxF=='PROMPT' then call .prompt
when xxxF=='REDO' then call .redo
otherwise call er 'unknown command:' xxx yyy
end /*select*/
end /*forever*/


do forever /*do it until the fat lady sings.*/
say xxxF'ting···' /*say goodbye, 'cause it's polite to do*/
if prompter() then iterate /*Nothing entered? So try again.*/
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
select /*now then, let's rock & roll. */
er: say; say; say '****error****'; say; say arg(1); say; say; return
when wordpos(xxxF,cmdX)\==0 then call .cmdX
.prompt: if yyyU\=='' then prompt=yyy; return
when xxxF=='EXIT' | xxxF="QUIT" then leave /*the fat lady sung.*/
.cmdX: xxxF yyy; return
when xxxF=='HISTORY' then call .history
/*──────────────────────────────────────────────────────────────────────────────────────*/
when xxxF=='HELP' then call .help
err: say; say; say center(' error! ', max(40, linesize()%2), "*"); say
when xxxF=='PROMPT' then call .prompt
when xxxF=='REDO' then call .redo
do j=1 for arg(); say arg(j); say; end; say; exit 13
/*──────────────────────────────────────────────────────────────────────────────────────*/
otherwise call er 'unknown command:' xxx yyy
noValue: syntax: call err 'REXX program' condition('C') "error",,
end /*select*/
condition('D'),'REXX source statement (line' sigl"):",sourceline(sigl)
end /*forever*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
.help: cmdsH= 'ATTRIB CHDIR CLS COPY DEL DIR ECHO EDIT FC FIND MEM MKDIR MORE PRINT',
'REM RMDIR SET TREE TYPE VER XCOPY' /*these have their own help via /? */


cmds= '?|Help|MANual ATTRIButes CALendar CD|CHDIR|CHANGEDir COPY DELete|ERASE',
say xxxF'ting...' /*say goodbye, 'cause it's polite*/
'DELete|ERASE DIR ECHO EDIT FC|FILECOMPAre FIND HISTory Kedit LLL',
exit /*stick a fork in it, we're done.*/
'MEM MD|MKDIR|MAKEDir MORE PRINT PROMPT Quit|EXIT',
/*───────────────────────────────error handling subroutines and others.─*/
'R4 RD|RMDIR|REMOVEDir REGINA REMark Rexx REDO SET TREE Type VER'
er: say; say; say '****error!****'; say; say arg(1); say; say; return
err: say; say; say center(' error! ',max(40,linesize()%2),"*"); say
do j=1 for arg(); say arg(j); say; end; say; exit 13


say center(strip(xxx yyy),sw-1,'═'); cmds_=cmds; yyyF=unabbrev(yyy)
novalue: syntax: call err 'REXX program' condition('C') "error",,
condition('D'),'REXX source statement (line' sigl"):",,
sourceline(sigl)
/*──────────────────────────────────.CMDX subroutine────────────────────*/
.cmdX: xxxF yyy; return
/*──────────────────────────────────.HELP subroutine────────────────────*/
.help: cmdsH='ATTRIB CHDIR CLS COPY DEL DIR ECHO EDIT FC FIND MEM',
'MKDIR MORE PRINT REM RMDIR SET TREE TYPE VER XCOPY'
/*the above commands have their own help via /? */


help. = 'No help is available for the' yyy "command."
cmds='?|Help|MANual ATTRIButes CALendar CD|CHDIR|CHANGEDir COPY DELete|ERASE',
help.cal = 'shows a calendar for the current month or specified month.'
'DELete|ERASE DIR ECHO EDIT FC|FILECOMPAre FIND HISTory Kedit LLL',
help.kedit = 'KEDITs the file specified.'
'MEM MD|MKDIR|MAKEDir MORE PRINT PROMPT Quit|EXIT',
help.lll = 'shows a formatted listing of files in the current directory.'
'R4 RD|RMDIR|REMOVEDir REGINA REMark Rexx REDO SET TREE Type VER'
help.prompt= 'sets the PROMPT message to the specified text.'
help.quit = 'quits (exits) this program.'
help.redo = 're-does the command # specified (or the last command).'
help.rexx = 'executes the REXX program specified.'


if yyy=='' then do j=1 while cmds_\==''
say center(strip(xxx yyy),sw-1,'═'); cmds_=cmds; yyyF=unabbrev(yyy)
parse var cmds_ x cmds_
say left('',sw%2) changestr('|',x," | ")
end /*j*/
else select
when wordpos(yyyF,cmdsH)\==0 then yyyF '/?'
otherwise cmd?=yyyF
if left(help.yyyF,1)\==' ' then say yyyF ' ' help.yyyF
else say help.yyyF
end /*select*/
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
.history: say center('history', sw-1, '═'); w=length(hist#)
do j=1 for hist#; say right(j,w) '═══►' @hist.j; end /*j*/
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
prompter: if redoing then do; redoing=0; z=hist#-1 /*special case, bare REDO. */
parse var @hist.z xxx yyy
end
else do; if prompt\=='' then do; say; say prompt; end
parse pull xxx yyy
end


xxxU=xxx; upper xxxU; if xxx=='' then return 1 /*No input? Then try again*/
help. = ' No help is available for the' yyy "command."
yyyU=yyy; upper yyyU; yyyU=strip(yyyU)
help.cal = 'shows a calendar for the current month or specified month.'
hist#=hist#+1; /*bump the history counter.*/
help.kedit = 'KEDITs the file specified.'
@hist.hist#=strip(xxx yyy) /*assign to history. */
help.lll = 'shows a formatted listing of files in the current directory.'
xxxF=unAbbrev(xxx) /*maybe expand abbreviation*/
help.prompt= 'sets the PROMPT message to the specified text.'
return 0
help.quit = 'quits (exits) this program.'
/*──────────────────────────────────────────────────────────────────────────────────────*/
help.redo = 're-does the command # specified (or the last command).'
.redo: select
help.rexx = 'executes the REXX program specified.'
when yyyU=='' then redoing=1 /*assume they want the last cmd. */

when words(yyy)\==1 then call er 'too many args specified for' xxx
if yyy=='' then do j=1 while cmds_\==''
parse var cmds_ x cmds_
when \datatype(yyy,'W') then call er "2nd arg isn't numeric for" xxx
say left('',sw%2) changestr('|',x," | ")
otherwise nop
end /*j*/
end /*select*/
if redoing then return /*handle with kid gloves. */
else select
yyy=yyy/1 /*normalize it: +7 7. 1e1 007 7.0*/
when wordpos(yyyF,cmdsH)\==0 then yyyF '/?'
otherwise cmd?=yyyF
say 'Re-doing:' @hist.yyy
@hist.yyy
if left(help.yyyF,1)\==' ' then say yyyF ' ' help.yyyF
return
else say help.yyyF
/*──────────────────────────────────────────────────────────────────────────────────────*/
end /*select*/
return
/*──────────────────────────────────.HISTORY subroutine─────────────────*/
.history: say center('history',sw-1,'═'); w=length(hist#)
do j=1 for hist#
say right(j,w) '===>' @hist.j
end /*j*/
return
/*──────────────────────────────────.PROMPT subroutine──────────────────*/
.prompt: if yyyU\=='' then prompt=yyy; return
/*──────────────────────────────────PROMPTER subroutine─────────────────*/
prompter: if redoing then do /*special case for naked REDO */
redoing=0; z=hist#-1
parse var @hist.z xxx yyy
end
else do
if prompt\=='' then do; say; say prompt; end
parse pull xxx yyy
end

xxxU=xxx; upper xxxU; if xxx=='' then return 1 /*No input? Try again*/
yyyU=yyy; upper yyyU; yyyU=strip(yyyU)
hist#=hist#+1; /*bump the history counter. */
@hist.hist#=strip(xxx yyy) /*assign to history. */
xxxF=unAbbrev(xxx) /*expand the abbreviation (maybe)*/
return 0
/*──────────────────────────────────.redo subroutine────────────────────*/
.redo:
select
when yyyU=='' then redoing=1 /*assume they want the last cmd. */
when words(yyy)\==1 then call er 'too many args specified for' xxx
when \datatype(yyy,'W') then call er "2nd arg isn't numeric for" xxx
otherwise nop
end /*select*/
if redoing then return /*handle with kid gloves. */
yyy=yyy/1 /*normalize it: +7 7. 1e1 007 7.0*/
say 'Re-doing:' @hist.yyy
@hist.yyy
return
/*──────────────────────────────────UNABBREV subroutine─────────────────*/
unabbrev: procedure; arg ccc
unabbrev: procedure; arg ccc
select
select
when abbrev('ATTRIBUTES',ccc,6) then return 'ATTRIB'
when abbrev('ATTRIBUTES' , ccc, 6) then return 'ATTRIB'
when abbrev('CALENDAR',ccc,3) then return 'CAL'
when abbrev('CALENDAR' , ccc, 3) then return 'CAL'
when abbrev('CHANGEDIR',ccc,7) |,
when abbrev('CHANGEDIR' , ccc, 7) |,
ccc=='CHDIR' |,
ccc=='CHDIR' |,
ccc=='CD' then return 'CHDIR'
ccc=='CD' then return 'CHDIR'
when abbrev('CLEARSCREEN',ccc,5) then return 'CLS'
when abbrev('CLEARSCREEN', ccc, 5) then return 'CLS'
when abbrev('FILECOMPARE',ccc,9) then return 'FC'
when abbrev('FILECOMPARE', ccc, 9) then return 'FC'
when abbrev('DELETE',ccc,3) |,
when abbrev('DELETE' , ccc, 3) |,
ccc=='ERASE' then return 'DEL'
ccc=='ERASE' then return 'DEL'
when abbrev('HISTORY',ccc,4) then return 'HISTORY'
when abbrev('HISTORY' , ccc, 4) then return 'HISTORY'
when abbrev('HELP',ccc,1) |,
when abbrev('HELP' , ccc, 1) |,
abbrev('MANUAL',ccc,3) |,
abbrev('MANUAL' , ccc, 3) |,
ccc=='?' then return 'HELP'
ccc=='?' then return 'HELP'
when abbrev('MAKEDIR',ccc,5) |,
when abbrev('MAKEDIR' , ccc, 5) |,
ccc=='MKDIR' |,
ccc=='MKDIR' |,
ccc=='MD' then return 'MKDIR'
ccc=='MD' then return 'MKDIR'
when abbrev('KEDIT',ccc,1) then return 'KEDIT'
when abbrev('KEDIT' , ccc, 1) then return 'KEDIT'
when abbrev('QUIT',ccc,1) then return 'QUIT'
when abbrev('QUIT' , ccc, 1) then return 'QUIT'
when abbrev('REMARK',ccc,3) then return 'REM'
when abbrev('REMARK' , ccc, 3) then return 'REM'
when abbrev('REMOVEDIR',ccc,7) |,
when abbrev('REMOVEDIR' , ccc, 7) |,
ccc=='RMDIR' |,
ccc=='RMDIR' |,
ccc=='RD' then return 'RMDIR'
ccc=='RD' then return 'RMDIR'
when abbrev('REXX',ccc,1) then return 'REXX'
when abbrev('REXX' , ccc, 1) then return 'REXX'
when abbrev('TYPE',ccc,1) then return 'TYPE'
when abbrev('TYPE' , ccc, 1) then return 'TYPE'
otherwise nop
otherwise nop
end /*select*/
end /*select*/
return ccc</lang>
return ccc
</lang>
This REXX program makes use of &nbsp; '''LINESIZE''' &nbsp; REXX program (or BIF) which is used to determine the screen width (or linesize) of the terminal (console).
This REXX program makes use of &nbsp; '''LINESIZE''' &nbsp; REXX program (or BIF) which is used to determine the screen width (or linesize) of the terminal (console).
<br>The &nbsp; '''LINESIZE.REX''' &nbsp; REXX program is included here &nbsp; ──► &nbsp; [[LINESIZE.REX]].<br>
<br>The &nbsp; '''LINESIZE.REX''' &nbsp; REXX program is included here &nbsp; ──► &nbsp; [[LINESIZE.REX]].<br>

Revision as of 23:26, 25 April 2016

Readline interface 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.

A readline interface is a line editing facility that provides auto completion facilities and the ability to recall previously typed lines. Build a simple application that accepts at least two commands and uses a readline style interface.

The interface should provide

  • the ability to recall previously typed commands
  • commandline editing
  • application specific commands

The application could be based on Simple database or something else.

If the language already provides a readline interface, then it should be demonstrated how build an application specific readline interface (one that only understands the applications commands) and also show how to customize the builtin readline to provide the above features and especially add application specific commands.

See also: Input loop

C

A program that does absolutely nothing. Type 'help' for help. <lang c>#include <readline/readline.h>

  1. include <readline/history.h>
  2. include <string.h>

int main() { char *s; using_history(); while (1) { s = readline("This be a prompt> ");

if (!s || !strcmp(s, "quit")) { puts("bye."); return 0; }

if (!strcmp(s, "help")) puts("commands: ls, cat, quit"); else if (!strcmp(s, "ls") || !strcmp(s, "cat")) { printf("command `%s' not implemented yet.\n", s); add_history(s); } else puts("Yes...?"); } }</lang>

PARI/GP

gp uses readline, but it can also be used directly in PARI:

<lang c>#include <stdio.h>

  1. include <pari/pari.h>
  2. include <readline/readline.h>
  3. include <setjmp.h>

jmp_buf env; void gp_err_recover(long numerr) { longjmp(env, numerr); }

/* History handling (%1, %2, etc.)*/ pari_stack s_history; GEN *history; GEN parihist(long p) {

 if (p > 0 && p<=s_history.n)
   return history[p-1];
 else if (p<=0 && s_history.n+p-1>=0)
   return history[s_history.n+p-1];
 pari_err(talker,"History result %ld not available [%%1-%%%ld]",p,s_history.n);
 return NULL; /* not reached */

}

int main(int argc, char **argv) {

 char *in, *out;
 GEN z;
 entree hist={"%",0,(void*)parihist,13,"D0,L,","last history item."};
 printf("Welcome to minigp!\n");
 pari_init(8000000,500000);
 cb_pari_err_recover = gp_err_recover;
 pari_add_function(&hist);
 stack_init(&s_history,sizeof(*history),(void**)&history);
 (void)setjmp(env);
 while(1)
 {
   in = readline("? ");
   if (!in) break;
   if (!*in) continue;
   z = gp_read_str(in);
   stack_pushp(&s_history,(void*)gclone(z)); /*Add to history */
   out = GENtostr(z);
   printf("%%%ld = %s\n",s_history.n,out);
   free(in); free(out); avma=top;
 }
 return 0;

}</lang>

Code thanks to Bill Allombert

Pike

watch.pike is the solution from Simple database#Pike. this solution demonstrates how to retrofit an application with a readline interface by inheriting the original and overriding specific functions.

<lang pike>#!/usr/bin/pike

inherit "watch.pike";

Stdio.Readline readln = Stdio.Readline();

void print_help() {

   write("The following commands are available: \n");
   write(sort(indices(functions))*", ");
   write("\n");

}

void watch_add() {

   ::watch_add(db);

}

void watch_list() {

   ::watch_list(db);

}

void do_exit() {

   exit(0);

}

mapping functions = ([ "add":watch_add,

                      "list":watch_list,
                      "load":watch_load,
                      "save":watch_save,
                      "help":print_help,
                      "quit":do_exit ]);

string prompt_read(string prompt) {

   return readln->read(prompt+": ");

}

void main() {

 Stdio.Readline.History readline_history = Stdio.Readline.History(512);
 readln->enable_history(readline_history);
 string prompt="> ";


 print_help();
 while(1)
 {
   string input=readln->read(prompt);
   if(!input)
     exit(0);
   if(input != "")
   {
      if (functions[input])
          functions[input]();
      else
      {
          write("unknown command\n");
          print_help();
      }
   }
 }

}</lang>

Sample session:

pike watch_rl.pike
The following commands are available:
add, help, list, load, quit, save
> load
> list
  Rosetta Code                            2 Simple Database                (27.10.2011.)
> add
Series: Rdm asks!
Title: Balanced ternary
Episode: 1
Date watched: 1.11.2011
Add new series? [y/n]: : y
> list
  Rdm asks!                               1 Balanced ternary               (1.11.2011.)
  Rosetta Code                            2 Simple Database                (27.10.2011.)
> hello
unknown command
The following commands are available: 
add, help, list, load, quit, save
> save
> quit

Racket

Racket's has a readline interface which is not used by default due to its license. This includes the usual readline-style editing, and tab-completion for Racket bindings. It is possible to use it as a library, or as a REPL convenience (both uses described in the documentation) -- but it is better to use xrepl which provides an enhanced command-line REPL and includes the readline interaction.

REXX

This REXX programs supports a REDO (re-do) with very limited editing to keep the program simple.
It has a history (which can be interrogated) and some of the simple (MS) DOS commands.
The HELP (?), REDO, error checking, and abbreviations took up most of the program.
"User" commands (subroutines) are identified with a leading period (.) to make it easier to understand what's what. <lang rexx>/*REXX program implements a simple "readline" shell (modeled after a DOS shell). */ trace off /*suppress echoing of non-zero retCodes*/ signal on syntax; signal on noValue /*handle REXX program errors. */ cmdX='ATTRIB CAL CHDIR COPY DEL DIR ECHO EDIT FC FIND KEDIT LLL MEM MKDIR MORE REM REXX',

                  'RMDIR SET TYPE VER XCOPY'    /* ◄──── the legal/known commands.     */

cls= 'CLS' /*define the program to clear screen. */ @hist.= '*** command not defined. ***' /*initialize the history database. */ hist#=0 /*the number of commands in the history*/ prompt='Enter command ──or──  ? ──or── Quit' /*the default PROMPT message text. */ sw=linesize() /*some REXX don't have this BIF. */ cls /*start with a clean slate (terminal). */ redoing=0 /*flag for executing naked ReDO cmd. */

                                                /* [↓]  do it 'til the fat lady sings. */
 do forever;    if prompter()  then iterate     /*Nothing entered?  Then go try again. */
      select                                    /* [↓]  now then, let's rock n' roll.  */
      when wordpos(xxxF,cmdX)\==0      then call .cmdX
      when xxxF=='EXIT' | xxxF="QUIT"  then leave         /*da fat lady is done singing*/
      when xxxF=='HISTORY'             then call .history
      when xxxF=='HELP'                then call .help
      when xxxF=='PROMPT'              then call .prompt
      when xxxF=='REDO'                then call .redo
      otherwise                             call er 'unknown command:'  xxx  yyy
      end   /*select*/
 end        /*forever*/

say xxxF'ting···' /*say goodbye, 'cause it's polite to do*/ exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ er: say; say; say '****error****'; say; say arg(1); say; say; return .prompt: if yyyU\== then prompt=yyy; return .cmdX: xxxF yyy; return /*──────────────────────────────────────────────────────────────────────────────────────*/ err: say; say; say center(' error! ', max(40, linesize()%2), "*"); say

                              do j=1  for arg();  say arg(j);  say;  end;   say;  exit 13

/*──────────────────────────────────────────────────────────────────────────────────────*/ noValue: syntax: call err 'REXX program' condition('C') "error",,

                   condition('D'),'REXX source statement (line' sigl"):",sourceline(sigl)

/*──────────────────────────────────────────────────────────────────────────────────────*/ .help: cmdsH= 'ATTRIB CHDIR CLS COPY DEL DIR ECHO EDIT FC FIND MEM MKDIR MORE PRINT',

               'REM RMDIR SET TREE TYPE VER XCOPY'  /*these have their own help via /? */
        cmds= '?|Help|MANual ATTRIButes CALendar CD|CHDIR|CHANGEDir COPY DELete|ERASE',
              'DELete|ERASE DIR ECHO EDIT FC|FILECOMPAre FIND HISTory Kedit LLL',
              'MEM MD|MKDIR|MAKEDir MORE PRINT PROMPT Quit|EXIT',
              'R4 RD|RMDIR|REMOVEDir REGINA REMark Rexx REDO SET TREE Type VER'
         say center(strip(xxx yyy),sw-1,'═');    cmds_=cmds;    yyyF=unabbrev(yyy)
         help.      = 'No help is available for the'    yyy    "command."
         help.cal   = 'shows a calendar for the current month or specified month.'
         help.kedit = 'KEDITs the file specified.'
         help.lll   = 'shows a formatted listing of files in the current directory.'
         help.prompt= 'sets the PROMPT message to the specified text.'
         help.quit  = 'quits (exits) this program.'
         help.redo  = 're-does the command # specified  (or the last command).'
         help.rexx  = 'executes the REXX program specified.'
         if yyy== then do j=1 while cmds_\==
                         parse var cmds_ x cmds_
                         say left(,sw%2) changestr('|',x,"  |  ")
                         end    /*j*/
                    else select
                         when wordpos(yyyF,cmdsH)\==0 then yyyF '/?'
                         otherwise cmd?=yyyF
                         if left(help.yyyF,1)\==' ' then say yyyF ' ' help.yyyF
                                                    else say help.yyyF
                         end    /*select*/
         return

/*──────────────────────────────────────────────────────────────────────────────────────*/ .history: say center('history', sw-1, '═'); w=length(hist#)

                        do j=1  for hist#;   say right(j,w)  '═══►'  @hist.j;   end /*j*/
         return

/*──────────────────────────────────────────────────────────────────────────────────────*/ prompter: if redoing then do; redoing=0; z=hist#-1 /*special case, bare REDO. */

                               parse var  @hist.z  xxx  yyy
                          end
                     else do;  if prompt\==  then do;   say;   say prompt;   end
                               parse pull xxx yyy
                          end
         xxxU=xxx;   upper xxxU;  if xxx==  then return 1 /*No input?  Then try again*/
         yyyU=yyy;   upper yyyU;  yyyU=strip(yyyU)
         hist#=hist#+1;                                     /*bump the history counter.*/
         @hist.hist#=strip(xxx yyy)                         /*assign to history.       */
         xxxF=unAbbrev(xxx)                                 /*maybe expand abbreviation*/
         return 0

/*──────────────────────────────────────────────────────────────────────────────────────*/ .redo: select

           when yyyU==           then redoing=1     /*assume they want the last cmd. */
           when words(yyy)\==1     then call er 'too many args specified for' xxx
           when \datatype(yyy,'W') then call er "2nd arg isn't numeric for" xxx
           otherwise               nop
           end    /*select*/
      if redoing  then return                         /*handle with kid gloves.        */
      yyy=yyy/1                                       /*normalize it: +7 7. 1e1 007 7.0*/
      say 'Re-doing:'  @hist.yyy
      @hist.yyy
      return

/*──────────────────────────────────────────────────────────────────────────────────────*/ unabbrev: procedure; arg ccc

                             select
                             when abbrev('ATTRIBUTES' , ccc, 6)     then return 'ATTRIB'
                             when abbrev('CALENDAR'   , ccc, 3)     then return 'CAL'
                             when abbrev('CHANGEDIR'  , ccc, 7) |,
                                    ccc=='CHDIR'                |,
                                    ccc=='CD'                       then return 'CHDIR'
                             when abbrev('CLEARSCREEN', ccc, 5)     then return 'CLS'
                             when abbrev('FILECOMPARE', ccc, 9)     then return 'FC'
                             when abbrev('DELETE'     , ccc, 3) |,
                                    ccc=='ERASE'                    then return 'DEL'
                             when abbrev('HISTORY'    , ccc, 4)     then return 'HISTORY'
                             when abbrev('HELP'       , ccc, 1) |,
                                  abbrev('MANUAL'     , ccc, 3) |,
                                    ccc=='?'                        then return 'HELP'
                             when abbrev('MAKEDIR'    , ccc, 5) |,
                                    ccc=='MKDIR'                |,
                                    ccc=='MD'                       then return 'MKDIR'
                             when abbrev('KEDIT'      , ccc, 1)     then return 'KEDIT'
                             when abbrev('QUIT'       , ccc, 1)     then return 'QUIT'
                             when abbrev('REMARK'     , ccc, 3)     then return 'REM'
                             when abbrev('REMOVEDIR'  , ccc, 7) |,
                                    ccc=='RMDIR'                |,
                                    ccc=='RD'                       then return 'RMDIR'
                             when abbrev('REXX'       , ccc, 1)     then return 'REXX'
                             when abbrev('TYPE'       , ccc, 1)     then return 'TYPE'
                             otherwise nop
                             end    /*select*/
         return ccc

</lang> This REXX program makes use of   LINESIZE   REXX program (or BIF) which is used to determine the screen width (or linesize) of the terminal (console).
The   LINESIZE.REX   REXX program is included here   ──►   LINESIZE.REX.

Some older REXXes don't have a changestr BIF, so one is included here ──► CHANGESTR.REX.

output   showing a sample session:

Enter command   ──or──  ?  ──or──  Quit
hel
═══════════════════════════════════════════hel══════════════════════════════════
                                              ?  |  Help  |  MANual
                                              ATTRIButes
                                              CALendar
                                              CD  |  CHDIR  |  CHANGEDir
                                              COPY
                                              DELete  |  ERASE
                                              DELete  |  ERASE
                                              DIR
                                              ECHO
                                              EDIT
                                              FC  |  FILECOMPAre
                                              FIND
                                              HISTory
                                              Kedit
                                              LLL
                                              MEM
                                              MD  |  MKDIR  |  MAKEDir
                                              MORE
                                              PRINT
                                              PROMPT
                                              Quit  |  EXIT
                                              R4
                                              RD  |  RMDIR  |  REMOVEDir
                                              REGINA
                                              REMark
                                              Rexx
                                              REDO
                                              SET
                                              TREE
                                              Type
                                              VER

Enter command   ──or──  ?  ──or──  Quit
rexx $mayan 123,456,787,654,321
╔════╗ ╔════╗ ╔════╗ ╔════╗ ╔════╗ ╔════╗ ╔════╗ ╔════╗ ╔════╗ ╔════╗ ╔════╗
║    ║ ║    ║ ║    ║ ║    ║ ║    ║ ║    ║ ║    ║ ║ ∙  ║ ║    ║ ║ ∙  ║ ║    ║
║ ∙∙ ║ ║    ║ ║    ║ ║    ║ ║ ∙∙ ║ ║    ║ ║    ║ ║────║ ║────║ ║────║ ║    ║
║────║ ║    ║ ║    ║ ║────║ ║────║ ║ ∙  ║ ║    ║ ║────║ ║────║ ║────║ ║    ║
║────║ ║ ∙  ║ ║ ∙∙ ║ ║────║ ║────║ ║────║ ║ ∙∙ ║ ║────║ ║────║ ║────║ ║ ∙  ║
╚════╝ ╚════╝ ╚════╝ ╚════╝ ╚════╝ ╚════╝ ╚════╝ ╚════╝ ╚════╝ ╚════╝ ╚════╝

Enter command   ──or──  ?  ──or──  Quit
cal
 ┌────────────────────────────────────────────────────────────────────────────┐
 │                                                                            │
 │                                                                            │
 │                                June   2012                                 │
 │                                                                            │
 │                                                                            │
 │  Sunday     Monday     Tuesday   Wednesday  Thursday    Friday    Saturday │
 ├──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┤
 │          │          │          │          │          │          │          │
 │          │          │          │          │          │          │          │
 │          │          │          │          │          │  ─  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    │
 │          │          │          │          │          │          │          │
 │          │          │          │          │          │          │          │
 └──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘


Enter command   ──or──  ?  ──or──  Quit
LLL
mm/dd/yyyy hh:mm ──filesize── ─────────────────filename D:\*.*─────────────────
06/01/2012 10:36       2,322  DIV_GRID.TXT
05/13/2012 20:39       1,044  MOD_EXP.REX
05/13/2012 19:39         694  MOD_EXP.TXT
06/01/2012 02:32       1,439  RCOMBN.RX_
06/01/2012 02:24       7,181  READLINT.REX
05/29/2012 22:48          60  sub00.REX
──────────────────────12,740  <──────────────total bytes  [6 files]

Enter command   ──or──  ?  ──or──  Quit
dir
 Volume in drive D is -----D-----
 Volume Serial Number is 68D0-6356

 Directory of D:\

05/09/2012  12:40    <DIR>          Config.Msi
12/16/2005  19:54    <DIR>          Documents and Settings
01/15/2006  10:33    <DIR>          Program Files
04/14/2010  19:58    <DIR>          Recycled
12/16/2005  20:29    <DIR>          System Volume Information
01/26/2011  00:21    <DIR>          temp
12/16/2005  19:42    <DIR>          WINDOWS
01/06/2006  14:43               211 boot.ini
06/01/2012  10:36             2,322 DIV_GRID.TXT
12/16/2005  20:18                 0 IO.SYS
05/13/2012  20:39             1,044 MOD_EXP.REX
05/13/2012  19:39               694 MOD_EXP.TXT
12/16/2005  20:18                 0 MSDOS.SYS
08/04/2004  12:00            47,564 NTDETECT.COM
08/04/2004  12:00           250,032 ntldr
06/01/2012  02:32             1,439 RCOMBN.RX_
06/01/2012  02:24             7,181 READLINT.REX
05/29/2012  22:48                60 sub00.REX
              11 File(s)        310,547 bytes
               7 Dir(s)   6,793,314,304 bytes free

Enter command   ──or──  ?  ──or──  Quit
help ver
════════════════════════════════════════help ver═════════════════════════════════════════
Displays the Windows XP version.

VER

Enter command   ──or──  ?  ──or──  Quit
ver

Microsoft Windows XP [Version 5.1.2600]

Enter command   ──or──  ?  ──or──  Quit
q
QUITting...

Ruby

This application provides auto-completion for these commands: search download open help history quit url prev past.
Auto-completion is applied by pressing the tab-key.
A history of the commands is navigated by the up- and down-keys. All commands are just echoed, except "quit" which exits.
It uses the Ruby 2.3 method Hash#to_proc. <lang ruby>require "readline" require "abbrev"

commands = %w[search download open help history quit url prev past] Readline.completion_proc = commands.abbrev.to_proc

while buf = Readline.readline(">", true) # true means: keep history.

 exit if buf.strip == "quit"
 p buf

end</lang>