DEV Community

aagamezl
aagamezl

Posted on • Edited on

Creating a Multichoice CLI Selector in PowerShell

In this article, we will explore how to create a command-line interface (CLI) multichoice selector in PowerShell. This selector allows users to navigate through a list of options and make multiple selections using the keyboard. We will break down the code provided and explain the relevant parts to understand how it works.

This is how it will look:

Image description

Setting Up Terminal Manipulation Functions

Let's start by setting up the terminal manipulation functions that will allow us to control the appearance of the terminal window.

# Function to clear the terminal
function Clear-Terminal {
  $ESC = [char]27
  $hideCursor = "${ESC}[?25l"
  Write-Host -NoNewline $hideCursor # Hide terminal cursor

  [Console]::SetCursorPosition(0, 0)
  [Console]::Clear()
}

# Function to close the terminal
Function Close-Terminal {
  $ESC = [char]27
  $showCursor = "${ESC}[?25h"

  Write-Host -NoNewline $showCursor
}
Enter fullscreen mode Exit fullscreen mode

Here, we have two functions: Clear-Terminal and Close-Terminal. The Clear-Terminal function clears the terminal screen and hides the cursor, while the Close-Terminal function shows the cursor again when we want to exit the script. These functions use escape sequences to hide and show the cursor, respectively. They provide a cleaner and more interactive user experience.

Function to Confirm the Selection and Exit

Next, we have the function responsible for confirming the selected options and exiting the script.

# Function to confirm the selection and exit
function Confirm-Selection {
  $selectedOptions.sort()
  $selectedOptionNames = $selectedOptions | ForEach-Object { $listOptions[$_] }

  Write-Host "`nSelected Options: $($selectedOptionNames -join ", ")`n"

  Close-Terminal

  exit
}
Enter fullscreen mode Exit fullscreen mode

In this function, the selected options are sorted, and their corresponding names are retrieved from the $listOptions array. These selected option names are then displayed using Write-Host. Finally, the Close-Terminal function is called to show the cursor, and the script exits.

Function to Display the List with Selected Options

Now, let's take a look at the function responsible for rendering the list of options on the screen.

# Function to display the list with the selected options
function Display-List {
  Clear-Terminal

  Write-Host "Select option(s) (press spacebar to toggle selection):`n"

  $listOptions | ForEach-Object -Begin { $index = 0 } -Process {
    $isSelected = $selectedOptions.Contains($index)
    $prefix = if ($index -eq $selectedOption.Value) { [char]0x276F } else { " " }
    $checkbox = if ($isSelected) { [char]0x25C9 } else { [char]0x25CB }
    $formattedOption = "$prefix $checkbox $_"

    Write-Host $formattedOption
    $index++
  }
}
Enter fullscreen mode Exit fullscreen mode

This function first calls Clear-Terminal to clear the terminal screen and hide the cursor. Then, it displays the prompt message using Write-Host. Next, it iterates over the $listOptions array using ForEach-Object.

Inside the loop, it checks whether the current option is selected by using $selectedOptions.Contains($index). Based on the selection status, it assigns different values to the $checkbox variable: "[char]0x25C9" (filled circle) if selected or "[char]0x25CB" (empty circle) if not selected. It also assigns a prefix to highlight the currently selected option using "[char]0x276F" (right-pointing arrow) or a space.

After formatting the option string, it is displayed on the screen using Write-Host. Finally, the $index variable is incremented to keep track of the option's index.

Function to Handle Key Press Events

Now, let's explore the function responsible for handling different keypress events.

