From 981bc4340a07c787d98c08ec3854a2da4c9eee46 Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Fri, 12 Jul 2024 16:23:55 +0300 Subject: [PATCH 01/18] Add Xresources-based theme --- src/posting/app.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/posting/app.py b/src/posting/app.py index 8af01e60..80b0b099 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -701,6 +701,52 @@ def on_mount(self) -> None: ) self.theme_change_signal = Signal[ColorSystem](self, "theme-changed") self.theme = self.settings.theme + self.try_to_add_xresources_theme() + + def try_to_add_xresources_theme(self) -> None: + try: + xrdb = subprocess.run( + ["xrdb", "-query"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + except FileNotFoundError: + return + + if xrdb.returncode == 0: + lines = xrdb.stdout.splitlines() + color_system_kwargs = {} + for line in lines: + if line.startswith("*"): + parts = line.split(":") + if len(parts) == 2: + name, value = parts[0].strip("*").strip(), parts[1].strip() + if name == "color0": + color_system_kwargs["primary"] = value + elif name == "color8": + color_system_kwargs["secondary"] = value + elif name == "color1": + color_system_kwargs["error"] = value + elif name == "color2": + color_system_kwargs["success"] = value + elif name == "color3": + color_system_kwargs["warning"] = value + elif name == "color4": + color_system_kwargs["accent"] = value + elif name == "background": + color_system_kwargs["background"] = value + elif name == "color7": + color_system_kwargs["surface"] = value + color_system_kwargs["panel"] = value + + if len(color_system_kwargs) == 9: + self.themes["xresources-dark"] = ColorSystem( + **color_system_kwargs, dark=True + ) + self.themes['xresources-light'] = ColorSystem( + **color_system_kwargs, dark=False + ) def get_default_screen(self) -> MainScreen: self.main_screen = MainScreen( From 72d9e2cd72d29b8aa0f175010c3d596e067baf46 Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Fri, 12 Jul 2024 16:42:01 +0300 Subject: [PATCH 02/18] Add Xresources theme to docs --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 682c921b..a90c0b44 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,22 @@ POSTING_HEADING__VISIBLE="false" | `pager_json` (`POSTING_PAGER_JSON`) | Command to use for paging JSON. | | `editor` (`POSTING_EDITOR`) | Command to use for opening files in an external editor. | +### Xresources-based theme + +Posting supports using Xresources for theming. To use this, the `xrdb` executable has to be available in `$PATH` and `xrdb -query` has to return the following variables: + +| Xresources | [Textual color](https://textual.textualize.io/guide/design/#base-colors) | +|-------------|--------------------------------------------------------------------------| +| *color0 | primary | +| *color8 | secondary | +| *color1 | error | +| *color2 | success | +| *color3 | warning | +| *color4 | accent | +| *background | background | +| *color7 | surface, panel | + +If these conditions are met, themes called `xresources-dark` and `xresources-light` are added to the list. ## Importing OpenAPI Specifications From bb8229db30e8a00e5a58bf1a639c33b5c79dd179 Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Fri, 12 Jul 2024 16:56:23 +0300 Subject: [PATCH 03/18] Properly import subprocess in app.py --- src/posting/app.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/posting/app.py b/src/posting/app.py index 80b0b099..ef3d3e3f 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -1,5 +1,6 @@ from pathlib import Path from typing import Any, Literal +import subprocess import httpx from rich.console import Group from rich.text import Text @@ -706,10 +707,10 @@ def on_mount(self) -> None: def try_to_add_xresources_theme(self) -> None: try: xrdb = subprocess.run( - ["xrdb", "-query"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, + ["xrdb", "-query"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, ) except FileNotFoundError: return From 0f691805fbcab3b3b398f9a55d86449663728893 Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Tue, 16 Jul 2024 18:23:25 +0300 Subject: [PATCH 04/18] Add use_xresources and improve add_xresources_theme --- src/posting/app.py | 88 +++++++++++++++++++++++++------------------ src/posting/config.py | 3 ++ 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/posting/app.py b/src/posting/app.py index ef3d3e3f..71205ff9 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -1,6 +1,7 @@ from pathlib import Path -from typing import Any, Literal +from typing import Any, Literal, Union import subprocess +import itertools import httpx from rich.console import Group from rich.text import Text @@ -680,6 +681,21 @@ class Posting(PostingApp): ), } + XRDB_MAPPING = { + "color0": ["primary"], + "color8": ["secondary"], + "color1": ["error"], + "color2": ["success"], + "color3": ["warning"], + "color4": ["accent"], + "background": ["background"], + "color7": ["surface", "panel"], + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.maybe_add_xresources_theme() + theme: Reactive[str | None] = reactive("posting", init=False) _jumping: Reactive[bool] = reactive(False, init=False, bindings=True) @@ -702,9 +718,11 @@ def on_mount(self) -> None: ) self.theme_change_signal = Signal[ColorSystem](self, "theme-changed") self.theme = self.settings.theme - self.try_to_add_xresources_theme() - def try_to_add_xresources_theme(self) -> None: + def maybe_add_xresources_theme(self) -> None: + if self.settings.use_xresources is not True: + return + try: xrdb = subprocess.run( ["xrdb", "-query"], @@ -713,41 +731,37 @@ def try_to_add_xresources_theme(self) -> None: text=True, ) except FileNotFoundError: - return + raise RuntimeError("xrdb is not found. Check that xrdb -query works") - if xrdb.returncode == 0: - lines = xrdb.stdout.splitlines() - color_system_kwargs = {} - for line in lines: - if line.startswith("*"): - parts = line.split(":") - if len(parts) == 2: - name, value = parts[0].strip("*").strip(), parts[1].strip() - if name == "color0": - color_system_kwargs["primary"] = value - elif name == "color8": - color_system_kwargs["secondary"] = value - elif name == "color1": - color_system_kwargs["error"] = value - elif name == "color2": - color_system_kwargs["success"] = value - elif name == "color3": - color_system_kwargs["warning"] = value - elif name == "color4": - color_system_kwargs["accent"] = value - elif name == "background": - color_system_kwargs["background"] = value - elif name == "color7": - color_system_kwargs["surface"] = value - color_system_kwargs["panel"] = value - - if len(color_system_kwargs) == 9: - self.themes["xresources-dark"] = ColorSystem( - **color_system_kwargs, dark=True - ) - self.themes['xresources-light'] = ColorSystem( - **color_system_kwargs, dark=False - ) + if xrdb.returncode != 0: + raise RuntimeError( + f"xrdb -query existed with code {xrdb.returncode}. Check that xrdb -query works" + ) + + lines = xrdb.stdout.splitlines() + color_kwargs = list(itertools.chain(*self.XRDB_MAPPING.values())) + color_kwargs_values: dict[str, Union[None, str]] = {kwarg: None for kwarg in color_kwargs} + + for line in lines: + if line.startswith("*"): + parts = line.split(":") + if len(parts) == 2: + name, value = parts[0].strip("*").strip(), parts[1].strip() + for kwarg in self.XRDB_MAPPING.get(name, []): + color_kwargs_values[kwarg] = value + + unset_colors = [ + kwarg for kwarg in color_kwargs if color_kwargs_values[kwarg] is None + ] + if len(unset_colors) > 0: + raise RuntimeError(f'xrdb -query did not return the following colors: {unset_colors}') + + self.themes["xresources-dark"] = ColorSystem( + **color_kwargs_values, dark=True + ) + self.themes["xresources-light"] = ColorSystem( + **color_kwargs_values, dark=False + ) def get_default_screen(self) -> MainScreen: self.main_screen = MainScreen( diff --git a/src/posting/config.py b/src/posting/config.py index 66330638..c629b98a 100644 --- a/src/posting/config.py +++ b/src/posting/config.py @@ -89,6 +89,9 @@ class Settings(BaseSettings): editor: str | None = Field(default=os.getenv("EDITOR")) """The command to use for editing.""" + use_xresources: bool = Field(default=False) + """If true, try to use Xresources to create dark and light themes.""" + @classmethod def settings_customise_sources( cls, From be876dcf88ad16c8af18f318f79e62147b71b57d Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Tue, 16 Jul 2024 18:24:16 +0300 Subject: [PATCH 05/18] Update README with use_xresources --- README.md | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a90c0b44..25f4dd7c 100644 --- a/README.md +++ b/README.md @@ -234,23 +234,26 @@ POSTING_HEADING__VISIBLE="false" ### Available configuration options -| Config Key (Env Var) | Values (Default) | Description | -|----------------------|------------------|-------------| -| `theme` (`POSTING_THEME`) | `"posting"`, `"galaxy"`, `"monokai"`, `"solarized-light"`, `"nautilus"`, `"nebula"`, `"alpine"`, `"cobalt"`, `"twilight"`, `"hacker"` (Default: `"posting"`) | Sets the theme of the application. | -| `layout` (`POSTING_LAYOUT`) | `"vertical"`, `"horizontal"` (Default: `"horizontal"`) | Sets the layout of the application. | -| `use_host_environment` (`POSTING_USE_HOST_ENVIRONMENT`) | `true`, `false` (Default: `false`) | Allow/deny using environment variables from the host machine in requests via `$env:` syntax. When disabled, only variables defined explicitly in `.env` files will be available for use. | -| `animation` (`POSTING_ANIMATION`) | `"none"`, `"basic"`, `"full"` (Default: `"none"`) | Controls the animation level. | -| `response.prettify_json` (`POSTING_RESPONSE__PRETTIFY_JSON`) | `true`, `false` (Default: `true`) | If enabled, JSON responses will be pretty-formatted. | -| `heading.visible` (`POSTING_HEADING__VISIBLE`) | `true`, `false` (Default: `true`) | Show/hide the app header. | -| `heading.show_host` (`POSTING_HEADING__SHOW_HOST`) | `true`, `false` (Default: `true`) | Show/hide the hostname in the app header. | -| `url_bar.show_value_preview` (`POSTING_URL_BAR__SHOW_VALUE_PREVIEW`) | `true`, `false` (Default: `true`) | Show/hide the variable value preview below the URL bar. | -| `pager` (`POSTING_PAGER`) | Command to use for paging text. | -| `pager_json` (`POSTING_PAGER_JSON`) | Command to use for paging JSON. | -| `editor` (`POSTING_EDITOR`) | Command to use for opening files in an external editor. | +| Config Key (Env Var) | Values (Default) | Description | +|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `theme` (`POSTING_THEME`) | `"posting"`, `"galaxy"`, `"monokai"`, `"solarized-light"`, `"nautilus"`, `"nebula"`, `"alpine"`, `"cobalt"`, `"twilight"`, `"hacker"` (Default: `"posting"`) | Sets the theme of the application. | +| `layout` (`POSTING_LAYOUT`) | `"vertical"`, `"horizontal"` (Default: `"horizontal"`) | Sets the layout of the application. | +| `use_host_environment` (`POSTING_USE_HOST_ENVIRONMENT`) | `true`, `false` (Default: `false`) | Allow/deny using environment variables from the host machine in requests via `$env:` syntax. When disabled, only variables defined explicitly in `.env` files will be available for use. | +| `animation` (`POSTING_ANIMATION`) | `"none"`, `"basic"`, `"full"` (Default: `"none"`) | Controls the animation level. | +| `response.prettify_json` (`POSTING_RESPONSE__PRETTIFY_JSON`) | `true`, `false` (Default: `true`) | If enabled, JSON responses will be pretty-formatted. | +| `heading.visible` (`POSTING_HEADING__VISIBLE`) | `true`, `false` (Default: `true`) | Show/hide the app header. | +| `heading.show_host` (`POSTING_HEADING__SHOW_HOST`) | `true`, `false` (Default: `true`) | Show/hide the hostname in the app header. | +| `url_bar.show_value_preview` (`POSTING_URL_BAR__SHOW_VALUE_PREVIEW`) | `true`, `false` (Default: `true`) | Show/hide the variable value preview below the URL bar. | +| `pager` (`POSTING_PAGER`) | Command to use for paging text. | | +| `pager_json` (`POSTING_PAGER_JSON`) | Command to use for paging JSON. | | +| `editor` (`POSTING_EDITOR`) | Command to use for opening files in an external editor. | | +| `use_xresources` (`POSTING_USE_XRESOURCES`) | `true`, `false` (Default: `false`) | Try to create themes caled `xresources-dark` and `xresources-light` (see the section below) | ### Xresources-based theme -Posting supports using Xresources for theming. To use this, the `xrdb` executable has to be available in `$PATH` and `xrdb -query` has to return the following variables: +Posting supports using Xresources for theming. To use this, enable the `use_xresources` option (see above). + +It requries the `xrdb` executable in `$PATH` and `xrdb -query` returning the following variables: | Xresources | [Textual color](https://textual.textualize.io/guide/design/#base-colors) | |-------------|--------------------------------------------------------------------------| From b8d77fd7dce32ab4d9f2c137f4e5e726aa354645 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 22 Jul 2024 22:26:33 +0100 Subject: [PATCH 06/18] Fix app constructor --- src/posting/app.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/posting/app.py b/src/posting/app.py index 79931366..d7c05865 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -63,6 +63,7 @@ from posting.widgets.request.url_bar import UrlInput, UrlBar from posting.widgets.response.response_area import ResponseArea from posting.widgets.response.response_trace import Event, ResponseTrace +from posting.xresources import load_xresources_themes class AppHeader(Horizontal): @@ -587,8 +588,6 @@ class Posting(App[None]): Binding("f1,ctrl+question_mark", "help", "Help"), ] - - def __init__( self, settings: Settings, @@ -596,13 +595,11 @@ def __init__( collection: Collection, collection_specified: bool = False, ) -> None: - super().__init__() - SETTINGS.set(settings) available_themes: dict[str, ColorSystem] = {**BUILTIN_THEMES} if settings.use_xresources: - available_themes |= self.get_xresources_themes() + available_themes |= load_xresources_themes() # TODO - load user themes from "~/.config/posting/themes" @@ -613,6 +610,8 @@ def __init__( self.collection_specified = collection_specified self.animation_level = settings.animation + super().__init__() + theme: Reactive[str | None] = reactive("posting", init=False) _jumping: Reactive[bool] = reactive(False, init=False, bindings=True) From 1b2dbe23cdc7c5310835a660c5c10cabe718351d Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 22 Jul 2024 22:59:40 +0100 Subject: [PATCH 07/18] Theme loading, fixing app constructor --- src/posting/app.py | 4 ++-- src/posting/locations.py | 7 ++++++ src/posting/themes.py | 47 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/posting/app.py b/src/posting/app.py index d7c05865..0b7e9ce4 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -604,14 +604,14 @@ def __init__( # TODO - load user themes from "~/.config/posting/themes" self.themes = available_themes + super().__init__() + self.settings = settings self.environment_files = environment_files self.collection = collection self.collection_specified = collection_specified self.animation_level = settings.animation - super().__init__() - theme: Reactive[str | None] = reactive("posting", init=False) _jumping: Reactive[bool] = reactive(False, init=False, bindings=True) diff --git a/src/posting/locations.py b/src/posting/locations.py index 724bf0cf..70169394 100644 --- a/src/posting/locations.py +++ b/src/posting/locations.py @@ -14,6 +14,13 @@ def data_directory() -> Path: return _posting_directory(xdg_data_home()) +def themes_directory() -> Path: + """Return (possibly creating) the themes directory.""" + themes_dir = data_directory() / "themes" + themes_dir.mkdir(exist_ok=True, parents=True) + return themes_dir + + def default_collection_directory() -> Path: """Return (possibly creating) the default collection directory.""" return data_directory() / "default" diff --git a/src/posting/themes.py b/src/posting/themes.py index 167aadf4..57d8c00c 100644 --- a/src/posting/themes.py +++ b/src/posting/themes.py @@ -1,4 +1,51 @@ +from pydantic import BaseModel from textual.design import ColorSystem +import yaml + +from posting.locations import themes_directory + + +class Theme(BaseModel): + name: str + primary: str + secondary: str | None = None + background: str | None = None + surface: str | None = None + panel: str | None = None + warning: str | None = None + error: str | None = None + success: str | None = None + accent: str | None = None + primary_background: str | None = None + secondary_background: str | None = None + dark: bool = True + + # Optional metadata + author: str | None = None + description: str | None = None + homepage: str | None = None + + def to_color_system(self) -> ColorSystem: + return ColorSystem(**self.model_dump(exclude={"name"})) + + +def load_user_themes() -> dict[str, ColorSystem]: + """Load user themes from "~/.config/posting/themes".""" + directory = themes_directory() + themes: dict[str, ColorSystem] = {} + for path in directory.iterdir(): + if path.suffix == ".yaml": + with path.open() as theme_file: + theme_content = yaml.load(theme_file, Loader=yaml.FullLoader) + try: + themes[theme_content["name"]] = Theme( + **theme_content + ).to_color_system() + except KeyError: + raise ValueError( + f"Invalid theme file {path}. A `name` is required." + ) + return themes BUILTIN_THEMES: dict[str, ColorSystem] = { From 914614f9b0c47f3fe4465aee3d56287c03464af0 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 22 Jul 2024 23:01:21 +0100 Subject: [PATCH 08/18] Add note to some code that needs to remain --- src/posting/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/posting/app.py b/src/posting/app.py index 0b7e9ce4..0445f1f6 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -604,6 +604,10 @@ def __init__( # TODO - load user themes from "~/.config/posting/themes" self.themes = available_themes + + # We need to call this after the themes are loaded, + # because our `get_css_variables` override depends on + # the themes dict being available. super().__init__() self.settings = settings From 65ebbcfb7e25201f09c6551cc844c1d35094eceb Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 22 Jul 2024 23:02:20 +0100 Subject: [PATCH 09/18] Attaching themes --- src/posting/app.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/posting/app.py b/src/posting/app.py index 0445f1f6..cc53177f 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -38,7 +38,7 @@ from posting.help_screen import HelpScreen from posting.jump_overlay import JumpOverlay from posting.jumper import Jumper -from posting.themes import BUILTIN_THEMES +from posting.themes import BUILTIN_THEMES, load_user_themes from posting.types import CertTypes, PostingLayout from posting.user_host import get_user_host_string from posting.variables import SubstitutionError, get_variables @@ -600,12 +600,11 @@ def __init__( available_themes: dict[str, ColorSystem] = {**BUILTIN_THEMES} if settings.use_xresources: available_themes |= load_xresources_themes() - - # TODO - load user themes from "~/.config/posting/themes" + available_themes |= load_user_themes() self.themes = available_themes - # We need to call this after the themes are loaded, + # We need to call super.__init__ after the themes are loaded, # because our `get_css_variables` override depends on # the themes dict being available. super().__init__() From a61a7747b476d6d01cd4df56cfa9499a00ff7f94 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 22 Jul 2024 23:55:12 +0100 Subject: [PATCH 10/18] First pass at a theme system --- README.md | 44 ++++++++++++++++----- pyproject.toml | 2 +- src/posting/__main__.py | 12 ++++-- src/posting/app.py | 18 ++++----- src/posting/config.py | 5 ++- src/posting/themes.py | 68 ++++++++++++++++++++------------ src/posting/widgets/text_area.py | 15 ++----- src/posting/xresources.py | 17 ++++++-- 8 files changed, 117 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index eef625ca..5b8d8080 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,7 @@ Dotenv files are separate from collections, although you may wish to include the | `focus.on_response` (`POSTING_FOCUS__ON_RESPONSE`) | `"body"`, `"tabs"` (Default: `unset`)| Automatically focus the response tabs or response body text area when a response is received. | | `text_input.blinking_cursor` (`POSTING_TEXT_INPUT__BLINKING_CURSOR`) | `true`, `false` (Default: `true`) | If enabled, the cursor will blink in input widgets and text area widgets. | | `use_xresources` (`POSTING_USE_XRESOURCES`) | `true`, `false` (Default: `false`) | Try to create themes called `xresources-dark` and `xresources-light` (see the section below) | +| `themes_directory` (`POSTING_THEMES_DIRECTORY`) | (Default: `${XDG_DATA_HOME}/posting/themes`) | The directory containing user themes. | ## SSL certificate configuration @@ -277,19 +278,30 @@ ssl: password: '***********' # optional password for key_file ``` -## Importing OpenAPI Specifications - -Note: this feature is highly experimental. +## Theming -Posting can convert OpenAPI 3.x specs into collections. +Place custom themes in the themes directory and Posting will load them on startup. Theme files must be suffixed with `.yaml`, but the rest of the filename is unused by Posting. -To import an OpenAPI Specification, use the `posting import path/to/openapi.yaml` command. +You can check where Posting will look for themes by running `posting locate themes` in your terminal. -You can optionally supply an output directory. +Here's an example theme file: -If no output directory is supplied, the default collection directory will be used. +```yaml +name: example # use this name in your config file +primary: '#4e78c4' +secondary: '#f39c12' +accent: '#e74c3c' +background: '#0e1726' +surface: '#17202a' +error: '#e74c3c' +syntax: 'dracula' # auto-switch syntax highlighting theme + +# Optional metadata +author: Darren Burns +description: A dark theme with a blue primary color. +homepage: https://github.com/darrenburns/posting +``` -Posting will attempt to build a file structure in the collection that aligns with the URL structure of the imported API. ### X resources themes @@ -308,4 +320,18 @@ It requires the `xrdb` executable on your `PATH` and `xrdb -query` must return t | *background | background color | | *color7 | surface/panel color | -If these conditions are met, themes called `xresources-dark` and `xresources-light` will be available for use. \ No newline at end of file +If these conditions are met, themes called `xresources-dark` and `xresources-light` will be available for use. + +## Importing OpenAPI Specifications + +Note: this feature is highly experimental. + +Posting can convert OpenAPI 3.x specs into collections. + +To import an OpenAPI Specification, use the `posting import path/to/openapi.yaml` command. + +You can optionally supply an output directory. + +If no output directory is supplied, the default collection directory will be used. + +Posting will attempt to build a file structure in the collection that aligns with the URL structure of the imported API. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f0a8df18..9e7a6baa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,4 +69,4 @@ markers = [ ] [tool.coverage.run] -relative_files = true \ No newline at end of file +relative_files = true diff --git a/src/posting/__main__.py b/src/posting/__main__.py index 05031341..59823126 100644 --- a/src/posting/__main__.py +++ b/src/posting/__main__.py @@ -8,7 +8,11 @@ from posting.collection import Collection from posting.config import Settings from posting.importing.open_api import import_openapi_spec -from posting.locations import config_file, default_collection_directory +from posting.locations import ( + config_file, + default_collection_directory, + themes_directory, +) def create_config_file() -> None: @@ -68,7 +72,6 @@ def default(collection: str | None = None, env: tuple[str, ...] = ()) -> None: @cli.command() @click.argument( "thing_to_locate", - type=click.Choice(["config", "collection"]), ) def locate(thing_to_locate: str) -> None: if thing_to_locate == "config": @@ -77,10 +80,13 @@ def locate(thing_to_locate: str) -> None: elif thing_to_locate == "collection": print("Default collection directory:") print(default_collection_directory()) + elif thing_to_locate == "themes": + print("Themes directory:") + print(themes_directory()) else: # This shouldn't happen because the type annotation should enforce that # the only valid options are "config" and "collection". - raise ValueError(f"Unknown thing to locate: {thing_to_locate}") + print(f"Unknown thing to locate: {thing_to_locate!r}") @cli.command(name="import") diff --git a/src/posting/app.py b/src/posting/app.py index cc53177f..61479977 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -38,7 +38,7 @@ from posting.help_screen import HelpScreen from posting.jump_overlay import JumpOverlay from posting.jumper import Jumper -from posting.themes import BUILTIN_THEMES, load_user_themes +from posting.themes import BUILTIN_THEMES, Theme, load_user_themes from posting.types import CertTypes, PostingLayout from posting.user_host import get_user_host_string from posting.variables import SubstitutionError, get_variables @@ -597,7 +597,7 @@ def __init__( ) -> None: SETTINGS.set(settings) - available_themes: dict[str, ColorSystem] = {**BUILTIN_THEMES} + available_themes: dict[str, Theme] = {**BUILTIN_THEMES} if settings.use_xresources: available_themes |= load_xresources_themes() available_themes |= load_user_themes() @@ -637,7 +637,7 @@ def on_mount(self) -> None: }, screen=self.screen, ) - self.theme_change_signal = Signal[ColorSystem](self, "theme-changed") + self.theme_change_signal = Signal[Theme](self, "theme-changed") self.theme = self.settings.theme def get_default_screen(self) -> MainScreen: @@ -657,14 +657,14 @@ def get_default_screen(self) -> MainScreen: def get_css_variables(self) -> dict[str, str]: if self.theme: - system = self.themes.get(self.theme) - if system: - theme = system.generate() + theme = self.themes.get(self.theme) + if theme: + color_system = theme.to_color_system().generate() else: - theme = {} + color_system = {} else: - theme = {} - return {**super().get_css_variables(), **theme} + color_system = {} + return {**super().get_css_variables(), **color_system} def command_layout(self, layout: Literal["vertical", "horizontal"]) -> None: self.main_screen.layout = layout diff --git a/src/posting/config.py b/src/posting/config.py index 981ffa4c..f44769a6 100644 --- a/src/posting/config.py +++ b/src/posting/config.py @@ -12,7 +12,7 @@ from textual.types import AnimationLevel import yaml -from posting.locations import config_file +from posting.locations import config_file, themes_directory from posting.types import PostingLayout @@ -89,6 +89,9 @@ class Settings(BaseSettings): theme: str = Field(default="posting") """The name of the theme to use.""" + themes_directory: Path = Field(default=themes_directory()) + """The directory containing user themes.""" + layout: PostingLayout = Field(default="vertical") """Layout for the app.""" diff --git a/src/posting/themes.py b/src/posting/themes.py index 57d8c00c..788b94bd 100644 --- a/src/posting/themes.py +++ b/src/posting/themes.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import BaseModel, Field from textual.design import ColorSystem import yaml @@ -6,7 +6,7 @@ class Theme(BaseModel): - name: str + name: str = Field(exclude=True) primary: str secondary: str | None = None background: str | None = None @@ -16,31 +16,31 @@ class Theme(BaseModel): error: str | None = None success: str | None = None accent: str | None = None - primary_background: str | None = None - secondary_background: str | None = None dark: bool = True + syntax: str = Field(default="posting", exclude=True) + """Posting can associate a syntax highlighting theme which will + be switched to automatically when the app theme changes.""" # Optional metadata - author: str | None = None - description: str | None = None - homepage: str | None = None + author: str | None = Field(default=None, exclude=True) + description: str | None = Field(default=None, exclude=True) + homepage: str | None = Field(default=None, exclude=True) def to_color_system(self) -> ColorSystem: - return ColorSystem(**self.model_dump(exclude={"name"})) + return ColorSystem(**self.model_dump()) -def load_user_themes() -> dict[str, ColorSystem]: +def load_user_themes() -> dict[str, Theme]: """Load user themes from "~/.config/posting/themes".""" directory = themes_directory() - themes: dict[str, ColorSystem] = {} + themes: dict[str, Theme] = {} for path in directory.iterdir(): - if path.suffix == ".yaml": + path_suffix = path.suffix + if path_suffix == ".yaml" or path_suffix == ".yml": with path.open() as theme_file: - theme_content = yaml.load(theme_file, Loader=yaml.FullLoader) + theme_content = yaml.load(theme_file, Loader=yaml.FullLoader) or {} try: - themes[theme_content["name"]] = Theme( - **theme_content - ).to_color_system() + themes[theme_content["name"]] = Theme(**theme_content) except KeyError: raise ValueError( f"Invalid theme file {path}. A `name` is required." @@ -48,8 +48,9 @@ def load_user_themes() -> dict[str, ColorSystem]: return themes -BUILTIN_THEMES: dict[str, ColorSystem] = { - "posting": ColorSystem( +BUILTIN_THEMES: dict[str, Theme] = { + "posting": Theme( + name="posting", primary="#004578", secondary="#0178D4", warning="#ffa62b", @@ -57,8 +58,10 @@ def load_user_themes() -> dict[str, ColorSystem]: success="#4EBF71", accent="#ffa62b", dark=True, + syntax="posting", ), - "monokai": ColorSystem( + "monokai": Theme( + name="monokai", primary="#F92672", # Pink secondary="#66D9EF", # Light Blue warning="#FD971F", # Orange @@ -69,8 +72,10 @@ def load_user_themes() -> dict[str, ColorSystem]: surface="#3E3D32", # Slightly lighter gray-green panel="#3E3D32", # Same as surface for consistency dark=True, + syntax="monokai", ), - "solarized-light": ColorSystem( + "solarized-light": Theme( + name="solarized-light", primary="#268bd2", secondary="#2aa198", warning="#cb4b16", @@ -80,8 +85,10 @@ def load_user_themes() -> dict[str, ColorSystem]: background="#fdf6e3", surface="#eee8d5", panel="#eee8d5", + syntax="github_light", ), - "nautilus": ColorSystem( + "nautilus": Theme( + name="nautilus", primary="#0077BE", # Ocean Blue secondary="#20B2AA", # Light Sea Green warning="#FFD700", # Gold (like sunlight on water) @@ -92,8 +99,10 @@ def load_user_themes() -> dict[str, ColorSystem]: background="#001F3F", # Dark Blue (deep ocean) surface="#003366", # Navy Blue (shallower water) panel="#005A8C", # Steel Blue (water surface) + syntax="posting", ), - "galaxy": ColorSystem( + "galaxy": Theme( + name="galaxy", primary="#8A2BE2", # Improved Deep Magenta (Blueviolet) secondary="#9370DB", # Softer Dusky Indigo (Medium Purple) warning="#FFD700", # Gold, more visible than orange @@ -104,8 +113,10 @@ def load_user_themes() -> dict[str, ColorSystem]: background="#0F0F1F", # Very Dark Blue, almost black surface="#1E1E3F", # Dark Blue-Purple panel="#2D2B55", # Slightly Lighter Blue-Purple + syntax="posting-dracula", ), - "nebula": ColorSystem( + "nebula": Theme( + name="nebula", primary="#4169E1", # Royal Blue, more vibrant than Midnight Blue secondary="#9400D3", # Dark Violet, more vibrant than Indigo Dye warning="#FFD700", # Kept Gold for warnings @@ -116,8 +127,10 @@ def load_user_themes() -> dict[str, ColorSystem]: background="#0A0A23", # Dark Navy, closer to a night sky surface="#1C1C3C", # Dark Blue-Purple panel="#2E2E5E", # Slightly Lighter Blue-Purple + syntax="posting-dracula", ), - "alpine": ColorSystem( + "alpine": Theme( + name="alpine", primary="#4A90E2", # Clear Sky Blue secondary="#81A1C1", # Misty Blue warning="#EBCB8B", # Soft Sunlight @@ -129,7 +142,8 @@ def load_user_themes() -> dict[str, ColorSystem]: surface="#3B4252", # Darker Blue-Grey panel="#434C5E", # Lighter Blue-Grey ), - "cobalt": ColorSystem( + "cobalt": Theme( + name="cobalt", primary="#334D5C", # Deep Cobalt Blue secondary="#4878A6", # Slate Blue warning="#FFAA22", # Amber, suitable for warnings related to primary @@ -141,7 +155,8 @@ def load_user_themes() -> dict[str, ColorSystem]: panel="#2D3E46", # Storm Gray background="#1F262A", # Charcoal ), - "twilight": ColorSystem( + "twilight": Theme( + name="twilight", primary="#367588", secondary="#5F9EA0", warning="#FFD700", @@ -153,7 +168,8 @@ def load_user_themes() -> dict[str, ColorSystem]: surface="#3B3B6D", panel="#4C516D", ), - "hacker": ColorSystem( + "hacker": Theme( + name="hacker", primary="#00FF00", # Bright Green (Lime) secondary="#32CD32", # Lime Green warning="#ADFF2F", # Green Yellow diff --git a/src/posting/widgets/text_area.py b/src/posting/widgets/text_area.py index 55a94230..bbd66ed8 100644 --- a/src/posting/widgets/text_area.py +++ b/src/posting/widgets/text_area.py @@ -14,6 +14,7 @@ from textual.widgets import TextArea, Label, Select, Checkbox from textual.widgets.text_area import Selection, TextAreaTheme from posting.config import SETTINGS +from posting.themes import Theme from posting.widgets.select import PostingSelect @@ -217,16 +218,8 @@ def on_mount(self) -> None: self.on_theme_change(self.app.themes[self.app.theme]) self.app.theme_change_signal.subscribe(self, self.on_theme_change) - def on_theme_change(self, theme: ColorSystem) -> None: - app_theme = self.app.theme - if app_theme == "monokai": - self.theme = "posting-monokai" - elif app_theme == "galaxy" or app_theme == "nebula": - self.theme = "posting-dracula" - elif not theme._dark: - self.theme = "github_light" - else: - self.theme = "posting" + def on_theme_change(self, theme: Theme) -> None: + self.theme = theme.syntax self.refresh() @on(TextArea.Changed) @@ -411,7 +404,7 @@ def action_toggle_visual_mode(self): self.visual_mode = not self.visual_mode def watch_visual_mode(self, value: bool) -> None: - self.cursor_blink = not value + self.cursor_blink = SETTINGS.get().text_input.blinking_cursor and not value if not value: self.selection = Selection.cursor(self.selection.end) diff --git a/src/posting/xresources.py b/src/posting/xresources.py index 023584dd..161a20ec 100644 --- a/src/posting/xresources.py +++ b/src/posting/xresources.py @@ -1,6 +1,7 @@ import subprocess from typing import Any -from textual.design import ColorSystem + +from posting.themes import Theme XRDB_MAPPING = { "color0": ["primary"], @@ -14,7 +15,7 @@ } -def load_xresources_themes() -> dict[str, ColorSystem]: +def load_xresources_themes() -> dict[str, Theme]: """Runs xrdb -query and returns a dictionary of theme_name -> ColorSystem objects.""" try: result = subprocess.run( @@ -40,6 +41,14 @@ def load_xresources_themes() -> dict[str, ColorSystem]: raise RuntimeError(f"Missing colors from xrdb: {missing_colors_string}") return { - "xresources-dark": ColorSystem(**supplied_colors, dark=True), - "xresources-light": ColorSystem(**supplied_colors, dark=False), + "xresources-dark": Theme( + name="xresources-dark", + **supplied_colors, + dark=True, + ), + "xresources-light": Theme( + name="xresources-light", + **supplied_colors, + dark=False, + ), } From 38bcfb35e8e08d257e2c4e1ecdb235080d086f71 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 22 Jul 2024 23:59:47 +0100 Subject: [PATCH 11/18] Add some comments to the example theme file --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5b8d8080..bd08794c 100644 --- a/README.md +++ b/README.md @@ -288,12 +288,14 @@ Here's an example theme file: ```yaml name: example # use this name in your config file -primary: '#4e78c4' -secondary: '#f39c12' -accent: '#e74c3c' -background: '#0e1726' -surface: '#17202a' -error: '#e74c3c' +primary: '#4e78c4' # buttons, fixed table columns +secondary: '#f39c12' # method selector, some minor labels +accent: '#e74c3c' # header text, scrollbars, cursors, focus highlights +background: '#0e1726' # background colors +surface: '#17202a' # panels, etc +error: '#e74c3c' # error messages +success: '#2ecc71' # success messages +warning: '#f1c40f' # warning messages syntax: 'dracula' # auto-switch syntax highlighting theme # Optional metadata @@ -302,7 +304,6 @@ description: A dark theme with a blue primary color. homepage: https://github.com/darrenburns/posting ``` - ### X resources themes Posting supports using X resources for theming. To use this, enable the `use_xresources` option (see above). From 8d62f6e642f7453f9bd5c9634b63340686d60483 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jul 2024 00:01:23 +0100 Subject: [PATCH 12/18] Re-add missing header in readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bd08794c..63f1e90e 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,8 @@ POSTING_HEADING__VISIBLE="false" Dotenv files are separate from collections, although you may wish to include them inside a collection to make it easy to version and share with others. +### Available configuration options + | Config Key (Env Var) | Values (Default) | Description | |----------------------|------------------|-------------| | `theme` (`POSTING_THEME`) | `"posting"`, `"galaxy"`, `"monokai"`, `"solarized-light"`, `"nautilus"`, `"nebula"`, `"alpine"`, `"cobalt"`, `"twilight"`, `"hacker"` (Default: `"posting"`) | Sets the theme of the application. | From a1e9d2428a64881ab8fd2aacbe0ff7a83ba6fd16 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 24 Jul 2024 20:20:16 +0100 Subject: [PATCH 13/18] Remove use_xresources from docs until confirmed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63f1e90e..0397fb14 100644 --- a/README.md +++ b/README.md @@ -240,8 +240,8 @@ Dotenv files are separate from collections, although you may wish to include the | `focus.on_startup` (`POSTING_FOCUS__ON_STARTUP`) | `"url"`, `"method", "collection"` (Default: `"url"`) | Automatically focus the URL bar, method, or collection browser when the app starts. | | `focus.on_response` (`POSTING_FOCUS__ON_RESPONSE`) | `"body"`, `"tabs"` (Default: `unset`)| Automatically focus the response tabs or response body text area when a response is received. | | `text_input.blinking_cursor` (`POSTING_TEXT_INPUT__BLINKING_CURSOR`) | `true`, `false` (Default: `true`) | If enabled, the cursor will blink in input widgets and text area widgets. | -| `use_xresources` (`POSTING_USE_XRESOURCES`) | `true`, `false` (Default: `false`) | Try to create themes called `xresources-dark` and `xresources-light` (see the section below) | | `themes_directory` (`POSTING_THEMES_DIRECTORY`) | (Default: `${XDG_DATA_HOME}/posting/themes`) | The directory containing user themes. | + ## SSL certificate configuration From c970c159060aefcbae36bf58bd02a66ac1f50af5 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 24 Jul 2024 20:27:32 +0100 Subject: [PATCH 14/18] Use the themes directory configured by the user --- src/posting/themes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/posting/themes.py b/src/posting/themes.py index 788b94bd..9bc558b2 100644 --- a/src/posting/themes.py +++ b/src/posting/themes.py @@ -1,6 +1,7 @@ from pydantic import BaseModel, Field from textual.design import ColorSystem import yaml +from posting.config import SETTINGS from posting.locations import themes_directory @@ -32,7 +33,7 @@ def to_color_system(self) -> ColorSystem: def load_user_themes() -> dict[str, Theme]: """Load user themes from "~/.config/posting/themes".""" - directory = themes_directory() + directory = SETTINGS.get().themes_directory themes: dict[str, Theme] = {} for path in directory.iterdir(): path_suffix = path.suffix From 066e891b7876a1e7635475f74ad999e29131bbe1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 24 Jul 2024 22:28:27 +0100 Subject: [PATCH 15/18] Testing new themes system, ensuring tree cursor is always readable (auto) --- .coverage | Bin 53248 -> 53248 bytes README.md | 2 +- pyproject.toml | 1 + requirements-dev.lock | 6 + src/posting/__main__.py | 4 +- src/posting/config.py | 4 +- src/posting/locations.py | 8 +- src/posting/posting.scss | 2 + src/posting/themes.py | 4 +- ...test_loads_and_shows_discovery_options.svg | 170 ++++----- ..._set_on_startup_and_in_command_palette.svg | 187 ++++++++++ tests/conftest.py | 345 ------------------ tests/sample-configs/custom_theme.yaml | 10 + tests/sample-themes/one.yml | 11 + tests/sample-themes/two.yaml | 10 + tests/test_snapshots.py | 15 + 16 files changed, 338 insertions(+), 441 deletions(-) create mode 100644 tests/__snapshots__/test_snapshots/TestCustomTheme.test_theme_set_on_startup_and_in_command_palette.svg delete mode 100644 tests/conftest.py create mode 100644 tests/sample-configs/custom_theme.yaml create mode 100644 tests/sample-themes/one.yml create mode 100644 tests/sample-themes/two.yaml diff --git a/.coverage b/.coverage index b787d5ecfe31853b0b81ecd1ed3081a5e7dde8e6..66d545e893787c09ab05dbabe3bdf0ec3819bf27 100644 GIT binary patch delta 2961 zcmZXWdvH@#9>>qk?>4zN_mz~iNgrv_l0N9WO$+7K7LiBVw1A+rP#Q{&ZN)xp+7)-9 zsjs6e;5r;eVRpi{f9!{bGkWM~o9b5)KN3!a8BDkn)n<_N--5YB`{) zNN;OVPj{rRqpPhb(%;q=j`W3lBZWQpd&&SU`T1yjM_c`21w|a^K9< z(qDAh(i@I+_xHAjjV}HYK%IoEbCCUkc)2~?a_5ci#l<8&;3ZnX&K1$9gm?q#G!x0z zfWQ|3>X@37!si2OWXT7C8GIg~wKPc}lL0%QJ6-lcKp>$SLGJG9>*($>mMxt{s%!jw z4xpB)xq|Jk?%vK&_|A^L?p|6^oK2TaA{S~@ar8{up!;cTgm!>8O`{S?l3FH+JZtay*wtii0_vn^f01 zNl#GWQprFtgG-^)iuq(f>!(*#LZObXj=oTcZY(W{#DX5qMbhfhIVYX!;}hvku+pt{ zB9}mLoriY-ijdFhJmf@O0yl#U*A;N_M6ORwv;&&+^IPj{4|j$mFZt?6xvp{{6>0y{ z-qnt1yR~Eusm}}Zq*KCr^%gZp{ahN5Tb0d1s;X*T>Ph9Iwo03=J}M_DZzjjD6c7hQg$m3Dw~wGN{*5s`4mak9(><>7=+?$%PD2X5mFL6?2T``WqAO|W^&Uf)&LxH3-#5l7xSA@dwqF&V{qMwzA|VMk)4Z(k z!j-*tbaZIs^yNnv9DMV=bALO51Pi{6j@dHa=ghdCMrzs-btf|88Y52TM7*2^4p(#K zqGAhPYLq6P+GXTkFgNvcV(;(0Vmw4Pi&FP%@s z6(qRB<(OksS*#XZZdAo}nJRD@*|MV|dKdQp-$Qx*)Q=D4`i7)fx89A=(fRnRGtVDA zv$H>rJ(P*$vlAOePEWq_)Yp2}wtaA6>h=E6(*t?mexrvbP1|zbyn1cf%#q=Tl)(bC z6PMDZi%lwvEdCg$|$G=gv{692wk-(`M8Nn>+;E$ zJF}w#UQd(wyf3qPH(p1>1-S$M8#n*e)gOi<(RRy@o!m2nV;3%HxRH)~XALJIl>6z6 zue}B`ZlFqXPHgTFn#_a*yq2c<*|FKSm0cm)+5o&Hg!JFn|i-8I#_d;#PYkr#2}SBJ*-zM522 zbZiT^7%w(jn(1%U;omJRPsNMqg7fUnvFNAYdM-uv5q(mR4e4Y0^&$O=Zo^e{I75rQ zZ42rJP0f{8qApxXKHTnFl7<)1p-MS+YGJn2a9p0+-k#X-2=ol-ry&i`H44Om!@qqS=rwFdQ&wo+TJRcjUM9<5l*(Y%^VeNwY&0{z+iUVTg*SO2Y!s-LKX^!w$E z61C8^O^N75tq++^c9z-EIYyb27%kilz#K*sl$xpYO;Ey8KeRFyLNntmXk_%k8b%MS zU`&P@Mgb}q%`lrXDw^V;oC!RXF#`A*OJNgZ3A8X4!$!s;2r(AG2F847V$6f}jJdFm zF$WqLvtcb`7Tm#@3AZ!Ogw+;4N>85ww=p3dRxx^EC1V=YGrFOUF%^Q0DNxIp1OY}D zEN67WGR8z$%9sF47#(mc;|y5L7!S8F+F_B!u#L$E)lAT!icy71Mg+#`p0FO;iSh~;=2uU=EH&|7^(wF3QztN{HZtPt)4{eOnTI;a2u delta 2798 zcmZvedr;KZ702&y&t>=b+uv(hmSvZHuq?|PcELpfMZj1qEa0QjfyT#*NDwtZL2H;% zm!$URw9(*r+Ze~GVy8*dmm0MujaBI%PGYobUsJ0hq()n36qSzA7-f6+;#Q~a&dz+! z`JQvnx%YR^KKcfrZvYPBY6$W#5<|6jmQlVU|3uy_x5$;UQ+h02koHO&q(;dvVexbE zu((rf5rZNa|76@}>^4>!Md35ysPG+OzL3V>=0D_j@ZaKJJe+8{ZqPY{#8uQNaGeVQftP!6Uk0u4kh6nK&xrQNoIvC zI2%wib9zH6&YEcEB=3dHI1^9{i(CqsurKZy>r6N!?o6eQH(qU3orOy$kva$V0BU8e z57pVZG;+2s4ZGvJfqFA`0b0o7h4qP?lT_9FI0so#@5QMT3!UWM`b2C8v?$i!6b`p{ zw66_^>Dk;iKr>@YrM2CyvBP>&0L^B#91TX!N^7N%=?yYYjy-sqXKe52TDLZ~(n8iW zq~Rn$JuLllgGlx_Bx5t6@KeERPXsi*r@JN4)!DPQy`wF#uBWv-yplSHeRD>*RY86M zQm?D0)kEqJ)KnEIzu>#Y>pW3jP;!)O;@i?j`MdlAwL|$-5!LzX6lIGPkcZ@Vl;!dY zsY&{Su}2yZSBNDdHvZZ8V`G(35qXM#YoYfaeyOt9m^Nzfio zrw?&rWD4@&n@k)C@#J`|5$ESZBNIky6LDUA$w0_NQbR_Z8-&$Q(@mryB;p)DH2-IP zZ%Dz}@sQ!zkr^RjJYhs3SL|m^7DE&%@(x_C4E0sb; z7MI9ZMwA9BXOnIR0~ytEsMDu0!Z+p1vi+_m&**U%hL*^N$~tkBrh+heON2oKTiT zZhhYt2~g9Mb^?tJ*~8~{-{D-El`0kb(cs7U#;FYzs-f*nvGYooh58p_bG1ZU5z4_vZ$9o93qeBxO7}5*Zyj zHlF$LL?wqXS~>`N%|%0s%(*ou+1KwbH)xJn6)}`7XsOh)7(=EPc$a3Uc7?F%;MhNw z?SAEA)$YjA_g}e;)?AKk=-X0-t(uMe>J6JEC05K}G-%1OA_*P3GR;E%_C{HxM@#yj zYdH3Qui;gC4XcNm8rd~0IPm_G;rst}4Y8}Z_17~ecSgtWM4}Jk?~li$zYbj3V*cg% zNXgjk=@-$(!F zXw|X37mcZcRz)pKz<&)rG-#DE)jwviYtyM!QaJixS0ei8P-AtlE}3yNsHioU3+(@~#{>b_|lW zDO4%Q8h_IEbu<{+Jhbtv^>;@7l@@I>)$=kx+6sI8bl>b{>T+u(RIc=qOWPdPL29}) zP6Ty*U!K}=FC|f%L$$>q`ts9517 zGu}+q=To!8L7LwTM)IhcmpZ=DJrdqjwH0XDS<6&O%b{|r%?~40+#?XQZ0cH4mR8Ov znx|#OuGeE7kGAshJubU?{L4qtS$s5FT3TNbUEdZ!^ph0}d#*+9M27E7yC042EzR`Q z?(m`Q7@7Let51d>oV~JD-kB2q?M%%ZtEo2=OaJtMNlT;Myz2NVQ+>2>@zTl%5hJZs ztn?z~d&+CdKa~;XqH;m$RX$M$l@Ez^yDv}eQr=Tns>{?Sb)GUnzeTFmGPOu~Tg_2D zs!cU1+f|PJ8=p1OjIv?qER_0mJCB{Tf&nR(|7!8oZr~|W}eyVkl z$lOAh&X@ybj2RGQbc3H!1P7xLQW*uXGxA_F;0W!mhl#!kkjw%YEQ|n2jKwg6u?VUe z15m~2hf2l*s9?;8X^eR=l`$8}8MC33F$< ## SSL certificate configuration diff --git a/pyproject.toml b/pyproject.toml index 9e7a6baa..5749fe73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ dev-dependencies = [ "syrupy>=4.6.1", "pytest-xdist>=3.6.1", "pytest-cov>=5.0.0", + "pytest-textual-snapshot>=1.0.0", ] [tool.hatch.metadata] diff --git a/requirements-dev.lock b/requirements-dev.lock index 54572f5c..f2914dac 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -50,6 +50,7 @@ idna==3.7 iniconfig==2.0.0 # via pytest jinja2==3.1.4 + # via pytest-textual-snapshot linkify-it-py==2.0.3 # via markdown-it-py markdown-it-py==3.0.0 @@ -84,9 +85,11 @@ pyperclip==1.8.2 # via posting pytest==8.3.1 # via pytest-cov + # via pytest-textual-snapshot # via pytest-xdist # via syrupy pytest-cov==5.0.0 +pytest-textual-snapshot==1.0.0 pytest-xdist==3.6.1 python-dotenv==1.0.1 # via posting @@ -94,13 +97,16 @@ python-dotenv==1.0.1 pyyaml==6.0.1 # via posting rich==13.7.1 + # via pytest-textual-snapshot # via textual sniffio==1.3.1 # via anyio # via httpx syrupy==4.6.1 + # via pytest-textual-snapshot textual==0.73.0 # via posting + # via pytest-textual-snapshot # via textual-autocomplete # via textual-dev textual-autocomplete==3.0.0a9 diff --git a/src/posting/__main__.py b/src/posting/__main__.py index 59823126..550477c6 100644 --- a/src/posting/__main__.py +++ b/src/posting/__main__.py @@ -11,7 +11,7 @@ from posting.locations import ( config_file, default_collection_directory, - themes_directory, + theme_directory, ) @@ -82,7 +82,7 @@ def locate(thing_to_locate: str) -> None: print(default_collection_directory()) elif thing_to_locate == "themes": print("Themes directory:") - print(themes_directory()) + print(theme_directory()) else: # This shouldn't happen because the type annotation should enforce that # the only valid options are "config" and "collection". diff --git a/src/posting/config.py b/src/posting/config.py index f44769a6..d9b0d45a 100644 --- a/src/posting/config.py +++ b/src/posting/config.py @@ -12,7 +12,7 @@ from textual.types import AnimationLevel import yaml -from posting.locations import config_file, themes_directory +from posting.locations import config_file, theme_directory from posting.types import PostingLayout @@ -89,7 +89,7 @@ class Settings(BaseSettings): theme: str = Field(default="posting") """The name of the theme to use.""" - themes_directory: Path = Field(default=themes_directory()) + theme_directory: Path = Field(default=theme_directory()) """The directory containing user themes.""" layout: PostingLayout = Field(default="vertical") diff --git a/src/posting/locations.py b/src/posting/locations.py index 70169394..936537e4 100644 --- a/src/posting/locations.py +++ b/src/posting/locations.py @@ -14,11 +14,11 @@ def data_directory() -> Path: return _posting_directory(xdg_data_home()) -def themes_directory() -> Path: +def theme_directory() -> Path: """Return (possibly creating) the themes directory.""" - themes_dir = data_directory() / "themes" - themes_dir.mkdir(exist_ok=True, parents=True) - return themes_dir + theme_dir = data_directory() / "themes" + theme_dir.mkdir(exist_ok=True, parents=True) + return theme_dir def default_collection_directory() -> Path: diff --git a/src/posting/posting.scss b/src/posting/posting.scss index f6376b2b..b63a04e6 100644 --- a/src/posting/posting.scss +++ b/src/posting/posting.scss @@ -242,6 +242,8 @@ TextArea { Tree { & > .tree--cursor { + text-style: not dim; + color: $text; background: $panel-lighten-1 70%; } &:focus > .tree--cursor { diff --git a/src/posting/themes.py b/src/posting/themes.py index 9bc558b2..f2fac958 100644 --- a/src/posting/themes.py +++ b/src/posting/themes.py @@ -3,7 +3,7 @@ import yaml from posting.config import SETTINGS -from posting.locations import themes_directory +from posting.locations import theme_directory class Theme(BaseModel): @@ -33,7 +33,7 @@ def to_color_system(self) -> ColorSystem: def load_user_themes() -> dict[str, Theme]: """Load user themes from "~/.config/posting/themes".""" - directory = SETTINGS.get().themes_directory + directory = SETTINGS.get().theme_directory themes: dict[str, Theme] = {} for path in directory.iterdir(): path_suffix = path.suffix diff --git a/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg b/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg index fc0f52a3..178b46c8 100644 --- a/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg +++ b/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg @@ -19,161 +19,161 @@ font-weight: 700; } - .terminal-2132566647-matrix { + .terminal-2001626355-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2132566647-title { + .terminal-2001626355-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2132566647-r1 { fill: #b3b3b3 } -.terminal-2132566647-r2 { fill: #c5c8c6 } -.terminal-2132566647-r3 { fill: #dfdfdf } -.terminal-2132566647-r4 { fill: #cca544 } -.terminal-2132566647-r5 { fill: #b2bec7;text-decoration: underline; } -.terminal-2132566647-r6 { fill: #b2bec7 } -.terminal-2132566647-r7 { fill: #7da2be } -.terminal-2132566647-r8 { fill: #313131 } -.terminal-2132566647-r9 { fill: #fea62b } -.terminal-2132566647-r10 { fill: #b5b5b5 } -.terminal-2132566647-r11 { fill: #b0b8bd } -.terminal-2132566647-r12 { fill: #808080 } -.terminal-2132566647-r13 { fill: #211505 } -.terminal-2132566647-r14 { fill: #696969 } -.terminal-2132566647-r15 { fill: #503714 } -.terminal-2132566647-r16 { fill: #797b7c;font-weight: bold } -.terminal-2132566647-r17 { fill: #b5b5b6;font-weight: bold } -.terminal-2132566647-r18 { fill: #dfdfdf;font-weight: bold } -.terminal-2132566647-r19 { fill: #565656 } -.terminal-2132566647-r20 { fill: #717171 } -.terminal-2132566647-r21 { fill: #1f1f1f } -.terminal-2132566647-r22 { fill: #1c1c1c } -.terminal-2132566647-r23 { fill: #717171;font-weight: bold } -.terminal-2132566647-r24 { fill: #00672d } -.terminal-2132566647-r25 { fill: #707374 } -.terminal-2132566647-r26 { fill: #0d0d0d;font-weight: bold } -.terminal-2132566647-r27 { fill: #818181 } -.terminal-2132566647-r28 { fill: #161616 } -.terminal-2132566647-r29 { fill: #1a1a1a } -.terminal-2132566647-r30 { fill: #306f43;font-weight: bold } -.terminal-2132566647-r31 { fill: #cc9434;font-weight: bold } -.terminal-2132566647-r32 { fill: #afafaf } + .terminal-2001626355-r1 { fill: #b3b3b3 } +.terminal-2001626355-r2 { fill: #c5c8c6 } +.terminal-2001626355-r3 { fill: #dfdfdf } +.terminal-2001626355-r4 { fill: #cca544 } +.terminal-2001626355-r5 { fill: #b2bec7;text-decoration: underline; } +.terminal-2001626355-r6 { fill: #b2bec7 } +.terminal-2001626355-r7 { fill: #7da2be } +.terminal-2001626355-r8 { fill: #313131 } +.terminal-2001626355-r9 { fill: #fea62b } +.terminal-2001626355-r10 { fill: #b5b5b5 } +.terminal-2001626355-r11 { fill: #b0b8bd } +.terminal-2001626355-r12 { fill: #808080 } +.terminal-2001626355-r13 { fill: #211505 } +.terminal-2001626355-r14 { fill: #696969 } +.terminal-2001626355-r15 { fill: #503714 } +.terminal-2001626355-r16 { fill: #797b7c;font-weight: bold } +.terminal-2001626355-r17 { fill: #b5b5b6;font-weight: bold } +.terminal-2001626355-r18 { fill: #dfdfdf;font-weight: bold } +.terminal-2001626355-r19 { fill: #565656 } +.terminal-2001626355-r20 { fill: #717171 } +.terminal-2001626355-r21 { fill: #1f1f1f } +.terminal-2001626355-r22 { fill: #1c1c1c } +.terminal-2001626355-r23 { fill: #717171;font-weight: bold } +.terminal-2001626355-r24 { fill: #00672d } +.terminal-2001626355-r25 { fill: #707374 } +.terminal-2001626355-r26 { fill: #0d0d0d;font-weight: bold } +.terminal-2001626355-r27 { fill: #818181 } +.terminal-2001626355-r28 { fill: #161616 } +.terminal-2001626355-r29 { fill: #1a1a1a } +.terminal-2001626355-r30 { fill: #306f43;font-weight: bold } +.terminal-2001626355-r31 { fill: #cc9434;font-weight: bold } +.terminal-2001626355-r32 { fill: #afafaf } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Posting + Posting - - - - -Posting                                                                    - -GET Send  -Search for commands… -╭─ Collection── Request ─╮ - GET echo  theme: posting                                tions - GET get ranSet the theme to posting━━━━━━━━━━━━ - POS echo po  theme: monokai                                ╱╱╱╱╱╱╱╱╱╱╱╱ -▼ jsonplacehSet the theme to monokai╱╱╱╱╱╱╱╱╱╱╱╱ -▼ posts/  theme: solarized-light                        ╱╱╱╱╱╱╱╱╱╱╱╱ - GET getSet the theme to solarized-lightd header  - GET get  theme: nautilus                               ────────────╯ - POS creSet the theme to nautilus Response ─╮ - DEL del  theme: galaxy                                  -│────────────Set the theme to galaxy━━━━━━━━━━━━ -This is an   theme: nebula                                  -server we cSet the theme to nebula -see exactly  theme: alpine                                  -request is Set the theme to alpine -sent.  theme: cobalt                                 Wrap X -╰── sample-co────────────╯ - ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  + + + + +Posting                                                                    + +GET Send  +Search for commands… +╭─ Collection── Request ─╮ + GET echo  theme: posting                                tions + GET get ranSet the theme to posting━━━━━━━━━━━━ + POS echo po  theme: monokai                                ╱╱╱╱╱╱╱╱╱╱╱╱ +▼ jsonplacehSet the theme to monokai╱╱╱╱╱╱╱╱╱╱╱╱ +▼ posts/  theme: solarized-light                        ╱╱╱╱╱╱╱╱╱╱╱╱ + GET getSet the theme to solarized-lightd header  + GET get  theme: nautilus                               ────────────╯ + POS creSet the theme to nautilus Response ─╮ + DEL del  theme: galaxy                                  +│────────────Set the theme to galaxy━━━━━━━━━━━━ +This is an   theme: nebula                                  +server we cSet the theme to nebula +see exactly  theme: alpine                                  +request is Set the theme to alpine +sent.  theme: cobalt                                 Wrap X +╰── sample-co────────────╯ + ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  diff --git a/tests/__snapshots__/test_snapshots/TestCustomTheme.test_theme_set_on_startup_and_in_command_palette.svg b/tests/__snapshots__/test_snapshots/TestCustomTheme.test_theme_set_on_startup_and_in_command_palette.svg new file mode 100644 index 00000000..5f817c27 --- /dev/null +++ b/tests/__snapshots__/test_snapshots/TestCustomTheme.test_theme_set_on_startup_and_in_command_palette.svg @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Posting + + + + + + + + + + +Posting                                                                    + +GET Send  +anothertest +╭─ Collection── Request ─╮ + GET echo  theme: anothertesttions + GET get ranSet the theme to anothertest━━━━━━━━━━━━ + POS echo po╱╱╱╱╱╱╱╱╱╱╱╱ +▼ jsonplaceholder/││╱╱╱╱╱╱╱╱╱╱╱╱╱╱There are no headers.╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ posts/││╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get all      ││NameValue Add header  + GET get one      │╰─────────────────────────────────────────────────╯ + POS create       │╭────────────────────────────────────── Response ─╮ + DEL delete a post││BodyHeadersCookiesTrace +│───────────────────────││━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +This is an echo ││ +server we can use to ││ +see exactly what ││ +request is being ││ +sent.││1:1read-onlyJSONWrap X +╰── sample-collections ─╯╰─────────────────────────────────────────────────╯ + ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  + + + + diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index dea9a3f7..00000000 --- a/tests/conftest.py +++ /dev/null @@ -1,345 +0,0 @@ -from __future__ import annotations -import inspect - -import os -import pickle -import re -import shutil -from dataclasses import dataclass -from datetime import datetime -from operator import attrgetter -from os import PathLike -from pathlib import Path, PurePath -from tempfile import mkdtemp -from typing import Any, Awaitable, Union, Optional, Callable, Iterable, TYPE_CHECKING - -import pytest -from _pytest.config import ExitCode -from _pytest.fixtures import FixtureRequest -from _pytest.main import Session -from _pytest.terminal import TerminalReporter -from jinja2 import Template -from rich.console import Console -from syrupy import SnapshotAssertion -from syrupy.extensions.single_file import SingleFileSnapshotExtension, WriteMode -from textual.app import App - -if TYPE_CHECKING: - from _pytest.nodes import Item - from textual.pilot import Pilot - - -class SVGImageExtension(SingleFileSnapshotExtension): - _file_extension = "svg" - _write_mode = WriteMode.TEXT - - -class TemporaryDirectory: - """A temporary that survives forking. - - This provides something akin to tempfile.TemporaryDirectory, but this - version is not removed automatically when a process exits. - """ - - def __init__(self, name: str = ""): - if name: - self.name = name - else: - self.name = mkdtemp(None, None, None) - - def cleanup(self): - """Clean up the temporary directory.""" - shutil.rmtree(self.name, ignore_errors=True) - - -@dataclass -class PseudoConsole: - """Something that looks enough like a Console to fill a Jinja2 template.""" - - legacy_windows: bool - size: ConsoleDimensions - - -@dataclass -class PseudoApp: - """Something that looks enough like an App to fill a Jinja2 template. - - This can be pickled OK, whereas the 'real' application involved in a test - may contain unpickleable data. - """ - - console: PseudoConsole - - -def rename_styles(svg: str, suffix: str) -> str: - """Rename style names to prevent clashes when combined in HTML report.""" - return re.sub(r"terminal-(\d+)-r(\d+)", rf"terminal-\1-r\2-{suffix}", svg) - - -def pytest_addoption(parser): - parser.addoption( - "--snapshot-report", - action="store", - default="snapshot_report.html", - help="Snapshot test output HTML path.", - ) - - -def app_stash_key() -> pytest.StashKey: - try: - return app_stash_key._key - except AttributeError: - from textual.app import App - - app_stash_key._key = pytest.StashKey[App]() - return app_stash_key() - - -def node_to_report_path(node: Item) -> Path: - """Generate a report file name for a test node.""" - tempdir = get_tempdir() - path, _, name = node.reportinfo() - temp = Path(path.parent) - base = [] - while temp != temp.parent and temp.name != "tests": - base.append(temp.name) - temp = temp.parent - parts = [] - if base: - parts.append("_".join(reversed(base))) - parts.append(path.name.replace(".", "_")) - parts.append(name.replace("[", "_").replace("]", "_")) - return Path(tempdir.name) / "_".join(parts) - - -@pytest.fixture -def snap_compare( - snapshot: SnapshotAssertion, request: FixtureRequest -) -> Callable[[str | PurePath], bool]: - """ - This fixture returns a function which can be used to compare the output of a Textual - app with the output of the same app in the past. This is snapshot testing, and it - used to catch regressions in output. - """ - # Switch so one file per snapshot, stored as plain simple SVG file. - snapshot = snapshot.use_extension(SVGImageExtension) - - def compare( - app_path: str | PurePath | App[Any], - press: Iterable[str] = (), - terminal_size: tuple[int, int] = (80, 24), - run_before: Callable[[Pilot], Awaitable[None] | None] | None = None, - ) -> bool: - """ - Compare a current screenshot of the app running at app_path, with - a previously accepted (validated by human) snapshot stored on disk. - When the `--snapshot-update` flag is supplied (provided by syrupy), - the snapshot on disk will be updated to match the current screenshot. - - Args: - app_path (str): The path of the app. Relative paths are relative to the location of the - test this function is called from. - press (Iterable[str]): Key presses to run before taking screenshot. "_" is a short pause. - terminal_size (tuple[int, int]): A pair of integers (WIDTH, HEIGHT), representing terminal size. - run_before: An arbitrary callable that runs arbitrary code before taking the - screenshot. Use this to simulate complex user interactions with the app - that cannot be simulated by key presses. - - Returns: - Whether the screenshot matches the snapshot. - """ - from textual._import_app import import_app - - node = request.node - - if isinstance(app_path, App): - app = app_path - else: - path = Path(app_path) - if path.is_absolute(): - # If the user supplies an absolute path, just use it directly. - app = import_app(str(path.resolve())) - else: - # If a relative path is supplied by the user, it's relative to the location of the pytest node, - # NOT the location that `pytest` was invoked from. - node_path = node.path.parent - resolved = (node_path / app_path).resolve() - app = import_app(str(resolved)) - - from textual._doc import take_svg_screenshot - - actual_screenshot = take_svg_screenshot( - app=app, - press=press, - terminal_size=terminal_size, - run_before=run_before, - ) - console = Console(legacy_windows=False, force_terminal=True) - p_app = PseudoApp(PseudoConsole(console.legacy_windows, console.size)) - - result = snapshot == actual_screenshot - expected_svg_text = str(snapshot) - full_path, line_number, name = node.reportinfo() - - data = ( - result, - expected_svg_text, - actual_screenshot, - p_app, - full_path, - line_number, - name, - inspect.getdoc(node.function) or "", - ) - data_path = node_to_report_path(request.node) - data_path.write_bytes(pickle.dumps(data)) - - return result - - return compare - - -@dataclass -class SvgSnapshotDiff: - """Model representing a diff between current screenshot of an app, - and the snapshot on disk. This is ultimately intended to be used in - a Jinja2 template.""" - - snapshot: Optional[str] - actual: Optional[str] - test_name: str - path: PathLike - line_number: int - app: App - environment: dict - docstring: str - - -def pytest_sessionstart( - session: Session, -) -> None: - """Set up a temporary directory to store snapshots. - - The temporary directory name is stored in an environment vairable so that - pytest-xdist worker child processes can retrieve it. - """ - if os.environ.get("PYTEST_XDIST_WORKER") is None: - tempdir = TemporaryDirectory() - os.environ["TEXTUAL_SNAPSHOT_TEMPDIR"] = tempdir.name - - -def get_tempdir(): - """Get the TemporaryDirectory.""" - return TemporaryDirectory(os.environ["TEXTUAL_SNAPSHOT_TEMPDIR"]) - - -def pytest_sessionfinish( - session: Session, - exitstatus: Union[int, ExitCode], -) -> None: - """Called after whole test run finished, right before returning the exit status to the system. - Generates the snapshot report and writes it to disk. - """ - if os.environ.get("PYTEST_XDIST_WORKER") is None: - tempdir = get_tempdir() - diffs, num_snapshots_passing = retrieve_svg_diffs(tempdir) - save_svg_diffs(diffs, session, num_snapshots_passing) - tempdir.cleanup() - - -def retrieve_svg_diffs( - tempdir: TemporaryDirectory, -) -> tuple[list[SvgSnapshotDiff], int]: - """Retrieve snapshot diffs from the temporary directory.""" - diffs: list[SvgSnapshotDiff] = [] - pass_count = 0 - - n = 0 - for data_path in Path(tempdir.name).iterdir(): - ( - passed, - expect_svg_text, - svg_text, - app, - full_path, - line_index, - name, - docstring, - ) = pickle.loads(data_path.read_bytes()) - pass_count += 1 if passed else 0 - if not passed: - n += 1 - diffs.append( - SvgSnapshotDiff( - snapshot=rename_styles(str(expect_svg_text), f"exp{n}"), - actual=rename_styles(svg_text, f"act{n}"), - test_name=name, - path=full_path, - line_number=line_index + 1, - app=app, - environment=dict(os.environ), - docstring=docstring, - ) - ) - return diffs, pass_count - - -def save_svg_diffs( - diffs: list[SvgSnapshotDiff], - session: Session, - num_snapshots_passing: int, -) -> None: - """Save any detected differences to an HTML formatted report.""" - if diffs: - diff_sort_key = attrgetter("test_name") - diffs = sorted(diffs, key=diff_sort_key) - - this_file_path = Path(__file__) - snapshot_template_path = ( - this_file_path.parent / "resources" / "snapshot_report_template.jinja2" - ) - - snapshot_report_path = session.config.getoption("--snapshot-report") - snapshot_report_path = Path(snapshot_report_path) - snapshot_report_path = Path.cwd() / snapshot_report_path - snapshot_report_path.parent.mkdir(parents=True, exist_ok=True) - template = Template(snapshot_template_path.read_text()) - - num_fails = len(diffs) - num_snapshot_tests = len(diffs) + num_snapshots_passing - - rendered_report = template.render( - diffs=diffs, - passes=num_snapshots_passing, - fails=num_fails, - pass_percentage=100 * (num_snapshots_passing / max(num_snapshot_tests, 1)), - fail_percentage=100 * (num_fails / max(num_snapshot_tests, 1)), - num_snapshot_tests=num_snapshot_tests, - now=datetime.utcnow(), - ) - with open(snapshot_report_path, "w+", encoding="utf-8") as snapshot_file: - snapshot_file.write(rendered_report) - - session.config._textual_snapshots = diffs - session.config._textual_snapshot_html_report = snapshot_report_path - - -def pytest_terminal_summary( - terminalreporter: TerminalReporter, - exitstatus: ExitCode, - config: pytest.Config, -) -> None: - """Add a section to terminal summary reporting. - Displays the link to the snapshot report that was generated in a prior hook. - """ - if os.environ.get("PYTEST_XDIST_WORKER") is None: - diffs = getattr(config, "_textual_snapshots", None) - console = Console(legacy_windows=False, force_terminal=True) - if diffs: - snapshot_report_location = config._textual_snapshot_html_report - console.print("[b red]Textual Snapshot Report", style="red") - console.print( - f"\n[black on red]{len(diffs)} mismatched snapshots[/]\n" - f"\n[b]View the [link=file://{snapshot_report_location}]failure report[/].\n" - ) - console.print(f"[dim]{snapshot_report_location}\n") diff --git a/tests/sample-configs/custom_theme.yaml b/tests/sample-configs/custom_theme.yaml new file mode 100644 index 00000000..86fcb6cc --- /dev/null +++ b/tests/sample-configs/custom_theme.yaml @@ -0,0 +1,10 @@ +# Use a user-defined theme from the themes dir. +theme: serene_ocean # corresponds to two.yaml +use_host_environment: false +response: + show_size_and_time: false +heading: + show_host: false + show_version: false +text_input: + blinking_cursor: false diff --git a/tests/sample-themes/one.yml b/tests/sample-themes/one.yml new file mode 100644 index 00000000..d0ac929b --- /dev/null +++ b/tests/sample-themes/one.yml @@ -0,0 +1,11 @@ +name: anothertest +primary: '#2ecc71' +secondary: '#3498db' +accent: '#9b59b6' +background: '#ecf0f1' +surface: '#bdc3c7' +error: '#e74c3c' +warning: '#f39c12' +success: '#27ae60' +panel: '#95a5a6' +dark: true diff --git a/tests/sample-themes/two.yaml b/tests/sample-themes/two.yaml new file mode 100644 index 00000000..3778c49e --- /dev/null +++ b/tests/sample-themes/two.yaml @@ -0,0 +1,10 @@ +name: serene_ocean +primary: '#1E88E5' # Ocean Blue +secondary: '#00ACC1' # Teal +accent: '#D32F2F' # Crimson Red +background: '#E3F2FD' # Light Sky Blue +surface: '#FFFFFF' # White +error: '#D32F2F' # Crimson Red +success: '#43A047' # Forest Green +text: '#212121' # Almost Black +dark: false diff --git a/tests/test_snapshots.py b/tests/test_snapshots.py index 4f34ed11..2f216e6d 100644 --- a/tests/test_snapshots.py +++ b/tests/test_snapshots.py @@ -10,6 +10,7 @@ TEST_DIR = Path(__file__).parent CONFIG_DIR = TEST_DIR / "sample-configs" ENV_DIR = TEST_DIR / "sample-envs" +THEME_DIR = TEST_DIR / "sample-themes" SAMPLE_COLLECTIONS = TEST_DIR / "sample-collections" POSTING_MAIN = TEST_DIR / "posting_snapshot_app.py" @@ -410,3 +411,17 @@ async def run_before(pilot: Pilot): await pilot.press("ctrl+l") assert snap_compare(app, run_before=run_before) + + +@use_config("custom_theme.yaml") +@patch_env("POSTING_THEME_DIRECTORY", str(THEME_DIR.resolve())) +class TestCustomTheme: + def test_theme_set_on_startup_and_in_command_palette(self, snap_compare): + """Check that the theme is set on startup and available in the command palette.""" + + async def run_before(pilot: Pilot): + await pilot.press("ctrl+p") + await disable_blink_for_active_cursors(pilot) + await pilot.press(*"anothertest") + + assert snap_compare(POSTING_MAIN, run_before=run_before) From 2b738e1705a701111a74f6586d3f2c232670a2f3 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 24 Jul 2024 22:48:22 +0100 Subject: [PATCH 16/18] Slight style change to ensure tree text is legible --- .coverage | Bin 53248 -> 53248 bytes src/posting/posting.scss | 2 +- src/posting/widgets/collection/browser.py | 3 +- ...test_request_loaded_into_view__headers.svg | 210 +++++++++--------- ...uest.test_dialog_loads_and_can_be_used.svg | 188 ++++++++-------- 5 files changed, 202 insertions(+), 201 deletions(-) diff --git a/.coverage b/.coverage index 66d545e893787c09ab05dbabe3bdf0ec3819bf27..94a3906c6385eaca1f34ea55b05146ba5cc0d395 100644 GIT binary patch delta 211 zcmV;^04)E2paX!Q1F!;sY?}Zx3gZI|lMHzb5DV1{%MaiS!V9SnzYClTk`JB>i4TJh za0?R;Q44<$3JT*3bPH+@w+mhls0&pNkq%f6I15P*EDHe+7YjcQ0}b*G(G9u{q79P` zfem2|Rt+Z&6bmB_3Jm@X^bF|?z_Sq$H42lYim(BUvsH_>14l%LLj?f|fd~`+{~eh; zVA}udn>YP&05E%Z)_Mil$6tW?7%OmD06cv91^ndC>-W-k-f?mm0O&pg0R8*CcXR2z NvrYiq0kct$uRwlBOs@a{ delta 210 zcmV;@04@K3paX!Q1F!;sY@7fy3giO}lnitnsS8#Pk`7r8ISWb-Eeiq;7z;oS1P${I(ha)}qYab| zf(>E~R}Cl)6$>N{3k?4Z^$h9^!Ltz%HVTuYim(BVvs8<=14cuJLj?f|fd&)){~eh; zVA}udn>YP&05E$yYrO*O<1fH`j1{;n03JU50)F!6^?T_%?>IRO0Cb-Lfc|~nySen< MStkJQvr&()Ko)vSO#lD@ diff --git a/src/posting/posting.scss b/src/posting/posting.scss index b63a04e6..b77da5d6 100644 --- a/src/posting/posting.scss +++ b/src/posting/posting.scss @@ -242,7 +242,7 @@ TextArea { Tree { & > .tree--cursor { - text-style: not dim; + text-style: b; color: $text; background: $panel-lighten-1 70%; } diff --git a/src/posting/widgets/collection/browser.py b/src/posting/widgets/collection/browser.py index 79aaa1d6..43065906 100644 --- a/src/posting/widgets/collection/browser.py +++ b/src/posting/widgets/collection/browser.py @@ -126,7 +126,8 @@ def render_label( base_style + TOGGLE_STYLE, ) node_label.append("/") - node_label.stylize(Style(dim=True, bold=True)) + if self._cursor_node is not node: + node_label.stylize(Style(dim=True, bold=True)) else: method = ( f"{'█ ' if node is self.currently_open else ' '}{node.data.method[:3]} " diff --git a/tests/__snapshots__/test_snapshots/TestLoadingRequest.test_request_loaded_into_view__headers.svg b/tests/__snapshots__/test_snapshots/TestLoadingRequest.test_request_loaded_into_view__headers.svg index 9322e1bd..7a25cac2 100644 --- a/tests/__snapshots__/test_snapshots/TestLoadingRequest.test_request_loaded_into_view__headers.svg +++ b/tests/__snapshots__/test_snapshots/TestLoadingRequest.test_request_loaded_into_view__headers.svg @@ -19,202 +19,202 @@ font-weight: 700; } - .terminal-230549395-matrix { + .terminal-1428808752-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-230549395-title { + .terminal-1428808752-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-230549395-r1 { fill: #e0e0e0 } -.terminal-230549395-r2 { fill: #c5c8c6 } -.terminal-230549395-r3 { fill: #ffcf56 } -.terminal-230549395-r4 { fill: #dfeef9;text-decoration: underline; } -.terminal-230549395-r5 { fill: #dfeef9 } -.terminal-230549395-r6 { fill: #9dcbee } -.terminal-230549395-r7 { fill: #737373 } -.terminal-230549395-r8 { fill: #e1e1e1 } -.terminal-230549395-r9 { fill: #dde6ed } -.terminal-230549395-r10 { fill: #a0a0a0 } -.terminal-230549395-r11 { fill: #fea62b } -.terminal-230549395-r12 { fill: #e0e0e0;font-weight: bold } -.terminal-230549395-r13 { fill: #64451a } -.terminal-230549395-r14 { fill: #8d8d8d } -.terminal-230549395-r15 { fill: #6c6c6c } -.terminal-230549395-r16 { fill: #272727 } -.terminal-230549395-r17 { fill: #242424 } -.terminal-230549395-r18 { fill: #8d8d8d;font-weight: bold } -.terminal-230549395-r19 { fill: #008139 } -.terminal-230549395-r20 { fill: #794f14;font-weight: bold } -.terminal-230549395-r21 { fill: #8c9092 } -.terminal-230549395-r22 { fill: #313131;font-weight: bold } -.terminal-230549395-r23 { fill: #313131 } -.terminal-230549395-r24 { fill: #a2a2a2 } -.terminal-230549395-r25 { fill: #1c1c1c } -.terminal-230549395-r26 { fill: #b77923 } -.terminal-230549395-r27 { fill: #888888 } -.terminal-230549395-r28 { fill: #a4a4a4 } -.terminal-230549395-r29 { fill: #787878 } -.terminal-230549395-r30 { fill: #212121 } -.terminal-230549395-r31 { fill: #3c8b54;font-weight: bold } -.terminal-230549395-r32 { fill: #ffba41;font-weight: bold } -.terminal-230549395-r33 { fill: #dbdbdb } + .terminal-1428808752-r1 { fill: #e0e0e0 } +.terminal-1428808752-r2 { fill: #c5c8c6 } +.terminal-1428808752-r3 { fill: #ffcf56 } +.terminal-1428808752-r4 { fill: #dfeef9;text-decoration: underline; } +.terminal-1428808752-r5 { fill: #dfeef9 } +.terminal-1428808752-r6 { fill: #9dcbee } +.terminal-1428808752-r7 { fill: #737373 } +.terminal-1428808752-r8 { fill: #e1e1e1 } +.terminal-1428808752-r9 { fill: #dde6ed } +.terminal-1428808752-r10 { fill: #a0a0a0 } +.terminal-1428808752-r11 { fill: #fea62b } +.terminal-1428808752-r12 { fill: #e0e0e0;font-weight: bold } +.terminal-1428808752-r13 { fill: #64451a } +.terminal-1428808752-r14 { fill: #8d8d8d } +.terminal-1428808752-r15 { fill: #6c6c6c } +.terminal-1428808752-r16 { fill: #272727 } +.terminal-1428808752-r17 { fill: #242424 } +.terminal-1428808752-r18 { fill: #8d8d8d;font-weight: bold } +.terminal-1428808752-r19 { fill: #008139 } +.terminal-1428808752-r20 { fill: #211505;font-weight: bold } +.terminal-1428808752-r21 { fill: #8c9092 } +.terminal-1428808752-r22 { fill: #313131;font-weight: bold } +.terminal-1428808752-r23 { fill: #313131 } +.terminal-1428808752-r24 { fill: #a2a2a2 } +.terminal-1428808752-r25 { fill: #1c1c1c } +.terminal-1428808752-r26 { fill: #b77923 } +.terminal-1428808752-r27 { fill: #888888 } +.terminal-1428808752-r28 { fill: #a4a4a4 } +.terminal-1428808752-r29 { fill: #787878 } +.terminal-1428808752-r30 { fill: #212121 } +.terminal-1428808752-r31 { fill: #3c8b54;font-weight: bold } +.terminal-1428808752-r32 { fill: #ffba41;font-weight: bold } +.terminal-1428808752-r33 { fill: #dbdbdb } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Posting + Posting - + - - -Posting                                                                    - -GETEnter a URL... Send  - -╭─ Collection ──────────╮╭─────────────────────────────────────── Request ─╮ - GET echoHeadersBodyQueryAuthInfoOptions - GET get random user━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - POS echo post╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▼ jsonplaceholder/╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▶ posts/╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▼ todos/╱╱╱╱╱╱╱╱╱╱╱╱╱╱There are no headers.╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - GET get all╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - GET get one╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▼ users/╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - GET get a user╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - GET get all usersNameValue Add header  - POS create a user╰─────────────────────────────────────────────────╯ - PUT update a user╭────────────────────────────────────── Response ─╮ - DEL delete a userBodyHeadersCookiesTrace -━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - - - - - -1:1read-onlyJSONWrap X -╰── sample-collections ─╯╰─────────────────────────────────────────────────╯ - ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  + + +Posting                                                                    + +GETEnter a URL... Send  + +╭─ Collection ──────────╮╭─────────────────────────────────────── Request ─╮ + GET echoHeadersBodyQueryAuthInfoOptions + GET get random user━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + POS echo post╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ jsonplaceholder/╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▶ posts/╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ todos/╱╱╱╱╱╱╱╱╱╱╱╱╱╱There are no headers.╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get all╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get one╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ users/╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get a user╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get all usersNameValue Add header  + POS create a user╰─────────────────────────────────────────────────╯ + PUT update a user╭────────────────────────────────────── Response ─╮ + DEL delete a userBodyHeadersCookiesTrace +━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + + + + + + +1:1read-onlyJSONWrap X +╰── sample-collections ─╯╰─────────────────────────────────────────────────╯ + ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  diff --git a/tests/__snapshots__/test_snapshots/TestNewRequest.test_dialog_loads_and_can_be_used.svg b/tests/__snapshots__/test_snapshots/TestNewRequest.test_dialog_loads_and_can_be_used.svg index 7dfb6807..03bd99a3 100644 --- a/tests/__snapshots__/test_snapshots/TestNewRequest.test_dialog_loads_and_can_be_used.svg +++ b/tests/__snapshots__/test_snapshots/TestNewRequest.test_dialog_loads_and_can_be_used.svg @@ -19,171 +19,171 @@ font-weight: 700; } - .terminal-1544675084-matrix { + .terminal-1506597801-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1544675084-title { + .terminal-1506597801-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1544675084-r1 { fill: #9c9c9c } -.terminal-1544675084-r2 { fill: #c5c8c6 } -.terminal-1544675084-r3 { fill: #dfdfdf } -.terminal-1544675084-r4 { fill: #b2903c } -.terminal-1544675084-r5 { fill: #9ca6ae;text-decoration: underline; } -.terminal-1544675084-r6 { fill: #9ca6ae } -.terminal-1544675084-r7 { fill: #6d8ea6 } -.terminal-1544675084-r8 { fill: #505050 } -.terminal-1544675084-r9 { fill: #313131 } -.terminal-1544675084-r10 { fill: #e0e0e0;font-weight: bold } -.terminal-1544675084-r11 { fill: #9d9d9d } -.terminal-1544675084-r12 { fill: #9aa1a5 } -.terminal-1544675084-r13 { fill: #707070 } -.terminal-1544675084-r14 { fill: #121212 } -.terminal-1544675084-r15 { fill: #463012 } -.terminal-1544675084-r16 { fill: #e0e0e0 } -.terminal-1544675084-r17 { fill: #626262 } -.terminal-1544675084-r18 { fill: #e1e1e1 } -.terminal-1544675084-r19 { fill: #4b4b4b } -.terminal-1544675084-r20 { fill: #1b1b1b } -.terminal-1544675084-r21 { fill: #8d8d8d } -.terminal-1544675084-r22 { fill: #191919 } -.terminal-1544675084-r23 { fill: #626262;font-weight: bold } -.terminal-1544675084-r24 { fill: #737373 } -.terminal-1544675084-r25 { fill: #a5a5a5 } -.terminal-1544675084-r26 { fill: #005a27 } -.terminal-1544675084-r27 { fill: #6a6b6d;font-weight: bold } -.terminal-1544675084-r28 { fill: #626466 } -.terminal-1544675084-r29 { fill: #3e3e3e } -.terminal-1544675084-r30 { fill: #e3e3e3 } -.terminal-1544675084-r31 { fill: #e4e4e4 } -.terminal-1544675084-r32 { fill: #211505 } -.terminal-1544675084-r33 { fill: #0d0d0d } -.terminal-1544675084-r34 { fill: #717171 } -.terminal-1544675084-r35 { fill: #131313 } -.terminal-1544675084-r36 { fill: #5f5f5f } -.terminal-1544675084-r37 { fill: #727272 } -.terminal-1544675084-r38 { fill: #545454 } -.terminal-1544675084-r39 { fill: #171717 } -.terminal-1544675084-r40 { fill: #2a613a;font-weight: bold } -.terminal-1544675084-r41 { fill: #ffba41;font-weight: bold } -.terminal-1544675084-r42 { fill: #dadada } + .terminal-1506597801-r1 { fill: #9c9c9c } +.terminal-1506597801-r2 { fill: #c5c8c6 } +.terminal-1506597801-r3 { fill: #dfdfdf } +.terminal-1506597801-r4 { fill: #b2903c } +.terminal-1506597801-r5 { fill: #9ca6ae;text-decoration: underline; } +.terminal-1506597801-r6 { fill: #9ca6ae } +.terminal-1506597801-r7 { fill: #6d8ea6 } +.terminal-1506597801-r8 { fill: #505050 } +.terminal-1506597801-r9 { fill: #313131 } +.terminal-1506597801-r10 { fill: #e0e0e0;font-weight: bold } +.terminal-1506597801-r11 { fill: #9d9d9d } +.terminal-1506597801-r12 { fill: #9aa1a5 } +.terminal-1506597801-r13 { fill: #707070 } +.terminal-1506597801-r14 { fill: #121212 } +.terminal-1506597801-r15 { fill: #463012 } +.terminal-1506597801-r16 { fill: #e0e0e0 } +.terminal-1506597801-r17 { fill: #626262 } +.terminal-1506597801-r18 { fill: #e1e1e1 } +.terminal-1506597801-r19 { fill: #4b4b4b } +.terminal-1506597801-r20 { fill: #1b1b1b } +.terminal-1506597801-r21 { fill: #8d8d8d } +.terminal-1506597801-r22 { fill: #191919 } +.terminal-1506597801-r23 { fill: #626262;font-weight: bold } +.terminal-1506597801-r24 { fill: #737373 } +.terminal-1506597801-r25 { fill: #a5a5a5 } +.terminal-1506597801-r26 { fill: #005a27 } +.terminal-1506597801-r27 { fill: #9e9e9f;font-weight: bold } +.terminal-1506597801-r28 { fill: #626466 } +.terminal-1506597801-r29 { fill: #3e3e3e } +.terminal-1506597801-r30 { fill: #e3e3e3 } +.terminal-1506597801-r31 { fill: #e4e4e4 } +.terminal-1506597801-r32 { fill: #211505 } +.terminal-1506597801-r33 { fill: #0d0d0d } +.terminal-1506597801-r34 { fill: #717171 } +.terminal-1506597801-r35 { fill: #131313 } +.terminal-1506597801-r36 { fill: #5f5f5f } +.terminal-1506597801-r37 { fill: #727272 } +.terminal-1506597801-r38 { fill: #545454 } +.terminal-1506597801-r39 { fill: #171717 } +.terminal-1506597801-r40 { fill: #2a613a;font-weight: bold } +.terminal-1506597801-r41 { fill: #ffba41;font-weight: bold } +.terminal-1506597801-r42 { fill: #dadada } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Posting + Posting - + - - -Posting                                                                    - -GETEnter▁▁ New request ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ Send  - -╭─ Collection ────Title                            ─────── Request ─╮ - GET echo        foo                            oOptions - GET get random u━━━━━━━━━━━━━━━━━ - POS echo post   File name optional╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▼ jsonplaceholderfoo.posting.yamlrs.╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▼ posts/╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - GET get all Description optional Add header  - GET get one bar─────────────────╯ - POS create  ────── Response ─╮ - DEL delete aDirectory                         -▼ comments/jsonplaceholder/posts          ━━━━━━━━━━━━━━━━━ - GET get co - GET get co▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - PUT edit a comm││ -▼ todos/││ - GET get all      ││1:1read-onlyJSONWrap X -╰── sample-collections ─╯╰─────────────────────────────────────────────────╯ - f3 Pager  f4 Editor  ESC Cancel  ^n Create  + + +Posting                                                                    + +GETEnter▁▁ New request ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ Send  + +╭─ Collection ────Title                            ─────── Request ─╮ + GET echo        foo                            oOptions + GET get random u━━━━━━━━━━━━━━━━━ + POS echo post   File name optional╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ jsonplaceholderfoo.posting.yamlrs.╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ posts/╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get all Description optional Add header  + GET get one bar─────────────────╯ + POS create  ────── Response ─╮ + DEL delete aDirectory                         +▼ comments/jsonplaceholder/posts          ━━━━━━━━━━━━━━━━━ + GET get co + GET get co▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + PUT edit a comm││ +▼ todos/││ + GET get all      ││1:1read-onlyJSONWrap X +╰── sample-collections ─╯╰─────────────────────────────────────────────────╯ + f3 Pager  f4 Editor  ESC Cancel  ^n Create  From cd30aee7739ea6fa0049b8ab821361fad18d39c1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 24 Jul 2024 22:59:25 +0100 Subject: [PATCH 17/18] Updating a test --- .coverage | Bin 53248 -> 53248 bytes src/posting/app.py | 1 + ...test_loads_and_shows_discovery_options.svg | 265 ++++++++++-------- tests/test_snapshots.py | 4 +- 4 files changed, 159 insertions(+), 111 deletions(-) diff --git a/.coverage b/.coverage index 94a3906c6385eaca1f34ea55b05146ba5cc0d395..1118fb7985b3ff4b89dcc673cfb3f171c3dc9f82 100644 GIT binary patch delta 36 scmZozz}&Eac|%H%Xz+{M|M!1m{T0yk^=H4^zP;?y4ADHBD|_0V0aeHm-~a#s delta 36 scmZozz}&Eac|%H%sP>n)|Koo%)oL94^~YWL{=H`223@wzl|Ajw07*&_s{jB1 diff --git a/src/posting/app.py b/src/posting/app.py index 61479977..8ee23d96 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -600,6 +600,7 @@ def __init__( available_themes: dict[str, Theme] = {**BUILTIN_THEMES} if settings.use_xresources: available_themes |= load_xresources_themes() + available_themes |= load_user_themes() self.themes = available_themes diff --git a/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg b/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg index 178b46c8..2d7186df 100644 --- a/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg +++ b/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg @@ -1,4 +1,4 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Posting + Posting - - - - -Posting                                                                    - -GET Send  -Search for commands… -╭─ Collection── Request ─╮ - GET echo  theme: posting                                tions - GET get ranSet the theme to posting━━━━━━━━━━━━ - POS echo po  theme: monokai                                ╱╱╱╱╱╱╱╱╱╱╱╱ -▼ jsonplacehSet the theme to monokai╱╱╱╱╱╱╱╱╱╱╱╱ -▼ posts/  theme: solarized-light                        ╱╱╱╱╱╱╱╱╱╱╱╱ - GET getSet the theme to solarized-lightd header  - GET get  theme: nautilus                               ────────────╯ - POS creSet the theme to nautilus Response ─╮ - DEL del  theme: galaxy                                  -│────────────Set the theme to galaxy━━━━━━━━━━━━ -This is an   theme: nebula                                  -server we cSet the theme to nebula -see exactly  theme: alpine                                  -request is Set the theme to alpine -sent.  theme: cobalt                                 Wrap X -╰── sample-co────────────╯ - ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  + + + + +Posting                                                                                                            + +GETEnter a URL... Send  +Search for commands… +╭─ Collection ───────────────────────────────────────── Request ─╮ + GET echo  theme: posting                                 + GET get random user            Set the theme to posting━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + POS echo post                    theme: monokai                                ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ jsonplaceholder/Set the theme to monokai╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ posts/  theme: solarized-light                        ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get all                Set the theme to solarized-lighters.╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get one                  theme: nautilus                               ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + POS create                 Set the theme to nautilus╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + DEL delete a post            theme: galaxy                                 ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ comments/Set the theme to galaxy╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get comments           theme: nebula                                  Add header  + GET get comments (via parSet the theme to nebula────────────────────────────────╯ + PUT edit a comment         theme: alpine                                 ───────────────────── Response ─╮ +▼ todos/Set the theme to alpine + GET get all                  theme: cobalt                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + GET get one                Set the theme to cobalt +▼ users/  theme: twilight                                + GET get a user             Set the theme to twilight + GET get all users            theme: hacker                                  + POS create a user          Set the theme to hacker + PUT update a user            theme: example                                 +│────────────────────────────────Set the theme to example +This is an echo server we can u +to see exactly what request is ││ +being sent.││1:1read-onlyJSONWrap X +╰─────────────── sample-collections ─╯╰────────────────────────────────────────────────────────────────────────────╯ + ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  diff --git a/tests/test_snapshots.py b/tests/test_snapshots.py index 2f216e6d..981a2bd4 100644 --- a/tests/test_snapshots.py +++ b/tests/test_snapshots.py @@ -128,7 +128,9 @@ async def run_before(pilot: Pilot): await pilot.press("ctrl+p") await disable_blink_for_active_cursors(pilot) - assert snap_compare(POSTING_MAIN, run_before=run_before) + assert snap_compare( + POSTING_MAIN, run_before=run_before, terminal_size=(120, 34) + ) def test_can_type_to_filter_options(self, snap_compare): """Check that we can run a command from the command palette.""" From 88eb5d34606bddfadf0e0f92cd2feb1566e7fdec Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 24 Jul 2024 23:23:01 +0100 Subject: [PATCH 18/18] Config for toggling loading builtin themes --- .coverage | Bin 53248 -> 53248 bytes README.md | 4 +- src/posting/app.py | 9 +- src/posting/config.py | 8 + ...test_loads_and_shows_discovery_options.svg | 220 +++++++++--------- tests/sample-configs/general.yaml | 1 + tests/sample-configs/modified_config.yaml | 1 + 7 files changed, 130 insertions(+), 113 deletions(-) diff --git a/.coverage b/.coverage index 1118fb7985b3ff4b89dcc673cfb3f171c3dc9f82..967bbc1e231e90f0dc359dad64ae51bfdd89ba89 100644 GIT binary patch delta 216 zcmV;}04M)|paX!Q1F!;sY?=Tw3gH6`k_>nZ4hz!@$q(QQzze4jz6+WQkPn>;hYx}e zZwnC*Pz!wz2@2s0atmkP(77INN0uAvE&<(i_p$(D^ zfDK>`RShN$6AK{?2@L%V^9<+=zq1h#G71Q1f;9yJ34uxRExX_ zXNEHc0SSRllNgLl7A*^X`~TOzKp#r5KL63kxA$NJV2c5>d5lH_6(5E<1OW*^34XBh S=U|&{*gui4TJh za0?R;Q44<$3JT*3bPH+@w+mhls0&pNkq%f6I15P*EDHe+7YjcQ0}b*G(G9u{q79P` zfem2|Rt+Z&6bmB_3Jm@X^bF|?z_Sq$H3|r3f;9yJ34u(LsERm~;finpjFVD}ya;B7 zGX((&flQMYj7$|%=-dCl_67P-g7x{2M!vlV8vt7jvv`a~0~8*HIRpU-J_&xX^5 ## SSL certificate configuration diff --git a/src/posting/app.py b/src/posting/app.py index 8ee23d96..9edce7f2 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -597,11 +597,16 @@ def __init__( ) -> None: SETTINGS.set(settings) - available_themes: dict[str, Theme] = {**BUILTIN_THEMES} + available_themes: dict[str, Theme] = {"posting": BUILTIN_THEMES["posting"]} + + if settings.load_builtin_themes: + available_themes |= BUILTIN_THEMES + if settings.use_xresources: available_themes |= load_xresources_themes() - available_themes |= load_user_themes() + if settings.load_user_themes: + available_themes |= load_user_themes() self.themes = available_themes diff --git a/src/posting/config.py b/src/posting/config.py index d9b0d45a..c98a4abb 100644 --- a/src/posting/config.py +++ b/src/posting/config.py @@ -92,6 +92,14 @@ class Settings(BaseSettings): theme_directory: Path = Field(default=theme_directory()) """The directory containing user themes.""" + load_user_themes: bool = Field(default=True) + """If enabled, load user themes from the theme directory, allowing them + to be specified in config and selected via the command palette.""" + + load_builtin_themes: bool = Field(default=True) + """If enabled, load builtin themes, allowing them to be specified + in config and selected via the command palette.""" + layout: PostingLayout = Field(default="vertical") """Layout for the app.""" diff --git a/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg b/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg index 2d7186df..a04f36d9 100644 --- a/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg +++ b/tests/__snapshots__/test_snapshots/TestCommandPalette.test_loads_and_shows_discovery_options.svg @@ -19,206 +19,206 @@ font-weight: 700; } - .terminal-198778595-matrix { + .terminal-3659734164-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-198778595-title { + .terminal-3659734164-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-198778595-r1 { fill: #b3b3b3 } -.terminal-198778595-r2 { fill: #c5c8c6 } -.terminal-198778595-r3 { fill: #dfdfdf } -.terminal-198778595-r4 { fill: #cca544 } -.terminal-198778595-r5 { fill: #b2bec7;text-decoration: underline; } -.terminal-198778595-r6 { fill: #b2bec7 } -.terminal-198778595-r7 { fill: #7da2be } -.terminal-198778595-r8 { fill: #313131 } -.terminal-198778595-r9 { fill: #1a1004 } -.terminal-198778595-r10 { fill: #636363 } -.terminal-198778595-r11 { fill: #b5b5b5 } -.terminal-198778595-r12 { fill: #fea62b } -.terminal-198778595-r13 { fill: #b0b8bd } -.terminal-198778595-r14 { fill: #808080 } -.terminal-198778595-r15 { fill: #211505 } -.terminal-198778595-r16 { fill: #696969 } -.terminal-198778595-r17 { fill: #503714 } -.terminal-198778595-r18 { fill: #797b7c;font-weight: bold } -.terminal-198778595-r19 { fill: #b5b5b6;font-weight: bold } -.terminal-198778595-r20 { fill: #dfdfdf;font-weight: bold } -.terminal-198778595-r21 { fill: #717171 } -.terminal-198778595-r22 { fill: #1f1f1f } -.terminal-198778595-r23 { fill: #1c1c1c } -.terminal-198778595-r24 { fill: #717171;font-weight: bold } -.terminal-198778595-r25 { fill: #00672d } -.terminal-198778595-r26 { fill: #b4b4b4 } -.terminal-198778595-r27 { fill: #707374 } -.terminal-198778595-r28 { fill: #818181 } -.terminal-198778595-r29 { fill: #161616 } -.terminal-198778595-r30 { fill: #0d0d0d;font-weight: bold } -.terminal-198778595-r31 { fill: #6c6c6c } -.terminal-198778595-r32 { fill: #838383 } -.terminal-198778595-r33 { fill: #606060 } -.terminal-198778595-r34 { fill: #1a1a1a } -.terminal-198778595-r35 { fill: #306f43;font-weight: bold } -.terminal-198778595-r36 { fill: #cc9434;font-weight: bold } -.terminal-198778595-r37 { fill: #afafaf } + .terminal-3659734164-r1 { fill: #b3b3b3 } +.terminal-3659734164-r2 { fill: #c5c8c6 } +.terminal-3659734164-r3 { fill: #dfdfdf } +.terminal-3659734164-r4 { fill: #cca544 } +.terminal-3659734164-r5 { fill: #b2bec7;text-decoration: underline; } +.terminal-3659734164-r6 { fill: #b2bec7 } +.terminal-3659734164-r7 { fill: #7da2be } +.terminal-3659734164-r8 { fill: #313131 } +.terminal-3659734164-r9 { fill: #1a1004 } +.terminal-3659734164-r10 { fill: #636363 } +.terminal-3659734164-r11 { fill: #b5b5b5 } +.terminal-3659734164-r12 { fill: #fea62b } +.terminal-3659734164-r13 { fill: #b0b8bd } +.terminal-3659734164-r14 { fill: #808080 } +.terminal-3659734164-r15 { fill: #211505 } +.terminal-3659734164-r16 { fill: #696969 } +.terminal-3659734164-r17 { fill: #503714 } +.terminal-3659734164-r18 { fill: #797b7c;font-weight: bold } +.terminal-3659734164-r19 { fill: #b5b5b6;font-weight: bold } +.terminal-3659734164-r20 { fill: #dfdfdf;font-weight: bold } +.terminal-3659734164-r21 { fill: #717171 } +.terminal-3659734164-r22 { fill: #1f1f1f } +.terminal-3659734164-r23 { fill: #1c1c1c } +.terminal-3659734164-r24 { fill: #717171;font-weight: bold } +.terminal-3659734164-r25 { fill: #00672d } +.terminal-3659734164-r26 { fill: #b4b4b4 } +.terminal-3659734164-r27 { fill: #707374 } +.terminal-3659734164-r28 { fill: #818181 } +.terminal-3659734164-r29 { fill: #161616 } +.terminal-3659734164-r30 { fill: #0d0d0d;font-weight: bold } +.terminal-3659734164-r31 { fill: #6c6c6c } +.terminal-3659734164-r32 { fill: #838383 } +.terminal-3659734164-r33 { fill: #606060 } +.terminal-3659734164-r34 { fill: #1a1a1a } +.terminal-3659734164-r35 { fill: #306f43;font-weight: bold } +.terminal-3659734164-r36 { fill: #cc9434;font-weight: bold } +.terminal-3659734164-r37 { fill: #afafaf } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Posting + Posting - - - - -Posting                                                                                                            - -GETEnter a URL... Send  -Search for commands… -╭─ Collection ───────────────────────────────────────── Request ─╮ - GET echo  theme: posting                                 - GET get random user            Set the theme to posting━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - POS echo post                    theme: monokai                                ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▼ jsonplaceholder/Set the theme to monokai╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▼ posts/  theme: solarized-light                        ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - GET get all                Set the theme to solarized-lighters.╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - GET get one                  theme: nautilus                               ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - POS create                 Set the theme to nautilus╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - DEL delete a post            theme: galaxy                                 ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ -▼ comments/Set the theme to galaxy╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ - GET get comments           theme: nebula                                  Add header  - GET get comments (via parSet the theme to nebula────────────────────────────────╯ - PUT edit a comment         theme: alpine                                 ───────────────────── Response ─╮ -▼ todos/Set the theme to alpine - GET get all                  theme: cobalt                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - GET get one                Set the theme to cobalt -▼ users/  theme: twilight                                - GET get a user             Set the theme to twilight - GET get all users            theme: hacker                                  - POS create a user          Set the theme to hacker - PUT update a user            theme: example                                 -│────────────────────────────────Set the theme to example -This is an echo server we can u -to see exactly what request is ││ -being sent.││1:1read-onlyJSONWrap X -╰─────────────── sample-collections ─╯╰────────────────────────────────────────────────────────────────────────────╯ - ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  + + + + +Posting                                                                                                            + +GETEnter a URL... Send  +Search for commands… +╭─ Collection ───────────────────────────────────────── Request ─╮ + GET echo  theme: posting                                 + GET get random user            Set the theme to posting━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + POS echo post                    theme: monokai                                ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ jsonplaceholder/Set the theme to monokai╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ posts/  theme: solarized-light                        ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get all                Set the theme to solarized-lighters.╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get one                  theme: nautilus                               ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + POS create                 Set the theme to nautilus╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + DEL delete a post            theme: galaxy                                 ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ +▼ comments/Set the theme to galaxy╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + GET get comments           theme: nebula                                  Add header  + GET get comments (via parSet the theme to nebula────────────────────────────────╯ + PUT edit a comment         theme: alpine                                 ───────────────────── Response ─╮ +▼ todos/Set the theme to alpine + GET get all                  theme: cobalt                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + GET get one                Set the theme to cobalt +▼ users/  theme: twilight                                + GET get a user             Set the theme to twilight + GET get all users            theme: hacker                                  + POS create a user          Set the theme to hacker + PUT update a user            layout: horizontal                             +│────────────────────────────────Change layout to horizontal +This is an echo server we can u +to see exactly what request is ││ +being sent.││1:1read-onlyJSONWrap X +╰─────────────── sample-collections ─╯╰────────────────────────────────────────────────────────────────────────────╯ + ^j Send  ^t Method  ^s Save  ^n New  ^p Commands  ^o Jump  f1 Help  diff --git a/tests/sample-configs/general.yaml b/tests/sample-configs/general.yaml index d1457c2c..47da5e71 100644 --- a/tests/sample-configs/general.yaml +++ b/tests/sample-configs/general.yaml @@ -1,4 +1,5 @@ use_host_environment: false +load_user_themes: false response: show_size_and_time: false heading: diff --git a/tests/sample-configs/modified_config.yaml b/tests/sample-configs/modified_config.yaml index 1d988c30..d0c09d3c 100644 --- a/tests/sample-configs/modified_config.yaml +++ b/tests/sample-configs/modified_config.yaml @@ -1,6 +1,7 @@ theme: galaxy layout: horizontal use_host_environment: false +load_user_themes: false response: show_size_and_time: false focus: