{"id":15713684,"url":"https://github.com/crystal-term/prompt","last_synced_at":"2025-04-14T16:23:47.170Z","repository":{"id":91429651,"uuid":"248694559","full_name":"crystal-term/prompt","owner":"crystal-term","description":"A beautiful and powerful interactive command line prompt","archived":false,"fork":false,"pushed_at":"2024-12-07T00:06:18.000Z","size":93,"stargazers_count":32,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T05:07:09.050Z","etag":null,"topics":["crystal-lang","crystal-language","prompt","readline","terminal","user-input"],"latest_commit_sha":null,"homepage":null,"language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/crystal-term.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-20T07:34:24.000Z","updated_at":"2025-02-05T07:05:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"63a8e5a5-434b-41a3-a1c2-944e2d5c6b61","html_url":"https://github.com/crystal-term/prompt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crystal-term%2Fprompt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crystal-term%2Fprompt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crystal-term%2Fprompt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crystal-term%2Fprompt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crystal-term","download_url":"https://codeload.github.com/crystal-term/prompt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248914782,"owners_count":21182497,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crystal-lang","crystal-language","prompt","readline","terminal","user-input"],"created_at":"2024-10-03T21:32:53.599Z","updated_at":"2025-04-14T16:23:47.145Z","avatar_url":"https://github.com/crystal-term.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./assets/term-logo.png\" alt=\"term logo\"\u003e\n\u003c/div\u003e\n\n# Term::Prompt\n\n![spec status](https://github.com/crystal-term/prompt/workflows/specs/badge.svg)\n\n\u003e A terminal prompt for tasks that have non-deterministic time frame.\n\n**Term::Screen** provides an independent prompt component for crystal-term.\n\n[![asciicast](https://asciinema.org/a/acKxSZcBD3I8BUlDBHtw02qtJ.svg)](https://asciinema.org/a/acKxSZcBD3I8BUlDBHtw02qtJ)\n\n## Features\n\n- Number of prompt types for gathering user input\n- A robust API for validating complex inputs\n- User friendly error feedback\n- Intuitive DSL for creating complex menus\n- Ability to page long menus\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n   ```yaml\n   dependencies:\n     term-prompt:\n       github: crystal-term/prompt\n   ```\n\n2. Run `shards install`\n\n## Usage\n\nIn order to start asking questions, you need to first create a prompt:\n\n```crystal\nrequire \"term-prompt\"\n\nprompt = Term::Prompt.new\n```\n\nAnd then call `ask` with the question for a simple input:\n\n```crystal\nprompt.ask(\"What is your name?\", default: ENV[\"USER\"])\n# =\u003e What is your name? (watzon)\n```\n\nTo ask for confirmation you can use `yes?` if you want the default answer to be yes, or `no?` if you want the default to be no. The return value will be a `Bool`.\n\n```crystal\nprompt.yes?(\"Do you love Crystal?\")\n# Do you love Crystal? (Y/n)\n```\n\nIf you want to hide the input from prying eyes, you can use `mask`:\n\n```crystal\nprompt.mask(\"Please enter your password:\")\n# =\u003e Please enter your password: ••••••••••••\n```\n\nAsking question with list of options couldn't be easier using `select` like so:\n\n```crystal\nprompt.select(\"Choose your destiny?\", %w(Scorpion Kano Jax))\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)\n# ‣ Scorpion\n#   Kano\n#   Jax\n```\n\nAlso, asking multiple choice questions is a breeze with multi_select:\n\n```crystal\nchoices = %w(vodka beer wine whisky bourbon)\nprompt.multi_select(\"Select drinks?\", choices)\n# =\u003e\n# Select drinks? (Use ↑/↓ arrow keys, press Space to select and Enter to finish)\"\n# ‣ ⬡ vodka\n#   ⬡ beer\n#   ⬡ wine\n#   ⬡ whisky\n#   ⬡ bourbon\n```\n\nTo ask for a selection from enumerated list you can use enum_select:\n\n```crystal\nchoices = %w(emacs nano vim)\nprompt.enum_select(\"Select an editor?\", choices)\n# =\u003e\n# Select an editor?\n#   1) emacs\n#   2) nano\n#   3) vim\n#   Choose 1-3 [1]:\n```\n\nIf you wish to collect more than one answer use collect:\n\n```crystal\nresult = prompt.collect do |c|\n  c.key(:name).ask(\"Name?\")\n\n  c.key(:age).ask(\"Age?\")\n\n  c.key(:address) do |c|\n    c.key(:street).ask(\"Street?\", required: true)\n    c.key(:city).ask(\"City?\")\n    c.key(:zip).ask(\"Zip?\", match: /\\A\\d{5}\\Z/)\n  end\nend\n# =\u003e\n# {:name =\u003e \"Chris\", :age =\u003e 27, :address =\u003e {:street =\u003e \"Street\", :city =\u003e \"City\", :zip =\u003e \"12345\"}}\n```\n\n## Interface\n\n### `#ask`\n\nIn order to ask a basic question, do:\n\n```crystal\nprompt.ask(\"What is your name?\")\n```\n\nHowever the `Question` class is far more robust than just that, and `#ask` accepts all of the same options that `Question.new` does.\n\n```crystal\nprompt.ask(\"What is your name?\", required: true, match: /[a-z\\s]+/i)\n```\n\n#### `:default`\n\nThe `default` option is used if the user presses the return key without entering a value:\n\n```crystal\nprompt.ask(\"What is your name?\", default: \"Anonymous\")\n# =\u003e\n# What is your name? (Anonymous)\n```\n\n#### `:value`\n\nTo pre-populate the input line for editing use `value` option:\n\n```crystal\nprompt.ask(\"What is your name?\", value: \"Chris\")\n# =\u003e\n# What is your name? Piotr\n```\n\n#### `:echo`\n\nTo control whether the input is shown back in terminal or not use `echo` option like so:\n\n```crystal\nprompt.ask(\"Password:\", echo: false)\n```\n\n#### `:required`\n\nTo ensure that the input is provided, use the `required` option:\n\n```crystal\nprompt.ask(\"What's your phone number?\", required: true)\n# What's your phone number?\n# \u003e\u003e Value is required\n```\n\n#### `:validators`\n\nValidators allow you to ensure that an input matches a specific constraint. `validators` is an array of `Validator` objects, or procs which match `Proc(Question, String?, Bool)`.\n\n```crystal\nclass NameValidator \u003c Term::Prompt::Validator\n  def call(question : Question, value : String?) : Bool\n    if name = value\n      names = name.split(/\\s+/)\n      if names.size \u003c 2\n        question.errors \u003c\u003c \"Enter your full name\"\n        return false\n      end\n    end\n    true\n  end\nend\n\nprompt.ask(\"What is your name?\", required: true, validators: [NameValidator.new])\n```\n\n### `#keypress`\n\nIn order to await a single keypress, you can use `#keypress`:\n\n```crystal\nprompt.keypress(\"Press any key\")\n# Press any key\n# =\u003e a\n```\n\nBy default any key is accepted but you can limit keys by using `:keys` option. Any key event names such as \u003ckbd\u003eSpace\u003c/kbd\u003e or \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003ek\u003c/kbd\u003e are valid:\n\n```crystal\nprompt.keypress(\"Press space or enter to continue\", keys: [:space, :return])\n```\n\n### `#multiline`\n\nAsking for multiline input can be done with the `multiline` method. The reading will terminate with the pressing of \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003ed\u003c/kbd\u003e or \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003ez\u003c/kbd\u003e. Empty lines will not be included in the returned input.\n\n```crystal\nprompt.multiline(\"Description?\")\n# Description? (Press CTRL-D or CTRL-Z to finish)\n# I know not all that may be coming,\n# but be it what it will,\n# I'll go to it laughing.\n# =\u003e \"I know not all that may be coming,\\n\"but be it what it will,\\nI'll go to it laughing.\\n\"\n```\n\nThe `multiline` uses similar options to those supported by `ask` prompt. For example, to provide default description:\n\n```crystal\nprompt.multiline(\"Description?\", default: \"A super sweet prompt.\")\n```\n\nOr, using the DSL:\n\n```crystal\nprompt.multiline(\"Description?\") do |q|\n  q.default = \"A super sweet prompt.\"\n  q.help = \"Press thy ctrl+d to end\"\nend\n```\n\n### `#mask`\n\nf you require input of confidential information use `mask` method. By default each character that is printed is replaced by a `•` symbol. All configuration options applicable to `#ask` method can be used with `mask` as well.\n\n```crystal\nprompt.mask(\"What is your secret?\")\n# =\u003e What is your secret? ••••\n```\n\nThe masking character can be changed by passing the `:mask` option:\n\n```crystal\nheart = prompt.decorate(prompt.symbols[:heart] + \" \", :magenta)\nprompt.mask(\"What is your secret?\", mask: heart)\n# =\u003e What is your secret? ❤  ❤  ❤  ❤  ❤\n```\n\nIf you don't wish to show any output use `:echo` option like so:\n\n```crystal\nprompt.mask(\"What is your secret?\", echo: false)\n```\n\n### `#yes?`/`#no?`\n\nIn order to display a query asking for boolean input from user use yes? like so:\n\n```crystal\nprompt.yes?(\"Do you like Ruby?\")\n# =\u003e\n# Do you like Ruby? (Y/n)\n```\n\nYou can further customize question by passing `suffix`, `positive`, and `negative` options. The `suffix` changes text of available options, the `positive` changes the display string for successful answer and `negative` changes the display string for a negative answer. The final value is a boolean provided the `convert` option evaluates to boolean.\n\nIt's enough to provide the `suffix` option for the prompt to accept matching answers with correct labels:\n\n```crystal\nprompt.yes?(\"Are you a human?\", suffix: \"Yup/nope\")\n# =\u003e\n# Are you a human? (Yup/nope)\n```\n\nAlternatively, instead of `suffix` option you can provide `positive` and `negative` labels:\n\n```crystal\nprompt.yes?(\"Are you a human?\") do |q|\n  q.default false\n  q.positive \"Yup\"\n  q.negative \"Nope\"\nend\n# =\u003e\n# Are you a human? (yup/Nope)\n```\n\nThere is also the opposite for asking the confirmation of a negative question:\n\n```crystal\nprompt.no?('Do you hate Crystal?')\n# =\u003e\n# Do you hate Crystal? (y/N)\n```\n\nSimilar to the `#yes?` method, you can supply the same options to customize the question.\n\n### menu choices\n\nThere are several ways to add choices to the below menu types. The simplest is to create an array of values:\n\n```crystal\nchoices = %w(small medium large)\n```\n\nBy default the choice name is also the value the prompt will return when selected. To provide custom values, you can provide a named tuple with keys as choice names and their respective values:\n\n```crystal\nchoices = {small: \"1\", medium: \"2\", large: \"3\"}\n```\n\nUnfortunately for now values have to be strings.\n\nFinally, you can define an array of choices where each choice is a hash value with `:name` \u0026 `:value` keys which can include other options for customizing individual choices:\n\n```crystal\nchoices = [\n  {name: \"small\", value: \"1\"},\n  {name: \"medium\", value: \"2\", disabled: \"(out of stock)\"},\n  {name: \"large\", value: \"3\"}\n]\n```\n\nYou can specify `:key` as an additional option which will be used as short name for selecting the choice via keyboard key press.\n\nAnother way to create menu with choices is using the DSL and the choice method. For example, the previous array of choices with hash values can be translated as:\n\n```crystal\nprompt.select(\"Which size?\") do |menu|\n  menu.choice name: \"small\",  value: \"1\"\n  menu.choice name: \"medium\", value: \"2\", disabled: \"(out of stock)\"\n  menu.choice name: \"large\",  value: \"3\"\nend\n```\n\nor in a more compact way:\n\n```crystal\nprompt.select(\"Which size?\") do |menu|\n  menu.choice \"small\",  \"1\"\n  menu.choice \"medium\", \"2\", disabled: \"(out of stock)\"\n  menu.choice \"large\",  \"3\"\nend\n```\n\n#### `:disabled`\n\nThe `:disabled` key indicates that a choice is currently unavailable to select. Disabled choices are displayed with a cross `✘` character next to them. If the choice is disabled, it cannot be selected. The value for the `:disabled` item is used next to the choice to provide reason for excluding it from the selection menu. For example:\n\n```crystal\nchoices = [\n  {name: 'small', value: \"1\"},\n  {name: 'medium', value: \"2\", disabled: \"(out of stock)\"}\n  {name: 'large', value: \"3\"}\n]\n```\n\n### `#select`\n\nFor asking questions involving list of options use the `select` method by passing a question and possible choices:\n\n```crystal\nprompt.select(\"Choose your destiny?\", %w(Scorpion Kano Jax))\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)\n# ‣ Scorpion\n#   Kano\n#   Jax\n```\n\nYou can also provide options through DSL using the `choice` method for single entry and/or `choices` for more than one choice:\n\n```crystal\nprompt.select(\"Choose your destiny?\") do |menu|\n  menu.choice \"Scorpion\"\n  menu.choice \"Kano\"\n  menu.choice \"Jax\"\nend\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)\n# ‣ Scorpion\n#   Kano\n#   Jax\n```\n\nBy default the choice name is used as return value, but you can provide custom values:\n\n```crystal\nprompt.select(\"Choose your destiny?\") do |menu|\n  menu.choice \"Scorpion\", \"1\"\n  menu.choice \"Kano\", \"2\"\n  menu.choice \"Jax\", \"Nice choice captain!\"\nend\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)\n# ‣ Scorpion\n#   Kano\n#   Jax\n```\n\nIf you wish you can also provide a simple named tuple to denote choice name and its value like so:\n\n```crystal\nchoices = {\"Scorpion\" =\u003e \"1\", \"Kano\" =\u003e \"2\", \"Jax\" =\u003e \"3\"}\nprompt.select(\"Choose your destiny?\", choices)\n```\n\nTo mark particular answer as selected use `default` with index of the option starting from 1:\n\n```crystal\nprompt.select(\"Choose your destiny?\") do |menu|\n  menu.default 3\n\n  menu.choice \"Scorpion\", \"1\"\n  menu.choice \"Kano\", \"2\"\n  menu.choice \"Jax\", \"3\"\nend\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)\n#   Scorpion\n#   Kano\n# ‣ Jax\n```\n\nYou can navigate the choices using the arrow keys. When reaching the top/bottom of the list, the selection does not cycle around by default. If you wish to enable cycling, you can pass `cycle: true` to select and `multi_select`:\n\n```crystal\nprompt.select(\"Choose your destiny?\", %w(Scorpion Kano Jax), cycle: true)\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)\n# ‣ Scorpion\n#   Kano\n#   Jax\n```\n\nFor ordered choices set `separator` to any delimiter String. In that way, you can use arrows keys and numbers (0-9) to select the item.\n\n```crystal\nprompt.select(\"Choose your destiny?\") do |menu|\n  menu.separator \")\"\n\n  menu.choice \"Scorpion\", \"1\"\n  menu.choice \"Kano\", \"2\"\n  menu.choice \"Jax\", \"3\"\nend\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow or number (0-9) keys, press Enter to select)\n#   1) Scorpion\n#   2) Kano\n# ‣ 3) Jax\n```\n\nYou can configure the help message and/or marker like so:\n\n```crystal\nchoices = %w(Scorpion Kano Jax)\nprompt.select(\"Choose your destiny?\", choices, help: \"(Bash keyboard)\", symbols: {marker: '\u003e'})\n# =\u003e\n# Choose your destiny? (Bash keyboard)\n# \u003e Scorpion\n#   Kano\n#   Jax\n```\n\n#### `:page_size`\n\nBy default the menu is paginated if selection grows beyond 6 items. To change this setting use `:page_size` option.\n\n```crystal\nletters = (\"A\"..\"Z\").to_a\nprompt.select(\"Choose your letter?\", letters, page_size: 4)\n# =\u003e\n# Which letter? (Use ↑/↓ and ←/→ arrow keys, press Enter to select)\n# ‣ A\n#   B\n#   C\n#   D\n```\n\nYou can also customize the page navigation text using `:help` option:\n\n```crystal\nletters = (\"A\"..\"Z\").to_a\nprompt.select(\"Choose your letter?\") do |menu|\n  menu.page_size 4\n  menu.help \"(Wiggle thy finger up/down and left/right to see more)\"\n  menu.choices letters\nend\n# =\u003e\n# Which letter? (Wiggle thy finger up/down and left/right to see more)\n# ‣ A\n#   B\n#   C\n#   D\n```\n\n#### `:disabled`\n\nTo disable menu choice, use the `:disabled` key with a value that explains the reason for the choice being unavailable. For example, out of all warriors, Goro is currently injured:\n\n```crystal\nwarriors = [\n  \"Scorpion\",\n  \"Kano\",\n  { name: \"Goro\", disabled: \"(injured)\" },\n  \"Jax\",\n  \"Kitana\",\n  \"Raiden\"\n]\n```\n\nThe disabled choice will be displayed with a cross ✘ character next to it and followed by an explanation:\n\n```crystal\nprompt.select(\"Choose your destiny?\", warriors)\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)\n# ‣ Scorpion\n#   Kano\n# ✘ Goro (injured)\n#   Jax\n#   Kitana\n#   Raiden\n```\n\n#### `:filter`\n\nTo activate dynamic list searching by letter/number key presses use the `:filter` option:\n\n```crystal\nwarriors = %w(Scorpion Kano Jax Kitana Raiden)\nprompt.select(\"Choose your destiny?\", warriors, filter: true)\n# =\u003e\n# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select, and letter keys to filter)\n# ‣ Scorpion\n#   Kano\n#   Jax\n#   Kitana\n#   Raiden\n```\n\nAfter the user presses \"k\":\n\n```crystal\n# =\u003e\n# Choose your destiny? (Filter: \"k\")\n# ‣ Kano\n#   Kitana\n```\n\nAfter the user presses \"ka\":\n\n```crystal\n# =\u003e\n# Choose your destiny? (Filter: \"ka\")\n# ‣ Kano\n```\n\nFilter characters can be deleted partially or entirely via Backspace and Delete respectively.\n\nIf the user changes or deletes a filter, the choices previously selected remain selected.\n\n## `#multi_select`\n\nFor asking questions involving multiple selections use the `#multi_select` method by passing the question and possible choices:\n\n```crystal\nchoices = %w(vodka beer wine whisky bourbon)\nprompt.multi_select(\"Select drinks?\", choices)\n# =\u003e\n#\n# Select drinks? (Use ↑/↓ arrow keys, press Space to select and Enter to finish)\"\n# ‣ ⬡ vodka\n#   ⬡ beer\n#   ⬡ wine\n#   ⬡ whisky\n#   ⬡ bourbon\n```\n\nAs a return value, `multi_select` will always return an array populated with the names of the choices. If you wish to return custom values for the available choices do:\n\n```crystal\nchoices = {vodka: \"1\", beer: \"2\", wine: \"3\", whisky: \"4\", bourbon: \"}\nprompt.multi_select(\"Select drinks?\", choices)\n\n# Provided that vodka and beer have been selected, the function will return\n# =\u003e [\"1\", \"2\"]\n```\n\nSimilar to the `#select` method, you can also provide options through the DSL using the `choice` method for single entry and/or `choices` for more than one choice:\n\n```crystal\nprompt.multi_select(\"Select drinks?\") do |menu|\n  menu.choice :vodka, \"1\"\n  menu.choice :beer, \"2\"\n  menu.choice :wine, \"3\"\n  menu.choices whisky: \"4\", bourbon: \"5\"\nend\n```\n\nTo mark choice(s) as selected use the `default` option with index(s) of the option(s) starting from 1:\n\n```crystal\nprompt.multi_select(\"Select drinks?\") do |menu|\n  menu.default 2, 5\n\n  menu.choice :vodka,   \"1\"\n  menu.choice :beer,    \"2\"\n  menu.choice :wine,    \"3\"\n  menu.choice :whisky,  \"4\"\n  menu.choice :bourbon, \"5\"\nend\n# =\u003e\n# Select drinks? beer, bourbon\n#   ⬡ vodka\n#   ⬢ beer\n#   ⬡ wine\n#   ⬡ whisky\n# ‣ ⬢ bourbon\n```\n\nLike select, for ordered choices set `separator` to any delimiter String. In that way, you can use arrows keys and the numbers (0-9) to select the item.\n\n```crystal\nprompt.multi_select(\"Select drinks?\") do |menu|\n  menu.separator \")\"\n\n  menu.choice :vodka,   \"1\"\n  menu.choice :beer,    \"2\"\n  menu.choice :wine,    \"3\"\n  menu.choice :whisky,  \"4\"\n  menu.choice :bourbon, \"5\"\nend\n# =\u003e\n# Select drinks? beer, bourbon\n#   ⬡ 1) vodka\n#   ⬢ 2) beer\n#   ⬡ 3) wine\n#   ⬡ 4) whisky\n# ‣ ⬢ 5) bourbon\n```\n\nAnd when you press enter you will see the following selected:\n\n```\n# Select drinks? beer, bourbon\n# =\u003e [\"2\", \"5\"]\n```\n\nAlso like, `select`, the method takes an option `cycle` (which defaults to false), which lets you configure whether the selection should cycle around when reaching the top/bottom of the list:\n\n```crystal\nprompt.multi_select(\"Select drinks?\", %w(vodka beer wine), cycle: true)\n```\n\nYou can configure help message and/or marker like so\n\n```crystal\nchoices = {vodka: \"1\", beer: \"2\", wine: \"3\", whisky: \"4\", bourbon: \"5\"}\nprompt.multi_select(\"Select drinks?\", choices, help: \"Press beer can against keyboard\")\n# =\u003e\n# Select drinks? (Press beer can against keyboard)\"\n# ‣ ⬡ vodka\n#   ⬡ beer\n#   ⬡ wine\n#   ⬡ whisky\n#   ⬡ bourbon\n```\n\nBy default the menu is paginated if selection grows beyond `6` items. To change this setting use the `:page_size` option:\n\n```crystal\nletters = (\"A\"..\"Z\").to_a\nprompt.multi_select(\"Choose your letter?\", letters, page_size: 4)\n# =\u003e\n# Which letter? (Use ↑/↓ and ←/→ arrow keys, press Space to select and Enter to finish)\n# ‣ ⬡ A\n#   ⬡ B\n#   ⬡ C\n#   ⬡ D\n```\n\n#### `:disabled`\n\nTo disable menu choice, use the `:disabled` key with a value that explains the reason for the choice being unavailable. For example, out of all drinks, the sake and beer are currently out of stock:\n\ndrinks = [\n  \"bourbon\",\n  {name: \"sake\", disabled: \"(out of stock)\"},\n  \"vodka\",\n  {name: \"beer\", disabled: \"(out of stock)\"},\n  \"wine\",\n  \"whisky\"\n]\n\nThe disabled choice will be displayed with a cross `✘` character next to it and followed by an explanation:\n\n```crystal\nprompt.multi_select(\"Choose your favourite drink?\", drinks)\n# =\u003e\n# Choose your favourite drink? (Use ↑/↓ arrow keys, press Space to select and Enter to finish)\n# ‣ ⬡ bourbon\n#   ✘ sake (out of stock)\n#   ⬡ vodka\n#   ✘ beer (out of stock)\n#   ⬡ wine\n#   ⬡ whisky\n```\n\n#### `:echo`\n\nTo control whether the selected items are shown on the question header use the `:echo` option:\n\n```crystal\nchoices = %w(vodka beer wine whisky bourbon)\nprompt.multi_select(\"Select drinks?\", choices, echo: false)\n# =\u003e\n# Select drinks?\n#   ⬡ vodka\n#   ⬢ 2) beer\n#   ⬡ 3) wine\n#   ⬡ 4) whisky\n# ‣ ⬢ 5) bourbon\n```\n\n#### `:filter`\n\nTo activate dynamic list filtering on letter/number typing, use the `:filter` option:\n\n```crystal\nchoices = %w(vodka beer wine whisky bourbon)\nprompt.multi_select(\"Select drinks?\", choices, filter: true)\n# =\u003e\n# Select drinks? (Use ↑/↓ arrow keys, press Space to select and Enter to finish, and letter keys to filter)\n# ‣ ⬡ vodka\n#   ⬡ beer\n#   ⬡ wine\n#   ⬡ whisky\n#   ⬡ bourbon\n```\n\n#### `:filter`\n\nTo activate dynamic list filtering on letter/number typing, use the `:filter` option:\n\n```crystal\nchoices = %w(vodka beer wine whisky bourbon)\nprompt.multi_select(\"Select drinks?\", choices, filter: true)\n# =\u003e\n# Select drinks? (Use ↑/↓ arrow keys, press Space to select and Enter to finish, and letter keys to filter)\n# ‣ ⬡ vodka\n#   ⬡ beer\n#   ⬡ wine\n#   ⬡ whisky\n#   ⬡ bourbon\n```\n\nAfter the user presses \"w\":\n\n```\n# Select drinks? (Filter: \"w\")\n# ‣ ⬡ wine\n#   ⬡ whisky\n```\n\nFilter characters can be deleted partially or entirely via Backspace and Delete respectively.\n\nIf the user changes or deletes a filter, the choices previously selected remain selected.\n\nThe filter option is not compatible with `:separator`.\n\n#### `:min`\n\nTo force the minimum number of choices an user must select, use the `:min` option:\n\n```crystal\nchoices = %w(vodka beer wine whisky bourbon)\nprompt.multi_select(\"Select drinks?\", choices, min: 3)\n# =\u003e\n# Select drinks? (min. 3) vodka, beer\n#   ⬢ vodka\n#   ⬢ beer\n#   ⬡ wine\n#   ⬡ wiskey\n# ‣ ⬡ bourbon\n```\n\n#### `:max`\n\nTo limit the number of choices an user can select, use the `:max` option:\n\n```crystal\nchoices = %w(vodka beer wine whisky bourbon)\nprompt.multi_select(\"Select drinks?\", choices, max: 3)\n# =\u003e\n# Select drinks? (max. 3) vodka, beer, whisky\n#   ⬢ vodka\n#   ⬢ beer\n#   ⬡ wine\n#   ⬢ whisky\n# ‣ ⬡ bourbon\n```\n\n### `#enum_select`\n\nIn order to ask for standard selection from indexed list you can use `#enum_select` and pass question together with possible choices:\n\n```crystal\nchoices = %w(emacs nano vim)\nprompt.enum_select(\"Select an editor?\")\n# =\u003e\n#\n# Select an editor?\n#   1) nano\n#   2) vim\n#   3) emacs\n#   Choose 1-3 [1]:\n```\n\nSimilar to `select` and `multi_select`, you can provide question options through DSL using choice method and/or choices like so:\n\n```crystal\nchoices = %w(nano vim emacs)\nprompt.enum_select(\"Select an editor?\") do |menu|\n  menu.choice \"nano\",  \"/bin/nano\"\n  menu.choice \"vim\",   \"/usr/bin/vim\"\n  menu.choice \"emacs\", \"/usr/bin/emacs\"\nend\n# =\u003e\n#\n# Select an editor?\n#   1) nano\n#   2) vim\n#   3) emacs\n#   Choose 1-3 [1]:\n#\n# Select an editor? /bin/nano\n```\n\nYou can change the indexed numbers by passing `separator` option and the default option by using default like so\n\n```crystal\nchoices = %w(nano vim emacs)\nprompt.enum_select(\"Select an editor?\") do |menu|\n  menu.default 2\n  menu.separator \".\"\n\n  menu.choice \"nano\",  \"/bin/nano\"\n  menu.choice \"vim\",   \"/usr/bin/vim\"\n  menu.choice \"emacs\", \"/usr/bin/emacs\"\nend\n# =\u003e\n#\n# Select an editor?\n#   1. nano\n#   2. vim\n#   3. emacs\n#   Choose 1-3 [2]:\n#\n# Select an editor? /usr/bin/vim\n```\n\n#### `:page_size`\n\nBy default the menu is paginated if selection grows beyond `6` items. To change this setting use `:page_size` configuration.\n\n```crystal\nletters = (\"A\"..\"Z\").to_a\nprompt.enum_select(\"Choose your letter?\", letters, page_size: 4)\n# =\u003e\n# Which letter?\n#   1) A\n#   2) B\n#   3) C\n#   4) D\n#   Choose 1-26 [1]:\n# (Press tab/right or left to reveal more choices)\n```\n\n#### `:disabled`\n\nTo make a choice unavailable use the `:disabled` option and, if you wish, provide a reason:\n\n```crystal\nchoices = [\n  {name: \"Emacs\", disabled: \"(not installed)\"},\n  \"Atom\",\n  \"GNU nano\",\n  {name: \"Notepad++\", disabled: \"(not installed)\"},\n  \"Sublime\",\n  \"Vim\"\n]\n```\n\nThe disabled choice will be displayed with a cross `✘` character next to it and followed by an explanation:\n\n```crystal\nprompt.enum_select(\"Select an editor\", choices)\n# =\u003e\n# Select an editor\n# ✘ 1) Emacs (not installed)\n#   2) Atom\n#   3) GNU nano\n# ✘ 4) Notepad++ (not installed)\n#   5) Sublime\n#   6) Vim\n#   Choose 1-6 [2]:\n```\n\n### `#expand`\n\nThe `expand` method provides a compact way to ask a question with many options.\n\nThe first argument to expand is the message to display, and the second is a list of choices. As opposed to `select`, `multi_select`, and `enum_select`, the choices need to be NamedTuples which include the `key`, `name`, and `value` keys (all strings). The key must be a single character. The help choice is added automatically as the last option under the \u003ckbd\u003eh\u003c/kbd\u003e key.\n\n```crystal\nchoices = [\n  {\n    key: \"y\",\n    name: \"overwrite this file\",\n    value: \"yes\"\n  }, {\n    key: \"n\",\n    name: \"do not overwrite this file\",\n    value: \"no\"\n  }, {\n    key: \"q\",\n    name: \"quit; do not overwrite this file \",\n    value: \"quit\"\n  }\n]\n```\n\nThe choices can also be provided through the DSL using the `choice` method:\n\n```crystal\nprompt.expand(\"Overwrite shard.yml?\") do |q|\n  q.choice key: \"y\", name: \"Overwrite\"      value: \"ok\"\n  q.choice key: \"n\", name: \"Skip\",          value: \"no\"\n  q.choice key: \"a\", name: \"Overwrite all\", value: \"all\"\n  q.choice key: \"d\", name: \"Show diff\",     value: \"diff\"\n  q.choice key: \"q\", name: \"Quit\",          value: \"quit\"\nend\n```\n\nThe first element in the array of choices or provided via the choice DSL will be the default choice, you can change that by passing default option.\n\n```crystal\nprompt.expand('Overwrite shard.yml?', choices, default: 1)\n# =\u003e\n# Overwrite shard.yml? (enter \"h\" for help) [y,n,q,h]\n```\n\nEach time user types an option a hint will be displayed:\n\n```crystal\n# Overwrite shard.yml? (enter \"h\" for help) [y,n,a,d,q,h] y\n# \u003e\u003e overwrite this file\n```\n\nIf user types h and presses enter, an expanded view will be shown which further allows to refine the choice:\n\n```crystal\n# Overwrite shard.yml?\n#   y - overwrite this file\n#   n - do not overwrite this file\n#   q - quit; do not overwrite this file\n#   h - print help\n#   Choice [y]:\n```\n\nRun `examples/expand.cr` to see the prompt in action.\n\n#### `:auto_hint`\n\nTo show hint by default use te `:auto_hint` option:\n\n```crystal\nprompt.expand('Overwrite Gemfile?', choices, auto_hint: true)\n# =\u003e\n# Overwrite shard.yml? (enter \"h\" for help) [y,n,q,h]\n# \u003e\u003e overwrite this file\n```\n\n### `#slider`\n\nIf you have constrained range of numbers for user to choose from you may consider using a `slider`.\n\nThe slider provides easy visual way of picking a value marked with the `●` symbol. You can set `:min` (defaults to 0), `:max`, and `:step` (defaults to 1) options to configure slider range:\n\n```crystal\nprompt.slider(\"Volume\", max: 100, step: 5)\n# =\u003e\n# Volume ──────────●────────── 50\n# (Use arrow keys, press Enter to select)\n```\n\nYou can also change the default slider formatting using the `:format`. The value must contain the `:slider` token to show current value and any `sprintf` compatible flag for number display, in our case `%d`:\n\n```crystal\nprompt.slider(\"Volume\", max: 100, step: 5, default: 75, format: \"|:slider| %d%%\")\n# =\u003e\n# Volume |───────────────●──────| 75%\n# (Use arrow keys, press Enter to select)\n```\n\nAs of now only whole numbers are supported.\n\nIf you wish to change the slider handle and the slider range display use `:symbols` option:\n\n```crystal\nprompt.slider(\"Volume\", max: 100, step: 5, default: 75, symbols: {bullet: \"x\", line: \"_\"})\n# =\u003e\n# Volume _______________x______ 75%\n# (Use arrow keys, press Enter to select)\n```\n\nSlider can be configured through a DSL as well:\n\n```crystal\nprompt.slider(\"What size?\") do |range|\n  range.max 100\n  range.step 5\n  range.default 75\n  range.format \"|:slider| %d%\"\nend\n# =\u003e\n# Volume |───────────────●──────| 75%\n# (Use arrow keys, press Enter to select)\n```\n\n### `#say`\n\nTo simply print message out to standard output use `say` like so:\n\n```crystal\nprompt.say(...)\n```\n\nThe `say` method also accepts option `:color` which supports all the colors provided by [Cor](https://github.com/watzon/cor), as well as a Cor object itself, or an `{R, G, B}` tuple.\n\n`Term::Prompt` provides more specific versions of `say` method to better express intention behind the message such as `ok`, `warn`, and `error`.\n\n#### `#ok`\n\nTo print message(s) in green do:\n\n```crystal\nprompt.ok(...)\n```\n\n#### `#warn`\n\nTo print message(s) in yellow do:\n\n```crystal\nprompt.warn(...)\n```\n\n#### `#error`\n\nTo print message(s) in red do:\n\n```crystal\nprompt.error(...)\n```\n\n## Settings\n\n### `:symbols`\n\nMany prompts use symbols to display information. You can overwrite the default symbols for all the prompts using the `:symbols` key and hash of symbol names as value:\n\n```crystal\nprompt = Term::Prompt.new(symbols: { marker: \"\u003e\" })\n```\n\nThe following symbols can be overwritten:\n\n\n| Symbols     | Unicode | ASCII |\n| ----------- |:-------:|:-----:|\n|  tick       | `✓`     | `√`   |\n|  cross      | `✘`     | `x`   |\n|  marker     | `‣`     | `\u003e`   |\n|  dot        | `•`     | `.`   |\n|  bullet     | `●`     | `O`   |\n|  line       | `─`     | `-`   |\n|  radio_on   | `⬢`     | `(*)` |\n|  radio_off  | `⬡`     | `( )` |\n|  arrow_up   | `↑`     | `↑`   |\n|  arrow_down | `↓`     | `↓`   |\n|  arrow_left | `←`     | `←`   |\n|  arrow_right| `→`     | `→`   |\n\n### `:palette`\n\nColors are fetched from a `Palette` object, which contains 4 different colors. Their names and defaults are as follows:\n\n- `enabled` - `:dark_grey`\n- `active` - `:green`\n- `help` - `:dim_grey`\n- `error` - `:red`\n- `warning` - `:yellow`\n\nYou can provide your own palette object to change the colors. For example, to change the active color to pink:\n\n```crystal\npalette = Term::Prompt::Palette.new(active: :pink)\nprompt = Term::Prompt.new(palette: palette)\n```\n\n### `:interrupt`\n\nBy default `InputInterrupt` error will be raised when the user hits the interrupt key (Control-C). However, you can customize this behaviour by passing the `:interrupt` option. The available options are:\n\n    :signal - sends interrupt signal\n    :exit - exists with status code\n    :noop - skips handler\n\nFor example, to send interrupt signal do:\n\n```crystal\nprompt = Term::Prompt.new(interrupt: :signal)\n```\n\n### `:prefix`\n\nYou can prefix each question asked using the `:prefix` option. This option can be applied either globally for all prompts or individual for each one:\n\n```crystal\nprompt = Term::Prompt.new(prefix: \"[?] \")\n```\n\n## Contributing\n\n1. Fork it (\u003chttps://github.com/watzon/prompt/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Contributors\n\n- [Chris Watson](https://github.com/watzon) - creator and maintainer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrystal-term%2Fprompt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrystal-term%2Fprompt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrystal-term%2Fprompt/lists"}