Move cursor to *any position* using just ECHO command

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
Aacini
Expert
Posts: 1611
Joined: 06 Dec 2011 22:15
Location: México City, México

Move cursor to *any position* using just ECHO command

#1 Post by Aacini » 26 May 2017 22:46

This topic is based on a strange behavior (I would call it a bug) that happen when TAB (ASCII char 9 ) is combined with BS (ASCII char 8 ) in ECHO or SET /P commands. This behavior was first reported by user neorobin at this topic, although he didn't explained it.

When a TAB character is displayed in the screen, the Windows screen driver translates it to a series of blank spaces (usually 8 ), so in this case the TAB is destructive: any text previously displayed in the screen is overwritten by the spaces when the TAB is displayed.

The number of spaces vary accordingly to the position of the cursor when the TAB is displayed, so the final cusor position after the spaces is always multiple of 8. For example, if the cursor is at first column in a line, a TAB is displayed as 8 spaces, but if the cursor is at column 1, the TAB is displayed as 7 spaces, etc.

A behavior that may seems logical at first is that a BS character displayed after the TAB move the cursor back the same number of spaces displayed by the TAB, that is, the BS deletes the action of the TAB. What IMHO is a bug is that another BS displayed after the first one also move the cursor back the same number of spaces, and, if the cursor reach the left margin of the screen, the cursor wrap-around to end of the previous screen line!

For example, if the cursor is at first column in a line, a sequence of <TAB><BS><BS> characters move the cursor one line above and 7 columns at left of the right screen margin. If the screen have 80 characters wide, each additional group of 10 BS characters moves the cursor one line up more. After that, each additional BS character move the cursor 8 characters more to left in the same line. Finally, if another single standard character is displayed, then the following BS characters will move the cursor just one character to the left.

In this way, the cursor can be placed at any previous line and column in the screen by just calculating the proper number of BS characters to show after a TAB one, and after an additional standard char. If the desired position is below the current cursor position, just first place the cursor at the bottom of the screen via several ECHO/ commands, and after that move the cursor up and left to the desired location with this method.

The Batch file below is an example of previous procedure. The calculation of the number of BS characters and the ECHO command is really simple and can be added to any program in a very simple way, but this program is large and complex because it display a screen full of coordinates and allows you to enter the position of several marks, that will be displayed in the screen in the proper positions.

EDIT 2017-05-28: I added the method to correctly get a TAB character in both Windows XP and newer versions.

Code: Select all

@echo off
setlocal EnableDelayedExpansion

echo Enter coordinates in line,col order to move the cursor and show a mark
echo/
echo The valid ranges are:   0 ^<= line ^<= 30   and   0 ^<= column ^<= 71;
echo if a value is out of range, the bell will ring.
echo/
pause

rem Get a BEL, BS and a TAB characters
set "BEL="
for /F %%a in ('echo prompt $H ^| cmd') do set "BS=%%a"
set "TAB="
rem First, try the method for Windows XP
for /F "skip=4 delims=pR tokens=2" %%a in ('reg query hkcu\environment /v temp' ) do set "TAB=%%a"
rem Then, the method for newer versions
rem http://www.dostips.com/forum/viewtopic.php?f=3&t=1733&p=6840#p6853
for /F "tokens=2 delims=0" %%a in ('shutdown /? ^| findstr /BC:E') do if not defined TAB set "TAB=%%a"

rem Get a string with 162 BS characters, the maximum used in this program
set "BSs="
for /L %%i in (1,1,162) do set "BSs=!BSs!!BS!"

mode 80,32
set "line1="
for /L %%i in (1,1,8) do set "line1=!line1!0123456789"
set "line2="
for /L %%i in (1,1,8) do set "line2=!line2!^!l^!         "
set "line2=!line2:~0,-1!^!l^!"
set "line3="
for /L %%i in (1,1,8) do set "line3=!line3!^!l^! 2 4 6 8 "
set "line3=!line3:~0,-1!^!l^!"
set "twoORthree=23"
< NUL (
   for /L %%a in (1,1,3) do (
      set /P "=%line1%" & set "l=1"
      set /P "=%line2%" & set /A "l+=1"
      for /L %%b in (1,1,4) do (
         if %%a neq 2 (
            set /P "=%line3%"
         ) else if "!twoORthree:%%b=!" equ "%twoORthree%" (
            set /P "=%line3%"
         ) else (
            echo           %line3:~0,-15%
         )
         set /A "l+=1"
         set /P "=%line2%" & set /A "l+=1"
      )
   )
   set /P "=%line1:~0,-1%"
)

