Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

Call for EditKeys suggestions

Charles Dye

Super Moderator
May
4,948
126
Staff member
What features do you think are missing from TCC's command-line editor? What do you want to do with a keystroke that you can't do with a key alias?
 
Hey @Charles Dye
Many, many years ago, you provided a solution that would allow a Function Key With INPUT

It would be great, however, if there was a way to have an ON KEY LABEL command like Visual FoxPro has.

This would allow, while at the INPUT command, the ability to press a hot key to popup a help window, view the contents of a file, etc., and then pop back down to complete the INPUT command.

Here's example code from one of my Visual FoxPro programs;
Code:
ON KEY LABEL F1 DO Show_Acct
ON KEY LABEL F4 DO Zoom     
ON KEY LABEL F6 DO Report   
EDIT NOORGANIZE
ON KEY LABEL F1
ON KEY LABEL F4             
ON KEY LABEL F6

Translated to TCC;
Code:
ON KEY LABEL F1 DO Show_Acct
ON KEY LABEL F4 DO Zoom     
ON KEY LABEL F6 DO Report   
input /k"[0-9]-()" Enter your phone number: %%var
ON KEY LABEL F1
ON KEY LABEL F4             
ON KEY LABEL F6

While in INPUT, waiting for the phone number to be entered, I would be able to press;
  • the F1 key and run the Show_Acct procedure or
  • the F4 key and run the Zoom procedure or
  • the F6 key and run the Report procedure
Note well that the F1, F4 and F6 key would not be usable from the INPUT command, as they are hooked already, prior to the INPUT command being issued.

After the INPUT has been completed by pressing the ENTER key, I would then un-hook the F1, F4, and F6 functions keys from my User Defined procedures.

Anyway, this is something that would be useful to me.

Joe
 
Hmmm! I'm not sure it'll give what you want, but 4UTILS has _INKEY.

Code:
v:\> help _inkey

waiting keystroke or 0 if none

Maybe you could roll your own using _INKEY.

Using this BTM

Code:
unset string
do forever
    set k=%_inkey
    switch %k
        case 0
            delay /m 1
        case @13
            leave
        case @59
            echo you pressed F1
        case @60
            echo you pressed F2
        default
            set string=%[string]%k
    endswitch
enddo
echo you entered %string

and entering 1 ... 2 ... 3 ... F1 ... F2 ... 4 ... 5 ... 6 ... Enter, I got

Code:
v:\> swinkey.btm
you pressed F1
you pressed F2
you entered 123456

Note: Even looping that fast, TCC doesn't use 1% CPU (a bit to my surprize).
 
Translated to TCC;
Code:
ON KEY LABEL F1 DO Show_Acct
ON KEY LABEL F4 DO Zoom    
ON KEY LABEL F6 DO Report  
input /k"[0-9]-()" Enter your phone number: %%var
ON KEY LABEL F1
ON KEY LABEL F4            
ON KEY LABEL F6

I think that adding triggers to ON would have to be done in TCC, i.e. by Rex. I don't think I could do it in a plugin; if it's possible at all, it would be awfully kludgey. Sorry....
 
I used to have a key alias tied to Ctrl-L that would clear the screen (executing cls), as bash and most other CLIs in Unix do, but I ended up taking it out because it did not work if something had been typed into the command line already (producing stuff like "dir *.txt@cls").

It'd be nice to copy the Ctrl-L feature from the Unix CLI.
 
I used to have a key alias tied to Ctrl-L that would clear the screen (executing cls), as bash and most other CLIs in Unix do, but I ended up taking it out because it did not work if something had been typed into the command line already (producing stuff like "dir *.txt@cls").

It'd be nice to copy the Ctrl-L feature from the Unix CLI.

I actually have that one already.

I tried a couple of different approaches to save the command line and clear the screen before it dawned on me: The smart way to do this is not to clear the screen at all, but just scroll it so the prompt is at the top.
 
