Welcome!

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

SignUp Now!

Can PRE_EXEC refer to the about_to_be_executed command line?

May
12,845
164
I got the bright idea of using the PRE_EXEC alias to replace the current command line with itself followed by a timestamp, something like this (with some paraphrasing)

Code:
 alias pre_exec `echo cursor_up clear_to_EOL %cmdline2 (%_date %_time)`

That was disastrous. TCC went into a variable loop (I'm guessing) using 100% of one processor. To make matters worse, since I use global aliases, all subsequent TCCs did the same thing. With several such TCCs running, and my computer's fans screaming, I used TaskMgr to kill tham all. But that left the alias intact. I finally got out of that situation by using Explorer's Run dialog to execute tcc.exe /c unalias pre_exec. Fortunately that worked.

I can put the timestamp on the line following the command line, not referring to %cmdline2 and leaving the command line visible but I'm still wondering if I can accomplish my original goal of appending a timestamp to the command line. Any ideas?
 
I can put the timestamp on the line following the command line, not referring to %cmdline2 and leaving the command line visible but I'm still wondering if I can accomplish my original goal of appending a timestamp to the command line. Any ideas?

Plugin, hook the Enter key?
 
Plugin, hook the Enter key?
I asked myself the same question. I haven't tried it yet. I'll let you know if/when I do. I'm thinking that the keyhandler ought to be able to figure out if there's enough room for the timestamp to be placed after the command, on the same line, without wrapping, and, if not, put it on the next line.
 
I'm thinking that the keyhandler ought to be able to figure out if there's enough room for the timestamp to be placed after the command, on the same line, without wrapping, and, if not, put it on the next line.
A question, @Charles Dye. If I put the timestamp on the next line, then I want command output to start on the line after that (not where TCC intended to start the output). I can't seem to change where TCC starts the output. I've only tried incrementing KEYINFO::nRow and that didn't work. Do you think it can be done? Have you got any idea how?
 
I've never tried exactly what you're doing. But at a guess, you may need to update both nHomeRow and nRow.
 
I've never tried exactly what you're doing. But at a guess, you may need to update both nHomeRow and nRow.
Darn good guess! That worked.

1680664971850.png
 
Darn good guess! That worked.

View attachment 3873

Out of curiosity, how are you displaying that time? TCC internal functions like Printf() and QPuts() ?

I found this comment in CMath:

Code:
    if ( !Evaluate( Value ) ) {
        Printf( L"%s = ", Exp );
        SetColors( Highlight );
        QPuts( Value );
        SetColors( OldColor );
        CrLf();
        SetEnvironmentVariable( VarName, Value );
    }

Exit:

    //        Update the cursor position so the next prompt doesn't overwrite our output.
    //        Current versions of TCC don't need this, but TCC/LE does.

    GetAbsCursorPosition( &Row, &Column );
    ki->nHomeRow        = Row;
    ki->nHomeColumn        = 0;
    ki->nRow            = Row;
    ki->nColumn            = 0;
    return 0;
}

Perhaps I was mistaken about TCC?
 
Out of curiosity, how are you displaying that time? TCC internal functions like Printf() and QPuts() ?
The code is below but this will take some explaining. I did this in my 4WT plugin (for WindowsTerminal). Experience tells me that it'll work in a console with minimal (if any) changes. The actual output is buried in a CURSOR plugin command which does movement with VT sequences and writes, ultimately, with WriteConsole() (to a CONOUT$ handle). A CON structure wraps up handles to CONOUT$ and CONIN$, getting screen buffer properties, and WriteConsole(). That lets me avoid opening/closing handles; they're created by instantiating a CON and closed automatically when the CON goes out of scope. I'm only using it here to get the screen buffer info; CURSOR uses its own CON struct. I'll probably polish this for a while but I'm losing interest since, as often as I want such timestamps, PRE_EXEC/POST_EXEC are sufficient (and easy to turn on/off). I have also implemented the CURSOR command and the CON struct in my 4CONSOLE plugin.

Code:
    else if ( !lstrcmp(lpki->pszKey, L"Enter") && lpki->nColumn != lpki->nHomeColumn ) // command line not empty
    {
        WCHAR szTimeStamp[64] = L"\x1B[32;1m\"[%_DATE %_T9]\"\x1B[0m", szCursorCommandLine[128];
        ExpandVariables(szTimeStamp, 0);
        CON con;
        if ( con.csbi.dwSize.X - lpki->nColumn > 22 )
            // SAVE/RESTORE may not be necessary
            wsprintf(szCursorCommandLine, L"SAVE TO %d %d WRITES %s RESTORE", lpki->nRow + 1, lpki->nColumn + 2, szTimeStamp);
        else
        {
            wsprintf(szCursorCommandLine, L"TO %d %d WRITES %s", lpki->nRow + 2, 0, szTimeStamp);
            lpki->nHomeRow += 1;
            lpki->nRow += 1;
        }
        CURSOR(szCursorCommandLine);
    }
 
So basically, quite different from what I was doing. Guess it's no surprise if we're seeing slightly different behaviors.
 
It's simpler in a console. This works well. I could take 4 lines out of it since my keyhandler already instantiates a CON struct upon entry; that'll give me a filled in CONSOLESCREENBUFFERINFO and an autoopen/autoclose hOut. I'm using a plugin _T9 (HH:MM:SS.TTT); Does TCC have such a built-in?

Code:
    else if ( !lstrcmp(lpki->pszKey, L"Enter") && (lpki->nColumn != lpki->nHomeColumn || lpki->nRow != lpki->nHomeRow) ) // command line not empty
    {
        WCHAR szTimeStamp[64] = L"\x1B[32;1m[%_DATE %_T9]\x1B[0m";
        ExpandVariables(szTimeStamp, 0);

        HANDLE hOut = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        GetConsoleScreenBufferInfo(hOut, &csbi);

        if ( csbi.dwSize.X - lpki->nColumn > 25 ) // enough room on the same line as the command
        {
            SetConsoleCursorPosition(hOut, {(SHORT)(csbi.dwCursorPosition.X + 1), csbi.dwCursorPosition.Y}); // preceding space
        }
        else // put timestamp on the next line
        {
            lpki->nHomeRow += 1;
            lpki->nRow += 1;
            WriteConsoleW(hOut, L"\r\n", 2, nullptr, nullptr);
        }

        WriteConsoleW(hOut, szTimeStamp, lstrlenW(szTimeStamp), nullptr, nullptr);
        CloseHandle(hOut);
    }
 
I can put the timestamp on the line following the command line, not referring to %cmdline2 and leaving the command line visible but I'm still wondering if I can accomplish my original goal of appending a timestamp to the command line. Any ideas?

Hey @vefatica,
I'm doing what you want, except that the timestamp is hard-coded for column 40.

How can I get the length of the prompt, and the length of the command, and arguments, that have been entered?

Code:
E:\Utils>alias pre_exec                 [2023-04-07 07:45:28]
screen %@dec[%_row] 40 [%_date %_time] & echo.

E:\Utils>echo foo                       [2023-04-07 07:45:33]
foo

E:\Utils>echo this is a test            [2023-04-07 07:45:38]
this is a test

Added: Maybe just right-align the timestamp;
Code:
alias pre_exec=`screen %@dec[%_row] %@eval[%_columns-24] [%_date %_time] & echo.`

Joe
 
Last edited:
Added: Maybe just right-align the timestamp;
Trouble if the command line ends too near the right end ... yes/no?
How can I get the length of the prompt, and the length of the command, and arguments, that have been entered?
I don't think that info is available outside a plugin keyhandler. I imagine a plugin keyhandler, instead of doing the timestamp itself, could export internal _variables containing the desired info (specifically for use in PRE_EXEC). See the help, Reference\Plugins, for the declaration of a KEYINFO struct. That's the info which is passed to a keyhandler function. I reckon you'd only need something like _CMDLINEENDCOLUMN (= KEYINFO::nColumn) and you'd only have to update it when the key is "Enter".

Note that you can't even look at %CMDLINE2 (unexpanded current command) in PRE_EXEC without causing a variable loop.

I suppose you can get the length of the prompt if you know what's in it. For example, the length of my prompt ($e[91m$p$g$s$e[0m) is %@eval[%@len[%_cwd] + 2]. But that won't help without more info.

I hope I've missed something.

On another topic ... I've been playing with automatic timing of commands. This works pretty well.

1680883862473.png
 
Hey @vefatica,
Here's a .BTM that I have called pre_exec.btm
Code:
@setlocal
@echo off
::
:: alias pre_exec=e:\utils\pre_exec.btm
::
:: Disable Redirection (affects < , >, >&, >&>, etc.)
::
setdos /X-6
::
:: 80 is the maximum length to read from the screen
::
set theLine=%@readscr[%@dec[%_row],0,80]
::
:: Get the actual length of the line read from the screen
::
set theLineLength=%@len[%theLine]
::
:: Enable Redirection (affects < , >, >&, >&>, etc.)
::
setdos /X+6
::
:: Display the date time stamp to the right of the prompt/command line
::
screen %@dec[%_row] %@eval[%theLineLength + 1] [%_date %_time] & echo.
endlocal

I then create an alias;
Code:
alias pre_exec=e:\utils\pre_exec.btm
...which results in the following;
Code:
E:\Utils>echo foo 1 2 3 [2023-04-08 20:36:10]
foo 1 2 3

E:\Utils>echo foo 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80[2023-04-08 20:36:21]
foo 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80

E:\Utils>echo foo 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80[2023-04-08 20:36:29]
foo 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 81

You can adjust the length of the prompt/command line to read.

I have it set at 80.

Instead of a .BTM, this could be made into a Library Function.

Would appreciate you sharing any improvements that you make.

Joe
 
As for how much to read, I did this.

Code:
set theLine=%@readscr[%@dec[%_row],0,%_columns]

It does wrap if the command line ends too close to the right edge. To avoid that, and if you don't mind possibly overwriting part of the command, you could ... (21 is the length of the timestamp)

Code:
screen %@dec[%_row] %@min[%@eval[%theLineLength + 1],%@eval[%_columns - 21]] [%_date %_time] & echo.

You could always write it at column %@eval[%_columns - 21]. IMHO, that looks a little tidier. It might still overwrite some of the command.

Code:
screen %@dec[%_row] %@eval[%_columns - 21] [%_date %_time] & echo.

Since you have the length, you could figure out if there's enough room at the end (@EVAL[%_columns - %theLineLength] GE 21) and if not, put it on the next line.
 
I have this now.

Code:
@setlocal
@echo off
setdos /X-6
set theLine=%@readscr[%@dec[%_row],0,%_columns]
set theLineLength=%@len[%theLine]
setdos /X+6
if %@eval[%_columns - %theLineLength] LT 21 echo.
screen %@dec[%_row] %@eval[%_columns - 21] [%_date %_time] & echo.
endlocal

It looks OK.

1681003880127.png
 
Here's the latest. It seems to work well. Please give it a try.

Code:
@setlocal
@echo off