rem Initialize cursor position at middle of screen, to read input there
echo 9%TAB%!BSs!
set "currLine=16"

set "letters=_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
set "num=1"

:loop
set "position="
set /P "position=Enter line,col for mark !letters:~%num%,1!:      !BSs:~0,5!"
if not defined position goto end

for /F "tokens=1,2 delims=, " %%x in ("%position%") do set /A y=%%x, x=%%y, error=1
if 0 leq %y% if %y% leq 30  if 0 leq %x% if %x% leq 71  set "error="
if defined error (
   echo %TAB%!BSs:~0,12!!BEL!
   goto loop
)

if %y% leq %currLine% (

   rem New position above: move cursor up and show point
   call :MoveCursorupAndShow "(currLine-y-1)" x "!letters:~%num%,1!(%y%,%x%)"

   rem And move cursor down, back to original input line
   set /A down=currLine-y-2, cntBSup+=10
   for /L %%i in (1,1,!down!) do echo/
   if !down! lss 0 for /F %%i in ("!cntBSup!") do echo %TAB%!BSs:~0,%%i!

) else (

   rem New position below: first, move cursor to last line
   for /L %%i in (1,1,15) do echo/
   rem Then, move cursor up and show point
   call :MoveCursorupAndShow "(30-y)" x "!letters:~%num%,1!(%y%,%x%)"

   rem And move cursor up, back to original input line, using the same method
   set /A down=30-y
   for /L %%i in (1,1,!down!) do echo/
   echo %TAB%!BSs!

)

set /A num+=1
if %num% leq 26 goto loop
:end
cls
goto :EOF



:MoveCursorupAndShow  Linesup  Column  "Text"
set /A "cntBSup = 2 + 10 * %~1 + (9-(%2+6)/8), cntBSleft = 8-(%2+6)%%8"
set "BSsLeft="
if %cntBSleft% gtr 0 set "BSsLeft=_!BSs:~0,%cntBSleft%!"
echo %TAB%!BSs:~0,%cntBSup%!!BSsLeft!%~3
exit /B

The image below is an output example of previous program after 8 marks were displayed at various positions.

Image

Tested on Windows 8.1

Antonio

einstein1969
Expert
Posts: 763
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Move cursor to *any position* using just ECHO command

#2 Post by einstein1969 » 27 May 2017 10:40

Good explain Antonio,

It's possible create a generic "goto 0,0"/HOME function? How?

Einstein1969

miskox
Posts: 330
Joined: 28 Jun 2010 03:46

Re: Move cursor to *any position* using just ECHO command

#3 Post by miskox » 28 May 2017 02:33

Great work!

Well, does not work for me. (XP PRO 32 bit)

First: 'Enter line' line is not in the middle of the screen:

Code: Select all

1         1         1         1         1         1         1         1        1
2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 82
3         3         3         3         3         3         3         3        3
4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 84
5         5         5         5         5         5         5         5        5
6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 86
7         7         7         7         7         7         7         7        7
8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 88
9         9         9         9         9         9         9         9        9
01234567890123456789012345678901234567890123456789012345678901234567890123456789
1         1         1         1         1         1         1         1        1
2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 82
3         3         3         3         3         3         3         3        3
          4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8
5         5         5         5         5         5         5         5        5
          6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8
7         7         7         7         7         7         7         7        7
8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 88
9         9         9         9         9         9         9         9        9
01234567890123456789012345678901234567890123456789012345678901234567890123456789
1         1         1         1         1         1         1         1        1
2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 8 2 2 4 6 82
3         3         3         3         3         3         3         3        3
4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 8 4 2 4 6 84
5         5         5         5         5         5         5         5        5
6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 86
7         7         7         7         7         7         7         7        7
8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 88
9         9         9         9         9         9         9         9        9
01234567890123456789012345678901234567890123456789012345678901234567890123456789

Enter line,col for mark A:


Second there is an error if I don't enter both values

Code: Select all

6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 8 6 2 4 6 86
7         7         7         7         7         7         7         7        7
8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 8 8 2 4 6 88
9         9         9         9         9         9         9         9        9
01234567890123456789012345678901234567890123456789012345678901234567890123456789

Enter line,col for mark A: 5
Missing operand.
'leq' is not recognized as an internal or external command,
operable program or batch file.
A(5,)