Does that work if you've done a cls/c right before? (also, does it work with prompt lines taller than one line?)

For the first question, I'm not sure what 'work' means in this case. The buffer can't be emptier than it already is after CLS /C.

As for the multiline prompt, in this case all but the last line is scrolled out view. TCC reports the line where text entry begins, which is usually the last line of the prompt text. If there are multiple lines, I don't think there is any good way of distinguishing the prompt text from any other text which happens to be in the screen buffer.
 
As for the multiline prompt, in this case all but the last line is scrolled out view. TCC reports the line where text entry begins, which is usually the last line of the prompt text. If there are multiple lines, I don't think there is any good way of distinguishing the prompt text from any other text which happens to be in the screen buffer.
The only way I can think of to determine where the last prompt was issued is to use the POST_EXEC alias, which (I think) can be implemented in a plugin. Charles, can you think of another way?
 
The only way I can think of to determine where the last prompt was issued is to use the POST_EXEC alias, which (I think) can be implemented in a plugin. Charles, can you think of another way?
Kind of a kludge, but easy to implement: Get the value of %PROMPT, and count instances of $_ .
 
Kind of a kludge, but easy to implement: Get the value of %PROMPT, and count instances of $_ .
There's wrapping too. There's enough stuff that can be put in the prompt to make it use many lines even without $_.

In a plugin I tried these; it worked pretty well. THe struct g holds globally available variables.

Code:
INT WINAPI POST_EXEC (LPWSTR psz)
{
    if ( g.hConOut != INVALID_HANDLE_VALUE )
    {
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        GetConsoleScreenBufferInfo(g.hConOut, &csbi);
        g.nLastPromptRow = csbi.dwCursorPosition.Y + 1;
    }
    return 0;
}

Code:
    else if ( !lstrcmp(lpki->pszKey, L"Ctrl-L") )
    {
        if ( g.hConOut != INVALID_HANDLE_VALUE )
        {
            CONSOLE_SCREEN_BUFFER_INFO csbi;
            GetConsoleScreenBufferInfo(g.hConOut, &csbi);
            INT nLinesToScroll = g.nLastPromptRow - csbi.srWindow.Top - 2;
            while ( nLinesToScroll-- >= 0 )
            {
                SendMessage(g.hWndConsole, WM_VSCROLL, SB_LINEDOWN, NULL);
            }
        }
        lpki->pszKey[0] = 0;
    }
 
Last edited:
I used to have a key alias tied to Ctrl-L that would clear the screen (executing cls), as bash and most other CLIs in Unix do, but I ended up taking it out because it did not work if something had been typed into the command line already (producing stuff like "dir *.txt@cls").

It'd be nice to copy the Ctrl-L feature from the Unix CLI.
You can get rid of the error mentioned above by prefixing the alias with '^e". That will erase the command line in progress but it won't restore it.

Code:
v:\> alias @@ctrl-l
^e@cls /s
 
Wow @vefatica, thank you for that great tip!

I was going to ask if that was documented anywhere, but it is indeed documented in the help file;

ALIAS command - create new command names
To insure that a keystroke alias, esp. an autoexecute one, is on the command line by itself, use the character defined by the EraseLine key directive option (by default, the Esc key, represented as ^e) as the first character of the alias value.

Joe
 
@Charles Dye, you mentioned experimenting a bit with restoring a command line in progress. Did you have any success and, if so, can you give me any tips?

FWIW, barehandedly scrolling the console (as I did way above) isn't necessary. Issuing ^e[2J^e[H will do it (not exactly in keeping with what the docs say).

@Joe Caverly, I don't know how far back that quote from the help goes but I've had <ESC>exit<Return> hardware-assigned to a key for as long as I can remember.
 
@Charles Dye, you mentioned experimenting a bit with restoring a command line in progress. Did you have any success and, if so, can you give me any tips?

