Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cli/azd/pkg/ux/internal/console_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

//go:build !windows

package internal

import "os"

// disableVirtualTerminalInput is a no-op on non-Windows platforms.
// The ENABLE_VIRTUAL_TERMINAL_INPUT console flag only exists on Windows.
func disableVirtualTerminalInput(_ *os.File) error {
return nil
}
52 changes: 52 additions & 0 deletions cli/azd/pkg/ux/internal/console_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

//go:build windows

package internal

import (
"os"
"syscall"
"unsafe"
)

var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
getConsoleMode = kernel32.NewProc("GetConsoleMode")
setConsoleMode = kernel32.NewProc("SetConsoleMode")
)

const enableVirtualTerminalInput uint32 = 0x0200

// disableVirtualTerminalInput clears the ENABLE_VIRTUAL_TERMINAL_INPUT flag
// on the console input handle. When this flag is set (e.g. by PowerShell),
// Windows translates arrow key presses into ANSI escape sequences instead of
// delivering them as native virtual key codes. The survey library's Windows
// ReadRune only handles native VK codes, so leaving VTI enabled causes
// escape sequence fragments to leak through as individual runes.
//
// This is called after survey's SetTermMode (which saves/restores the original
// console state), so RestoreTermMode will re-enable VTI if it was originally set.
func disableVirtualTerminalInput(f *os.File) error {
handle := f.Fd()

var mode uint32
//nolint:gosec // Win32 API requires unsafe pointer
r, _, err := getConsoleMode.Call(uintptr(handle), uintptr(unsafe.Pointer(&mode)))
if r == 0 {
return err
}

if mode&enableVirtualTerminalInput == 0 {
return nil // VTI not enabled, nothing to do
}

newMode := mode &^ enableVirtualTerminalInput
r, _, err = setConsoleMode.Call(uintptr(handle), uintptr(newMode))
if r == 0 {
return err
}

return nil
}
6 changes: 6 additions & 0 deletions cli/azd/pkg/ux/internal/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ func (i *Input) ReadInput(ctx context.Context, config *InputConfig, handler KeyP
errChan <- err
return
}
// On Windows, clear ENABLE_VIRTUAL_TERMINAL_INPUT so the console
// delivers native virtual key codes instead of ANSI escape sequences.
// survey's RestoreTermMode will restore the original console state.
if err := disableVirtualTerminalInput(os.Stdin); err != nil {
log.Printf("Warning: could not disable virtual terminal input: %v\n", err)
}
defer func() {
if err := rr.RestoreTermMode(); err != nil {
log.Printf("Error restoring terminal mode: %v\n", err)
Expand Down
Loading