Enter line,col for mark B: 5,5
B(5,5)









Enter line,col for mark C:


As Einstein suggested: a 0,0/HOME version (the shortest) and another callable something like:

Code: Select all

call movecursor x y


Great work!
Saso

Aacini
Expert
Posts: 1611
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: Move cursor to *any position* using just ECHO command

#4 Post by Aacini » 28 May 2017 02:51

miskox wrote:Great work!

Well, does not work for me. (XP PRO 32 bit)

First: 'Enter line' line is not in the middle of the screen:

Code: Select all

snip


Second there is an error if I don't enter both values

Code: Select all

snip


. . .

Great work!
Saso


Yes. It seems that the method used to create a TAB character doesn't work in Win XP. You may change this line:

Code: Select all

for /F "tokens=2 delims=0" %%a in ('shutdown /? ^| findstr /BC:E') do if not defined TAB set "TAB=%%a"

... by this one:

Code: Select all

for /F "skip=4 delims=pR tokens=1,2" %%a in ('reg query hkcu\environment /v temp' ) do set "TAB=%%b"

... or just directly enter a TAB character in a SET "TAB=here" command.

EDIT 2017-05-28: I just added both methods in the original code in a way that it should correctly get a TAB in all Windows versions now.

This program is just an example of how to use this method to move the cursor, so it lacks some error checking.

Antonio

Aacini
Expert
Posts: 1611
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: Move cursor to *any position* using just ECHO command

#5 Post by Aacini » 28 May 2017 08:54

miskox wrote:As Einstein suggested: a 0,0/HOME version (the shortest) and another callable something like:

Code: Select all

call movecursor x y

. . . .


Well, from the description of how this method works, it should be clear that it can only be used to go to a point in the screen relative to another, known position, and the above program was written with the purpose of exemplify this point.

This means that a generic "movecursor x y" subroutine that may work in all cases is just not possible, unless you always know the current cursor position that is the case in animation/game programs.

However, you may send an enough number of BS's to the screen in order to assure that the cursor reaches screen home no matter where is placed, but in this case the additional BS's causes a strange "The system can not write on the specified device" error message. Fortunately, we may hide this error if we redirect Stderr to NUL, although the cursor don't ends at (0,0) position, but at column 1 of first line.

Code: Select all

@echo off
setlocal EnableDelayedExpansion

rem Get a BS and TAB characters
for /F %%a in ('echo prompt $H ^| cmd') do set "BS=%%a"
set "TAB="
for /F "skip=4 delims=pR tokens=2" %%a in ('reg query hkcu\environment /v temp' ) do set "TAB=%%a"
for /F "tokens=2 delims=0" %%a in ('shutdown /? ^| findstr /BC:E') do if not defined TAB set "TAB=%%a"

rem Get current sizes of screen buffer
set "lins="
set "cols="
for /F "tokens=2 delims=:" %%a in ('mode con') do (
   if not defined lins (
      set /A "lins=%%a"
   ) else if not defined cols (
      set /A "cols=%%a"
   )
)

rem Go to screen Home from any place
set /A "cntBS = 2 + (cols + 7) / 8 * lins"
set "BSs="
for /L %%i in (1,1,%cntBS%) do set "BSs=!BSs!!BS!"
2>NUL echo %TAB%!BSs!
set /P "=Now cursor is at line 0 col 1:"

Antonio

jeb
Expert
Posts: 914
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Move cursor to *any position* using just ECHO command

#6 Post by jeb » 28 May 2017 09:17

Fascinating!

I didn't recognized the originial thread before.
But it's so easy with the TAB character.
I'm astonished that nobody tries this before, many thankst to nerobin :!:

@Aacini
I tried your tab creation with reg.exe, but it doesn't work for me with XP32-German.

But I build a version with shutdown that works with Wi7x64 and also XP32

Code: Select all

@echo off
for /F "tokens=1 delims=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 " %%a in ('shutdown /?') do set "TAB=%%a"
set "tab=%tab:~0,1%"
echo -%tab%-

aGerman
Expert
Posts: 3669
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Move cursor to *any position* using just ECHO command

#7 Post by aGerman » 28 May 2017 10:26

