From c4dad452c95401c0fdf3fb01996f736ab43bf96a Mon Sep 17 00:00:00 2001 From: Wallace Breza Date: Fri, 10 Apr 2026 10:08:45 -0700 Subject: [PATCH] fix: clear ENABLE_VIRTUAL_TERMINAL_INPUT for correct arrow key handling on Windows (#7641) The survey library's Windows RuneReader dispatches arrow keys via wVirtualKeyCode when unicodeChar == 0. When ENABLE_VIRTUAL_TERMINAL_INPUT is set (as in Windows Terminal, PowerShell 7, VS Code, and Ghostty), the console delivers ANSI escape sequences instead, bypassing the VK code path and leaking literal characters into filter text. Clear the ENABLE_VIRTUAL_TERMINAL_INPUT flag after SetTermMode() so the console delivers native virtual-key codes. The original console mode is restored by RestoreTermMode() when input completes. Fixes #7641 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/pkg/ux/internal/console_other.go | 13 +++++++ cli/azd/pkg/ux/internal/console_windows.go | 43 ++++++++++++++++++++++ cli/azd/pkg/ux/internal/input.go | 11 ++++++ 3 files changed, 67 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..6d5d2ee82f2 --- /dev/null +++ b/cli/azd/pkg/ux/internal/console_other.go @@ -0,0 +1,13 @@ +// 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. +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..8e2f9011e86 --- /dev/null +++ b/cli/azd/pkg/ux/internal/console_windows.go @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +//go:build windows + +package internal + +import ( + "os" + + "golang.org/x/sys/windows" +) + +const enableVirtualTerminalInput uint32 = 0x0200 + +// disableVirtualTerminalInput clears the ENABLE_VIRTUAL_TERMINAL_INPUT +// console mode flag so that Windows delivers native virtual-key codes +// (KEY_EVENT_RECORD with wVirtualKeyCode) instead of ANSI escape +// sequences for arrow keys and other special keys. +// +// The survey library's Windows RuneReader expects native VK codes +// (it checks unicodeChar == 0, then switches on wVirtualKeyCode). +// When ENABLE_VIRTUAL_TERMINAL_INPUT is set — as it is by default in +// Windows Terminal, PowerShell 7, VS Code, and Ghostty — the console +// emits ESC [ A / ESC [ B / etc. instead, which leak through as +// literal characters. +// +// This function is called after SetTermMode() and its effect is +// reversed when RestoreTermMode() restores the original console mode. +func disableVirtualTerminalInput(f *os.File) error { + h := windows.Handle(f.Fd()) + + var mode uint32 + if err := windows.GetConsoleMode(h, &mode); err != nil { + return err + } + + if mode&enableVirtualTerminalInput == 0 { + return nil // VTI not set, nothing to do + } + + return windows.SetConsoleMode(h, mode&^enableVirtualTerminalInput) +} diff --git a/cli/azd/pkg/ux/internal/input.go b/cli/azd/pkg/ux/internal/input.go index d41279bd163..0104700f9c9 100644 --- a/cli/azd/pkg/ux/internal/input.go +++ b/cli/azd/pkg/ux/internal/input.go @@ -97,6 +97,17 @@ 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. The survey library's RuneReader expects + // VK codes for arrow key dispatch. RestoreTermMode() will + // restore the original console mode when input completes. + if err := disableVirtualTerminalInput(os.Stdin); err != nil { + // Non-fatal: worst case is the pre-existing ANSI leak. + _ = err + } + defer func() { if err := rr.RestoreTermMode(); err != nil { log.Printf("Error restoring terminal mode: %v\n", err)