Add getCursorPos() to std/terminal (#22749)

This would be handy for making terminal apps which display content below
the prompt (e.g. `fzf` does this).

Need to test it on windows before I remove "draft" status.

---------

Co-authored-by: Matt Rixman <MatrixManAtYrService@users.noreply.github.com>
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
This commit is contained in:
Matt Rixman
2023-10-08 16:56:18 +00:00
committed by GitHub
parent efa64aa49b
commit 5bcea05caf

View File

@@ -60,7 +60,7 @@ runnableExamples("-r:off"):
import macros
import strformat
from strutils import toLowerAscii, `%`
from strutils import toLowerAscii, `%`, parseInt
import colors
when defined(windows):
@@ -96,6 +96,7 @@ const
fgPrefix = "\e[38;2;"
bgPrefix = "\e[48;2;"
ansiResetCode* = "\e[0m"
getPos = "\e[6n"
stylePrefix = "\e["
when defined(windows):
@@ -220,6 +221,9 @@ when defined(windows):
raiseOSError(osLastError())
return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y))
proc getCursorPos*(): tuple [x, y: int] {.raises: [ValueError, IOError, OSError].} =
return getCursorPos(getStdHandle(STD_OUTPUT_HANDLE))
proc setCursorPos(h: Handle, x, y: int) =
var c: COORD
c.x = int16(x)
@@ -267,6 +271,48 @@ else:
mode.c_cc[VTIME] = 0.cuchar
discard fd.tcSetAttr(time, addr mode)
proc getCursorPos*(): tuple [x, y: int] {.raises: [ValueError, IOError].} =
## Returns cursor position (x, y)
## writes to stdout and expects the terminal to respond via stdin
var
xStr = ""
yStr = ""
ch: char
ct: int
readX = false
# use raw mode to ask terminal for cursor position
let fd = getFileHandle(stdin)
var oldMode: Termios
discard fd.tcGetAttr(addr oldMode)
fd.setRaw()
stdout.write(getPos)
flushFile(stdout)
try:
# parse response format: [yyy;xxxR
while true:
let n = readBuffer(stdin, addr ch, 1)
if n == 0 or ch == 'R':
if xStr == "" or yStr == "":
raise newException(ValueError, "Got character position message that was missing data")
break
ct += 1
if ct > 16:
raise newException(ValueError, "Got unterminated character position message from terminal")
if ch == ';':
readX = true
elif ch in {'0'..'9'}:
if readX:
xStr.add(ch)
else:
yStr.add(ch)
finally:
# restore previous terminal mode
discard fd.tcSetAttr(TCSADRAIN, addr oldMode)
return (parseInt(xStr), parseInt(yStr))
proc terminalWidthIoctl*(fds: openArray[int]): int =
## Returns terminal width from first fd that supports the ioctl.