As already mentioned in neorobin's thread this technique doesn't work on Win10 by default :(
You would have to turn on Legacy Console (Checkbox "Use Legacy Console" in the "Options" tab).

Steffen

aGerman
Expert
Posts: 3669
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Move cursor to *any position* using just ECHO command

#8 Post by aGerman » 28 May 2017 15:08

Ugly workaround :|

Code: Select all

@echo off
setlocal EnableDelayedExpansion

rem Determine if the new Windows 10 console window is used
set "is_new_console=0"
for /f "tokens=2 delims=[" %%i in ('ver') do for /f "tokens=2 delims=. " %%j in ("%%i") do (
  if %%j geq 10 for /f "tokens=3" %%k in ('2^>nul reg query "HKCU\Console" /v "ForceV2"') do set /a is_new_console=%%k
)
rem If new console was used
if %is_new_console%==1 (
  rem Change the related registry value
  >nul reg add "HKCU\Console" /t REG_DWORD /v "ForceV2" /d 0 /f
  rem Restart the script
  start cmd /c %~fs0
  rem Suspend the script execution for a moment
  >nul timeout /t 1 /nobreak
  rem Restore the registry value
  >nul reg add "HKCU\Console" /t REG_DWORD /v "ForceV2" /d 1 /f
  rem Quit the script
  goto :EOF
)

echo Enter coordinates in line,col order to move the cursor and show a mark
:: etc ...


Steffen

thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Re: Move cursor to *any position* using just ECHO command

#9 Post by thefeduke » 29 May 2017 14:34

Aacini wrote:This means that a generic "movecursor x y" subroutine that may work in all cases is just not possible, unless you always know the current cursor position that is the case in animation/game programs.

However, you may send an enough number of BS's to the screen in order to assure that the cursor reaches screen home no matter where is placed, but in this case the additional BS's causes a strange "The system can not write on the specified device" error message. Fortunately, we may hide this error if we redirect Stderr to NUL, although the cursor don't ends at (0,0) position, but at column 1 of first line.
Given the limitations of "cursor awareness", I could not resist attempting this:
Edited: wrote:My attempt is flawed, but I left the post instead of unsaying it. It was not immediately obvious to me until I tried to generalize the code for column widths other than 80. I did not take care to return the cursor to a known "safe" area and was leaving destructive blanks in random places. Antonio's demo has two such areas. The last line is blanks already and the prompt over-writes the other. Antonio's explanation was quite clear and means so much more to me after a careful reread. My apologies if I wasted your time.
Edit: I shall post again to highlight this change and add more comments.

Code: Select all

@echo off

Call :MoveCsrByEcho %*

Exit /B

:MoveCsrByEcho Xvalue Yvalue StringValue[opt]
@echo off
setlocal EnableDelayedExpansion

rem Get BS and TAB characters
for /F %%a in ('echo prompt $H ^| cmd') do set "BS=%%a"
set "TAB="
for /F "skip=4 delims=pR tokens=2" %%a in ('reg query hkcu\environment /v temp' ) do set "TAB=%%a"
for /F "tokens=2 delims=0" %%a in ('shutdown /? ^| findstr /BC:E') do if not defined TAB set "TAB=%%a"

rem Get current sizes of screen buffer
set "lins="
set "cols="
for /F "tokens=2 delims=:" %%a in ('mode con') do (
   if not defined lins (
      set /A "lins=%%a"
   ) else if not defined cols (
      set /A "cols=%%a"
   )
)

rem Get target coordinates and display string
Set "X=%~1"
If Not Defined X Set "X=0"
Set "Y=%~2"
If Not Defined Y Set "Y=0"
rem cannot access 0,0 with this technique - force 1,0
If /I "%Y%" EQU "0" IF /I "%X%" EQU "0" Set "X=1"
rem next tab for x=72 or more is 1 of next line and backspace cannot traverse
Set "X7=0%X%"
Set "X7=1%X7:~-2%"
If %X7%0 GTR 1710 Set "X=71"
Set "string=%~3"
If Not Defined string Set "string= "

rem Go to screen Home from any place
set /A "cntBS = 2 + (cols + 7) / 8 * lins"
set "BSs="
for /L %%i in (1,1,%cntBS%) do set "BSs=!BSs!!BS!"
2>NUL echo %TAB%!BSs!

rem Go one past desired Y
for /L %%i in (0,1,%Y%) do Echo.

rem backtrack to tab just after desired x
set /A "cntBS1 = (cols + 7 - X) / 8"
set /A "Xtab= 1 + cols + 8 "
set "BSs="
for /L %%i in (1,1,%cntBS1%) do (
    set "BSs=!BSs!!BS!"
    set /A "Xtab-=8"
)

rem break backtabbing with a space and backspace to desired x
Set /A "BSx= 1 + Xtab - X"
set "BSs=!BSs! "
for /L %%i in (1,1,%BSx%) do (
    set "BSs=!BSs!!BS!"
)

rem display a string at (x,y) - positioning does nothing without display string
If /I "%string%" EQU " " (
    echo %TAB%%BSs%%string%^<-[%X%,%Y%]
) Else (
rem _______________ Required for positioning
    echo %TAB%%BSs%%string%
rem Chosen string  ________
)

Exit /B
Here is some sample output:

Code: Select all

 Cannot go more left      <-[25,0]                                 <-[66,0]
        \Yanci\Documents\Scripts>movecsrbyecho 25 0
C:\Users\Yanci\Documents\Scripts>movecsrbyecho 66 0
C:\Users\Yanci\Documents\Scripts>movecsrbyecho "" 0 "Cannot go more left"
C:\Users\Yanci\Documents\Scripts>movecsrbyecho 48 5
                                                 <-[48,5]
 <-[0,6]
        \Yanci\Documents\Scripts>MoveCsrByEcho 0 6
C:\Users\Yanci\Documents\Scripts>MoveCsrByEcho 57 12



                                                          <-[57,12]

C:\Users\Yanci\Documents\Scripts>
Is it in the nature of ECHO that leading and trailing blanks are ignored, so that this script cannot actually leave the cursor positioned without displaying some concrete output which then moves to the next line?

Tested using Legacy Prompt on two Windows 10 systems:
Win7-based Win10 Anniversary Edition
Win8.1-based Win10 Creators Edition

Because CE has enhanced DOS prompt support and this technique works with the buffer and not the window dimensions additional care is required to keep up with action outside the visible window.

John A

thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Re: Move cursor to *any position* using just ECHO command

#10 Post by thefeduke » 02 Jun 2017 15:11

Please note that I have edited my previous reply. I have more comments here:
Aacini wrote:. . .
This program is just an example of how to use this method to move the cursor, . . .
. . .

I observed that the positioning only works if the positioning string is followed by one or more displayable characters. So this technique actually displays output at a chosen position, as opposed to leaving the cursor there like "cursorpos" and the like. If there is no output, the ECHO seems to act like a null operation. It does not advance a line as in ECHO. of ECHO(, nor does it act like ECHO with no operand.

Antonio, I draw attention to your output example in the first post for point B(5,10). It is followed by an underscore; the very character used to break the back-tabbing chain. This is the tip of an iceberg that confused me a lot during my testing because I tried shorter strings and used blank to break the chain. The underscore is produced as the next character after each tab in each line, and this behavior seams to be related to outputting a string too short to over-write the underscore. This will not occur to the right and below (10,10) and will occur in two adjacent places above and to the left of (9,9). The output string in this demo varies between 6 and 8 characters.

So this might add another constraint to that of avoiding the last eight positions. Still a very interesting discovery.

John A

Aacini
Expert
Posts: 1611
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: Move cursor to *any position* using just ECHO command

#11 Post by Aacini » 03 Jun 2017 15:00

thefeduke wrote:I observed that the positioning only works if the positioning string is followed by one or more displayable characters. So this technique actually displays output at a chosen position, as opposed to leaving the cursor there like "cursorpos" and the like. If there is no output, the ECHO seems to act like a null operation. It does not advance a line as in ECHO. of ECHO(, nor does it act like ECHO with no operand.

In order to just position the cursor you need to use a SET /P command instead of an ECHO one, but in this case the first char in SET /P must be a non-space printable character, so this method can only be used if the character at the current cursor position is not a space and you know what character is.

thefeduke wrote:Antonio, I draw attention to your output example in the first post for point B(5,10). It is followed by an underscore; the very character used to break the back-tabbing chain. This is the tip of an iceberg that confused me a lot during my testing because I tried shorter strings and used blank to break the chain. The underscore is produced as the next character after each tab in each line, and this behavior seams to be related to outputting a string too short to over-write the underscore.

Yes. You may change the underscore by a space, for example, but the result is the same: the space will overwrite the original character that appear in the screen at such a position if the new displayed string is too short to overwrite the space.


For these reasons, this method is best suited for applications in which you always know the contents of the screen in all positions, so the proper character may be used in the positioning string in order to preserve the original screen contents. This type of applications are animations/games. As I said before, there is no way to use this method to create a general-use cursor positioning subroutine that works in all cases.

Antonio

Post Reply