{"id":48351038,"url":"https://github.com/chenasraf/input-form.nvim","last_synced_at":"2026-04-05T09:01:24.950Z","repository":{"id":349236173,"uuid":"1201545849","full_name":"chenasraf/input-form.nvim","owner":"chenasraf","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-04T22:42:49.000Z","size":404,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-04T23:30:13.969Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Lua","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/chenasraf.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-04T20:29:14.000Z","updated_at":"2026-04-04T22:42:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chenasraf/input-form.nvim","commit_stats":null,"previous_names":["chenasraf/input-form.nvim"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/chenasraf/input-form.nvim","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Finput-form.nvim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Finput-form.nvim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Finput-form.nvim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Finput-form.nvim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chenasraf","download_url":"https://codeload.github.com/chenasraf/input-form.nvim/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Finput-form.nvim/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31430011,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T08:13:15.228Z","status":"ssl_error","status_checked_at":"2026-04-05T08:13:11.839Z","response_time":75,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2026-04-05T09:01:19.157Z","updated_at":"2026-04-05T09:01:24.944Z","avatar_url":"https://github.com/chenasraf.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# input-form.nvim\n\nA small Neovim plugin for building bordered, keyboard-navigable **forms** in a\nfloating window. Create a single window containing multiple typed inputs\n(single-line text, multiline text, select dropdowns), collect results via an\n`on_submit` callback.\n\n![input form example](/input-form.gif)\n\n## Features\n\n- Bordered floating window with optional title\n- Keyboard-navigable: `\u003cTab\u003e` / `\u003cS-Tab\u003e` to move between inputs\n- Input types: `text`, `multiline`, `select`\n- Select dropdowns open with `\u003cCR\u003e`; arrows navigate; `\u003cCR\u003e` confirms\n- Submit with `\u003cC-s\u003e` — results delivered as a `{ [name] = value }` table\n- Cancel with `\u003cEsc\u003e`\n- Lazy: `create_form` builds the form; `:show()` renders it when you want\n- `:hide()` / `:show()` re-open a form while preserving in-progress values\n- Fully configurable keymaps, border, width, title\n- Auto-generated help doc (`:h input-form`)\n- Tested with `mini.test`\n\n## Installation\n\n### lazy.nvim\n\n```lua\n{\n  \"chenasraf/input-form.nvim\",\n  config = function()\n    require(\"input-form\").setup()\n  end,\n}\n```\n\n### packer.nvim\n\n```lua\nuse({\n  \"chenasraf/input-form.nvim\",\n  config = function()\n    require(\"input-form\").setup()\n  end,\n})\n```\n\n### vim-plug\n\n```vim\nPlug 'chenasraf/input-form.nvim'\nlua require('input-form').setup()\n```\n\n## Usage\n\n```lua\nlocal f = require(\"input-form\")\n\nlocal form = f.create_form({\n  inputs = {\n    { name = \"id\", label = \"Enter ID\", type = \"text\", default = \"sample ID\" },\n    {\n      name = \"choice\",\n      label = \"Select an option\",\n      type = \"select\",\n      options = {\n        { id = \"opt1\", label = \"Option 1\" },\n        { id = \"opt2\", label = \"Option 2\" },\n      },\n    },\n    { name = \"body\", label = \"Enter multiline text\", type = \"multiline\" },\n  },\n  on_submit = function(results)\n    vim.print(results) -- { id = \"...\", choice = \"opt1\", body = \"...\" }\n  end,\n  on_cancel = function()\n    vim.notify(\"cancelled\")\n  end,\n})\n\n-- Create once, show on demand:\nform:show()\n```\n\n`create_form` returns a form object. Nothing is rendered until you call\n`form:show()`. This lets you construct the form in one place and open it from a\nkeymap, autocommand, or anywhere else:\n\n```lua\nvim.keymap.set(\"n\", \"\u003cleader\u003exf\", function()\n  form:show()\nend)\n```\n\n### Form methods\n\n| Method         | Description                                                      |\n| -------------- | ---------------------------------------------------------------- |\n| `form:show()`  | Open the form. No-op if already visible.                         |\n| `form:hide()`  | Close windows but keep values so `:show()` resumes where you left off. |\n| `form:close()` | Permanently tear down the form.                                  |\n| `form:submit()`| Gather values, close, and invoke `on_submit(results)`.           |\n| `form:cancel()`| Close and invoke `on_cancel()` if provided.                      |\n| `form:results()`| Return `{ [name] = value }` without closing.                    |\n\n### Input spec reference\n\nAll inputs share `name` (string, required — the key in the result table) and\n`label` (string, shown above the field).\n\n#### `text`\n\n```lua\n{ name = \"id\", label = \"Enter ID\", type = \"text\", default = \"sample ID\" }\n```\n\n#### `multiline`\n\n```lua\n{ name = \"body\", label = \"Notes\", type = \"multiline\", default = \"\", height = 5 }\n```\n\n- `height` (optional) — number of rows for the input; falls back to\n  `config.multiline.height`.\n\n#### `select`\n\n```lua\n{\n  name = \"choice\",\n  label = \"Pick one\",\n  type = \"select\",\n  default = \"opt1\", -- optional; defaults to first option's id\n  options = {\n    { id = \"opt1\", label = \"Option 1\" },\n    { id = \"opt2\", label = \"Option 2\" },\n  },\n}\n```\n\n`value()` returns the selected `id` (not the label).\n\n## Validation\n\nEach input spec accepts an optional `validator` function:\n\n```lua\nvalidator = fun(value: any): string|nil\n```\n\nReturn a non-empty error message string to mark the input invalid, or `nil` /\n`\"\"` when valid. The error message is shown in the input's bottom border\n(red), and the border + label turn red too. Validation runs:\n\n- **On blur** — the first time the user leaves the field it is marked\n  \"touched\" and the validator runs. Nothing is shown before that.\n- **On change** — once touched, each buffer change re-runs the validator.\n- **On submit** — `form:submit()` force-validates every input (touched or\n  not). If any input has an error, submission is blocked, all errors are\n  rendered, and focus moves to the first invalid input.\n\n### Built-in validators\n\n```lua\nlocal V = require(\"input-form\").validators\n\nV.non_empty([msg])                  -- require a non-empty value\nV.min_length(n, [msg])              -- at least `n` characters\nV.max_length(n, [msg])              -- at most `n` characters\nV.matches(lua_pattern, [msg])       -- match a Lua pattern\nV.is_number([msg])                  -- tonumber() must succeed\nV.one_of({ \"a\", \"b\", ... }, [msg])  -- value must be in the list\nV.custom(predicate, msg)            -- wrap a `fun(v): boolean` predicate\nV.chain(v1, v2, ...)                -- run validators in order, first error wins\n```\n\nExample:\n\n```lua\nlocal f = require(\"input-form\")\nlocal V = f.validators\n\nf.create_form({\n  inputs = {\n    {\n      name = \"id\",\n      label = \"Enter ID\",\n      type = \"text\",\n      validator = V.chain(\n        V.non_empty(),\n        V.min_length(3),\n        V.matches(\"^[%w_-]+$\", \"Only letters, digits, - and _\")\n      ),\n    },\n    {\n      name = \"age\",\n      label = \"Age\",\n      type = \"text\",\n      validator = V.chain(V.non_empty(), V.is_number()),\n    },\n  },\n  on_submit = function(results)\n    vim.print(results) -- only runs if every validator passes\n  end,\n}):show()\n```\n\nCustom validators are just functions — no need to use the builder helpers if\nyou'd rather write one inline:\n\n```lua\nvalidator = function(value)\n  if value == \"admin\" then\n    return \"Username 'admin' is reserved\"\n  end\nend\n```\n\n### Select chevrons\n\nThe glyphs shown on the right side of `select` inputs to indicate the\ndropdown state are configurable under `style.chevron`:\n\n```lua\nrequire(\"input-form\").setup({\n  style = {\n    chevron = {\n      closed = \"⌄\", -- default\n      open   = \"⌃\", -- default\n    },\n  },\n})\n```\n\nUse whatever you like — e.g. ASCII fallbacks for terminals without good\nUnicode support:\n\n```lua\nstyle = { chevron = { closed = \" v\", open = \" ^\" } }\n```\n\nA leading space is recommended so the glyph doesn't sit flush against the\nlabel.\n\n### Highlight groups\n\nAll highlight groups the plugin uses are listed under `style.highlights` in\nthe config and can be overridden via `setup()`. Each entry is passed directly\nto `vim.api.nvim_set_hl(0, name, spec)`, so anything `nvim_set_hl` accepts\nworks (`fg`, `bg`, `link`, `bold`, `italic`, `default`, etc.).\n\n```lua\nrequire(\"input-form\").setup({\n  style = {\n    highlights = {\n      -- error state\n      InputFormFieldError       = { fg = \"#ff5555\", italic = true },\n      InputFormFieldErrorBorder = { fg = \"#ff5555\" },\n      InputFormFieldErrorTitle  = { fg = \"#ff5555\", bold = true },\n      -- help footer\n      InputFormHelp             = { fg = \"#88ccff\" },\n      -- parent frame border via link\n      InputFormBorder           = { link = \"Comment\" },\n    },\n  },\n})\n```\n\nAvailable groups:\n\n| Group                       | Purpose                                      |\n| --------------------------- | -------------------------------------------- |\n| `InputFormNormal`           | Parent form window background                |\n| `InputFormBorder`           | Parent form border                           |\n| `InputFormTitle`            | Parent form title                            |\n| `InputFormHelp`             | Footer help line (key hints)                 |\n| `InputFormField`            | Individual input field background            |\n| `InputFormFieldBorder`      | Individual input field border                |\n| `InputFormFieldTitle`       | Individual input field label (on top border) |\n| `InputFormFieldError`       | Error message footer on an invalid field     |\n| `InputFormFieldErrorBorder` | Invalid field border                         |\n| `InputFormFieldErrorTitle`  | Invalid field label                          |\n| `InputFormDropdown`         | Select dropdown background                   |\n| `InputFormDropdownActive`   | Highlighted dropdown row                     |\n\nUser overrides fully **replace** the default spec per group (they are not\ndeep-merged at the field level), so you don't need to re-specify\n`default = true`. Highlights are re-applied on every `form:show()`, so a\n`setup({ style = { highlights = ... } })` call that happens after the first\nform has been rendered still takes effect on the next open.\n\n## Configuration\n\nDefaults:\n\n```lua\nrequire(\"input-form\").setup({\n  window = {\n    border = \"rounded\",    -- any nvim_open_win border\n    width = 60,            -- number of columns; \u003c= 1 treated as ratio\n    title = \" Form \",\n    title_pos = \"center\",\n    winblend = 0,\n    padding = 0,           -- cells between the outer border and inputs (all sides)\n    gap = 0,               -- blank rows between adjacent inputs\n  },\n  keymaps = {\n    next = \"\u003cTab\u003e\",\n    prev = \"\u003cS-Tab\u003e\",\n    submit = \"\u003cC-s\u003e\",\n    cancel = \"\u003cEsc\u003e\",\n    open_select = \"\u003cCR\u003e\",\n  },\n  select = {\n    max_height = 10,\n  },\n  multiline = {\n    height = 5,\n  },\n})\n```\n\nPer-form overrides: pass `title` and/or `width` in the `create_form` spec.\n\n## Help\n\nHelp tags are registered automatically on the first `require('input-form')`,\nso `setup()` is not required for them either:\n\n```\n:h input-form\n```\n\n## For plugin developers — using input-form.nvim as a dependency\n\nYou can depend on `input-form.nvim` from another plugin without forcing your\nusers to call `setup()`. The module is safe to use immediately after require:\n\n```lua\n-- In your plugin's code:\nlocal ok, input_form = pcall(require, 'input-form')\nif not ok then\n  vim.notify('my-plugin: input-form.nvim is required', vim.log.levels.ERROR)\n  return\nend\n\ninput_form.create_form({\n  inputs = { ... },\n  on_submit = function(results) ... end,\n}):show()\n```\n\nKey points:\n\n- **No `setup()` required.** Defaults are loaded at module-load time and\n  `create_form` / `form:show()` work on a bare `require('input-form')`. End\n  users of your plugin don't need to know input-form.nvim exists.\n- **Per-form overrides.** Pass `title`, `width`, `on_cancel`, etc. directly in\n  the `create_form` spec — no need to mutate global config for one-off tweaks.\n- **Baseline config.** If your plugin wants a different baseline (say, a\n  non-default border style for all forms it opens), call\n  `require('input-form').setup({ ... })` once during your plugin's own\n  initialization. This is idempotent and safe to call even if the end user\n  has already called setup — later calls deep-merge over earlier ones.\n- **Respect the user.** Prefer per-form overrides over global `setup()` when\n  possible so you don't stomp on a user who has configured input-form.nvim\n  for their own keymaps or other plugins that use it.\n- **Declaring the dep.** With lazy.nvim, add it to your `dependencies`:\n  ```lua\n  {\n    'your-name/your-plugin.nvim',\n    dependencies = { 'chenasraf/input-form.nvim' },\n  }\n  ```\n\n## Contributing \u0026 development\n\n```\nmake deps           # install mini.nvim into deps/\nmake test           # run the test suite (mini.test)\nmake documentation  # regenerate doc/input-form.txt (mini.doc)\nmake lint           # stylua check\n```\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenasraf%2Finput-form.nvim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchenasraf%2Finput-form.nvim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenasraf%2Finput-form.nvim/lists"}