From 0832c185c0d62a7eab9a7d6fb5a91a83c3e77376 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 4 May 2025 22:09:41 +0200 Subject: [PATCH] Adding upstream version 4.0.4. Signed-off-by: Daniel Baumann --- .github/workflows/ci.yml | 32 + .gitignore | 163 +++ .vscode/settings.json | 7 + LICENSE | 21 + README.md | 285 +++++ examples/_headers.py | 767 +++++++++++++ examples/basic_single_column.py | 35 + examples/basic_two_column.py | 47 + examples/basic_two_column_heavy_styles.py | 55 + examples/dynamic_data.py | 38 + examples/left_column_styling.py | 39 + examples/partial_completions.py | 43 + examples/path_completions_absolute.py | 43 + pyproject.toml | 38 + ...sor_and_cursor_prefix_as_search_string.svg | 156 +++ ..._click_and_cursor_prefix_search_string.svg | 156 +++ ...ks_terminal_cursor_when_parent_scrolls.svg | 156 +++ ...st_candidate_can_be_selected_via_click.svg | 154 +++ ...hosen_while_input_widget_has_selection.svg | 154 +++ ...ide_after_summoning_by_pressing_escape.svg | 155 +++ ...t_hide_after_typing_by_pressing_escape.svg | 154 +++ .../test_many_matching_candidates.svg | 155 +++ ...tocomplete_dropdowns_on_a_single_input.svg | 153 +++ ..._autocomplete_dropdowns_on_same_screen.svg | 155 +++ ...idate_should_complete_input__enter_key.svg | 154 +++ ...ndidate_should_complete_input__tab_key.svg | 154 +++ .../test_single_matching_candidate.svg | 155 +++ .../test_summon_by_pressing_down.svg | 155 +++ ...ssing_down_after_performing_completion.svg | 155 +++ ..._one_full_match_does_not_show_dropdown.svg | 154 +++ .../test_tab_still_works_after_completion.svg | 154 +++ ...ction_works_while_autocomplete_is_open.svg | 155 +++ ...est_background_color_and_removed_style.svg | 153 +++ ..._change_and_dropdown_background_change.svg | 156 +++ ...st_dropdown_styles_match_textual_theme.svg | 153 +++ .../test_foreground_color_and_text_style.svg | 153 +++ .../test_max_height_and_scrolling.svg | 154 +++ tests/snapshots/test_cursor_tracking.py | 81 ++ tests/snapshots/test_function_candidates.py | 3 + tests/snapshots/test_input.py | 255 +++++ tests/snapshots/test_prevent_default.py | 49 + tests/snapshots/test_styling.py | 112 ++ textual_autocomplete/__init__.py | 18 + textual_autocomplete/_autocomplete.py | 550 +++++++++ textual_autocomplete/_path_autocomplete.py | 187 +++ textual_autocomplete/fuzzy_search.py | 162 +++ textual_autocomplete/py.typed | 0 uv.lock | 1012 +++++++++++++++++ 48 files changed, 7595 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/_headers.py create mode 100644 examples/basic_single_column.py create mode 100644 examples/basic_two_column.py create mode 100644 examples/basic_two_column_heavy_styles.py create mode 100644 examples/dynamic_data.py create mode 100644 examples/left_column_styling.py create mode 100644 examples/partial_completions.py create mode 100644 examples/path_completions_absolute.py create mode 100644 pyproject.toml create mode 100644 tests/snapshots/__snapshots__/test_cursor_tracking/test_dropdown_tracks_input_cursor_and_cursor_prefix_as_search_string.svg create mode 100644 tests/snapshots/__snapshots__/test_cursor_tracking/test_dropdown_tracks_input_cursor_on_click_and_cursor_prefix_search_string.svg create mode 100644 tests/snapshots/__snapshots__/test_cursor_tracking/test_dropdown_tracks_terminal_cursor_when_parent_scrolls.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_candidate_can_be_selected_via_click.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_completion_still_works_if_chosen_while_input_widget_has_selection.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_hide_after_summoning_by_pressing_escape.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_hide_after_typing_by_pressing_escape.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_many_matching_candidates.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_multiple_autocomplete_dropdowns_on_a_single_input.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_multiple_autocomplete_dropdowns_on_same_screen.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_selecting_candidate_should_complete_input__enter_key.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_selecting_candidate_should_complete_input__tab_key.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_single_matching_candidate.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_summon_by_pressing_down.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_summon_by_pressing_down_after_performing_completion.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_summon_when_only_one_full_match_does_not_show_dropdown.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_tab_still_works_after_completion.svg create mode 100644 tests/snapshots/__snapshots__/test_input/test_text_selection_works_while_autocomplete_is_open.svg create mode 100644 tests/snapshots/__snapshots__/test_styling/test_background_color_and_removed_style.svg create mode 100644 tests/snapshots/__snapshots__/test_styling/test_cursor_color_change_and_dropdown_background_change.svg create mode 100644 tests/snapshots/__snapshots__/test_styling/test_dropdown_styles_match_textual_theme.svg create mode 100644 tests/snapshots/__snapshots__/test_styling/test_foreground_color_and_text_style.svg create mode 100644 tests/snapshots/__snapshots__/test_styling/test_max_height_and_scrolling.svg create mode 100644 tests/snapshots/test_cursor_tracking.py create mode 100644 tests/snapshots/test_function_candidates.py create mode 100644 tests/snapshots/test_input.py create mode 100644 tests/snapshots/test_prevent_default.py create mode 100644 tests/snapshots/test_styling.py create mode 100644 textual_autocomplete/__init__.py create mode 100644 textual_autocomplete/_autocomplete.py create mode 100644 textual_autocomplete/_path_autocomplete.py create mode 100644 textual_autocomplete/fuzzy_search.py create mode 100644 textual_autocomplete/py.typed create mode 100644 uv.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ccfd9df --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: Continuous Integration + +on: + pull_request: + push: + branches: + - "main" + +env: + PYTEST_ADDOPTS: "--color=yes" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install a specific version of uv + uses: astral-sh/setup-uv@v5 + with: + version: "0.6.3" + enable-cache: true + + - name: Set up Python + run: uv python install 3.9 + + - name: Install Dependencies + run: uv sync --all-extras --dev + + - name: Run Tests + run: | + uv run pytest tests/ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8433d0d --- /dev/null +++ b/.gitignore @@ -0,0 +1,163 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +snapshot_report.html +sandbox/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9b38853 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b73b759 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Darren Burns + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddaaee5 --- /dev/null +++ b/README.md @@ -0,0 +1,285 @@ +# textual-autocomplete + +A simple autocomplete dropdown library for [Textual](https://github.com/textualize/textual) `Input` widgets. + +![autocomplete-readme-header](https://github.com/user-attachments/assets/eda6f78a-fbaa-4a5b-ac1d-223e41f6eabb) + +Compatible with **Textual 2.0 and above**. + +## Installation + +I recommend using [uv](https://docs.astral.sh/uv/) to manage your dependencies and install `textual-autocomplete`: + +```bash +uv add textual-autocomplete +``` + +If you prefer `pip`, `poetry`, or something else, those will work too. + +## Quick Start + +Here's the simplest possible way to add autocomplete to your Textual app: + +```python +from textual.app import App, ComposeResult +from textual.widgets import Input +from textual_autocomplete import AutoComplete, DropdownItem + +class ColorFinder(App): + def compose(self) -> ComposeResult: + # Create a standard Textual input + text_input = Input(placeholder="Type a color...") + yield text_input + + # Add an autocomplete to the same screen, and pass in the input widget. + yield AutoComplete( + text_input, # Target input widget + candidates=["Red", "Green", "Blue", "Yellow", "Purple", "Orange"] + ) + +if __name__ == "__main__": + app = ColorFinder() + app.run() +``` + +That's it! As you type in the input field, matching options will appear in a dropdown below. + +## Core Features + +- 🔍 **Fuzzy matching** - Find matches even with typos +- ⌨️ **Keyboard navigation** - Arrow keys, Tab, Enter, and Escape +- 🎨 **Rich styling options** - Customizable highlighting and appearance +- 📝 **Dynamic content** - Supply items as a list or from a callback function +- 🔍 **Path completions** - Built-in support for filesystem path completions + +## Examples + +### With Left Metadata Column + +Add a metadata column (like icons) to provide additional context. +These columns are display-only, and do not influence the search process. + +```python +from textual.app import App, ComposeResult +from textual.widgets import Input +from textual_autocomplete import AutoComplete, DropdownItem + +# Create dropdown items with a left metadata column. +ITEMS = [ + DropdownItem(main="Python", prefix="🐍"), + DropdownItem(main="JavaScript", prefix="📜"), + DropdownItem(main="TypeScript", prefix="🔷"), + DropdownItem(main="Java", prefix="☕"), +] + +class LanguageSearcher(App): + def compose(self) -> ComposeResult: + text_input = Input(placeholder="Programming language...") + yield text_input + yield AutoComplete(text_input, candidates=ITEMS) + +if __name__ == "__main__": + app = LanguageSearcher() + app.run() +``` + +### Styled Two-Column Layout + +Add rich styling to your metadata columns using [Textual markup](https://textual.textualize.io/guide/content/#markup). + +```python +from textual.app import App, ComposeResult +from textual.content import Content +from textual.widgets import Input, Label +from textual_autocomplete import AutoComplete, DropdownItem + +# Languages with their popularity rank +LANGUAGES_WITH_RANK = [ + (1, "Python"), + (2, "JavaScript"), + (3, "Java"), + (4, "C++"), + (5, "TypeScript"), + (6, "Go"), + (7, "Ruby"), + (8, "Rust"), +] + +# Create dropdown items with styled rank in prefix +CANDIDATES = [ + DropdownItem( + language, # Main text to be completed + prefix=Content.from_markup( + f"[$text-primary on $primary-muted] {rank:>2} " + ), # Prefix with styled rank + ) + for rank, language in LANGUAGES_WITH_RANK +] + +class LanguageSearcher(App): + def compose(self) -> ComposeResult: + yield Label("Start typing a programming language:") + text_input = Input(placeholder="Type here...") + yield text_input + yield AutoComplete(target=text_input, candidates=CANDIDATES) + +if __name__ == "__main__": + app = LanguageSearcher() + app.run() +``` + +## Keyboard Controls + +- **↑/↓** - Navigate through options +- **↓** - Summon the dropdown +- **Enter/Tab** - Complete the selected option +- **Escape** - Hide dropdown + +## Styling + +The dropdown can be styled using Textual CSS: + +```css + AutoComplete { + /* Customize the dropdown */ + & AutoCompleteList { + max-height: 6; /* The number of lines before scrollbars appear */ + color: $text-primary; /* The color of the text */ + background: $primary-muted; /* The background color of the dropdown */ + border-left: wide $success; /* The color of the left border */ + } + + /* Customize the matching substring highlighting */ + & .autocomplete--highlight-match { + color: $text-accent; + text-style: bold; + } + + /* Customize the text the cursor is over */ + & .option-list--option-highlighted { + color: $text-success; + background: $error 50%; /* 50% opacity, blending into background */ + text-style: italic; + } + } +``` + +Here's what that looks like when applied: + +image + +By using Textual CSS like in the example above, you can ensure the shades of colors remain +consistent across different themes. Here's the same dropdown with the Textual app theme switched to `gruvbox`: + +image + +### Styling the prefix + +You can style the prefix using Textual Content markup. + +```python +DropdownItem( + main="Python", + prefix=Content.from_markup( + "[$text-success on $success-muted] 🐍" + ), +) +``` + +## Completing Paths + +`textual-autocomplete` includes a `PathAutoComplete` widget that can be used to autocomplete filesystem paths. + +```python +from textual.app import App, ComposeResult +from textual.containers import Container +from textual.widgets import Button, Input, Label + +from textual_autocomplete import PathAutoComplete + +class FileSystemPathCompletions(App[None]): + def compose(self) -> ComposeResult: + yield Label("Choose a file!", id="label") + input_widget = Input(placeholder="Enter a path...") + yield input_widget + yield PathAutoComplete(target=input_widget, path="../textual") + + +if __name__ == "__main__": + app = FileSystemPathCompletions() + app.run() +``` + +Here's what that looks like in action: + +https://github.com/user-attachments/assets/25b80e34-0a35-4962-9024-f2dab7666689 + +`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. + +## Dynamic Data with Callbacks + +Instead 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. + +This 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). + +The app below displays the length of the text in the input widget in the prefix of the dropdown items. + +```python +from textual.app import App, ComposeResult +from textual.widgets import Input + +from textual_autocomplete import AutoComplete +from textual_autocomplete._autocomplete import DropdownItem, TargetState + + +class DynamicDataApp(App[None]): + def compose(self) -> ComposeResult: + input_widget = Input() + yield input_widget + yield AutoComplete(input_widget, candidates=self.candidates_callback) + + def candidates_callback(self, state: TargetState) -> list[DropdownItem]: + left = len(state.text) + return [ + DropdownItem(item, prefix=f"{left:>2} ") + for item in [ + "Apple", + "Banana", + "Cherry", + "Orange", + "Pineapple", + "Strawberry", + "Watermelon", + ] + ] + + +if __name__ == "__main__": + app = DynamicDataApp() + app.run() +``` + +Notice the count displayed in the prefix increment and decrement based on the character count in the input. + +![Screen Recording 2025-03-18 at 18 26 42](https://github.com/user-attachments/assets/ca0e039b-8ae0-48ac-ba96-9ec936720ded) + +## Customizing Behavior + +If you need custom behavior, `AutoComplete` can be subclassed. + +A good example of how to subclass and customize behavior is the `PathAutoComplete` widget, which is a subclass of `AutoComplete`. + +Some methods you may want to be aware of which you can override: + +- `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. +- `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. +- `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. +- `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). + +## More Examples + +Check out the [examples directory](./examples) for more runnable examples. + +## Contributing + +Contributions are welcome! Feel free to open issues or submit pull requests on GitHub. diff --git a/examples/_headers.py b/examples/_headers.py new file mode 100644 index 0000000..1648805 --- /dev/null +++ b/examples/_headers.py @@ -0,0 +1,767 @@ +from dataclasses import dataclass + + +@dataclass +class Header: + name: str + example: str + description: str + + +headers = [ + { + "section": "Authentication", + "name": "WWW-Authenticate", + "description": "Defines the authentication method that should be used to access a resource.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/WWW-Authenticate", + }, + { + "section": "Authentication", + "name": "Authorization", + "description": "Contains the credentials to authenticate a user-agent with a server.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Authorization", + }, + { + "section": "Authentication", + "name": "Proxy-Authenticate", + "description": "Defines the authentication method that should be used to access a resource behind a proxy server.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Proxy-Authenticate", + }, + { + "section": "Authentication", + "name": "Proxy-Authorization", + "description": "Contains the credentials to authenticate a user agent with a proxy server.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Proxy-Authorization", + }, + { + "section": "Caching", + "name": "Age", + "description": "The time, in seconds, that the object has been in a proxy cache.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Age", + }, + { + "section": "Caching", + "name": "Cache-Control", + "description": "Directives for caching mechanisms in both requests and responses.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Cache-Control", + }, + { + "section": "Caching", + "name": "Clear-Site-Data", + "description": "Clears browsing data (e.g. cookies, storage, cache) associated with the requesting website.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Clear-Site-Data", + }, + { + "section": "Caching", + "name": "Expires", + "description": "The date/time after which the response is considered stale.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Expires", + }, + { + "section": "Caching", + "name": "No-Vary-Search", + "description": "Specifies a set of rules that define how a URL's query parameters will affect cache matching. These rules dictate whether the same URL with different URL parameters should be saved as separate browser cache entries.", + "experimental": True, + "url": "/en-US/docs/Web/HTTP/Headers/No-Vary-Search", + }, + { + "section": "Conditionals", + "name": "Last-Modified", + "description": "The last modification date of the resource, used to compare several versions of the same resource. It is less accurate than ETag, but easier to calculate in some environments. Conditional requests using If-Modified-Since and If-Unmodified-Since use this value to change the behavior of the request.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Last-Modified", + }, + { + "section": "Conditionals", + "name": "ETag", + "description": "A unique string identifying the version of the resource. Conditional requests using If-Match and If-None-Match use this value to change the behavior of the request.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/ETag", + }, + { + "section": "Conditionals", + "name": "If-Match", + "description": "Makes the request conditional, and applies the method only if the stored resource matches one of the given ETags.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/If-Match", + }, + { + "section": "Conditionals", + "name": "If-None-Match", + "description": "Makes the request conditional, and applies the method only if the stored resource doesn't match any of the given ETags. This is used to update caches (for safe requests), or to prevent uploading a new resource when one already exists.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/If-None-Match", + }, + { + "section": "Conditionals", + "name": "If-Modified-Since", + "description": "Makes the request conditional, and expects the resource to be transmitted only if it has been modified after the given date. This is used to transmit data only when the cache is out of date.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/If-Modified-Since", + }, + { + "section": "Conditionals", + "name": "If-Unmodified-Since", + "description": "Makes the request conditional, and expects the resource to be transmitted only if it has not been modified after the given date. This ensures the coherence of a new fragment of a specific range with previous ones, or to implement an optimistic concurrency control system when modifying existing documents.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since", + }, + { + "section": "Conditionals", + "name": "Vary", + "description": "Determines how to match request headers to decide whether a cached response can be used rather than requesting a fresh one from the origin server.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Vary", + }, + { + "section": "Connection management", + "name": "Connection", + "description": "Controls whether the network connection stays open after the current transaction finishes.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Connection", + }, + { + "section": "Connection management", + "name": "Keep-Alive", + "description": "Controls how long a persistent connection should stay open.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Keep-Alive", + }, + { + "section": "Content negotiation", + "name": "Accept", + "description": "Informs the server about the types of data that can be sent back.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Accept", + }, + { + "section": "Content negotiation", + "name": "Accept-Encoding", + "description": "The encoding algorithm, usually a compression algorithm, that can be used on the resource sent back.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Accept-Encoding", + }, + { + "section": "Content negotiation", + "name": "Accept-Language", + "description": "Informs the server about the human language the server is expected to send back. This is a hint and is not necessarily under the full control of the user: the server should always pay attention not to override an explicit user choice (like selecting a language from a dropdown).", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Accept-Language", + }, + { + "section": "Controls", + "name": "Expect", + "description": "Indicates expectations that need to be fulfilled by the server to properly handle the request.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Expect", + }, + { + "section": "Controls", + "name": "Max-Forwards", + "description": "When using TRACE, indicates the maximum number of hops the request can do before being reflected to the sender.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Max-Forwards", + }, + { + "section": "Cookies", + "name": "Cookie", + "description": "Contains stored HTTP cookies previously sent by the server with the Set-Cookie header.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Cookie", + }, + { + "section": "Cookies", + "name": "Set-Cookie", + "description": "Send cookies from the server to the user-agent.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Set-Cookie", + }, + { + "section": "CORS", + "name": "Access-Control-Allow-Credentials", + "description": "Indicates whether the response to the request can be exposed when the credentials flag is true.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials", + }, + { + "section": "CORS", + "name": "Access-Control-Allow-Headers", + "description": "Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers", + }, + { + "section": "CORS", + "name": "Access-Control-Allow-Methods", + "description": "Specifies the methods allowed when accessing the resource in response to a preflight request.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods", + }, + { + "section": "CORS", + "name": "Access-Control-Allow-Origin", + "description": "Indicates whether the response can be shared.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin", + }, + { + "section": "CORS", + "name": "Access-Control-Expose-Headers", + "description": "Indicates which headers can be exposed as part of the response by listing their names.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers", + }, + { + "section": "CORS", + "name": "Access-Control-Max-Age", + "description": "Indicates how long the results of a preflight request can be cached.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age", + }, + { + "section": "CORS", + "name": "Access-Control-Request-Headers", + "description": "Used when issuing a preflight request to let the server know which HTTP headers will be used when the actual request is made.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers", + }, + { + "section": "CORS", + "name": "Access-Control-Request-Method", + "description": "Used when issuing a preflight request to let the server know which HTTP method will be used when the actual request is made.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method", + }, + { + "section": "CORS", + "name": "Origin", + "description": "Indicates where a fetch originates from.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Origin", + }, + { + "section": "CORS", + "name": "Timing-Allow-Origin", + "description": "Specifies origins that are allowed to see values of attributes retrieved via features of the Resource Timing API, which would otherwise be reported as zero due to cross-origin restrictions.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin", + }, + { + "section": "Downloads", + "name": "Content-Disposition", + "description": 'Indicates if the resource transmitted should be displayed inline (default behavior without the header), or if it should be handled like a download and the browser should present a "Save As" dialog.', + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Disposition", + }, + { + "section": "Message body information", + "name": "Content-Length", + "description": "The size of the resource, in decimal number of bytes.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Length", + }, + { + "section": "Message body information", + "name": "Content-Type", + "description": "Indicates the media type of the resource.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Type", + }, + { + "section": "Message body information", + "name": "Content-Encoding", + "description": "Used to specify the compression algorithm.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Encoding", + }, + { + "section": "Message body information", + "name": "Content-Language", + "description": "Describes the human language(s) intended for the audience, so that it allows a user to differentiate according to the users' own preferred language.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Language", + }, + { + "section": "Message body information", + "name": "Content-Location", + "description": "Indicates an alternate location for the returned data.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Location", + }, + { + "section": "Proxies", + "name": "Forwarded", + "description": "Contains information from the client-facing side of proxy servers that is altered or lost when a proxy is involved in the path of the request.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Forwarded", + }, + { + "section": "Proxies", + "name": "Via", + "description": "Added by proxies, both forward and reverse proxies, and can appear in the request headers and the response headers.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Via", + }, + { + "section": "Redirects", + "name": "Location", + "description": "Indicates the URL to redirect a page to.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Location", + }, + { + "section": "Request context", + "name": "From", + "description": "Contains an Internet email address for a human user who controls the requesting user agent.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/From", + }, + { + "section": "Request context", + "name": "Host", + "description": "Specifies the domain name of the server (for virtual hosting), and (optionally) the TCP port number on which the server is listening.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Host", + }, + { + "section": "Request context", + "name": "Referer", + "description": "The address of the previous web page from which a link to the currently requested page was followed.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Referer", + }, + { + "section": "Request context", + "name": "Referrer-Policy", + "description": "Governs which referrer information sent in the Referer header should be included with requests made.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Referrer-Policy", + }, + { + "section": "Request context", + "name": "User-Agent", + "description": "Contains a characteristic string that allows the network protocol peers to identify the application type, operating system, software vendor or software version of the requesting software user agent.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/User-Agent", + }, + { + "section": "Response context", + "name": "Allow", + "description": "Lists the set of HTTP request methods supported by a resource.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Allow", + }, + { + "section": "Response context", + "name": "Server", + "description": "Contains information about the software used by the origin server to handle the request.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Server", + }, + { + "section": "Range requests", + "name": "Accept-Ranges", + "description": "Indicates if the server supports range requests, and if so in which unit the range can be expressed.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Accept-Ranges", + }, + { + "section": "Range requests", + "name": "Range", + "description": "Indicates the part of a document that the server should return.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Range", + }, + { + "section": "Range requests", + "name": "If-Range", + "description": "Creates a conditional range request that is only fulfilled if the given etag or date matches the remote resource. Used to prevent downloading two ranges from incompatible version of the resource.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/If-Range", + }, + { + "section": "Range requests", + "name": "Content-Range", + "description": "Indicates where in a full body message a partial message belongs.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Range", + }, + { + "section": "Security", + "name": "Cross-Origin-Embedder-Policy", + "description": "Allows a server to declare an embedder policy for a given document.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy", + }, + { + "section": "Security", + "name": "Cross-Origin-Opener-Policy", + "description": "Prevents other domains from opening/controlling a window.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy", + }, + { + "section": "Security", + "name": "Cross-Origin-Resource-Policy", + "description": "Prevents other domains from reading the response of the resources to which this header is applied. See also CORP explainer article.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy", + }, + { + "section": "Security", + "name": "Content-Security-Policy", + "description": "Controls resources the user agent is allowed to load for a given page.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Security-Policy", + }, + { + "section": "Security", + "name": "Content-Security-Policy-Report-Only", + "description": "Allows web developers to experiment with policies by monitoring, but not enforcing, their effects. These violation reports consist of JSON documents sent via an HTTP POST request to the specified URI.", + "experimental": False, + "url": "/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only", + }, + { + "section": "Security", + "name": "Permissions-Policy", + "description": "Provides a mechanism to allow and deny the use of browser features in a website's own frame, and in