# Function to handle key press events
function Handle-Key-Press([System.ConsoleKeyInfo]$keyInfo) {
  $key = $keyInfo.Key

  # Perform actions based on the pressed key
  switch ($key) {
    "DownArrow" {
      $selectedOption.Value = (($selectedOption.Value + 1) % $listOptions.Length)
      Display-List($selectedOption.Value, $selectedOptions)
    }

    "UpArrow" {
      $selectedOption.Value = (($selectedOption.Value - 1 + $listOptions.Length) % $listOptions.Length)
      Display-List($selectedOption.Value, $selectedOptions)
    }

    "Spacebar" {
      $currentOption = $selectedOption.Value
      $isSelected = $selectedOptions.Contains($currentOption)

      Write-Host "isSelected: $isSelected"

      if ($isSelected) {
        $optionIndex = $selectedOptions.IndexOf($currentOption)
        $selectedOptions.RemoveAt($optionIndex)
      } else {
        $selectedOptions.Add($currentOption)
      }

      Display-List($selectedOption.Value, $selectedOptions)
    }

    "Enter" {
      Confirm-Selection
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This function takes a [System.ConsoleKeyInfo] object, $keyInfo, as input, which contains information about the pressed key.

Inside the function, a switch statement is used to perform actions based on the pressed key. Here's what each case does:

  • DownArrow: Moves the currently selected option one position down in the list. It updates the $selectedOption.Value by incrementing it by 1 modulo the length of the $listOptions array. Then, it calls Display-List to refresh the list on the screen with the new selected option.

  • UpArrow: Moves the currently selected option one position up in the list. It updates the $selectedOption.Value by decrementing it by 1 and adding the length of the $listOptions array modulo the length of the $listOptions array. This calculation ensures that the index wraps around correctly. Then, it calls Display-List to refresh the list on the screen with the new selected option.

  • Spacebar: Toggles the selection of the current option. It checks if the current option is already selected by verifying its presence in the $selectedOptions array. If it is selected, it removes it from the array; otherwise, it adds it. Then, it calls Display-List to refresh the list on the screen with the updated selection.

  • Enter: Confirms the selection by calling the Confirm-Selection function.

These key press events provide the core functionality of the multichoice selector.

Initializing Variables and Displaying the Initial List

Before we start the main script loop, we need to initialize some variables and display the initial list.

# List options
$listOptions = @("Barcelona", "Munich", "Paris", "Turin")

# Selected options
$selectedOptions = New-Object System.Collections.ArrayList

# Current selected option
$selectedOption = [ref]0

# Display the initial list
Display-List($selectedOption.Value, $selectedOptions)
Enter fullscreen mode Exit fullscreen mode

Here, we define the $listOptions array, which contains the available

options to choose from. We also create the $selectedOptions variable as an instance of the System.Collections.ArrayList class to store the indices of the selected options. The $selectedOption variable is initialized as a reference to the currently selected option, starting with the index 0.

After initializing the variables, the initial list is displayed on the screen by calling the Display-List function with the current selection information.

Main Script Loop

Finally, we have the main script loop that continuously waits for keypress events and handles them accordingly.

while ($true) {
  $keyInfo = [System.Console]::ReadKey($true)
  Handle-Key-Press($keyInfo)

  Start-Sleep -Milliseconds 50
}
Enter fullscreen mode Exit fullscreen mode

The loop runs indefinitely (while ($true)) and uses [System.Console]::ReadKey($true) to read a keypress without displaying it on the screen. The obtained [System.ConsoleKeyInfo] object is then passed to the Handle-Key-Press function for further processing.

A short sleep delay of 50 milliseconds is added using Start-Sleep to prevent excessive CPU usage during the loop.

By combining all these functions and the main script loop, we create a CLI multichoice selector in PowerShell that allows users to navigate through options, select or deselect them, and confirm their selections.

Feel free to experiment with the code and enhance it to suit your specific requirements. Happy coding!

# Function to clear the terminal
function Clear-Terminal {
  $ESC = [char]27
  $hideCursor = "${ESC}[?25l"
  Write-Host -NoNewline $hideCursor # Hide terminal cursor

  [Console]::SetCursorPosition(0, 0)
  [Console]::Clear()
}

# Function to close the terminal
Function Close-Terminal {
  $ESC = [char]27
  $showCursor = "${ESC}[?25h"

  Write-Host -NoNewline $showCursor
}

# Function to confirm the selection and exit
function Confirm-Selection {
  $selectedOptions.sort()
  $selectedOptionNames = $selectedOptions | ForEach-Object { $listOptions[$_] }

  Write-Host "`nSelected Options: $($selectedOptionNames -join ", ")`n"

  Close-Terminal

  exit
}

# Function to display the list with the selected options
function Display-List {
  Clear-Terminal

  Write-Host "Select option(s) (press spacebar to toggle selection):`n"

  $listOptions | ForEach-Object -Begin { $index = 0 } -Process {
    $isSelected = $selectedOptions.Contains($index)
    $prefix = if ($index -eq $selectedOption.Value) { [char]0x276F } else { " " }
    $checkbox = if ($isSelected) { [char]0x25C9 } else { [char]0x25CB }
    $formattedOption = "$prefix $checkbox $_"

    Write-Host $formattedOption
    $index++
  }
}

# Function to handle key press events
function Handle-Key-Press([System.ConsoleKeyInfo]$keyInfo) {
  $key = $keyInfo.Key

  # Perform actions based on the pressed key
  switch ($key) {
    "DownArrow" {
      $selectedOption.Value = (($selectedOption.Value + 1) % $listOptions.Length)
      Display-List($selectedOption.Value, $selectedOptions)
    }

    "UpArrow" {
      $selectedOption.Value = (($selectedOption.Value - 1 + $listOptions.Length) % $listOptions.Length)
      Display-List($selectedOption.Value, $selectedOptions)
    }

    "Spacebar" {
      $currentOption = $selectedOption.Value
      $isSelected = $selectedOptions.Contains($currentOption)

      Write-Host "isSelected: $isSelected"

      if ($isSelected) {
        $optionIndex = $selectedOptions.IndexOf($currentOption)
        $selectedOptions.RemoveAt($optionIndex)
      } else {
        $selectedOptions.Add($currentOption)
      }

      Display-List($selectedOption.Value, $selectedOptions)
    }

    "Enter" {
      Confirm-Selection
    }
  }
}

# List options
$listOptions = @("Barcelona", "Munich", "Paris", "Turin")

# Selected options
$selectedOptions = New-Object System.Collections.ArrayList

# Current selected option
$selectedOption = [ref]0

# Display the initial list
Display-List($selectedOption.Value, $selectedOptions)

# Main script
while ($true) {
  $keyInfo = [System.Console]::ReadKey($true)
  Handle-Key-Press($keyInfo)

  Start-Sleep -Milliseconds 50
}
Enter fullscreen mode Exit fullscreen mode

If you have any questions or need further assistance, please let me know in the comments section below or message me Twitter or LinkedIn. Happy coding!.

Top comments (0)