This is a custom windows command to more easily change the directory in Windows consoles. Rather than typing out directory names you just use the arrow keys. Just build source to "cdui.exe" and add it to your PATH.
// cdui
//
// This program is intended to help users change directories more easily
// in the console. It provides a UI and lets you navigate with keypresses
// rather than having to type out directory names.
//
// Controls...
// - Left Arrow: cd to parent
// - Right Arrow: cd to selected child
// - Up Arrow: move selected child up
// - Down Arrow: move selected child down
// - Backspace: return to the previous directory
// - Enter: end cdui at the current directory
// - Escape: cancel cdui and return to the original directory
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
class Program
{
static void Main()
{
try
{
string originalDirectory = Directory.GetCurrentDirectory();
Stack<string> stack = new Stack<string>();
while (true)
{
HandleResize:
string[] directories = Directory.GetDirectories(Environment.CurrentDirectory);
(int Left, int Top)[] points = new (int Left, int Top)[directories.Length];
Console.Clear();
Console.WriteLine("cdui> " + Environment.CurrentDirectory);
for (int i = 0; i < directories.Length; i++)
{
points[i] = (Console.CursorLeft, Console.CursorTop);
Console.WriteLine(directories[i].Substring(Environment.CurrentDirectory.Length));
}
int consoleWindowWidth = Console.WindowWidth;
int consoleWindowHeight = Console.WindowHeight;
int consoleBufferWidth = Console.BufferWidth;
int consoleBufferHeight = Console.BufferHeight;
int selection = 0;
SameDirectory:
if (directories.Length > 0)
{
Console.SetCursorPosition(points[selection].Left, points[selection].Top);
}
while (true)
{
if (consoleWindowWidth != Console.WindowWidth ||
consoleWindowHeight != Console.WindowHeight ||
consoleBufferWidth != Console.BufferWidth ||
consoleBufferHeight != Console.BufferHeight)
{
goto HandleResize;
}
if (Console.KeyAvailable)
{
switch (Console.ReadKey(true).Key)
{
case ConsoleKey.UpArrow:
if (directories.Length > 0)
{
selection = selection == 0
? directories.Length - 1
: selection - 1;
}
goto SameDirectory;
case ConsoleKey.DownArrow:
if (directories.Length > 0)
{
selection = selection == directories.Length - 1
? 0
: selection + 1;
}
goto SameDirectory;
case ConsoleKey.LeftArrow:
DirectoryInfo directoryInfo = Directory.GetParent(Environment.CurrentDirectory);
if (directoryInfo is null)
{
goto SameDirectory;
}
Environment.CurrentDirectory = directoryInfo.FullName;
goto Continue;
case ConsoleKey.RightArrow:
if (directories.Length <= 0)
{
goto SameDirectory;
}
Environment.CurrentDirectory = directories[selection];
goto Continue;
case ConsoleKey.Backspace:
if (stack.Count > 0)
{
stack.Pop();
}
string back = stack.Count > 0
? stack.Pop()
: originalDirectory;
if (back == Environment.CurrentDirectory)
{
goto SameDirectory;
}
Environment.CurrentDirectory = back;
goto Continue;
case ConsoleKey.Enter:
goto Return;
case ConsoleKey.Escape:
Environment.CurrentDirectory = originalDirectory;
goto Return;
default:
goto SameDirectory;
}
}
Thread.Sleep(TimeSpan.FromMilliseconds(16));
}
Continue:
stack.Push(Environment.CurrentDirectory);
}
Return:
Console.Clear();
if (Environment.CurrentDirectory != originalDirectory)
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
CreateNoWindow = false,
WorkingDirectory = Environment.CurrentDirectory,
UseShellExecute = true,
}
};
process.Start();
process.SetAsForgroundWindow();
Process parent = Process.GetCurrentProcess().GetParent();
if (!(parent is null))
{
parent.Kill();
}
}
}
catch (Exception exception)
{
Console.Clear();
Console.WriteLine(exception);
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct ParentProcessUtilities
{
// ROCESS_BASIC_INFORMATION
internal IntPtr Reserved1;
internal IntPtr PebBaseAddress;
internal IntPtr Reserved2_0;
internal IntPtr Reserved2_1;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
}
public static class ProcessExtensions
{
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hwnd);
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(
IntPtr processHandle,
int processInformationClass,
ref ParentProcessUtilities processInformation,
int processInformationLength,
out int returnLength);
public static Process GetParent(this Process process) =>
GetParentProcess(process.Handle);
public static Process GetParentProcess(IntPtr handle)
{
ParentProcessUtilities pbi = new ParentProcessUtilities();
int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out _);
if (status != 0)
{
throw new Win32Exception(status);
}
try
{
return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
catch (ArgumentException)
{
return null;
}
}
public static void SetAsForgroundWindow(this Process process)
{
int status = SetForegroundWindow(process.MainWindowHandle);
if (status != 0)
{
throw new Win32Exception(status);
}
}
}