:: added /X-4 because %variables in %theLine screw up @len[%theLine] below
:: added /X-8 because escape sequences (e.g., ^q becoming ") could do the same
:: added /X-5 because & (and friends) do the same
:: added /X-7 because ` does the same
:: added /X-1 just for good measure
setdos /X-145678

set theRow=%@dec[%_row]
set theLine=%@readscr[%theRow,0,%_columns]
set theLineLength=%@len[%theLine]

setdos /X+145678

:: if there's not enough room, we're already on the empty line (%_row) after the command
iff %@eval[%_columns - %theLineLength] LT 22 then
    set theRow=%_row
    set theColumn=0
:: else enough room; the command still ends on %theRow; just move 1 space to the right
else
    set theColumn=%@inc[%theLineLength]
endiff

screen %theRow %theColumn [%_date %_time]
echo.

endlocal
 
You were quite "prompt" with your enhancements.

Your bright idea of March 10 works quite well.

Joe
 
@READSCR made the difference (thanks!). I must have known about it at one time because I've commented on it in the forum (years ago) but I don't think any memory of it would have surfaced on its own.

Do you use WindowsTerminal? The BTM fails there, always writing the timestamp on the next line. I'm hoping that the only reason for that is that in WT, rows and columns are 1-based (vs. 0-based in a console). I'll find out soon enough.
 
No problems in Windows Terminal (so far);

Code:
E:\Utils>echo %_isodate [2023-04-09 12:32:01]
2023-04-09

E:\Utils>echo Test | more [2023-04-09 12:32:13]
Test


E:\Utils>echo Test > clip9: && type clip9: [2023-04-09 12:32:27]
Test

E:\Utils>ver [2023-04-09 12:32:37]

TCC  29.00.17 x64   Windows 10 [Version 10.0.19044.2728]

E:\Utils>alias pre_exec [2023-04-09 12:33:38]
e:\utils\pre_exec.btm

This is the version of Windows Terminal that I am using;

1681058167780.png


Joe
 
This is the version of Windows Terminal that I am using;

Hmmm! I have both
1681058689321.png

and
1681058721153.png

They've changed the versioning (it's still ugly).
With either, using the latest BTM I posted, I see this.
1681059183939.png

I don't know whose WT is newer. Mine come from these downloads. I'll look for newer ones.
Code:
Microsoft.WindowsTerminalPreview_Win10_1.17.10234.0_8wekyb3d8bbwe.msixbundle
Microsoft.WindowsTerminal_Win10_1.16.10231.0_8wekyb3d8bbwe.msixbundle
 
So I installed these.
Code:
Microsoft.WindowsTerminalPreview_Win10_1.17.10234.0_8wekyb3d8bbwe.msixbundle
Microsoft.WindowsTerminal_Win10_1.16.10261.0_8wekyb3d8bbwe.msixbundle

The second one looks like yours. But I see
1681060282515.png

That's a difference between an installer install and an unpackaged install. I actually have this.
Code:
v:\> grep -E "Semantic|Store" d:\wt\exe\BuildInfo.xml
           SemanticVersion="1.16.230126001"
           StoreVersion="1.16.10261.0"

My BTM still misbehaves. Which BTM are you using?
 
My version of Windows Terminal is installed/upgraded from the Microsoft Store.

The last modified date for Windows Terminal on my system is;

1681060956396.png


According to the Github page, the latest public releases are v1.16.1026 (1.16.10261.0 and 1.16.10262.0), which is what I am using.

Joe
 
My buildinfo.xml contains;
Code:
<BuildInfo BinaryVersion="1.16.2301.26001"
           SemanticVersion="1.16.230126001"
           StoreVersion="1.16.10261.0"
           BuildDefId="unknown"/>

Code:
C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.16.10261.0_x64__8wekyb3d8bbwe\buildinfo.xml

Joe
 
So now we have the same version of WT. And I'm also using the BTM from post #17. The BTM works OK without my 4WT plugin. That's because the plugin exports _ROW and _COLUMN (1-based). I think I wanted those for use in VT control sequences. I probably shouldn't have used the same names at TCC.
 
I probably shouldn't have used the same names at TCC.
You could always prefix the variable with the plugin name. Example;
Code:
E:\Utils>echo %_4console$wincols [2023-04-09 16:09:32]
112

Joe
 
You could always prefix the variable with the plugin name. Example;
Code:
E:\Utils>echo %_4console$wincols [2023-04-09 16:09:32]
112

Joe
Yeah but I needed to go the other way (get the built-in _ROWS). Can that be done? I got rid of the plugin's _ROWS and _COLUMNS; I don't think I was using them. I made some changes to make it easy to change the timestamp and add color. I have this now.

Code:
@setlocal
@echo off

:: best to get the time ASAP; see the IFF and SCREEN below
set timestamp=%@char[0x25B8] %_time
set color=^e[32;1m

:: added /X-4 because %variables in %theLine screw up @len[%theLine] below
:: added /X-8 because escape sequences (e.g., ^q becoming ") could do the same
:: added /X-5 because & (and friends) do the same
:: added /X-7 because ` does the same
:: added /X-1 just for good measure
setdos /X-145678

set theRow=%@dec[%_row]
set theLine=%@readscr[%theRow,0,%_columns]
set theLineLength=%@len[%theLine]

setdos /X+145678

:: if there's not enough room, we're already on the empty line after the command
iff %@eval[%_columns - %theLineLength] LT %@inc[%@len[%timestamp]] then
    set theRow=%_row
    set theColumn=0
:: else the command still ends on %theRow; just move 1 space to the right
else
    set theColumn=%@inc[%theLineLength]
endiff

screen %theRow %theColumn %[color]%[timestamp]^e[0m
echo.

endlocal

1681072032077.png
 
Back
Top