From 09a0768d38d005030e8d5f737a5b5276db4204e8 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Fri, 10 Apr 2026 21:18:32 +0000 Subject: [PATCH 1/2] fix: clear ENABLE_VIRTUAL_TERMINAL_INPUT on Windows to fix PowerShell arrow keys (#7641) On Windows/PowerShell, the ENABLE_VIRTUAL_TERMINAL_INPUT console flag causes arrow keys to be delivered as ANSI escape sequences instead of native virtual key codes. The survey library's Windows ReadRune only handles native VK codes, so the escape sequence fragments leak through as individual runes. This fix clears the VTI flag after SetTermMode() during input reading. RestoreTermMode() restores the original console state (including VTI if it was originally set). Fixes #7641 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/pkg/ux/internal/console_other.go | 14 ++++++ cli/azd/pkg/ux/internal/console_windows.go | 51 ++++++++++++++++++++++ cli/azd/pkg/ux/internal/input.go | 6 +++ 3 files changed, 71 insertions(+) create mode 100644 cli/azd/pkg/ux/internal/console_other.go create mode 100644 cli/azd/pkg/ux/internal/console_windows.go diff --git a/cli/azd/pkg/ux/internal/console_other.go b/cli/azd/pkg/ux/internal/console_other.go new file mode 100644 index 00000000000..2a32269acfa --- /dev/null +++ b/cli/azd/pkg/ux/internal/console_other.go @@ -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 +} diff --git a/cli/azd/pkg/ux/internal/console_windows.go b/cli/azd/pkg/ux/internal/console_windows.go new file mode 100644 index 00000000000..f2736a38281 --- /dev/null +++ b/cli/azd/pkg/ux/internal/console_windows.go @@ -0,0 +1,51 @@ +// 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 + r, _, err := getConsoleMode.Call(uintptr(handle), uintptr(unsafe.Pointer(&mode))) //nolint:gosec // Win32 API requires unsafe pointer + 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 +} diff --git a/cli/azd/pkg/ux/internal/input.go b/cli/azd/pkg/ux/internal/input.go index d41279bd163..20428d01f91 100644 --- a/cli/azd/pkg/ux/internal/input.go +++ b/cli/azd/pkg/ux/internal/input.go @@ -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) From 4eb6c9cdcab597d4e851e11edeaf8a0356380077 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Fri, 10 Apr 2026 21:45:38 +0000 Subject: [PATCH 2/2] lint --- cli/azd/pkg/ux/internal/console_windows.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/azd/pkg/ux/internal/console_windows.go b/cli/azd/pkg/ux/internal/console_windows.go index f2736a38281..2cb4b8c1691 100644 --- a/cli/azd/pkg/ux/internal/console_windows.go +++ b/cli/azd/pkg/ux/internal/console_windows.go @@ -32,7 +32,8 @@ func disableVirtualTerminalInput(f *os.File) error { handle := f.Fd() var mode uint32 - r, _, err := getConsoleMode.Call(uintptr(handle), uintptr(unsafe.Pointer(&mode))) //nolint:gosec // Win32 API requires unsafe pointer + //nolint:gosec // Win32 API requires unsafe pointer + r, _, err := getConsoleMode.Call(uintptr(handle), uintptr(unsafe.Pointer(&mode))) if r == 0 { return err }