In short:
Save the prompt text: ReadConsoleOutputCharacter() and ReadConsoleOutputAttribute()
Clear the screen: FillConsoleOutputCharacter(), FillConsoleOutputAttribute(), SetConsoleCursorPosition() (or however you like)
Restore the prompt: WriteConsoleOutputCharacter() and WriteConsoleOutputAttribute()
Update TCC vars: ki->nRow, ki->nHomeRow, ki->fRedraw = 1

(I avoid ReadConsoleOutput() because I've found that sometimes it lies.)

All this was assuming a single-line prompt. It could be extended to multiple lines if you know how many.

FWIW, barehandedly scrolling the console (as I did way above) isn't necessary. Issuing ^e[2J^e[H will do it (not exactly in keeping with what the docs say).

I don't want to assume ANSI. But if you know it will always be there for you, then sure.
 
Yipes! Scrolling the console with SendMessage is a lot easier; it preserves anything in progress. The only problem with that is knowing the row on which the prompt begins. I decided my POST_EXEC alias was OTT. Idea ... I wonder if a static variable in the keyhandler itself could keep track of where the last prompt was issued (the keyhandler gets nKey = 0 and pszKey = NULL (or maybe it's "") when the prompt is issued.
 
Yipes! Scrolling the console with SendMessage is a lot easier; it preserves anything in progress. The only problem with that is knowing the row on which the prompt begins. I decided my POST_EXEC alias was OTT. Idea ... I wonder if a static variable in the keyhandler itself could keep track of where the last prompt was issued (the keyhandler gets nKey = 0 and pszKey = NULL (or maybe it's "") when the prompt is issued.

OTT ?
 
My Idea worked. It moved the whole multiline prompt and left the command_line_in_progress intact. Below, a CONOUT is a struct which encapsulated a HANDLE to "CONOUT$" and GetConsoleScreenBufferInfo ... handy because that HANDLE is automatically closed whem the CONOUT goes out of scope. And SCROLL is in the same plugin; it just does SendMessage(hWndConsole, WM_SCROLL,...) the right number of times. I don't know how foolproof this is.

Code:
INT WINAPI KEYHANDLER( LPKEYINFO lpki )
{
    CONOUT con;
    static INT nLastPromptRow;
    if ( lpki->pszKey == nullptr && lpki->nKey == 0 )
    {
        con.GetInfo();
        nLastPromptRow = con.csbi.dwCursorPosition.Y;
    }

Code:
    else if ( !lstrcmp(lpki->pszKey, L"Ctrl-L") )
    {
        con.GetInfo();
        INT nRowsToScroll = nLastPromptRow - con.csbi.srWindow.Top - 1;
        WCHAR sz[32];
        wsprintf(sz, L"SCROLL v+%d", nRowsToScroll);
        Command(sz, 0);
        lpki->pszKey[0] = 0;
    }
 
I don't know why the forum wouldn't let me post these pics in the last post. Before pressing Ctrl-L.

1656696589354.png


After pressing Ctrl-L and completing the command.
1656696643564.png
 
Code:
INT WINAPI KEYHANDLER( LPKEYINFO lpki )
{
    CONOUT con;
    static INT nLastPromptRow;
    if ( lpki->pszKey == nullptr && lpki->nKey == 0 )
    {
        con.GetInfo();
        nLastPromptRow = con.csbi.dwCursorPosition.Y

    }

Alas, that won't do it. The 0,0 keystroke comes after the prompt has been written. So you'll get the row of the last line of the prompt.
 
Here is my attempt at emulating the ON KEY LABEL functionality of Visual FoxPro.
(Ref: Call for EditKeys suggestions)

It requires an updated version of the 4Utils plugin
(Ref: _Inkey from 4UTILS)

For the calendar, I am using qcal from the ISO8601 Plugin
(Ref: ISO8601 Plugin)

Note that this is example code only, and not a finished product.
Many things can be improved upon.

I have created my own EditLine .BTM that allows me to trap any and all keys during Data Entry.

For example:
Code:
e:\utils>editline.btm
This_is_a_test

I can press the F4 key, and draw a box just above my Edit Line.
Code:
┌───────────────────────────────┐
│                               │
│                               │
│                               │
│                               │
│                               │
└───────────────────────────────┘
F4\utils>editline.btm
This_is_a_test_

I can press the F5 key, and draw a calendar just above my Edit Line.
Code:
     July 2022

Su Mo Tu We Th Fr Sa
                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
31
F5\utils>editline.btm
This_is_a_test_

Future modifications include setting the maximum number of characters to which EditLine will accept.

I might also attempt to develop a PLUGIN for EditLine, but that's along ways off.
(I would not be offended if other PLUGIN developers wanted to do this).

EditLine.btm is doing what I need, but there are tweaks and addtions that can be made.

Feel free to make your own modifications to EditLine, but please share any modifications that you make.

Joe
Code:
:: _inkey is from 4UTILS plugin
:: Ref: https://jpsoft.com/forums/threads/_inkey-from-4utils.11225/post-63721
::
@setlocal
@echo off
set MaxInput=15

if defined theInput unset theInput

set InputRow=%_row
set InputColumn=0

:: I use Sysinternals DebugView to capture debugstring output.
:: (Ref: https://docs.microsoft.com/en-us/sysinternals/downloads/debugview)
debugstring %InputRow %InputColumn

Gosub EraseLine

do forever
  set theKey=%_inkey
  switch %theKey
  case 0
    delay /m 100
  case @8
    Gosub Backspace
    case @9
      ::Tab
  case @13
      set theInput=%@replace[_, ,%theInput]
    leave
    case @27
      Gosub EraseInput
  case @32
    Gosub Spacebar
  case @62
    Gosub F4
  case @63
      Gosub F5
    case @71
        ::Home
    case @72
        ::Up Arrow
    case @73
        ::PageUp
    case @75
        ::Left Arrow
    case @77
        ::Right Arrow
    case @79
        ::End
    case @80
        ::Down Arrow
    case @81
        ::Page Down
    case @83
      ::Delete
    default
      set theInput=%theInput``%theKey
      Gosub DisplayInput
  endswitch
enddo
endlocal theInput
quit

:Backspace
  set theLen=%@len[%theInput]
    set theLen=%@dec[%theLen]
    set theInput=%@left[%theLen,%theInput]
    Gosub EraseLine
    Gosub DisplayInput
Return

:DisplayInput
    screen %InputRow %InputColumn %theInput
Return

:DrawBox
  set bl=%@eval[%InputRow-2]
    set tl=%@eval[%bl-6]
    set tr=%@eval[%tl+6]
    set br=%@eval[(%InputRow-2)+6]
    debugstring bl=%bl  tl=%tl  tr=%tr  br=%br
    drawbox %tl 0 %bl %br 1 bri whi on blu fill blu
Return

:EraseInput
  set theInput=``
    Gosub EraseLine
Return

:EraseLine
  screen %InputRow %InputColumn %@repeat[%@char[95],%MaxInput]
    screen %InputRow %InputColumn``_
Return

:F4
  set PrevRow=%_row
    set PrevColumn=%_column
    scrput %@eval[%InputRow-1] %InputColumn bri whi on blu F4
    Gosub DrawBox
    screen %PrevRow %PrevColumn``
Return

:F5
  set PrevRow=%_row
    set PrevColumn=%_column
    scrput %@eval[%InputRow-1] %InputColumn bri whi on blu F5
    screen %@eval[%InputRow-11] %InputColumn
  :: qcal is from the ISO8601 Plugin
    :: http://prospero.unm.edu/plugins/iso8601.html
    qcal
    screen %PrevRow %PrevColumn``
Return

:Spacebar
  set theInput=%theInput`_`
    Gosub DisplayInput
Return
 
Back
Top