{"id":13794751,"url":"https://github.com/darrenburns/textual-autocomplete","last_synced_at":"2025-04-04T14:03:59.017Z","repository":{"id":64383728,"uuid":"569361959","full_name":"darrenburns/textual-autocomplete","owner":"darrenburns","description":"Easily add autocomplete dropdowns to your Textual apps","archived":false,"fork":false,"pushed_at":"2025-03-23T23:38:51.000Z","size":555,"stargazers_count":192,"open_issues_count":10,"forks_count":22,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-28T17:52:25.560Z","etag":null,"topics":["autocomplete","dropdown","library","python","terminal","textual","tui"],"latest_commit_sha":null,"homepage":"","language":"Python","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/darrenburns.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":"2022-11-22T16:49:26.000Z","updated_at":"2025-03-27T17:10:37.000Z","dependencies_parsed_at":"2024-05-29T01:43:07.812Z","dependency_job_id":"3cb90e6d-b9ce-41de-8533-1cc5e0251939","html_url":"https://github.com/darrenburns/textual-autocomplete","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darrenburns%2Ftextual-autocomplete","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darrenburns%2Ftextual-autocomplete/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darrenburns%2Ftextual-autocomplete/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darrenburns%2Ftextual-autocomplete/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/darrenburns","download_url":"https://codeload.github.com/darrenburns/textual-autocomplete/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247184813,"owners_count":20897840,"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":["autocomplete","dropdown","library","python","terminal","textual","tui"],"created_at":"2024-08-03T23:00:47.341Z","updated_at":"2025-04-04T14:03:58.986Z","avatar_url":"https://github.com/darrenburns.png","language":"Python","funding_links":[],"categories":["Community","Python"],"sub_categories":["Third Party Applications"],"readme":"# textual-autocomplete\n\nA simple autocomplete dropdown library for [Textual](https://github.com/textualize/textual) `Input` widgets.\n\n![autocomplete-readme-header](https://github.com/user-attachments/assets/eda6f78a-fbaa-4a5b-ac1d-223e41f6eabb)\n\nCompatible with **Textual 2.0 and above**.\n\n## Installation\n\nI recommend using [uv](https://docs.astral.sh/uv/) to manage your dependencies and install `textual-autocomplete`:\n\n```bash\nuv add textual-autocomplete\n```\n\nIf you prefer `pip`, `poetry`, or something else, those will work too.\n\n## Quick Start\n\nHere's the simplest possible way to add autocomplete to your Textual app:\n\n```python\nfrom textual.app import App, ComposeResult\nfrom textual.widgets import Input\nfrom textual_autocomplete import AutoComplete, DropdownItem\n\nclass ColorFinder(App):\n    def compose(self) -\u003e ComposeResult:\n        # Create a standard Textual input\n        text_input = Input(placeholder=\"Type a color...\")\n        yield text_input\n\n        # Add an autocomplete to the same screen, and pass in the input widget.\n        yield AutoComplete(\n            text_input,  # Target input widget\n            candidates=[\"Red\", \"Green\", \"Blue\", \"Yellow\", \"Purple\", \"Orange\"]\n        )\n\nif __name__ == \"__main__\":\n    app = ColorFinder()\n    app.run()\n```\n\nThat's it! As you type in the input field, matching options will appear in a dropdown below.\n\n## Core Features\n\n- 🔍 **Fuzzy matching** - Find matches even with typos\n- ⌨️ **Keyboard navigation** - Arrow keys, Tab, Enter, and Escape\n- 🎨 **Rich styling options** - Customizable highlighting and appearance\n- 📝 **Dynamic content** - Supply items as a list or from a callback function\n- 🔍 **Path completions** - Built-in support for filesystem path completions\n\n## Examples\n\n### With Left Metadata Column\n\nAdd a metadata column (like icons) to provide additional context.\nThese columns are display-only, and do not influence the search process.\n\n```python\nfrom textual.app import App, ComposeResult\nfrom textual.widgets import Input\nfrom textual_autocomplete import AutoComplete, DropdownItem\n\n# Create dropdown items with a left metadata column.\nITEMS = [\n    DropdownItem(main=\"Python\", prefix=\"🐍\"),\n    DropdownItem(main=\"JavaScript\", prefix=\"📜\"),\n    DropdownItem(main=\"TypeScript\", prefix=\"🔷\"),\n    DropdownItem(main=\"Java\", prefix=\"☕\"),\n]\n\nclass LanguageSearcher(App):\n    def compose(self) -\u003e ComposeResult:\n        text_input = Input(placeholder=\"Programming language...\")\n        yield text_input\n        yield AutoComplete(text_input, candidates=ITEMS)\n\nif __name__ == \"__main__\":\n    app = LanguageSearcher()\n    app.run()\n```\n\n### Styled Two-Column Layout\n\nAdd rich styling to your metadata columns using [Textual markup](https://textual.textualize.io/guide/content/#markup).\n\n```python\nfrom textual.app import App, ComposeResult\nfrom textual.content import Content\nfrom textual.widgets import Input, Label\nfrom textual_autocomplete import AutoComplete, DropdownItem\n\n# Languages with their popularity rank\nLANGUAGES_WITH_RANK = [\n    (1, \"Python\"),\n    (2, \"JavaScript\"),\n    (3, \"Java\"),\n    (4, \"C++\"),\n    (5, \"TypeScript\"),\n    (6, \"Go\"),\n    (7, \"Ruby\"),\n    (8, \"Rust\"),\n]\n\n# Create dropdown items with styled rank in prefix\nCANDIDATES = [\n    DropdownItem(\n        language,  # Main text to be completed\n        prefix=Content.from_markup(\n            f\"[$text-primary on $primary-muted] {rank:\u003e2} \"\n        ),  # Prefix with styled rank\n    )\n    for rank, language in LANGUAGES_WITH_RANK\n]\n\nclass LanguageSearcher(App):\n    def compose(self) -\u003e ComposeResult:\n        yield Label(\"Start typing a programming language:\")\n        text_input = Input(placeholder=\"Type here...\")\n        yield text_input\n        yield AutoComplete(target=text_input, candidates=CANDIDATES)\n\nif __name__ == \"__main__\":\n    app = LanguageSearcher()\n    app.run()\n```\n\n## Keyboard Controls\n\n- **↑/↓** - Navigate through options\n- **↓** - Summon the dropdown\n- **Enter/Tab** - Complete the selected option\n- **Escape** - Hide dropdown\n\n## Styling\n\nThe dropdown can be styled using Textual CSS:\n\n```css\n    AutoComplete {\n        /* Customize the dropdown */\n        \u0026 AutoCompleteList {\n            max-height: 6;  /* The number of lines before scrollbars appear */\n            color: $text-primary;  /* The color of the text */\n            background: $primary-muted;  /* The background color of the dropdown */\n            border-left: wide $success;  /* The color of the left border */\n        }\n\n        /* Customize the matching substring highlighting */\n        \u0026 .autocomplete--highlight-match {\n            color: $text-accent;\n            text-style: bold;\n        }\n\n        /* Customize the text the cursor is over */\n        \u0026 .option-list--option-highlighted {\n            color: $text-success;\n            background: $error 50%;  /* 50% opacity, blending into background */\n            text-style: italic;  \n        }\n    }\n```\n\nHere's what that looks like when applied:\n\n\u003cimg width=\"226\" alt=\"image\" src=\"https://github.com/user-attachments/assets/3fae3ecf-fdd3-4ff5-ac37-7ef3088c596e\" /\u003e\n\nBy using Textual CSS like in the example above, you can ensure the shades of colors remain\nconsistent across different themes. Here's the same dropdown with the Textual app theme switched to `gruvbox`:\n\n\u003cimg width=\"234\" alt=\"image\" src=\"https://github.com/user-attachments/assets/6bc4804d-7a4b-41ab-bba9-5745d87648b9\" /\u003e\n\n### Styling the prefix\n\nYou can style the prefix using Textual Content markup.\n\n```python\nDropdownItem(\n    main=\"Python\",\n    prefix=Content.from_markup(\n        \"[$text-success on $success-muted] 🐍\"\n    ),\n)\n```\n\n## Completing Paths\n\n`textual-autocomplete` includes a `PathAutoComplete` widget that can be used to autocomplete filesystem paths.\n\n```python\nfrom textual.app import App, ComposeResult\nfrom textual.containers import Container\nfrom textual.widgets import Button, Input, Label\n\nfrom textual_autocomplete import PathAutoComplete\n\nclass FileSystemPathCompletions(App[None]):\n    def compose(self) -\u003e ComposeResult:\n        yield Label(\"Choose a file!\", id=\"label\")\n        input_widget = Input(placeholder=\"Enter a path...\")\n        yield input_widget\n        yield PathAutoComplete(target=input_widget, path=\"../textual\")\n\n\nif __name__ == \"__main__\":\n    app = FileSystemPathCompletions()\n    app.run()\n```\n\nHere's what that looks like in action:\n\nhttps://github.com/user-attachments/assets/25b80e34-0a35-4962-9024-f2dab7666689\n\n`PathAutoComplete` has a bunch of parameters that can be used to customize the behavior - check the docstring for more details. It'll also cache directory contents after reading them once - but you can clear the cache if you need to using the `clear_directory_cache` method.\n\n## Dynamic Data with Callbacks\n\nInstead of supplying a static list of candidates, you can supply a callback function which returns a list of `DropdownItem` (candidates) that will be searched against.\n\nThis callback function will be called anytime the text in the target input widget changes or the cursor position changes (and since the cursor position changes when the user inserts text, you can expect 2 calls to this function for most keystrokes - cache accordingly if this is a problem).\n\nThe app below displays the length of the text in the input widget in the prefix of the dropdown items.\n\n```python\nfrom textual.app import App, ComposeResult\nfrom textual.widgets import Input\n\nfrom textual_autocomplete import AutoComplete\nfrom textual_autocomplete._autocomplete import DropdownItem, TargetState\n\n\nclass DynamicDataApp(App[None]):\n    def compose(self) -\u003e ComposeResult:\n        input_widget = Input()\n        yield input_widget\n        yield AutoComplete(input_widget, candidates=self.candidates_callback)\n\n    def candidates_callback(self, state: TargetState) -\u003e list[DropdownItem]:\n        left = len(state.text)\n        return [\n            DropdownItem(item, prefix=f\"{left:\u003e2} \")\n            for item in [\n                \"Apple\",\n                \"Banana\",\n                \"Cherry\",\n                \"Orange\",\n                \"Pineapple\",\n                \"Strawberry\",\n                \"Watermelon\",\n            ]\n        ]\n\n\nif __name__ == \"__main__\":\n    app = DynamicDataApp()\n    app.run()\n```\n\nNotice the count displayed in the prefix increment and decrement based on the character count in the input.\n\n![Screen Recording 2025-03-18 at 18 26 42](https://github.com/user-attachments/assets/ca0e039b-8ae0-48ac-ba96-9ec936720ded)\n\n## Customizing Behavior\n\nIf you need custom behavior, `AutoComplete` can be subclassed.\n\nA good example of how to subclass and customize behavior is the `PathAutoComplete` widget, which is a subclass of `AutoComplete`.\n\nSome methods you may want to be aware of which you can override:\n\n- `get_candidates`: Return a list of `DropdownItem` objects - called each time the input changes or the cursor position changes. Note that if you're overriding this in a subclass, you'll need to make sure that the `get_candidates` parameter passed into the `AutoComplete` constructor is set to `None` - this tells `textual-autocomplete` to use the subclassed method instead of the default.\n- `get_search_string`: The string that will be used to filter the candidates. You may wish to only use a portion of the input text to filter the candidates rather than the entire text.\n- `apply_completion`: Apply the completion to the target input widget. Receives the value the user selected from the dropdown and updates the `Input` directly using it's API.\n- `post_completion`: Called when a completion is selected. Called immediately after `apply_completion`. The default behaviour is just to hide the completion dropdown (after performing a completion, we want to immediately hide the dropdown in the default case).\n\n## More Examples\n\nCheck out the [examples directory](./examples) for more runnable examples.\n\n## Contributing\n\nContributions are welcome! Feel free to open issues or submit pull requests on GitHub.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarrenburns%2Ftextual-autocomplete","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdarrenburns%2Ftextual-autocomplete","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarrenburns%2Ftextual-autocomplete/lists"}