diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 1772620e98..b0fb210989 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -21,6 +21,17 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + # Python 3.9 is on macos-13 but not macos-latest (macos-14-arm64) + # https://github.com/actions/setup-python/issues/696#issuecomment-1637587760 + exclude: + - { python-version: "3.8", os: "macos-latest" } + - { python-version: "3.9", os: "macos-latest" } + - { python-version: "3.11", os: "macos-latest" } + include: + - { python-version: "3.8", os: "macos-13" } + - { python-version: "3.9", os: "macos-13" } + - { python-version: "3.11", os: "macos-13" } defaults: run: shell: bash @@ -32,18 +43,18 @@ jobs: uses: actions/setup-python@v4.7.1 with: python-version: ${{ matrix.python-version }} - cache: 'poetry' + cache: "poetry" - name: Install dependencies run: poetry install --no-interaction --extras syntax if: ${{ matrix.python-version != '3.12' }} - - name: Install dependencies for 3.12 # https://github.com/Textualize/textual/issues/3491#issuecomment-1854156476 + - name: Install dependencies for 3.12 # https://github.com/Textualize/textual/issues/3491#issuecomment-1854156476 run: poetry install --no-interaction if: ${{ matrix.python-version == '3.12' }} - name: Test with pytest run: | poetry run pytest tests -v --cov=./src/textual --cov-report=xml:./coverage.xml --cov-report term-missing if: ${{ matrix.python-version != '3.12' }} - - name: Test with pytest for 3.12 # https://github.com/Textualize/textual/issues/3491#issuecomment-1854156476 + - name: Test with pytest for 3.12 # https://github.com/Textualize/textual/issues/3491#issuecomment-1854156476 run: | poetry run pytest tests -v --cov=./src/textual --cov-report=xml:./coverage.xml --cov-report term-missing -m 'not syntax' if: ${{ matrix.python-version == '3.12' }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f53b9c445b..5724b5fb19 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: end-of-file-fixer # ensures that a file is either empty, or ends with one newline - id: mixed-line-ending # replaces or checks mixed line ending - repo: https://github.com/pycqa/isort - rev: '5.12.0' + rev: '5.13.2' hooks: - id: isort name: isort (python) diff --git a/CHANGELOG.md b/CHANGELOG.md index 190fb35e17..bde2d05678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,338 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added + +- `TextArea.line_number_start` reactive attribute https://github.com/Textualize/textual/pull/4471 +- Added `DOMNode.mutate_reactive` https://github.com/Textualize/textual/pull/4731 +- Added "quality" parameter to `textual.color.Gradient` https://github.com/Textualize/textual/pull/4739 +- Added `textual.color.Gradient.get_rich_color` https://github.com/Textualize/textual/pull/4739 +- `Widget.remove_children` now accepts an iterable if widgets in addition to a selector https://github.com/Textualize/textual/issues/4735 +- Raise `ValueError` with improved error message when number of cells inserted using `DataTable.add_row` doesn't match the number of columns in the table https://github.com/Textualize/textual/pull/4742 +- Add `Tree.move_cursor` to programmatically move the cursor without selecting the node https://github.com/Textualize/textual/pull/4753 +- Added `Footer` component style handling of padding for the key/description https://github.com/Textualize/textual/pull/4651 + +### Fixed + +- Fixed issue with `Tabs` where disabled tabs could still be activated by clicking the underline https://github.com/Textualize/textual/issues/4701 +- Fixed scroll_visible with margin https://github.com/Textualize/textual/pull/4719 +- Fixed programmatically disabling button stuck in hover state https://github.com/Textualize/textual/pull/4724 +- Fixed `DataTable` poor performance on startup and focus change when rows contain multi-line content https://github.com/Textualize/textual/pull/4748 +- Fixed `Tree` and `DirectoryTree` horizontal scrolling off-by-2 https://github.com/Textualize/textual/pull/4744 +- Fixed text-opacity in component styles https://github.com/Textualize/textual/pull/4747 +- Ensure `Tree.select_node` sends `NodeSelected` message https://github.com/Textualize/textual/pull/4753 + +### Changed + +- "Discover" hits in the command palette are no longer sorted alphabetically https://github.com/Textualize/textual/pull/4720 +- `TreeNodeSelected` messages are now posted before `TreeNodeExpanded` messages +when an expandable node is selected https://github.com/Textualize/textual/pull/4753 +- `Markdown.LinkClicked.href` is now automatically unquoted https://github.com/Textualize/textual/pull/4749 + + +## [0.72.0] - 2024-07-09 + +### Changed + +- More predictable DOM removals. https://github.com/Textualize/textual/pull/4708 + +### Fixed + +- Fixed clicking separator in OptionList moving cursor https://github.com/Textualize/textual/issues/4710 +- Fixed scrolling issue in OptionList https://github.com/Textualize/textual/pull/4709 + +## [0.71.0] - 2024-06-29 + +### Changed + +- Snapshot tests will normalize SVG output so that changes with no visual impact don't break snapshots, but this release will break most of them. +- Breaking change: `App.push_screen` now returns an Awaitable rather than a screen. https://github.com/Textualize/textual/pull/4672 +- Breaking change: `Screen.dismiss` now returns an Awaitable rather than a bool. https://github.com/Textualize/textual/pull/4672 + +### Fixed + +- Fixed grid + keyline when the grid has auto dimensions https://github.com/Textualize/textual/pull/4680 +- Fixed mouse code leakage https://github.com/Textualize/textual/pull/4681 +- Fixed link inside markdown table not posting a `Markdown.LinkClicked` message https://github.com/Textualize/textual/issues/4683 +- Fixed issue with mouse movements on non-active screen https://github.com/Textualize/textual/pull/4688 + +## [0.70.0] - 2024-06-19 + +### Fixed + +- Fixed erroneous mouse 'ButtonDown' reporting for mouse movement when any-event mode is enabled in xterm. https://github.com/Textualize/textual/pull/3647 + +## [0.69.0] - 2024-06-16 + +### Added + +- Added `App.simulate_key` https://github.com/Textualize/textual/pull/4657 + +### Fixed + +- Fixed issue with pop_screen launched from an action https://github.com/Textualize/textual/pull/4657 + +### Changed + +- `App.check_bindings` is now private +- `App.action_check_bindings` is now `App.action_simulate_key` + +## [0.68.0] - 2024-06-14 + +### Added + +- Added `ContentSwitcher.add_content` + +### Fixed + +- Improved handling of non-tty input https://github.com/Textualize/textual/pull/4647 + +## [0.67.1] - 2024-06-12 + +### Changed + +- Reverts Vim keys in DataTable, provides alternatives https://github.com/Textualize/textual/pull/4638 + +## [0.67.0] - 2024-06-11 + +### Added + +- Added support for Kitty's key protocol https://github.com/Textualize/textual/pull/4631 +- `ctrl+pageup`/`ctrl+pagedown` will scroll page left/right in DataTable https://github.com/Textualize/textual/pull/4633 +- `g`/`G` will scroll to the top/bottom of the DataTable https://github.com/Textualize/textual/pull/4633 +- Added simple `hjkl` key bindings to move the cursor in DataTable https://github.com/Textualize/textual/pull/4633 + +### Changed + +- `home` and `end` now works horizontally instead of vertically in DataTable https://github.com/Textualize/textual/pull/4633 +- `Tree` and `DirectoryTree` nodes now have a bigger click target, spanning the full line https://github.com/Textualize/textual/pull/4636 + +### Fixed + +- Fixed pageup/pagedown behavior in DataTable https://github.com/Textualize/textual/pull/4633 +- Added `App.CLOSE_TIMEOUT` https://github.com/Textualize/textual/pull/4635 +- Fixed deadlock on shutdown https://github.com/Textualize/textual/pull/4635 + +## [0.66.0] - 2024-06-08 + +### Changed + +- `get_content_height` will now return 0 if the renderable is Falsey https://github.com/Textualize/textual/pull/4617 +- Buttons may not be pressed within their "active_effect_duration" to prevent inadvertent activations https://github.com/Textualize/textual/pull/4621 +- `Screen.dismiss` is now a noop if the screen isn't active. Previously it would raise a `ScreenStackError`, now it returns `False`. https://github.com/Textualize/textual/pull/4621 +- Increased window for escape processing to 100ms https://github.com/Textualize/textual/pull/4625 +- Tooltips are now hidden when any key is pressed https://github.com/Textualize/textual/pull/4625 + +### Added + +- Added `Screen.is_active` +- Added `icon` reactive to Header widget https://github.com/Textualize/textual/pull/4627 +- Added `time_format` reactive to Header widget https://github.com/Textualize/textual/pull/4627 +- Added `tooltip` parameter to input widgets https://github.com/Textualize/textual/pull/4625 + +## [0.65.2] - 2024-06-06 + +### Fixed + +- Fixed issue with notifications and screen switches https://github.com/Textualize/textual/pull/4615 + +### Added + +- Added textual.rlock.RLock https://github.com/Textualize/textual/pull/4615 + +## [0.65.1] - 2024-06-05 + +### Fixed + +- Fixed hot reloading with hatch rule https://github.com/Textualize/textual/pull/4606 +- Fixed hatch style parsing https://github.com/Textualize/textual/pull/4606 + +## [0.65.0] - 2024-06-05 + +### Added + +- Added Command Palette Opened, Closed, and OptionHighlighted events https://github.com/Textualize/textual/pull/4600 +- Added hatch style https://github.com/Textualize/textual/pull/4603 + +### Fixed + +- Fixed DataTable cursor flicker on scroll https://github.com/Textualize/textual/pull/4598 + +### Changes + +- TabbedContent will automatically make tabs active when a widget in a pane is focused https://github.com/Textualize/textual/issues/4593 + +## [0.64.0] - 2024-06-03 + +### Fixed + +- Fix traceback on exit https://github.com/Textualize/textual/pull/4575 +- Fixed `Markdown.goto_anchor` no longer scrolling the heading into view https://github.com/Textualize/textual/pull/4583 +- Fixed Footer flicker on initial focus https://github.com/Textualize/textual/issues/4573 + +## [0.63.6] - 2024-05-29 + +### Fixed + +- Fixed issue with bindings not refreshing https://github.com/Textualize/textual/pull/4571 + +## [0.63.5] - 2024-05-28 + +### Fixed + +- Fixed data table disappearing from tabs https://github.com/Textualize/textual/pull/4567 + +### Added + +- Added `Styles.is_auto_width` and `Style.is_auto_height` + +## [0.63.4] - 2024-05-26 + +### Added + +- Added `immediate` switch to `Signal.publish` + +### Fixed + +- Fixed freeze in recompose from bindings https://github.com/Textualize/textual/pull/4558 + +## [0.63.3] - 2024-05-24 + +### Fixed + +- Fixed `Footer` grid size https://github.com/Textualize/textual/pull/4545 +- Fixed bindings not updated on auto focus https://github.com/Textualize/textual/pull/4551 + +### Changed + +- Attempting to mount on a non-mounted widget now raises a MountError https://github.com/Textualize/textual/pull/4547 + +## [0.63.2] - 2024-05-23 + +### Fixed + +- Fixed issue with namespaces in links https://github.com/Textualize/textual/pull/4546 + +## [0.63.1] - 2024-05-22 + +### Fixed + +- Fixed display of multiple bindings https://github.com/Textualize/textual/pull/4543 + +## [0.63.0] - 2024-05-22 + +### Fixed + +- Fixed actions in links https://github.com/Textualize/textual/pull/4540 + +### Changed + +- Breaking change: New Footer (likely a drop in replacement, unless you have customized styles) https://github.com/Textualize/textual/pull/4537 +- Stylistic changes to Markdown (simpler headers, less margin, etc) https://github.com/Textualize/textual/pull/4541 + +## [0.62.0] - 2024-05-20 + +### Added + +- Added `start` and `end` properties to Markdown Navigator +- Added `Widget.anchor`, `Widget.clear_anchor`, and `Widget.is_anchored` https://github.com/Textualize/textual/pull/4530 + +## [0.61.1] - 2024-05-19 + +### Fixed + +- Fixed auto grid columns ignoring gutter https://github.com/Textualize/textual/issues/4522 + +## [0.61.0] - 2024-05-18 + +### Added + +- Added `App.get_default_screen` https://github.com/Textualize/textual/pull/4520 +- Added dynamic binding via `DOMNode.check_action` https://github.com/Textualize/textual/pull/4516 +- Added `"focused"` action namespace so you can bind a key to an action on the focused widget https://github.com/Textualize/textual/pull/4516 +- Added "focused" to allowed action namespaces https://github.com/Textualize/textual/pull/4516 + +### Changed + +- Breaking change: Actions (as used in bindings) will no longer check the app if they are unhandled. This was undocumented anyway, and not that useful. https://github.com/Textualize/textual/pull/4516 +- Breaking change: Renamed `App.namespace_bindings` to `active_bindings` + + +## [0.60.1] - 2024-05-15 + +### Fixed + +- Dependency issue + +## [0.60.0] - 2024-05-14 + +### Fixed + +- Fixed auto width not working for option lists https://github.com/Textualize/textual/pull/4507 + +### Added + +- Added `DOMNode.query_children` https://github.com/Textualize/textual/pull/4508 + +## [0.59.0] - 2024-05-11 + +### Fixed + +- Fixed `SelectionList` issues after removing an option https://github.com/Textualize/textual/pull/4464 +- Fixed `ListView` bugs with the initial index https://github.com/Textualize/textual/pull/4452 +- Fixed `Select` not closing https://github.com/Textualize/textual/pull/4499 +- Fixed setting `loading=False` removing all child loading indicators https://github.com/Textualize/textual/pull/4499 + +### Changed + +- When displaying a message using `App.exit()`, the console no longer highlights things such as numbers. + +### Added + +- Added `message_signal` to MessagePump, to listen to events sent to another widget. https://github.com/Textualize/textual/pull/4487 +- Added `Widget.suppress_click` https://github.com/Textualize/textual/pull/4499 + +## [0.58.1] - 2024-05-01 + +### Fixed + +- Fixed issue with Markdown mounting content lazily https://github.com/Textualize/textual/pull/4466 +- Fixed intermittent issue with scrolling to focus https://github.com/Textualize/textual/commit/567caf8acb196260adf6a0a6250e3ff5093056d0 +- Fixed issue with scrolling to center https://github.com/Textualize/textual/pull/4469 + + +## [0.58.0] - 2024-04-25 + +### Fixed + +- Fixed `TextArea` to end mouse selection only if currently selecting https://github.com/Textualize/textual/pull/4436 +- Fixed issue with scroll_to_widget https://github.com/Textualize/textual/pull/4446 +- Fixed issue with margins https://github.com/Textualize/textual/pull/4441 + +### Changed + +- Added argument to signal callbacks https://github.com/Textualize/textual/pull/4438 + +## [0.57.1] - 2024-04-20 + +### Fixed + +- Fixed an off-by-one error in the line number of the `Document.end` property https://github.com/Textualize/textual/issues/4426 +- Fixed setting scrollbar colors not updating the scrollbar https://github.com/Textualize/textual/pull/4433 +- Fixed flushing in inline mode https://github.com/Textualize/textual/pull/4435 + +### Added + +- Added `Offset.clamp` and `Size.clamp_offset` https://github.com/Textualize/textual/pull/4435 + + +## [0.57.0] - 2024-04-19 + ### Fixed - Fixed `Integer` validator missing failure description when not a number https://github.com/Textualize/textual/issues/4413 - Fixed a crash in `DataTable` if you clicked a link in the border https://github.com/Textualize/textual/issues/4410 +- Fixed issue with cursor position https://github.com/Textualize/textual/pull/4429 ### Added @@ -49,7 +377,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Added `Size.with_width` and `Size.with_height` https://github.com/Textualize/textual/pull/4393 - + ### Fixed - Fixed issue with inline mode and multiple screens https://github.com/Textualize/textual/pull/4393 @@ -64,7 +392,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- Fixed mouse escape sequences being generated with `mouse=False` +- Fixed mouse escape sequences being generated with `mouse=False` ## [0.55.0] - 2024-04-1 @@ -314,6 +642,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed display of keys when used in conjunction with other keys https://github.com/Textualize/textual/pull/3050 - Fixed double detection of Escape on Windows https://github.com/Textualize/textual/issues/4038 + ## [0.47.1] - 2024-01-05 ### Fixed @@ -1883,6 +2212,35 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040 - New handler system for messages that doesn't require inheritance - Improved traceback handling +[0.72.0]: https://github.com/Textualize/textual/compare/v0.71.0...v0.72.0 +[0.71.0]: https://github.com/Textualize/textual/compare/v0.70.0...v0.71.0 +[0.70.0]: https://github.com/Textualize/textual/compare/v0.69.0...v0.70.0 +[0.69.0]: https://github.com/Textualize/textual/compare/v0.68.0...v0.69.0 +[0.68.0]: https://github.com/Textualize/textual/compare/v0.67.1...v0.68.0 +[0.67.1]: https://github.com/Textualize/textual/compare/v0.67.0...v0.67.1 +[0.67.0]: https://github.com/Textualize/textual/compare/v0.66.0...v0.67.0 +[0.66.0]: https://github.com/Textualize/textual/compare/v0.65.2...v0.66.0 +[0.65.2]: https://github.com/Textualize/textual/compare/v0.65.1...v0.65.2 +[0.65.1]: https://github.com/Textualize/textual/compare/v0.65.0...v0.65.1 +[0.65.0]: https://github.com/Textualize/textual/compare/v0.64.0...v0.65.0 +[0.64.0]: https://github.com/Textualize/textual/compare/v0.63.6...v0.64.0 +[0.63.6]: https://github.com/Textualize/textual/compare/v0.63.5...v0.63.6 +[0.63.5]: https://github.com/Textualize/textual/compare/v0.63.4...v0.63.5 +[0.63.4]: https://github.com/Textualize/textual/compare/v0.63.3...v0.63.4 +[0.63.3]: https://github.com/Textualize/textual/compare/v0.63.2...v0.63.3 +[0.63.2]: https://github.com/Textualize/textual/compare/v0.63.1...v0.63.2 +[0.63.1]: https://github.com/Textualize/textual/compare/v0.63.0...v0.63.1 +[0.63.0]: https://github.com/Textualize/textual/compare/v0.62.0...v0.63.0 +[0.62.0]: https://github.com/Textualize/textual/compare/v0.61.1...v0.62.0 +[0.61.1]: https://github.com/Textualize/textual/compare/v0.61.0...v0.61.1 +[0.61.0]: https://github.com/Textualize/textual/compare/v0.60.1...v0.61.0 +[0.60.1]: https://github.com/Textualize/textual/compare/v0.60.0...v0.60.1 +[0.60.0]: https://github.com/Textualize/textual/compare/v0.59.0...v0.60.0 +[0.59.0]: https://github.com/Textualize/textual/compare/v0.58.1...v0.59.0 +[0.58.1]: https://github.com/Textualize/textual/compare/v0.58.0...v0.58.1 +[0.58.0]: https://github.com/Textualize/textual/compare/v0.57.1...v0.58.0 +[0.57.1]: https://github.com/Textualize/textual/compare/v0.57.0...v0.57.1 +[0.57.0]: https://github.com/Textualize/textual/compare/v0.56.3...v0.57.0 [0.56.3]: https://github.com/Textualize/textual/compare/v0.56.2...v0.56.3 [0.56.2]: https://github.com/Textualize/textual/compare/v0.56.1...v0.56.2 [0.56.1]: https://github.com/Textualize/textual/compare/v0.56.0...v0.56.1 diff --git a/docs/FAQ.md b/docs/FAQ.md index 4b280e9dda..a449c56f72 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -68,7 +68,7 @@ For more information on ANSI colors in Textual, see [Why no Ansi Themes?](#why-d !!! tip See [*How To Center Things*](https://textual.textualize.io/how-to/center-things/) in the - Textual documentation for a more comprensive answer to this question. + Textual documentation for a more comprehensive answer to this question. To center a widget within a container use [`align`](https://textual.textualize.io/styles/align/). But remember that @@ -270,7 +270,7 @@ work in different environments you can try them out with `textual keys`. ## Why doesn't Textual look good on macOS? -You may find that the default macOS Terminal.app doesn't render Textual apps (and likely other TUIs) very well, particuarily when it comes to box characters. +You may find that the default macOS Terminal.app doesn't render Textual apps (and likely other TUIs) very well, particularly when it comes to box characters. For instance, you may find it displays misaligned blocks and lines like this: Screenshot 2023-06-19 at 10 43 02 diff --git a/docs/blog/images/calcinline.png b/docs/blog/images/calcinline.png new file mode 100644 index 0000000000..1cf54bb507 Binary files /dev/null and b/docs/blog/images/calcinline.png differ diff --git a/docs/blog/images/inline1.excalidraw.svg b/docs/blog/images/inline1.excalidraw.svg new file mode 100644 index 0000000000..4f04fb237b --- /dev/null +++ b/docs/blog/images/inline1.excalidraw.svg @@ -0,0 +1,21 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO2caW7bSFx1MDAxNoD/51x1MDAxNIIzP7qBuFL7XHUwMDEyYDCwZStyXHUwMDFj2+1VTmZcdTAwMWFcckqiLMZcdTAwMTQpkZRspVx1MDAxMWAwZ+hcdTAwMWNjljPlJPOKdkRKMlx1MDAxNcWb1EmUwLGruDxcdTAwMTa/t5fz+5NSaSVcdTAwMTl23ZVcdTAwMTelXHUwMDE197Lh+F4zci5WntnxgVx1MDAxYsVeXHUwMDE4wFx1MDAxNE1/jsN+1EiPbCdJN37x/HnHic7dpOs7XHJcdTAwMTdccry47/hx0m96IWqEnede4nbiv9mvu07H/Ws37DSTXGJlN1l1m15cdTAwMTJGV/dyfbfjXHUwMDA2SVxmV/87/FxcKv2efs1JXHUwMDE3uY3EXHTOfDc9IZ3KXHUwMDA0JJjxyeHdMEilpVJcdCOVXHUwMDE5zXvxXHUwMDA23C5xmzDZXHUwMDAykd1sxlx1MDAwZa3UX56u8lajfr6zj/VvXHUwMDAz1yHRRT+7a8vz/cNk6KdSNaIwjlfbTtJoZ0fESVx1MDAxNJ67Na+ZtD8vXm58dG5cdTAwMWPCQmRnRWH/rFx1MDAxZLixXVx1MDAwMzJcdTAwMWFccrtOw0uG6TPi0ejVQrwoZSOX9k6UIcxcZlWGXHUwMDFhZrRQajRtLyAl0lpcdTAwMWGuXHUwMDE55lRzbiZcdTAwMDQrhz68XHUwMDBlXHUwMDEw7ClOP5lodadxflx1MDAwNvJcdTAwMDXN0TFJ5Fx1MDAwNHHXieClZcddXFw/slx1MDAwMEGYMEJJw5RmLJOj7Xpn7Vx1MDAwNFx1MDAwZWFcdTAwMTIjTbDhXFwyJoimMpPGTV9cclx1MDAxMVxcK1xml8jOtjJ0t5opJb9mLyRcdTAwMDK+tuwpQd/38+tcdTAwMTk0r9dzbKJuJzZzwGWX6nebzlx1MDAxNVx1MDAxOERcdTAwMTEmKeZSgJSjed9cdTAwMGLOJy/nh43zjKV09MOzWzCsjChCmGCl4aVcbknmhtjpVVx1MDAwNqe7XHUwMDA3bqvSOKqel/1cdTAwMWHFXHUwMDFi61x1MDAwNVx1MDAxME+AuDh8OdJcdTAwMDLAoFx1MDAxNFOjXGah4/hqhGFcdTAwMWSoxFx1MDAwMpBRWj9cdTAwMWO+xCBDODVcXGqBQZBpfKlGQlx0hTU2RmEpmZ7GXHUwMDE3c0OtXHUwMDFhfjP4ur7vdeNcdTAwMWLhhftcdTAwMTTBK7li5Gvs78nxK+dQXGYvejtcdTAwMDde69xcdTAwMDSV+JeXRfZ3Jrrk8dBlXHUwMDFjXHSARVx1MDAwMppEXHUwMDEzg8ctr5JAXHUwMDBio5JcdTAwMDHZXHUwMDE4kOJ3Qfdpy1x1MDAxMVTQaWxcdENcdTAwMWN0XHUwMDA3nFx1MDAxZOhcdTAwMGblWvNpblx0XHUwMDA1OSU3YHIxZYxyNcktXHUwMDAwXHUwMDBilvtbsrozsFU5XHUwMDFiM4Gt4kRcdTAwMWH4Oze2g+5cdTAwMWXpuoPN9ZbgZ69cdTAwMTmpXVbLtSXHVlx1MDAxMsSNXHUwMDExlFBcclx1MDAxNlx1MDAxNYtxalx1MDAwNThxooQ2XHUwMDE4XFwx0fKO1NYxXHUwMDE2XHUwMDBmRS1cdTAwMDSANtigmHxcdTAwMTfYSlWELVx1MDAwM3NLhNBsbm533uDt/dPN3v4gpju7XXZcdTAwMTJcdTAwMGZcdTAwMWG/LTm32iCwgYJDLKshvuTj3EJcdTAwMTiBOVx1MDAwNJZcdTAwMWNsMeNE3IlbQutcdTAwMTA1P1x1MDAxOLdKQKzOXHQ131xmt4l7mdxcdTAwMThcIlx1MDAxNEZcYlxcW88oclB/idmzzsn62/CiXHUwMDE19Fx1MDAwZvc3TpOeXGLeXHUwMDBivdzRLVx1MDAxN1x1MDAxNCmIXHIwY0bAs04kZ1xuI8CDXHUwMDE5XGJcdTAwMWQ0VzznfiehJa79c/voXHUwMDE2zDpEKIaBJJgqxm/glopJTjlIXHUwMDA1XGbrXHUwMDA1Yio1hORUf1x1MDAwNaaZVGGQXHUwMDFjeu/Talx1MDAwMFx1MDAxZVx1MDAxYq04XHUwMDFkz1x1MDAxZo691JTflOOo41x1MDAwNY6/Mjaz5ntnluZcdTAwMTXfbY1jnnhccsdcdTAwMWZNJ2E3m23AnVx1MDAxYy9wo+mVXHQj78ze5ajwrvCcbnVkUVDu3dSd2LWzdlxc30oniTCTo6OckzCqXHUwMDE1pGPzR0CXeu2UvD5cdTAwMWGcvCtv1JPGsLKvPLLcWkmZLYpcdTAwMTBuuMBgwlxyXHUwMDFlzzmpxogxQVx1MDAxNbf+RIviksldtZJcdTAwMTKMXGZT2cvNlDGXLVxcKSPF0qQ2YoGp5ZU2mjyl96iNWfTyWVx1MDAxYv9S6lx1MDAwZZN2XHUwMDE4lLzAXCKPusPH1ctZ95/U0Fx1MDAxYlx1MDAxNdTcUkHJ5OhIQVx1MDAwNbYhXHUwMDA1o/O7TYdcZumx13ldebllZCu52KItvrPkXG5KJVx1MDAwMlx1MDAxZsVcdTAwMDSXRFKFc4FtXG5cbjZIQZpcdTAwMDYpNzdE6clcdTAwMTg0U1DaMi7ndylcblx0JOlccvpJclx1MDAxMF8rKCNcdTAwMTCFa/pcdTAwMWSp56eP//70xz9cdTAwMTf59+N//lx1MDAxMXz6418lr9NccqOklLS9uDTzXHUwMDAzXHUwMDA3X53RXG6jktXrXHUwMDEyvP4z9yeCf37xhTPsp1x1MDAxYnlB8lPw81x1MDAxY/f4+L9Fr81/XHUwMDFm11j+oOHPQsNcXK6LiJm+a3ZnjnA5OTzyYJA9U1tcdTAwMWKe24GpzfiClVtcdTAwMGX1anHjpFLeXHUwMDFjen7052jNXHTEId9cdTAwMTKUgF9cdTAwMThrV1x1MDAwZdP0ykBgr4WEIFx1MDAxM1x1MDAxNlx1MDAwNcs71SxcdTAwMWWhN6chMIHogyy8uUHyKD9Yby5f0J/qzVx0SNgxJ/NT3H7/+vByUD1+e+iL9XpMnMBcdTAwMWK2lz1cZlOIci6UwMZcdTAwMDae48VcdTAwMGJu0yRcdTAwMDW8QDjKJeeYPVx1MDAxY7730puDlFx1MDAwZkIkyG+/XHUwMDE5fGdVi1x1MDAxNS6C1+a98FLN/H3lkzZda1x1MDAxZZQjUj1uXHUwMDFlXHUwMDFjNdpq66R6seTVYsjxXHJYPKZcdTAwMDVcdTAwMDXDy+SE7dVcdTAwMWFcdTAwMDG6WsExmuW7zsvXnKPEdpRccv4+wNW0cFOPzfkgIzTztzmq+28q4dbh+erQK7d5r856tfLakoMrXHUwMDE50jaxXHUwMDA150JcdTAwMTlcdTAwMTZ40uoqXHUwMDA040yJtHt3p/1cdTAwMTBcdTAwMGbcnlx1MDAwM72TWkqyyIrVI3Kriquq1LZ7zNdY3Jc7nlx1MDAxYuNcdTAwMWVf9VuDau3dpnpVq95qJ88jgmtcYjhpKYhSVLHc1pDP2Fx1MDAxMiWxpFx1MDAxMGOSO8Zcblx1MDAwZtye00pcdI5cdTAwMDVZeFf53rAtLDTqwiCBMNugs3ZobmYvutH+5j7ZXHUwMDEwu1x1MDAxYvuy2Ze7ZH/TXe5cYpdcdTAwMGKBIOfBXG645Fx1MDAxOPLSySiBXCJbZSRcdTAwMTAvSVx1MDAwNS+keFx1MDAwZs9CXHUwMDFhdFx1MDAwNljHkK0tvEH3UDXH77NBR1Vx/Z/BmlOt8fyJ53FP7jjbtdO1zdW9gyZdq78/7peXWy0h3kVUUS5cdTAwMTUziimWlVwi0j2hhlwiSPFsRFxm5Fx1MDAxOVpcXP5/zP6cLVx0gFx1MDAwMqtcdTAwMWb9ueyYb7Q/XHUwMDA3IXqhfnJqgcFfUVx1MDAxOPLXvM6GqO329ereK1V/XelttypLrp9UI1xi8Vx1MDAxOIUn5ZBCT2zFolx1MDAxNFx1MDAxMckoXHUwMDEznCqueXGs96jtOaGxXmhQl2on+5otV3fTzlx1MDAxZlxymVx1MDAxOWcsVUPmR3vuXHUwMDA3XHKP2Z6jhWmfpmCoXHUwMDE4IfOX2EhvfbuqlcvLb99eXjr97kbz6HLJ3Vx1MDAxN+OIQV5cdTAwMDc+Styw/1x1MDAwYuJNJLFcdTAwMDT/JpWcVVx1MDAxYXYxI4zMdF9PW62GaZhcdTAwMWJKXHUwMDE1XHUwMDE0XHQsXHUwMDA1xpRzwvGNXowypLUxIIfdlkem8z/wK5xA1irvIVx1MDAwMfyMUFx1MDAwNlx1MDAxMbtcdTAwMWX5UOzsRudkZ4/AONi+PN9cdTAwMWVe+q3tpNojvai25Veyus5cdTAwMTitTlx1MDAxNIVcdTAwMTcro5lcdTAwMGbX381ypZLl+6VcdTAwMGbZXHUwMDA2pKSwIM1cdTAwMTTmkI3kXHUwMDA0+ZK2bPT97aNcck81XHUwMDBl+/XtNVapbe3t9e5VW5phYm9/j+pcdTAwMDKJXHUwMDE4YkZAslx1MDAwNeGTytec7flcdTAwMWFzZCRcdTAwMTd2+5PUbEa0t3h1XHUwMDExttZjf1liQeryaFx1MDAwNb8rhbpcdTAwMDFmZoo3Zlx1MDAxMG53vOr5XHUwMDBifrM1fFx1MDAwMaZffVx1MDAxMWWGXHUwMDExNlxubD9k6oTh8X2Fmk7sx79LYaFcdTAwMTBlrjRcdTAwMDKGpaKQjlxiIFlOo6xcdTAwMDRcYmJ/aVBcdTAwMThObJljXG5larRS1NxH6W9cdTAwMWFlelx1MDAxN5RT26xulebEiVx1MDAxMyXrXtD0grPJU9ygmc3kRL7+T1x1MDAwNLbmXGJG0pSp0bfyr2LEIVFnRHNJMDNgMXjusDOna99cdTAwMTSCjI1rMFx1MDAxOFQoa1CmlsV34qRcdTAwMWN2Op61ub+EXHUwMDEwb06KnT7SmlXHtutMvVx1MDAwNnio/Nyk3nbtXHUwMDE1x1x1MDAxZG32XSljO/1h9P2vz248epVyg6RUXHUwMDAwN4RccpCW6/zpq1xuKWNgkCtpofzi1YoxvrrcJMHZXHUwMDA1n+T/vZ1LxoVcdTAwMDVcdTAwMTj7aNpQOn9cdTAwMDB7vMferVV3XHUwMDFijvP2/M3uprOxeeRcdTAwMWQtd1x1MDAwMKvA1UlcYl+lxvC4dLw8XG4mXHUwMDFlaSo45pxSlf8ls6VzyFx1MDAwNixcdTAwMTiwI+6hZHrP/lhqXHUwMDEwLVx1MDAxNyfM64+fXFwrzIrT7Vx1MDAxZSZwyZFo8Ghe87qGk11mZeC5XHUwMDE36zeuu/3YmDiV32Lops/54cmH/1x1MDAwM7lI91xmIn0= + + + + + terminal$ python inline.py╭──────────────────────────────────────────╮│ import this ││ for n in range(10): ││ print(n) │╰──────────────────────────────────────────╯terminal$ python inline.py╭──────────────────────────────────────────╮│ import this ││ for n in range(10): ││ print(n) │╰──────────────────────────────────────────╯ diff --git a/docs/blog/images/inline2.excalidraw.svg b/docs/blog/images/inline2.excalidraw.svg new file mode 100644 index 0000000000..3f0f6f1ab2 --- /dev/null +++ b/docs/blog/images/inline2.excalidraw.svg @@ -0,0 +1,21 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO2a627bRlx1MDAxNoD/5ylcdTAwMDRlf7RAzMzMmWuAxcJ27Nhx41xc3NSut0VAkyOJNUWyJGVbKVxmXHUwMDE0fYbmMbq7z5Qn2TO0I0qUZKuOb73Qhi3O9fDMd+acM9RPXHUwMDBmWq12Ocxs+0mrbU9cdTAwMDI/jsLcP24/cuVHNi+iNMEqVt1cdTAwMTfpIFx1MDAwZqqWvbLMiiePXHUwMDFm9/380JZZ7Fx1MDAwN9Y7ioqBXHUwMDFmXHUwMDE35SCMUi9I+4+j0vaLf7m/237f/jNL+2GZe/UkSzaMyjQ/m8vGtm+TssDR/433rdZP1d8x6XJcdTAwMWKUftKNbdWhqqpcdTAwMDWkjJlm8XaaVNJSobk2Wkkxalx1MDAxMVx1MDAxNU9xwtKGWN1BoW1d44rar9+Q7GiLraytL29mS/L5yTc/RLv1vJ0ojnfKYVxcyVx1MDAxNeRpUSz1/DLo1S2KMk9cdTAwMGbtblx1MDAxNJa9T+pcdTAwMWIrXHUwMDFm9S1SVEXdK09cdTAwMDfdXmJcdTAwMGKnXHUwMDA1OipNMz+IyqErI2RUeqaKJ6265Fx1MDAwNO+48oBcdTAwMTFGqVx1MDAwMEo0UC5H1WdcdTAwMDNIT3AlJZdMXHRcdTAwMDGsKdlqXHUwMDFh44qgZFx1MDAwZkl11bJcdTAwMWT4wWFcdTAwMTdcdTAwMDVMwlGbMveTXCLzc1xct7rd8fkzXHUwMDBiXHUwMDA2XHUwMDFlXGIjlDSgNIBcdTAwMWG16Nmo2yuxXHRI4mlKXGbnXHUwMDEyQFDNamFcdTAwMGJbrY0mRoBSXHUwMDEyRlx1MDAxNU6EbDOsOPm+XpBcdTAwMWNcdNt0PZJBXHUwMDFjj+szXHTP9TlRceAq1saQq4dcdTAwMWFkoX9cdTAwMDZcdTAwMDZVXHUwMDE0JJOaaSlquOIoOWxcdTAwMGVcdTAwMTenwWHNUlV6+uhcblx1MDAxNFx1MDAxYkPmQqy0MIpcdTAwMThtXHUwMDE2hvjHr7rrq2+Wj9f2uqTc3szWNlx1MDAwM39jXHUwMDBlxFxyXHUwMDEw71xmX+NR4Fx1MDAwMjjnhCjJTYNe5UmpXHUwMDA1Z6CMJOPV104vNZ6hnFx1MDAxOY7zXHUwMDExZeg0vUx7Qlx0RZBRXFxcdTAwMTgpQTfpXHUwMDE1XHUwMDFjgKEhij9ccr02jqOsmMmuXHUwMDE2MI9dzXEtJfDF0e2+j168XHUwMDEw2TqYcqhXzP7zb/rv315cdTAwMDVdemvoXG7taW64UVpqQyVcdTAwMTI8yS6VXHUwMDFlXHUwMDAzMMo4KnBTa0r2u9h92PFcdTAwMDVcdTAwMTNsmltcblx1MDAxZSeMXHQj0Vx1MDAwYjCuNZ9cdTAwMDaXMk+gXHUwMDA3MLjlXHUwMDEyXHUwMDE0iXHVXHUwMDA01/UlnCui/lxu5Jqxx2ySXHUwMDBiwJkyv4Pc7bWXXHUwMDFi5bF+2SGr79ZcdTAwMGZiXHUwMDE28/f7nftNrlx1MDAxNp7SXHUwMDA0d13BiFZKq1x1MDAwNrjCI1x1MDAxNCFcdTAwMTGMaSXImJlfXHLcXHUwMDAzQsRNgUu5llxcMKn+XHUwMDEyWy7qalx1MDAxZbhC4V4jcVFcdTAwMTdcdTAwMDZ36+nhXHUwMDBifTLs7m1nvb2NV1svI1x1MDAwMUf3XHUwMDFiXFyK0GhcZlx1MDAwM1x1MDAxNG5Uwlx1MDAwMLrpXHUwMDA2udzjUtAq1EVi9GdcdTAwMDW7XHUwMDBmKTvQWt5cdTAwMTi5Qlx1MDAxYuCamj9cdTAwMGa5pT0pZ2HL5NxIgUpCccVAwcLc/rD79sSHr0Gvvn6z+3SfLNFnavN+R7lMSc9grFx1MDAwZkJcdTAwMDKT+LyNUIFg7Ek0brycUMVByrncUut+rlx1MDAxZeYqgVx0mDGYK1x1MDAwMmFcbvhcZnSZmEJVXHUwMDAyZcDUWFxi81x1MDAwN0C1lipNyp3ova3inInSdb9cdTAwMWbFw4llrVx1MDAxOK5YzvtR4sftiZrlOOo6otux7UyiXkaBXHUwMDFmj6rLNKtrXHUwMDAznMmPXHUwMDEym09rJs2jrpvl67mz4nPajdGu4o0tzoFfWFfryvWV7Fx1MDAxMoRolo7sXHUwMDEyWUVcdTAwMWUx3VnYLjl59tan20d+9JzRaOdN9mpJsfttl5J66Cw4hlx1MDAwZlxcYOJcdTAwMDeT7lx1MDAwNFxcSmhcdTAwMDTarZIuir85q2SUeFx1MDAwNsZimNpcdTAwMThrXHUwMDAz/WSMXG4loZzDXHUwMDFk+1xyReg4pNdojLUr+GSM/2hlw7KXJq0occR72fB2zfKi+ZtcdTAwMDY60z7N1eyTz81ThDuA0Fx1MDAxNOo1uMw8i63lYfx8XHUwMDA11nskXGJ3XHUwMDBls414S2T32zxcdTAwMDXxJCa9mF9cdTAwMGJcdTAwMDBkrlx1MDAxZaXChFx1MDAxYlx1MDAwZkP/KrdmnEvdkKs2T9YxlvPPOVx1MDAxYlx1MDAxMk6QWVFeLfDIPCWG4VxmQ9C7Nk82juiNmufHXHUwMDBmv3389ee7/P3wn++Sj7/+0or6WZqXrbJcdTAwMTdcdTAwMTWtXHUwMDBiL2x81qOT5i1n1y1cdTAwMDSga7+g5Msnl/RwV5ZHSflF8uVcdTAwMDJzfPjfXevmv7e7Wf5Nw1x1MDAxZoWGhVxcXHUwMDE3XHUwMDE1XHUwMDE3+q5cdTAwMGJfb1x1MDAwMJ3rwDDrk1x1MDAxMjd1tXiA2Vx1MDAwYsPO9ru9XHUwMDFmXHUwMDA3sYSlwVx0W9/v7r+731x1MDAxZYxhXGJcdIxi2qdcdTAwMThRXHUwMDA006hcdFx1MDAxN8ZcdTAwMTnxmCGcXGKXi4HiXHLBalx1MDAxN2axL9BLTto6gVx0zIzzXG7mXHRcIlx1MDAwNSHoJSknMz1cdTAwMTlcdTAwMDNPa1xmdkFSYJrOyFx1MDAwMYlcdTAwMDDsj20+3699gqjGXGLOS07nu7tRn7r3iIxju9594dM1sqo3l5dcdTAwMDa7+1n323ykiFx0Xv08T4/bo5rT80/343Ugp3PfaVx1MDAwM/IhheKLXHUwMDA3fGHk2+Dbbnm4WiztvVx1MDAxYa7oZ6+D19dqLmFauumvM+IznsRcXEtzQrTShky+zMbVQGvSXFxcdTAwMTJpwL1qvsfmXCJcdTAwMTU+giHmXHUwMDFhjkyuZC63XHUwMDA29JlBzYDZSD5cdTAwMGZmyo3WinJYfPO/2MTvYPNXl1x1MDAxZS5cYkxP8Fx1MDAxMSWbtfWDwuyFUFx1MDAwNVx1MDAwMIg7tvqcw4X5LFx1MDAwYuqhsinRWktFZ1x1MDAxZVVjloXJXHUwMDAyo0BcYroppae+k0E1+ih5Uyyz22V5TM1+Xq5ESVx1MDAxOCXdZlx1MDAxN5uEdc2YyOffWdpcXCBcdTAwMWOpsqZg4OQnuFx1MDAwNFxmd1x01LJcdTAwMTRcdTAwMTRcdTAwMTVdXHUwMDFmqjlV+pnTgoerQ1D51X5CzbSFx35Rrqb9fuT23FcpRpxNqasnWnbm2LP+1CrgM43XNe02cyNOOtr6U6tGu7pcdTAwMTl9/v7RzNZKeSCldF9cdTAwMDKQXHUwMDA0o5/xzlxcee49s+S0qmLissHmM+yuKXrr4Vx1MDAxZYz/d46+mqDtZ9lOiVx1MDAxNI2WXHUwMDE2aY7C86S3Vln7KLLHKzNtzV0uhKhcdTAwMTbHbT22Qvv0wen/XHUwMDAxc82RiyJ9 + + + + + terminal$ python inline.py╭──────────────────────────────────────────╮│ import this ││ for n in range(10): ││ print(n) │╰──────────────────────────────────────────╯ diff --git a/docs/blog/posts/inline-mode.md b/docs/blog/posts/inline-mode.md new file mode 100644 index 0000000000..0127932144 --- /dev/null +++ b/docs/blog/posts/inline-mode.md @@ -0,0 +1,148 @@ +--- +draft: false +date: 2024-04-20 +categories: + - DevLog +authors: + - willmcgugan +--- + +# Behind the Curtain of Inline Terminal Applications + +Textual recently added the ability to run *inline* terminal apps. +You can see this in action if you run the [calculator example](https://github.com/Textualize/textual/blob/main/examples/calculator.py): + +![Inline Calculator](../images/calcinline.png) + +The application appears directly under the prompt, rather than occupying the full height of the screen—which is more typical of TUI applications. +You can interact with this calculator using keys *or* the mouse. +When you press ++ctrl+c++ the calculator disappears and returns you to the prompt. + +Here's another app that creates an inline code editor: + +=== "Video" + +
+ +
+ + +=== "inline.py" + ```python + from textual.app import App, ComposeResult + from textual.widgets import TextArea + + + class InlineApp(App): + CSS = """ + TextArea { + height: auto; + max-height: 50vh; + } + """ + + def compose(self) -> ComposeResult: + yield TextArea(language="python") + + + if __name__ == "__main__": + InlineApp().run(inline=True) + + ``` + +This post will cover some of what goes on under the hood to make such inline apps work. + +It's not going to go in to too much detail. +I'm assuming most readers will be more interested in a birds-eye view rather than all the gory details. + + + +## Programming the terminal + +Firstly, let's recap how you program the terminal. +Broadly speaking, the terminal is a device for displaying text. +You write (or print) text to the terminal which typically appears at the end of a continually growing text buffer. +In addition to text you can also send [escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code), which are short sequences of characters that instruct the terminal to do things such as change the text color, scroll, or other more exotic things. + +We only need a few of these escape codes to implement inline apps. + +!!! note + + I will gloss over the exact characters used for these escape codes. + It's enough to know that they exist for now. + If you implement any of this yourself, refer to the [wikipedia article](https://en.wikipedia.org/wiki/ANSI_escape_code). + +## Rendering frames + +The first step is to display the app, which is simply text (possibly with escape sequences to change color and style). +The lines are terminated with a newline character (`"\n"`), *except* for the very last line (otherwise we get a blank line a the end which we don't need). +Rather than a final newline, we write an escape code that moves the *cursor* back to it's prior position. + +The cursor is where text will be written. +It's the same cursor you see as you type. +Normally it will be at the end of the text in the terminal, but it can be moved around terminal with escape codes. +It can be made invisible (as in Textual apps), but the terminal will keep track of the cursor, even if it can not be seen. + +Textual moves the cursor back to its original starting position so that subsequent frames will overwrite the previous frame. + +Here's a diagram that shows how the cursor is positioned: + +!!! note + + I've drawn the cursor in red, although it isn't typically visible. + + +
+--8<-- "docs/blog/images/inline1.excalidraw.svg" +
+ + +There is an additional consideration that comes in to play when the output has less lines than the previous frame. +If we were to write a shorter frame, it wouldn't fully overwrite the previous frame. +We would be left with a few lines of a previous frame that wouldn't update. + +The solution to this problem is to write an escape code that clears lines from the cursor downwards before we write a smaller frame. +You can see this in action in the above video. +The inline app can grow or shrink in size, and still be anchored to the bottom of the terminal. + +## Cursor input + +The cursor tells the terminal where any text will be written by the app, but it also assumes this will be where the user enters text. +If you enter CJK (Chinese Japanese Korean) text in to the terminal, you will typically see a floating control that points where new text will be written. +If you are on a Mac, the emoji entry dialog (++ctrl+cmd+space++) will also point at the current cursor position. To make this work in a sane way, we need to move the terminal's cursor to where any new text will appear. + +The following diagram shows the cursor moving to the point where new text is displayed. + +
+--8<-- "docs/blog/images/inline2.excalidraw.svg" +
+ +This only really impacts text entry (such as the [Input](https://textual.textualize.io/widget_gallery/#input) and [TextArea](https://textual.textualize.io/widget_gallery/#textarea) widgets). + +## Mouse control + +Inline apps in Textual support mouse input, which works the same as fullscreen apps. + +To use the mouse in the terminal you send an escape code which tells the terminal to write encoded mouse coordinates to standard input. +The mouse coordinates can then be parsed in much the same was as reading keys. + +In inline mode this works in a similar way, with an added complication that the mouse origin is at the top left of the terminal. +In other words if you move the mouse to the top left of the terminal you get coordinate (0, 0), but the app expects (0, 0) to be where it was displayed. + +In order for the app to know where the mouse is relative to it's origin, we need to *ask* the terminal where the cursor is. +We do this with an escape code, which tells the terminal to write the current cursor coordinate to standard input. +We can then subtract that coordinate from the physical mouse coordinates, so we can send the app mouse events relative to its on-screen origin. + + +## tl;dr + +[Escapes codes](https://en.wikipedia.org/wiki/ANSI_escape_code). + +## Found this interesting? + +If you are interested in Textual, join our [Discord server](https://discord.gg/Enf6Z3qhVr). + +Or follow me for more terminal shenanigans. + +- [@willmcgugan](https://twitter.com/willmcgugan) +- [mastodon.social/@willmcgugan](https://mastodon.social/@willmcgugan) diff --git a/docs/css_types/hatch.md b/docs/css_types/hatch.md new file mode 100644 index 0000000000..b489bd3d3c --- /dev/null +++ b/docs/css_types/hatch.md @@ -0,0 +1,31 @@ +# <hatch> + +The `` CSS type represents a character used in the [hatch](../styles/hatch.md) rule. + +## Syntax + +| Value | Description | +| ------------ | ------------------------------ | +| `cross` | A diagonal crossed line. | +| `horizontal` | A horizontal line. | +| `left` | A left leaning diagonal line. | +| `right` | A right leaning diagonal line. | +| `vertical` | A vertical line. | + + +## Examples + +### CSS + + +```css +.some-class { + hatch: cross green; +} +``` + +### Python + +```py +widget.styles.hatch = ("cross", "red") +``` diff --git a/docs/css_types/index.md b/docs/css_types/index.md index 7f48be2def..1cb426581e 100644 --- a/docs/css_types/index.md +++ b/docs/css_types/index.md @@ -8,5 +8,5 @@ The CSS types will be denoted by a keyword enclosed by angle brackets `<` and `> For example, the style [`align-horizontal`](../styles/align.md) references the CSS type [``](./horizontal.md): --8<-- "docs/snippets/syntax_block_start.md" -align-horizontal: <horizontal>; +align-horizontal: <horizontal>; --8<-- "docs/snippets/syntax_block_end.md" diff --git a/docs/events/screen_resume.md b/docs/events/screen_resume.md index cd5f17266d..177a23f4c7 100644 --- a/docs/events/screen_resume.md +++ b/docs/events/screen_resume.md @@ -5,3 +5,7 @@ title: ScreenResume ::: textual.events.ScreenResume options: heading_level: 1 + +## See also + +- [ScreenSuspend](screen_suspend.md) diff --git a/docs/events/screen_suspend.md b/docs/events/screen_suspend.md index f4c21a3355..d9f99ba43f 100644 --- a/docs/events/screen_suspend.md +++ b/docs/events/screen_suspend.md @@ -5,3 +5,7 @@ title: ScreenSuspend ::: textual.events.ScreenSuspend options: heading_level: 1 + +## See also + +- [ScreenResume](screen_resume.md) diff --git a/docs/examples/guide/actions/actions03.py b/docs/examples/guide/actions/actions03.py index ff68a58e97..a74527408e 100644 --- a/docs/examples/guide/actions/actions03.py +++ b/docs/examples/guide/actions/actions03.py @@ -3,9 +3,9 @@ TEXT = """ [b]Set your background[/b] -[@click=set_background('red')]Red[/] -[@click=set_background('green')]Green[/] -[@click=set_background('blue')]Blue[/] +[@click=app.set_background('red')]Red[/] +[@click=app.set_background('green')]Green[/] +[@click=app.set_background('blue')]Blue[/] """ diff --git a/docs/examples/guide/actions/actions04.py b/docs/examples/guide/actions/actions04.py index c233400e74..0aa794763f 100644 --- a/docs/examples/guide/actions/actions04.py +++ b/docs/examples/guide/actions/actions04.py @@ -3,9 +3,9 @@ TEXT = """ [b]Set your background[/b] -[@click=set_background('red')]Red[/] -[@click=set_background('green')]Green[/] -[@click=set_background('blue')]Blue[/] +[@click=app.set_background('red')]Red[/] +[@click=app.set_background('green')]Green[/] +[@click=app.set_background('blue')]Blue[/] """ diff --git a/docs/examples/guide/actions/actions05.py b/docs/examples/guide/actions/actions05.py index 341dc72153..d6e8e6d478 100644 --- a/docs/examples/guide/actions/actions05.py +++ b/docs/examples/guide/actions/actions05.py @@ -3,9 +3,9 @@ TEXT = """ [b]Set your background[/b] -[@click=set_background('cyan')]Cyan[/] -[@click=set_background('magenta')]Magenta[/] -[@click=set_background('yellow')]Yellow[/] +[@click=app.set_background('cyan')]Cyan[/] +[@click=app.set_background('magenta')]Magenta[/] +[@click=app.set_background('yellow')]Yellow[/] """ diff --git a/docs/examples/guide/actions/actions06.py b/docs/examples/guide/actions/actions06.py new file mode 100644 index 0000000000..5114637ebc --- /dev/null +++ b/docs/examples/guide/actions/actions06.py @@ -0,0 +1,48 @@ +from textual.app import App, ComposeResult +from textual.containers import HorizontalScroll +from textual.reactive import reactive +from textual.widgets import Footer, Placeholder + +PAGES_COUNT = 5 + + +class PagesApp(App): + BINDINGS = [ + ("n", "next", "Next"), + ("p", "previous", "Previous"), + ] + + CSS_PATH = "actions06.tcss" + + page_no = reactive(0) + + def compose(self) -> ComposeResult: + with HorizontalScroll(id="page-container"): + for page_no in range(PAGES_COUNT): + yield Placeholder(f"Page {page_no}", id=f"page-{page_no}") + yield Footer() + + def action_next(self) -> None: + self.page_no += 1 + self.refresh_bindings() # (1)! + self.query_one(f"#page-{self.page_no}").scroll_visible() + + def action_previous(self) -> None: + self.page_no -= 1 + self.refresh_bindings() # (2)! + self.query_one(f"#page-{self.page_no}").scroll_visible() + + def check_action( + self, action: str, parameters: tuple[object, ...] + ) -> bool | None: # (3)! + """Check if an action may run.""" + if action == "next" and self.page_no == PAGES_COUNT - 1: + return False + if action == "previous" and self.page_no == 0: + return False + return True + + +if __name__ == "__main__": + app = PagesApp() + app.run() diff --git a/docs/examples/guide/actions/actions06.tcss b/docs/examples/guide/actions/actions06.tcss new file mode 100644 index 0000000000..250ee3d753 --- /dev/null +++ b/docs/examples/guide/actions/actions06.tcss @@ -0,0 +1,4 @@ +#page-container { + # This hides the scrollbar + scrollbar-size: 0 0; +} diff --git a/docs/examples/guide/actions/actions07.py b/docs/examples/guide/actions/actions07.py new file mode 100644 index 0000000000..0344dd4609 --- /dev/null +++ b/docs/examples/guide/actions/actions07.py @@ -0,0 +1,44 @@ +from textual.app import App, ComposeResult +from textual.containers import HorizontalScroll +from textual.reactive import reactive +from textual.widgets import Footer, Placeholder + +PAGES_COUNT = 5 + + +class PagesApp(App): + BINDINGS = [ + ("n", "next", "Next"), + ("p", "previous", "Previous"), + ] + + CSS_PATH = "actions06.tcss" + + page_no = reactive(0, bindings=True) # (1)! + + def compose(self) -> ComposeResult: + with HorizontalScroll(id="page-container"): + for page_no in range(PAGES_COUNT): + yield Placeholder(f"Page {page_no}", id=f"page-{page_no}") + yield Footer() + + def action_next(self) -> None: + self.page_no += 1 + self.query_one(f"#page-{self.page_no}").scroll_visible() + + def action_previous(self) -> None: + self.page_no -= 1 + self.query_one(f"#page-{self.page_no}").scroll_visible() + + def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None: + """Check if an action may run.""" + if action == "next" and self.page_no == PAGES_COUNT - 1: + return None # (2)! + if action == "previous" and self.page_no == 0: + return None # (3)! + return True + + +if __name__ == "__main__": + app = PagesApp() + app.run() diff --git a/docs/examples/guide/reactivity/set_reactive03.py b/docs/examples/guide/reactivity/set_reactive03.py new file mode 100644 index 0000000000..80d3a45bd9 --- /dev/null +++ b/docs/examples/guide/reactivity/set_reactive03.py @@ -0,0 +1,21 @@ +from textual.app import App, ComposeResult +from textual.reactive import reactive +from textual.widgets import Input, Label + + +class MultiGreet(App): + names: reactive[list[str]] = reactive(list, recompose=True) # (1)! + + def compose(self) -> ComposeResult: + yield Input(placeholder="Give me a name") + for name in self.names: + yield Label(f"Hello, {name}") + + def on_input_submitted(self, event: Input.Changed) -> None: + self.names.append(event.value) + self.mutate_reactive(MultiGreet.names) # (2)! + + +if __name__ == "__main__": + app = MultiGreet() + app.run() diff --git a/docs/examples/styles/hatch.py b/docs/examples/styles/hatch.py new file mode 100644 index 0000000000..bbdce366ff --- /dev/null +++ b/docs/examples/styles/hatch.py @@ -0,0 +1,22 @@ +from textual.app import App, ComposeResult +from textual.containers import Horizontal, Vertical +from textual.widgets import Static + +HATCHES = ("cross", "horizontal", "custom", "left", "right") + + +class HatchApp(App): + CSS_PATH = "hatch.tcss" + + def compose(self) -> ComposeResult: + with Horizontal(): + for hatch in HATCHES: + static = Static(classes=f"hatch {hatch}") + static.border_title = hatch + with Vertical(): + yield static + + +if __name__ == "__main__": + app = HatchApp() + app.run() diff --git a/docs/examples/styles/hatch.tcss b/docs/examples/styles/hatch.tcss new file mode 100644 index 0000000000..b2bcbce119 --- /dev/null +++ b/docs/examples/styles/hatch.tcss @@ -0,0 +1,20 @@ +.hatch { + height: 1fr; + border: solid $secondary; + + &.cross { + hatch: cross $success; + } + &.horizontal { + hatch: horizontal $success 80%; + } + &.custom { + hatch: "T" $success 60%; + } + &.left { + hatch: left $success 40%; + } + &.right { + hatch: right $success 20%; + } +} diff --git a/docs/examples/widgets/classic_footer.py b/docs/examples/widgets/classic_footer.py new file mode 100644 index 0000000000..104b4fc93e --- /dev/null +++ b/docs/examples/widgets/classic_footer.py @@ -0,0 +1,25 @@ +from textual.app import App, ComposeResult +from textual.binding import Binding +from textual.widgets import ClassicFooter + + +class FooterApp(App): + BINDINGS = [ + Binding(key="q", action="quit", description="Quit the app"), + Binding( + key="question_mark", + action="help", + description="Show help screen", + key_display="?", + ), + Binding(key="delete", action="delete", description="Delete the thing"), + Binding(key="j", action="down", description="Scroll down", show=False), + ] + + def compose(self) -> ComposeResult: + yield ClassicFooter() + + +if __name__ == "__main__": + app = FooterApp() + app.run() diff --git a/docs/examples/widgets/data_table_cursors.py b/docs/examples/widgets/data_table_cursors.py index 8c33f2be51..8139b426f6 100644 --- a/docs/examples/widgets/data_table_cursors.py +++ b/docs/examples/widgets/data_table_cursors.py @@ -16,7 +16,7 @@ (10, "Darren Burns", "Scotland", 51.84), ] -cursors = cycle(["column", "row", "cell"]) +cursors = cycle(["column", "row", "cell", "none"]) class TableApp(App): diff --git a/docs/getting_started.md b/docs/getting_started.md index 46addc0bc2..60fd5023fa 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -38,7 +38,7 @@ pip install textual-dev ### From conda-forge -Textual is also available on [conda-forge](https://conda-forge.org/). The preferred package manager for conda-forge is currently [micromamba](https://mamba.readthedocs.io/en/latest/installation.html#micromamba): +Textual is also available on [conda-forge](https://conda-forge.org/). The preferred package manager for conda-forge is currently [micromamba](https://mamba.readthedocs.io/en/latest/installation/micromamba-installation.html): ``` micromamba install -c conda-forge textual @@ -104,8 +104,9 @@ cd textual/examples/ python code_browser.py ../ ``` +### Widget examples - +In addition to the example apps, you can also find the code listings used to generate the screenshots in these docs in the `docs/examples` directory. ## Need help? diff --git a/docs/guide/actions.md b/docs/guide/actions.md index 4e7c8f8c19..312ca53c8e 100644 --- a/docs/guide/actions.md +++ b/docs/guide/actions.md @@ -118,10 +118,88 @@ Textual supports the following action namespaces: - `app` invokes actions on the App. - `screen` invokes actions on the screen. +- `focused` invokes actions on the currently focused widget (if there is one). In the previous example if you wanted a link to set the background on the app rather than the widget, we could set a link to `app.set_background('red')`. +## Dynamic actions + +!!! tip "Added in version 0.61.0" + +There may be situations where an action is temporarily unavailable due to some internal state within your app. +For instance, consider an app with a fixed number of pages and actions to go to the next and previous page. +It doesn't make sense to go to the previous page if we are on the first, or the next page when we are on the last page. + +We could easily add this logic to the action methods, but the [footer][textual.widgets.Footer] would still display the keys even if they would have no effect. +The user may wonder why the app is showing keys that don't appear to work. + +We can solve this issue by implementing the [`check_action`][textual.dom.DOMNode.check_action] on our app, screen, or widget. +This method is called with the name of the action and any parameters, prior to running actions or refreshing the footer. +It should return one of the following values: + +- `True` to show the key and run the action as normal. +- `False` to hide the key and prevent the action running. +- `None` to disable the key (show dimmed), and prevent the action running. + +Let's write an app to put this into practice: + +=== "actions06.py" + + ```python title="actions06.py" hl_lines="27 32 35-43" + --8<-- "docs/examples/guide/actions/actions06.py" + ``` + + 1. Prompts the footer to refresh, if bindings change. + 2. Prompts the footer to refresh, if bindings change. + 3. Guards the actions from running and also what keys are displayed in the footer. + +=== "actions06.tcss" + + ```css title="actions06.tcss" + --8<-- "docs/examples/guide/actions/actions06.tcss" + ``` + +=== "Output" + + ```{.textual path="docs/examples/guide/actions/actions06.py"} + ``` + +This app has key bindings for ++n++ and ++p++ to navigate the pages. +Notice how the keys are hidden from the footer when they would have no effect. + +The actions above call [`refresh_bindings`][textual.dom.DOMNode.refresh_bindings] to prompt Textual to refresh the footer. +An alternative to doing this manually is to set `bindings=True` on a [reactive](./reactivity.md), which will refresh the bindings if the reactive changes. + +Let's make this change. +We will also demonstrate what the footer will show if we return `None` from `check_action` (rather than `False`): + + +=== "actions07.py" + + ```python title="actions06.py" hl_lines="17 36 38" + --8<-- "docs/examples/guide/actions/actions07.py" + ``` + + 1. The `bindings=True` causes the footer to refresh when `page_no` changes. + 2. Returning `None` disables the key in the footer rather than hides it + 3. Returning `None` disables the key in the footer rather than hides it. + +=== "actions06.tcss" + + ```css title="actions06.tcss" + --8<-- "docs/examples/guide/actions/actions06.tcss" + ``` + +=== "Output" + + ```{.textual path="docs/examples/guide/actions/actions07.py"} + ``` + +Note how the logic is the same but we don't need to explicitly call [`refresh_bindings`][textual.dom.DOMNode.refresh_bindings]. +The change to `check_action` also causes the disabled footer keys to be grayed out, indicating they are temporarily unavailable. + + ## Builtin actions Textual supports the following builtin actions which are defined on the app. @@ -129,16 +207,16 @@ Textual supports the following builtin actions which are defined on the app. - [action_add_class][textual.app.App.action_add_class] - [action_back][textual.app.App.action_back] - [action_bell][textual.app.App.action_bell] -- [action_check_bindings][textual.app.App.action_check_bindings] -- [action_focus][textual.app.App.action_focus] - [action_focus_next][textual.app.App.action_focus_next] - [action_focus_previous][textual.app.App.action_focus_previous] +- [action_focus][textual.app.App.action_focus] - [action_pop_screen][textual.app.App.action_pop_screen] - [action_push_screen][textual.app.App.action_push_screen] - [action_quit][textual.app.App.action_quit] - [action_remove_class][textual.app.App.action_remove_class] - [action_screenshot][textual.app.App.action_screenshot] -- [action_switch_screen][textual.app.App.action_switch_screen] +- [action_simulate_key][textual.app.App.action_simulate_key] - [action_suspend_process][textual.app.App.action_suspend_process] +- [action_switch_screen][textual.app.App.action_switch_screen] - [action_toggle_class][textual.app.App.action_toggle_class] - [action_toggle_dark][textual.app.App.action_toggle_dark] diff --git a/docs/guide/app.md b/docs/guide/app.md index 2431f389b9..54df97175d 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -47,6 +47,10 @@ Inline apps are useful for tools that integrate closely with the typical workflo To run an app in inline mode set the `inline` parameter to `True` when you call [App.run()][textual.app.App.run]. See [Style Inline Apps](../how-to/style-inline-apps.md) for how to apply additional styles to inline apps. +!!! note + + Inline mode is not currently supported on Windows. + ## Events Textual has an event system you can use to respond to key presses, mouse actions, and internal state changes. Event handlers are methods prefixed with `on_` followed by the name of the event. diff --git a/docs/guide/command_palette.md b/docs/guide/command_palette.md index 5fa1bebc08..484ccbe49c 100644 --- a/docs/guide/command_palette.md +++ b/docs/guide/command_palette.md @@ -113,7 +113,6 @@ this is to aid in command discoverability. - `discover` accepts no parameters (instead of the search value) - `discover` yields instances of [`DiscoveryHit`][textual.command.DiscoveryHit] (instead of instances of [`Hit`][textual.command.Hit]) -- discovery hits are sorted in ascending alphabetical order because there is no matching and no match score is generated Instances of [`DiscoveryHit`][textual.command.DiscoveryHit] contain information about how the hit should be displayed, an optional help string, and a callback which will be run if the user selects that command. diff --git a/docs/guide/events.md b/docs/guide/events.md index 0f06d6f7ce..9197949a33 100644 --- a/docs/guide/events.md +++ b/docs/guide/events.md @@ -268,7 +268,7 @@ The `on` decorator also accepts selectors as keyword arguments that may be used The snippet below shows how to match the message [`TabbedContent.TabActivated`][textual.widgets.TabbedContent.TabActivated] only when the tab with id `home` was activated: ```py -@on(TabbedContent.TabActivated, tab="#home") +@on(TabbedContent.TabActivated, pane="#home") def home_tab(self) -> None: self.log("Switched back to home tab.") ... diff --git a/docs/guide/queries.md b/docs/guide/queries.md index 5401257874..d33659f382 100644 --- a/docs/guide/queries.md +++ b/docs/guide/queries.md @@ -10,17 +10,20 @@ Selectors are a very useful idea and can do more than apply styles. We can also ## Query one -The [query_one][textual.dom.DOMNode.query_one] method gets a single widget in an app or other widget. If you call it with a selector it will return the first matching widget. +The [query_one][textual.dom.DOMNode.query_one] method is used to retrieve a single widget that matches a selector or a type. -Let's say we have a widget with an ID of `send` and we want to get a reference to it in our app. We could do this with the following: +Let's say we have a widget with an ID of `send` and we want to get a reference to it in our app. +We could do this with the following line of code: ```python send_button = self.query_one("#send") ``` -If there is no widget with an ID of `send`, Textual will raise a [NoMatches][textual.css.query.NoMatches] exception. Otherwise it will return the matched widget. +This will retrieve a widget with an ID of `send`, if there is exactly one. +If there are no matching widgets, Textual will raise a [NoMatches][textual.css.query.NoMatches] exception. +If there is more than one match, Textual will raise a [TooManyMatches][textual.css.query.TooManyMatches] exception. -You can also add a second parameter for the expected type. +You can also add a second parameter for the expected type, which will ensure that you get the type you are expecting. ```python send_button = self.query_one("#send", Button) @@ -32,9 +35,16 @@ If the matched widget is *not* a button (i.e. if `isinstance(widget, Button)` eq The second parameter allows type-checkers like MyPy to know the exact return type. Without it, MyPy would only know the result of `query_one` is a Widget (the base class). +You can also specify a widget type in place of a selector, which will return a widget of that type. +For instance, the following would return a `Button` instance (assuming there is a single Button). + +```python +my_button = self.query_one(Button) +``` + ## Making queries -Apps and widgets have a [query][textual.dom.DOMNode.query] method which finds (or queries) widgets. This method returns a [DOMQuery][textual.css.query.DOMQuery] object which is a list-like container of widgets. +Apps and widgets also have a [query][textual.dom.DOMNode.query] method which finds (or queries) widgets. This method returns a [DOMQuery][textual.css.query.DOMQuery] object which is a list-like container of widgets. If you call `query` with no arguments, you will get back a `DOMQuery` containing all widgets. This method is *recursive*, meaning it will also return child widgets (as many levels as required). diff --git a/docs/guide/reactivity.md b/docs/guide/reactivity.md index 0d191ad832..ff7a1cebdf 100644 --- a/docs/guide/reactivity.md +++ b/docs/guide/reactivity.md @@ -383,6 +383,31 @@ The following app contains a fix for this issue: The line `self.set_reactive(Greeter.greeting, greeting)` sets the `greeting` attribute but doesn't immediately invoke the watcher. +## Mutable reactives + +Textual can detect when you set a reactive to a new value, but it can't detect when you _mutate_ a value. +In practice, this means that Textual can detect changes to basic types (int, float, str, etc.), but not if you update a collection, such as a list or dict. + +You can still use collections and other mutable objects in reactives, but you will need to call [`mutate_reactive`][textual.dom.DOMNode.mutate_reactive] after making changes for the reactive superpowers to work. + +Here's an example, that uses a reactive list: + +=== "set_reactive03.py" + + ```python hl_lines="16" + --8<-- "docs/examples/guide/reactivity/set_reactive03.py" + ``` + + 1. Creates a reactive list of strings. + 2. Explicitly mutate the reactive list. + +=== "Output" + + ```{.textual path="docs/examples/guide/reactivity/set_reactive03.py" press="W,i,l,l,enter"} + ``` + +Note the call to `mutate_reactive`. Without it, the display would not update when a new name is appended to the list. + ## Data binding Reactive attributes from one widget may be *bound* (connected) to another widget, so that changes to a single reactive will automatically update another widget (potentially more than one). diff --git a/docs/guide/screens.md b/docs/guide/screens.md index 4f57412fd0..0b72bf589f 100644 --- a/docs/guide/screens.md +++ b/docs/guide/screens.md @@ -128,6 +128,7 @@ Like [pop_screen](#pop-screen), if the screen being replaced is not installed it You can also switch screens with the `"app.switch_screen"` action which accepts the name of the screen to switch to. + ## Screen opacity If a screen has a background color with an *alpha* component, then the background color will be blended with the screen beneath it. @@ -355,3 +356,12 @@ One for a dashboard, one for settings, and one for help. We've bound keys to each of these screens, so the user can switch between the screens. Pressing ++d++, ++s++, or ++h++ switches between these modes. + + +## Screen events + +Textual will send a [ScreenSuspend](../events/screen_suspend.md) event to screens that have become inactive due to another screen being pushed, or switching via a mode. + +When a screen becomes active, Textual will send a [ScreenResume](../events/screen_resume.md) event to the newly active screen. + +These events can be useful if you want to disable processing for a screen that is no longer visible, for example. diff --git a/docs/guide/workers.md b/docs/guide/workers.md index 71a06c093e..ecc3f541b0 100644 --- a/docs/guide/workers.md +++ b/docs/guide/workers.md @@ -10,6 +10,9 @@ There are many interesting uses for Textual which require reading data from an i When an app requests data from the network it is important that it doesn't prevent the user interface from updating. In other words, the requests should be concurrent (happen at the same time) as the UI updates. +This is also true for anything that could take a significant time (more than a few milliseconds) to complete. +For instance, reading from a [subprocess](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio-subprocess) or doing compute heavy work. + Managing this concurrency is a tricky topic, in any language or framework. Even for experienced developers, there are gotchas which could make your app lock up or behave oddly. Textual's Worker API makes concurrency far less error prone and easier to reason about. diff --git a/docs/robots.txt b/docs/robots.txt new file mode 100644 index 0000000000..d6d23f0eed --- /dev/null +++ b/docs/robots.txt @@ -0,0 +1 @@ +Sitemap: https://textual.textualize.io/sitemap.xml diff --git a/docs/styles/grid/index.md b/docs/styles/grid/index.md index 10c68267c0..f7ccd54045 100644 --- a/docs/styles/grid/index.md +++ b/docs/styles/grid/index.md @@ -16,17 +16,17 @@ For an in-depth look at the grid layout, visit the grid [guide](../../guide/layo ## Syntax --8<-- "docs/snippets/syntax_block_start.md" -column-span: <integer>; +column-span: <integer>; -grid-columns: <scalar>+; +grid-columns: <scalar>+; -grid-gutter: <scalar> [<scalar>]; +grid-gutter: <scalar> [<scalar>]; -grid-rows: <scalar>+; +grid-rows: <scalar>+; -grid-size: <integer> [<integer>]; +grid-size: <integer> [<integer>]; -row-span: <integer>; +row-span: <integer>; --8<-- "docs/snippets/syntax_block_end.md" Visit each style's reference page to learn more about how the values are used. diff --git a/docs/styles/hatch.md b/docs/styles/hatch.md new file mode 100644 index 0000000000..41cac23edb --- /dev/null +++ b/docs/styles/hatch.md @@ -0,0 +1,58 @@ +# Hatch + +The `hatch` style fills a widget's background with a repeating character for a pleasing textured effect. + +## Syntax + +--8<-- "docs/snippets/syntax_block_start.md" +hatch: (<hatch> | CHARACTER) <color> [<percentage>] +--8<-- "docs/snippets/syntax_block_end.md" + +The hatch type can be specified with a constant, or a string. For example, `cross` for cross hatch, or `"T"` for a custom character. + +The color can be any Textual color value. + +An optional percentage can be used to set the opacity. + +## Examples + + +An app to show a few hatch effects. + +=== "Output" + + ```{.textual path="docs/examples/styles/hatch.py"} + ``` + +=== "hatch.py" + + ```py + --8<-- "docs/examples/styles/hatch.py" + ``` + +=== "hatch.tcss" + + ```css + --8<-- "docs/examples/styles/hatch.tcss" + ``` + + +## CSS + +```css +/* Red cross hatch */ +hatch: cross red; +/* Right diagonals, 50% transparent green. */ +hatch: right green 50%; +/* T custom character in 80% blue. **/ +hatch: "T" blue 80%; +``` + + +## Python + +```py +widget.styles.hatch = ("cross", "red") +widget.styles.hatch = ("right", "rgba(0,255,0,128)") +widget.styles.hatch = ("T", "blue") +``` diff --git a/docs/widget_gallery.md b/docs/widget_gallery.md index ca82b5d4e3..1d5ef708cb 100644 --- a/docs/widget_gallery.md +++ b/docs/widget_gallery.md @@ -34,6 +34,21 @@ A classic checkbox control. ```{.textual path="docs/examples/widgets/checkbox.py"} ``` +## ClassicFooter + +The original Footer widget. + +!!! warning + + This has been replaced by [`Footer`](#footer), and will be removed in Textual v1.0 + +[ClassicFooter reference](./widgets/classic_footer.md){ .md-button .md-button--primary } + +```{.textual path="docs/examples/widgets/classic_footer.py" columns="70" lines="12"} +``` + + + ## Collapsible Content that may be toggled on and off by clicking a title. @@ -90,6 +105,7 @@ A footer to display and interact with key bindings. ``` + ## Header A header to display the app's title and subtitle. diff --git a/docs/widgets/classic_footer.md b/docs/widgets/classic_footer.md new file mode 100644 index 0000000000..0917ded739 --- /dev/null +++ b/docs/widgets/classic_footer.md @@ -0,0 +1,64 @@ +# ClassicFooter + +!!! warning "Deprecated widget" + + This is an older version of the Textual Footer, and will be replaced with [Footer](./footer.md) in Textual v1.0 + + +A simple footer widget which is docked to the bottom of its parent container. Displays +available keybindings for the currently focused widget. + +- [ ] Focusable +- [ ] Container + +## Example + +The example below shows an app with a single keybinding that contains only a `ClassicFooter` +widget. Notice how the `ClassicFooter` automatically displays the keybinding. + +=== "Output" + + ```{.textual path="docs/examples/widgets/classic_footer.py"} + ``` + +=== "footer.py" + + ```python + --8<-- "docs/examples/widgets/classic_footer.py" + ``` + +## Reactive Attributes + +| Name | Type | Default | Description | +| --------------- | ----- | ------- | --------------------------------------------------------------------------------------------------------- | +| `highlight_key` | `str` | `None` | Stores the currently highlighted key. This is typically the key the cursor is hovered over in the footer. | + +## Messages + +This widget posts no messages. + +## Bindings + +This widget has no bindings. + +## Component Classes + +The footer widget provides the following component classes: + +::: textual.widgets.ClassicFooter.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false + +## Additional Notes + +* You can prevent keybindings from appearing in the footer by setting the `show` argument of the `Binding` to `False`. +* You can customize the text that appears for the key itself in the footer using the `key_display` argument of `Binding`. + + +--- + + +::: textual.widgets.ClassicFooter + options: + heading_level: 2 diff --git a/docs/widgets/data_table.md b/docs/widgets/data_table.md index ab1981c0f1..321f9ac8e4 100644 --- a/docs/widgets/data_table.md +++ b/docs/widgets/data_table.md @@ -1,6 +1,8 @@ # DataTable -A table widget optimized for displaying a lot of data. +A widget to display text in a table. This includes the ability to update data, use a cursor to navigate data, respond to mouse clicks, delete rows or columns, and individually render each cell as a Rich Text renderable. DataTable provides an efficiently displayed and updated table capable for most applications. + +Applications may have custom rules for formatting, numbers, repopulating tables after searching or filtering, and responding to selections. The widget emits events to interface with custom logic. - [x] Focusable - [ ] Container @@ -55,13 +57,27 @@ The method [add_column][textual.widgets.DataTable.add_column] also accepts a `ke Keys are important because cells in a data table can change location due to factors like row deletion and sorting. Thus, using keys instead of coordinates allows us to refer to data without worrying about its current location in the table. -If you want to change the table based solely on coordinates, you can use the [coordinate_to_cell_key][textual.widgets.DataTable.coordinate_to_cell_key] method to convert a coordinate to a _cell key_, which is a `(row_key, column_key)` pair. +If you want to change the table based solely on coordinates, you may need to convert that coordinate to a cell key first using the [coordinate_to_cell_key][textual.widgets.DataTable.coordinate_to_cell_key] method. ### Cursors +A cursor allows navigating within a table with the keyboard or mouse. There are four cursor types: `"cell"` (the default), `"row"`, `"column"`, and `"none"`. + + Change the cursor type by assigning to +the [`cursor_type`][textual.widgets.DataTable.cursor_type] reactive attribute. The coordinate of the cursor is exposed via the [`cursor_coordinate`][textual.widgets.DataTable.cursor_coordinate] reactive attribute. -Three types of cursors are supported: `cell`, `row`, and `column`. -Change the cursor type by assigning to the [`cursor_type`][textual.widgets.DataTable.cursor_type] reactive attribute. + +Using the keyboard, arrow keys, ++page-up++, ++page-down++, ++home++ and ++end++ move the cursor highlight, emitting a [`CellHighlighted`][textual.widgets.DataTable.CellHighlighted] +message, then enter selects the cell, emitting a [`CellSelected`][textual.widgets.DataTable.CellSelected] message. If the +`cursor_type` is row, then [`RowHighlighted`][textual.widgets.DataTable.RowHighlighted] and [`RowSelected`][textual.widgets.DataTable.RowSelected] +are emitted, similarly for [`ColumnHighlighted`][textual.widgets.DataTable.ColumnHighlighted] and [`ColumnSelected`][textual.widgets.DataTable.ColumnSelected]. + +When moving the mouse over the table, a [`MouseMove`][textual.events.MouseMove] event is emitted, the cell hovered over is styled, +and the [`hover_coordinate`][textual.widgets.DataTable.hover_coordinate] reactive attribute is updated. Clicking the mouse +then emits the [`CellHighlighted`][textual.widgets.DataTable.CellHighlighted] and [`CellSelected`][textual.widgets.DataTable.CellSelected] +events. + +A new table starts with no cell highlighted, i.e., row and column are zero. You can force the first item to highlight with `move_cursor(row=1, column=1)`. All row and column indexes start at one. === "Column Cursor" @@ -78,18 +94,21 @@ Change the cursor type by assigning to the [`cursor_type`][textual.widgets.DataT ```{.textual path="docs/examples/widgets/data_table_cursors.py" press="c,c"} ``` +=== "No Cursor" + + ```{.textual path="docs/examples/widgets/data_table_cursors.py" press="c,c,c"} + ``` + === "data_table_cursors.py" ```python --8<-- "docs/examples/widgets/data_table_cursors.py" ``` -You can change the position of the cursor using the arrow keys, ++page-up++, ++page-down++, ++home++ and ++end++, -or by assigning to the `cursor_coordinate` reactive attribute. ### Updating data -Cells can be updated in the `DataTable` by using the [update_cell][textual.widgets.DataTable.update_cell] and [update_cell_at][textual.widgets.DataTable.update_cell_at] methods. +Cells can be updated using the [update_cell][textual.widgets.DataTable.update_cell] and [update_cell_at][textual.widgets.DataTable.update_cell_at] methods. ### Removing data @@ -143,12 +162,16 @@ visible as you scroll through the data table. ### Sorting -The `DataTable` can be sorted using the [sort][textual.widgets.DataTable.sort] method. In order to sort your data by a column, you can provide the `key` you supplied to the `add_column` method or a `ColumnKey`. You can then pass one more column keys to the `sort` method to sort by one or more columns. +The DataTable rows can be sorted using the [`sort`][textual.widgets.DataTable.sort] method. -Additionally, you can sort your `DataTable` with a custom function (or other callable) via the `key` argument. Similar to the `key` parameter of the built-in [sorted()](https://docs.python.org/3/library/functions.html#sorted) function, your function (or other callable) should take a single argument (row) and return a key to use for sorting purposes. +There are three methods of using [`sort`][textual.widgets.DataTable.sort]: -Providing both `columns` and `key` will limit the row information sent to your `key` function (or other callable) to only the columns specified. +* By Column. Pass columns in as parameters to sort by the natural order of one or more columns. Specify a column using either a [`ColumnKey`][textual.widgets.data_table.ColumnKey] instance or the `key` you supplied to [`add_column`][textual.widgets.DataTable.add_column]. For example, `sort("country", "region")` would sort by country, and, when the country values are equal, by region. +* By Key function. Pass a function as the `key` parameter to sort, similar to the [key function parameter](https://docs.python.org/3/howto/sorting.html#key-functions) of Python's [`sorted`](https://docs.python.org/3/library/functions.html#sorted) built-in. The function will be called once per row with a tuple of all row values. +* By both Column and Key function. You can specify which columns to include as parameters to your key function. For example, `sort("hours", "rate", key=lambda h, r: h*r)` passes two values to the key function for each row. +The `reverse` argument reverses the order of your sort. Note that correct sorting may require your key function to undo your formatting. + === "Output" ```{.textual path="docs/examples/widgets/data_table_sort.py"} @@ -160,14 +183,14 @@ Providing both `columns` and `key` will limit the row information sent to your ` --8<-- "docs/examples/widgets/data_table_sort.py" ``` -### Labelled rows +### Labeled rows A "label" can be attached to a row using the [add_row][textual.widgets.DataTable.add_row] method. This will add an extra column to the left of the table which the cursor cannot interact with. This column is similar to the leftmost column in a spreadsheet containing the row numbers. The example below shows how to attach simple numbered labels to rows. -=== "Labelled rows" +=== "Labeled rows" ```{.textual path="docs/examples/widgets/data_table_labels.py"} ``` @@ -186,7 +209,7 @@ The example below shows how to attach simple numbered labels to rows. | `show_row_labels` | `bool` | `True` | Show the row labels (if applicable) | | `fixed_rows` | `int` | `0` | Number of fixed rows (rows which do not scroll) | | `fixed_columns` | `int` | `0` | Number of fixed columns (columns which do not scroll) | -| `zebra_stripes` | `bool` | `False` | Display alternating colors on rows | +| `zebra_stripes` | `bool` | `False` | Style with alternating colors on rows | | `header_height` | `int` | `1` | Height of header row | | `show_cursor` | `bool` | `True` | Show the cursor | | `cursor_type` | `str` | `"cell"` | One of `"cell"`, `"row"`, `"column"`, or `"none"` | diff --git a/docs/widgets/footer.md b/docs/widgets/footer.md index fcb25cf836..dfd11e109b 100644 --- a/docs/widgets/footer.md +++ b/docs/widgets/footer.md @@ -1,11 +1,17 @@ # Footer +!!! tip "Added in version 0.63.0" + + This is a second iteration of the Footer. + The version prior to 0.63.0 is available as [ClassicFooter](./classic_footer.md) to help with backwards compatibility, but will be removed in v1.0. + A simple footer widget which is docked to the bottom of its parent container. Displays available keybindings for the currently focused widget. - [ ] Focusable - [ ] Container + ## Example The example below shows an app with a single keybinding that contains only a `Footer` @@ -24,9 +30,11 @@ widget. Notice how the `Footer` automatically displays the keybinding. ## Reactive Attributes -| Name | Type | Default | Description | -| --------------- | ----- | ------- | --------------------------------------------------------------------------------------------------------- | -| `highlight_key` | `str` | `None` | Stores the currently highlighted key. This is typically the key the cursor is hovered over in the footer. | +| Name | Type | Default | Description | +| ----------------- | ------ | ------- | ----------------------------------------------------------------------- | +| `upper_case_keys` | `bool` | `False` | Display the keys in upper case. | +| `ctrl_to_caret` | `bool` | `True` | Replace "ctrl+" with "^" to denote a key that requires holding ++CTRL++ | +| `compact` | `bool` | `False` | Display a more compact footer. | ## Messages @@ -38,12 +46,8 @@ This widget has no bindings. ## Component Classes -The footer widget provides the following component classes: +This widget has no component classes. -::: textual.widgets.Footer.COMPONENT_CLASSES - options: - show_root_heading: false - show_root_toc_entry: false ## Additional Notes diff --git a/docs/widgets/tabbed_content.md b/docs/widgets/tabbed_content.md index e6edb899cc..de317b957b 100644 --- a/docs/widgets/tabbed_content.md +++ b/docs/widgets/tabbed_content.md @@ -97,7 +97,7 @@ The following example contains a `TabbedContent` with three tabs. ## Styling The `TabbedContent` widget is composed of two main sub-widgets: a -[`Tabs`](tabs.md) and a [`ContentSwitcher`]((content_switcher.md)); you can +[`Tabs`](tabs.md) and a [`ContentSwitcher`](content_switcher.md); you can style them accordingly. The tabs within the `Tabs` widget will have prefixed IDs; each ID being the diff --git a/docs/widgets/text_area.md b/docs/widgets/text_area.md index 9e24442d30..0824f4c697 100644 --- a/docs/widgets/text_area.md +++ b/docs/widgets/text_area.md @@ -353,6 +353,8 @@ the `show_line_numbers` attribute to `True` or `False`. Setting this attribute will immediately repaint the `TextArea` to reflect the new value. +You can also change the start line number (the topmost line number in the gutter) by setting the `line_number_start` reactive attribute. + ### Extending `TextArea` Sometimes, you may wish to subclass `TextArea` to add some extra functionality. @@ -481,7 +483,7 @@ We can freely edit the text, and the syntax highlighting will update immediately Recall that we map names (like `@heading`) from the tree-sitter highlight query to Rich style objects inside the `TextAreaTheme.syntax_styles` dictionary. If you notice some highlights are missing after registering a language, the issue may be: -1. The current `TextAreaTheme` doesn't contain a mapping for the name in the highlight query. Adding a new to `syntax_styles` should resolve the issue. +1. The current `TextAreaTheme` doesn't contain a mapping for the name in the highlight query. Adding a new key-value pair to `syntax_styles` should resolve the issue. 2. The highlight query doesn't assign a name to the pattern you expect to be highlighted. In this case you'll need to update the highlight query to assign to the name. !!! tip @@ -506,6 +508,7 @@ A detailed view of these classes is out of scope, but do note that a lot of the | `theme` | `str` | `"css"` | The theme to use. | | `selection` | `Selection` | `Selection()` | The current selection. | | `show_line_numbers` | `bool` | `False` | Show or hide line numbers. | +| `line_number_start` | `int` | `1` | The start line number in the gutter. | | `indent_width` | `int` | `4` | The number of spaces to indent and width of tabs. | | `match_cursor_bracket` | `bool` | `True` | Enable/disable highlighting matching brackets under cursor. | | `cursor_blink` | `bool` | `True` | Enable/disable blinking of the cursor when the widget has focus. | diff --git a/docs/widgets/toast.md b/docs/widgets/toast.md index a324150e7f..0a7e321ce3 100644 --- a/docs/widgets/toast.md +++ b/docs/widgets/toast.md @@ -14,13 +14,21 @@ A widget which displays a notification message. You can customize the style of Toasts by targeting the `Toast` [CSS type](../guide/CSS.md#type-selector). For example: - ```scss Toast { padding: 3; } ``` +If you wish to change the location of Toasts, it is possible by targeting the `ToastRack` CSS type. +For example: + +```scss +ToastRack { + align: right top; +} +``` + The three severity levels also have corresponding [classes](../guide/CSS.md#class-name-selector), allowing you to target the different styles of notification. They are: diff --git a/examples/code_browser.tcss b/examples/code_browser.tcss index 21fc7cad00..a97a35fe9e 100644 --- a/examples/code_browser.tcss +++ b/examples/code_browser.tcss @@ -23,6 +23,7 @@ CodeBrowser.-show-tree #tree-view { #code-view { overflow: auto scroll; min-width: 100%; + hatch: right $primary; } #code { width: auto; diff --git a/examples/dictionary.py b/examples/dictionary.py index b8b31096dd..4965507482 100644 --- a/examples/dictionary.py +++ b/examples/dictionary.py @@ -13,7 +13,7 @@ class DictionaryApp(App): - """Searches ab dictionary API as-you-type.""" + """Searches a dictionary API as-you-type.""" CSS_PATH = "dictionary.tcss" @@ -22,18 +22,13 @@ def compose(self) -> ComposeResult: with VerticalScroll(id="results-container"): yield Markdown(id="results") - def on_mount(self) -> None: - """Called when app starts.""" - # Give the input focus, so we can start typing straight away - self.query_one(Input).focus() - async def on_input_changed(self, message: Input.Changed) -> None: """A coroutine to handle a text changed message.""" if message.value: self.lookup_word(message.value) else: # Clear the results - self.query_one("#results", Markdown).update("") + await self.query_one("#results", Markdown).update("") @work(exclusive=True) async def lookup_word(self, word: str) -> None: @@ -46,6 +41,7 @@ async def lookup_word(self, word: str) -> None: results = response.json() except Exception: self.query_one("#results", Markdown).update(response.text) + return if word == self.query_one(Input).value: markdown = self.make_word_markdown(results) diff --git a/examples/five_by_five.py b/examples/five_by_five.py index 6859cf86c6..a29cfcbafd 100644 --- a/examples/five_by_five.py +++ b/examples/five_by_five.py @@ -21,7 +21,7 @@ class Help(Screen): """The help screen for the application.""" - BINDINGS = [("escape,space,q,question_mark", "pop_screen", "Close")] + BINDINGS = [("escape,space,q,question_mark", "app.pop_screen", "Close")] """Bindings for the help screen.""" def compose(self) -> ComposeResult: @@ -159,8 +159,8 @@ class Game(Screen): BINDINGS = [ Binding("n", "new_game", "New Game"), - Binding("question_mark", "push_screen('help')", "Help", key_display="?"), - Binding("q", "quit", "Quit"), + Binding("question_mark", "app.push_screen('help')", "Help", key_display="?"), + Binding("q", "app.quit", "Quit"), Binding("up,w,k", "navigate(-1,0)", "Move Up", False), Binding("down,s,j", "navigate(1,0)", "Move Down", False), Binding("left,a,h", "navigate(0,-1)", "Move Left", False), diff --git a/examples/markdown.py b/examples/markdown.py index a6d26cb190..2cf43e4399 100644 --- a/examples/markdown.py +++ b/examples/markdown.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path from sys import argv @@ -7,6 +9,8 @@ class MarkdownApp(App): + """A simple Markdown viewer application.""" + BINDINGS = [ ("t", "toggle_table_of_contents", "TOC"), ("b", "back", "Back"), @@ -25,23 +29,41 @@ def compose(self) -> ComposeResult: yield MarkdownViewer() async def on_mount(self) -> None: - self.markdown_viewer.focus() + """Go to the first path when the app starts.""" try: await self.markdown_viewer.go(self.path) except FileNotFoundError: self.exit(message=f"Unable to load {self.path!r}") + def on_markdown_viewer_navigator_updated(self) -> None: + """Refresh bindings for forward / back when the document changes.""" + self.refresh_bindings() + def action_toggle_table_of_contents(self) -> None: + """Toggles display of the table of contents.""" self.markdown_viewer.show_table_of_contents = ( not self.markdown_viewer.show_table_of_contents ) async def action_back(self) -> None: + """Navigate backwards.""" await self.markdown_viewer.back() async def action_forward(self) -> None: + """Navigate forwards.""" await self.markdown_viewer.forward() + def check_action(self, action: str, _) -> bool | None: + """Check if certain actions can be performed.""" + if action == "forward" and self.markdown_viewer.navigator.end: + # Disable footer link if we can't go forward + return None + if action == "back" and self.markdown_viewer.navigator.start: + # Disable footer link if we can't go backward + return None + # All other keys display as normal + return True + if __name__ == "__main__": app = MarkdownApp() diff --git a/mkdocs-common.yml b/mkdocs-common.yml index 6834c0ff50..924179a95d 100644 --- a/mkdocs-common.yml +++ b/mkdocs-common.yml @@ -63,6 +63,7 @@ theme: name: Switch to light mode plugins: + git-revision-date-localized: search: autorefs: mkdocstrings: diff --git a/mkdocs-nav.yml b/mkdocs-nav.yml index 4fa9912b41..169f3ef0d0 100644 --- a/mkdocs-nav.yml +++ b/mkdocs-nav.yml @@ -30,6 +30,7 @@ nav: - "css_types/index.md" - "css_types/border.md" - "css_types/color.md" + - "css_types/hatch.md" - "css_types/horizontal.md" - "css_types/integer.md" - "css_types/keyline.md" @@ -96,6 +97,7 @@ nav: - "styles/grid/grid_rows.md" - "styles/grid/grid_size.md" - "styles/grid/row_span.md" + - "styles/hatch.md" - "styles/height.md" - "styles/keyline.md" - "styles/layer.md" @@ -139,6 +141,7 @@ nav: - Widgets: - "widgets/button.md" - "widgets/checkbox.md" + - "widgets/classic_footer.md" - "widgets/collapsible.md" - "widgets/content_switcher.md" - "widgets/data_table.md" diff --git a/poetry.lock b/poetry.lock index 483c8f9b4e..639c7a1f37 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,88 +1,88 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" -version = "3.9.4" +version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:76d32588ef7e4a3f3adff1956a0ba96faabbdee58f2407c122dd45aa6e34f372"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56181093c10dbc6ceb8a29dfeea1e815e1dfdc020169203d87fd8d37616f73f9"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7a5b676d3c65e88b3aca41816bf72831898fcd73f0cbb2680e9d88e819d1e4d"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1df528a85fb404899d4207a8d9934cfd6be626e30e5d3a5544a83dbae6d8a7e"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f595db1bceabd71c82e92df212dd9525a8a2c6947d39e3c994c4f27d2fe15b11"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0b09d76e5a4caac3d27752027fbd43dc987b95f3748fad2b924a03fe8632ad"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689eb4356649ec9535b3686200b231876fb4cab4aca54e3bece71d37f50c1d13"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3666cf4182efdb44d73602379a66f5fdfd5da0db5e4520f0ac0dcca644a3497"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b65b0f8747b013570eea2f75726046fa54fa8e0c5db60f3b98dd5d161052004a"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1885d2470955f70dfdd33a02e1749613c5a9c5ab855f6db38e0b9389453dce7"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0593822dcdb9483d41f12041ff7c90d4d1033ec0e880bcfaf102919b715f47f1"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:47f6eb74e1ecb5e19a78f4a4228aa24df7fbab3b62d4a625d3f41194a08bd54f"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c8b04a3dbd54de6ccb7604242fe3ad67f2f3ca558f2d33fe19d4b08d90701a89"}, - {file = "aiohttp-3.9.4-cp310-cp310-win32.whl", hash = "sha256:8a78dfb198a328bfb38e4308ca8167028920fb747ddcf086ce706fbdd23b2926"}, - {file = "aiohttp-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:e78da6b55275987cbc89141a1d8e75f5070e577c482dd48bd9123a76a96f0bbb"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c111b3c69060d2bafc446917534150fd049e7aedd6cbf21ba526a5a97b4402a5"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbdd51872cf170093998c87ccdf3cb5993add3559341a8e5708bcb311934c94"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bfdb41dc6e85d8535b00d73947548a748e9534e8e4fddd2638109ff3fb081df"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd9d334412961125e9f68d5b73c1d0ab9ea3f74a58a475e6b119f5293eee7ba"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35d78076736f4a668d57ade00c65d30a8ce28719d8a42471b2a06ccd1a2e3063"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:824dff4f9f4d0f59d0fa3577932ee9a20e09edec8a2f813e1d6b9f89ced8293f"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52b8b4e06fc15519019e128abedaeb56412b106ab88b3c452188ca47a25c4093"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae569fb1e7559d4f3919965617bb39f9e753967fae55ce13454bec2d1c54f09"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69b97aa5792428f321f72aeb2f118e56893371f27e0b7d05750bcad06fc42ca1"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d79aad0ad4b980663316f26d9a492e8fab2af77c69c0f33780a56843ad2f89e"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d6577140cd7db19e430661e4b2653680194ea8c22c994bc65b7a19d8ec834403"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:9860d455847cd98eb67897f5957b7cd69fbcb436dd3f06099230f16a66e66f79"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69ff36d3f8f5652994e08bd22f093e11cfd0444cea310f92e01b45a4e46b624e"}, - {file = "aiohttp-3.9.4-cp311-cp311-win32.whl", hash = "sha256:e27d3b5ed2c2013bce66ad67ee57cbf614288bda8cdf426c8d8fe548316f1b5f"}, - {file = "aiohttp-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d6a67e26daa686a6fbdb600a9af8619c80a332556245fa8e86c747d226ab1a1e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c5ff8ff44825736a4065d8544b43b43ee4c6dd1530f3a08e6c0578a813b0aa35"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d12a244627eba4e9dc52cbf924edef905ddd6cafc6513849b4876076a6f38b0e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcad56c8d8348e7e468899d2fb3b309b9bc59d94e6db08710555f7436156097f"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7e69a7fd4b5ce419238388e55abd220336bd32212c673ceabc57ccf3d05b55"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4870cb049f10d7680c239b55428916d84158798eb8f353e74fa2c98980dcc0b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2feaf1b7031ede1bc0880cec4b0776fd347259a723d625357bb4b82f62687b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939393e8c3f0a5bcd33ef7ace67680c318dc2ae406f15e381c0054dd658397de"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d2334e387b2adcc944680bebcf412743f2caf4eeebd550f67249c1c3696be04"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0198ea897680e480845ec0ffc5a14e8b694e25b3f104f63676d55bf76a82f1a"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e40d2cd22914d67c84824045861a5bb0fb46586b15dfe4f046c7495bf08306b2"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:aba80e77c227f4234aa34a5ff2b6ff30c5d6a827a91d22ff6b999de9175d71bd"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:fb68dc73bc8ac322d2e392a59a9e396c4f35cb6fdbdd749e139d1d6c985f2527"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f3460a92638dce7e47062cf088d6e7663adb135e936cb117be88d5e6c48c9d53"}, - {file = "aiohttp-3.9.4-cp312-cp312-win32.whl", hash = "sha256:32dc814ddbb254f6170bca198fe307920f6c1308a5492f049f7f63554b88ef36"}, - {file = "aiohttp-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:63f41a909d182d2b78fe3abef557fcc14da50c7852f70ae3be60e83ff64edba5"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c3770365675f6be220032f6609a8fbad994d6dcf3ef7dbcf295c7ee70884c9af"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:305edae1dea368ce09bcb858cf5a63a064f3bff4767dec6fa60a0cc0e805a1d3"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f121900131d116e4a93b55ab0d12ad72573f967b100e49086e496a9b24523ea"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b71e614c1ae35c3d62a293b19eface83d5e4d194e3eb2fabb10059d33e6e8cbf"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419f009fa4cfde4d16a7fc070d64f36d70a8d35a90d71aa27670bba2be4fd039"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b39476ee69cfe64061fd77a73bf692c40021f8547cda617a3466530ef63f947"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b33f34c9c7decdb2ab99c74be6443942b730b56d9c5ee48fb7df2c86492f293c"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c78700130ce2dcebb1a8103202ae795be2fa8c9351d0dd22338fe3dac74847d9"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:268ba22d917655d1259af2d5659072b7dc11b4e1dc2cb9662fdd867d75afc6a4"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:17e7c051f53a0d2ebf33013a9cbf020bb4e098c4bc5bce6f7b0c962108d97eab"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7be99f4abb008cb38e144f85f515598f4c2c8932bf11b65add0ff59c9c876d99"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d58a54d6ff08d2547656356eea8572b224e6f9bbc0cf55fa9966bcaac4ddfb10"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7673a76772bda15d0d10d1aa881b7911d0580c980dbd16e59d7ba1422b2d83cd"}, - {file = "aiohttp-3.9.4-cp38-cp38-win32.whl", hash = "sha256:e4370dda04dc8951012f30e1ce7956a0a226ac0714a7b6c389fb2f43f22a250e"}, - {file = "aiohttp-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:eb30c4510a691bb87081192a394fb661860e75ca3896c01c6d186febe7c88530"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:84e90494db7df3be5e056f91412f9fa9e611fbe8ce4aaef70647297f5943b276"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d4845f8501ab28ebfdbeab980a50a273b415cf69e96e4e674d43d86a464df9d"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69046cd9a2a17245c4ce3c1f1a4ff8c70c7701ef222fce3d1d8435f09042bba1"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b73a06bafc8dcc508420db43b4dd5850e41e69de99009d0351c4f3007960019"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:418bb0038dfafeac923823c2e63226179976c76f981a2aaad0ad5d51f2229bca"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a8f241456b6c2668374d5d28398f8e8cdae4cce568aaea54e0f39359cd928d"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935c369bf8acc2dc26f6eeb5222768aa7c62917c3554f7215f2ead7386b33748"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4e48c8752d14ecfb36d2ebb3d76d614320570e14de0a3aa7a726ff150a03c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:916b0417aeddf2c8c61291238ce25286f391a6acb6f28005dd9ce282bd6311b6"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9b6787b6d0b3518b2ee4cbeadd24a507756ee703adbac1ab6dc7c4434b8c572a"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:221204dbda5ef350e8db6287937621cf75e85778b296c9c52260b522231940ed"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:10afd99b8251022ddf81eaed1d90f5a988e349ee7d779eb429fb07b670751e8c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2506d9f7a9b91033201be9ffe7d89c6a54150b0578803cce5cb84a943d075bc3"}, - {file = "aiohttp-3.9.4-cp39-cp39-win32.whl", hash = "sha256:e571fdd9efd65e86c6af2f332e0e95dad259bfe6beb5d15b3c3eca3a6eb5d87b"}, - {file = "aiohttp-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:7d29dd5319d20aa3b7749719ac9685fbd926f71ac8c77b2477272725f882072d"}, - {file = "aiohttp-3.9.4.tar.gz", hash = "sha256:6ff71ede6d9a5a58cfb7b6fffc83ab5d4a63138276c771ac91ceaaddf5459644"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] @@ -164,13 +164,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "babel" -version = "2.14.0" +version = "2.15.0" description = "Internationalization utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] [package.dependencies] @@ -181,33 +181,33 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "black" -version = "24.1.1" +version = "24.4.2" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-24.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c"}, - {file = "black-24.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445"}, - {file = "black-24.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a"}, - {file = "black-24.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4"}, - {file = "black-24.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7"}, - {file = "black-24.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8"}, - {file = "black-24.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161"}, - {file = "black-24.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d"}, - {file = "black-24.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8"}, - {file = "black-24.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e"}, - {file = "black-24.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6"}, - {file = "black-24.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b"}, - {file = "black-24.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62"}, - {file = "black-24.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5"}, - {file = "black-24.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6"}, - {file = "black-24.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717"}, - {file = "black-24.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9"}, - {file = "black-24.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024"}, - {file = "black-24.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2"}, - {file = "black-24.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac"}, - {file = "black-24.1.1-py3-none-any.whl", hash = "sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168"}, - {file = "black-24.1.1.tar.gz", hash = "sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b"}, + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, ] [package.dependencies] @@ -383,63 +383,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, + {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, + {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, + {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, + {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, + {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, + {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, + {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, + {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, + {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, + {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, + {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, + {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, ] [package.extras] @@ -458,13 +458,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -472,13 +472,13 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.13.4" +version = "3.14.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, - {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [package.extras] @@ -692,13 +692,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -745,15 +745,29 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -897,13 +911,13 @@ files = [ [[package]] name = "mdit-py-plugins" -version = "0.4.0" +version = "0.4.1" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" files = [ - {file = "mdit_py_plugins-0.4.0-py3-none-any.whl", hash = "sha256:b51b3bb70691f57f974e257e367107857a93b36f322a9e6d44ca5bf28ec2def9"}, - {file = "mdit_py_plugins-0.4.0.tar.gz", hash = "sha256:d8ab27e9aed6c38aa716819fedfde15ca275715955f8a185a8e1cf90fb1d2c1b"}, + {file = "mdit_py_plugins-0.4.1-py3-none-any.whl", hash = "sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a"}, + {file = "mdit_py_plugins-0.4.1.tar.gz", hash = "sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c"}, ] [package.dependencies] @@ -938,34 +952,34 @@ files = [ [[package]] name = "mkdocs" -version = "1.5.3" +version = "1.6.0" description = "Project documentation with Markdown." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, - {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, + {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"}, + {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"}, ] [package.dependencies] click = ">=7.0" colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" -markdown = ">=3.2.1" +markdown = ">=3.3.6" markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" packaging = ">=20.5" pathspec = ">=0.11.1" -platformdirs = ">=2.2.0" pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -996,15 +1010,49 @@ files = [ [package.dependencies] mkdocs = "*" +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-git-revision-date-localized-plugin" +version = "1.2.5" +description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_git_revision_date_localized_plugin-1.2.5-py3-none-any.whl", hash = "sha256:d796a18b07cfcdb154c133e3ec099d2bb5f38389e4fd54d3eb516a8a736815b8"}, + {file = "mkdocs_git_revision_date_localized_plugin-1.2.5.tar.gz", hash = "sha256:0c439816d9d0dba48e027d9d074b2b9f1d7cd179f74ba46b51e4da7bb3dc4b9b"}, +] + +[package.dependencies] +babel = ">=2.7.0" +GitPython = "*" +mkdocs = ">=1.0" +pytz = "*" + [[package]] name = "mkdocs-material" -version = "9.5.18" +version = "9.5.23" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.18-py3-none-any.whl", hash = "sha256:1e0e27fc9fe239f9064318acf548771a4629d5fd5dfd45444fd80a953fe21eb4"}, - {file = "mkdocs_material-9.5.18.tar.gz", hash = "sha256:a43f470947053fa2405c33995f282d24992c752a50114f23f30da9d8d0c57e62"}, + {file = "mkdocs_material-9.5.23-py3-none-any.whl", hash = "sha256:ffd08a5beaef3cd135aceb58ded8b98bbbbf2b70e5b656f6a14a63c917d9b001"}, + {file = "mkdocs_material-9.5.23.tar.gz", hash = "sha256:4627fc3f15de2cba2bde9debc2fd59b9888ef494beabfe67eb352e23d14bf288"}, ] [package.dependencies] @@ -1012,7 +1060,7 @@ babel = ">=2.10,<3.0" colorama = ">=0.4,<1.0" jinja2 = ">=3.0,<4.0" markdown = ">=3.2,<4.0" -mkdocs = ">=1.5.3,<1.6.0" +mkdocs = ">=1.6,<2.0" mkdocs-material-extensions = ">=1.3,<2.0" paginate = ">=0.5,<1.0" pygments = ">=2.16,<3.0" @@ -1038,25 +1086,25 @@ files = [ [[package]] name = "mkdocs-rss-plugin" -version = "1.12.1" +version = "1.12.2" description = "MkDocs plugin which generates a static RSS feed using git log and page.meta." optional = false -python-versions = ">=3.8, <4" +python-versions = "<4,>=3.8" files = [ - {file = "mkdocs-rss-plugin-1.12.1.tar.gz", hash = "sha256:5df9bddfdc1465623def1b14c2656c5e8f62fa7c8bd1c0c667e01fc86105d415"}, - {file = "mkdocs_rss_plugin-1.12.1-py2.py3-none-any.whl", hash = "sha256:acfb8eec95f1db389b36770baf99af3b87e38484cc37993194a35aa173eb3fe8"}, + {file = "mkdocs_rss_plugin-1.12.2-py2.py3-none-any.whl", hash = "sha256:2e1d5cb871494f2634b9c3fe523a4246ba5c00c5f6627242101675d3c63b2bdd"}, + {file = "mkdocs_rss_plugin-1.12.2.tar.gz", hash = "sha256:313f127967ebcf14ad6f74f1f6f89f054deeb3a1de510770778ce150b75f2247"}, ] [package.dependencies] GitPython = ">=3.1,<3.2" mkdocs = ">=1.4,<2" pytz = {version = "==2022.*", markers = "python_version < \"3.9\""} -tzdata = {version = "==2023.*", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""} +tzdata = {version = "==2024.*", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""} [package.extras] dev = ["black", "flake8 (>=6,<8)", "flake8-bugbear (>=23.12)", "flake8-builtins (>=2.1)", "flake8-eradicate (>=1)", "flake8-isort (>=6)", "pre-commit (>=3,<4)"] -doc = ["mkdocs-git-committers-plugin-2 (>=1.2,<2.3)", "mkdocs-git-revision-date-localized-plugin (>=1,<1.3)", "mkdocs-material[imaging] (>=9.5.1,<10)", "mkdocs-minify-plugin (==0.8.*)", "mkdocstrings[python] (>=0.18,<1)", "termynal (>=0.11.1,<0.12)"] -test = ["feedparser (>=6.0.11,<6.1)", "jsonfeed-util (>=1.1.2,<2)", "mkdocs-material[imaging] (>=9)", "pytest-cov (>=4,<4.2)", "validator-collection (>=1.5,<1.6)"] +doc = ["mkdocs-git-committers-plugin-2 (>=1.2,<2.4)", "mkdocs-git-revision-date-localized-plugin (>=1,<1.3)", "mkdocs-material[imaging] (>=9.5.1,<10)", "mkdocs-minify-plugin (==0.8.*)", "mkdocstrings[python] (>=0.18,<1)", "termynal (>=0.11.1,<0.13)"] +test = ["feedparser (>=6.0.11,<6.1)", "jsonfeed-util (>=1.1.2,<2)", "mkdocs-material[imaging] (>=9)", "pytest-cov (>=4,<5.1)", "validator-collection (>=1.5,<1.6)"] [[package]] name = "mkdocstrings" @@ -1264,38 +1312,38 @@ files = [ [[package]] name = "mypy" -version = "1.9.0" +version = "1.10.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, + {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, + {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, + {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, + {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, + {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, + {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, + {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, + {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, + {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, + {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, + {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, + {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, ] [package.dependencies] @@ -1368,28 +1416,29 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1416,32 +1465,31 @@ virtualenv = ">=20.10.0" [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.7.1" +version = "10.8.1" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.7.1-py3-none-any.whl", hash = "sha256:f5cc7000d7ff0d1ce9395d216017fa4df3dde800afb1fb72d1c7d3fd35e710f4"}, - {file = "pymdown_extensions-10.7.1.tar.gz", hash = "sha256:c70e146bdd83c744ffc766b4671999796aba18842b268510a329f7f64700d584"}, + {file = "pymdown_extensions-10.8.1-py3-none-any.whl", hash = "sha256:f938326115884f48c6059c67377c46cf631c733ef3629b6eed1349989d1b30cb"}, + {file = "pymdown_extensions-10.8.1.tar.gz", hash = "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940"}, ] [package.dependencies] -markdown = ">=3.5" +markdown = ">=3.6" pyyaml = "*" [package.extras] @@ -1561,6 +1609,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1568,8 +1617,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1586,6 +1642,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1593,6 +1650,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1614,104 +1672,90 @@ pyyaml = "*" [[package]] name = "regex" -version = "2023.12.25" +version = "2024.5.15" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, - {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, - {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, - {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, - {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, - {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, - {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, - {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, - {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, - {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, - {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, - {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, - {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, - {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, - {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, + {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, + {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, + {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, + {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, + {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, + {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, + {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, + {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, + {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, + {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, + {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, ] [[package]] @@ -2083,13 +2127,13 @@ files = [ [[package]] name = "tzdata" -version = "2023.4" +version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, - {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] [[package]] @@ -2125,13 +2169,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, + {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, ] [package.dependencies] @@ -2140,7 +2184,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -2308,4 +2352,4 @@ syntax = ["tree-sitter", "tree-sitter-languages"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "4d935ffc4d465a2e51881f971fe3b9b62b9a1a8462ae511a512b5d7fae06a733" +content-hash = "7cf7f99ade9d00e6b4b190af1563eaeccb55e4e8e424f67ace7da2e3e9c62308" diff --git a/pyproject.toml b/pyproject.toml index 239a53e19f..167aa57c55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "0.56.4" +version = "0.72.0" homepage = "https://github.com/Textualize/textual" repository = "https://github.com/Textualize/textual" documentation = "https://textual.textualize.io/" @@ -52,25 +52,27 @@ tree-sitter-languages = { version = "1.10.2", optional = true } syntax = ["tree-sitter", "tree_sitter_languages"] [tool.poetry.group.dev.dependencies] -pytest = "^7.1.3" -black = "24.1.1" -mypy = "^1.0.0" -pytest-cov = "^2.12.1" +black = "24.4.2" +griffe = "0.32.3" +httpx = "^0.23.1" mkdocs = "^1.3.0" +mkdocs-exclude = "^1.0.2" +mkdocs-git-revision-date-localized-plugin = "^1.2.5" +mkdocs-material = "^9.0.11" +mkdocs-rss-plugin = "^1.5.0" mkdocstrings = { extras = ["python"], version = "^0.20.0" } mkdocstrings-python = "0.10.1" -mkdocs-material = "^9.0.11" -mkdocs-exclude = "^1.0.2" +mypy = "^1.0.0" pre-commit = "^2.13.0" -mkdocs-rss-plugin = "^1.5.0" -httpx = "^0.23.1" -types-setuptools = "^67.2.0.1" -textual-dev = "^1.2.0" +pytest = "^7.1.3" pytest-asyncio = "*" +pytest-cov = "^2.12.1" pytest-textual-snapshot = ">=0.4.0" +textual-dev = "^1.2.0" +types-setuptools = "^67.2.0.1" types-tree-sitter = "^0.20.1.4" types-tree-sitter-languages = "^1.7.0.1" -griffe = "0.32.3" +isort = "^5.13.2" [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/src/textual/_animator.py b/src/textual/_animator.py index 21234fee8b..8a669b2c8a 100644 --- a/src/textual/_animator.py +++ b/src/textual/_animator.py @@ -21,7 +21,7 @@ """Animation keys are the id of the object and the attribute being animated.""" EasingFunction = Callable[[float], float] -"""Signature for a function that parametrises animation speed. +"""Signature for a function that parametrizes animation speed. An easing function must map the interval [0, 1] into the interval [0, 1]. """ diff --git a/src/textual/_callback.py b/src/textual/_callback.py index 756a9352af..d2fd2403c2 100644 --- a/src/textual/_callback.py +++ b/src/textual/_callback.py @@ -48,7 +48,7 @@ async def _invoke(callback: Callable, *params: object) -> Any: return result -async def invoke(callback: Callable[[], Any], *params: object) -> Any: +async def invoke(callback: Callable[..., Any], *params: object) -> Any: """Invoke a callback with an arbitrary number of parameters. Args: diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index e3dd08a50f..f1ce3caa1d 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -1000,12 +1000,17 @@ def _get_renders( ) def render_update( - self, full: bool = False, screen_stack: list[Screen] | None = None + self, + full: bool = False, + screen_stack: list[Screen] | None = None, + simplify: bool = False, ) -> RenderableType | None: """Render an update renderable. Args: + full: Perform a full update if `True`, otherwise a partial update. screen_stack: Screen stack list. Defaults to None. + simplify: Simplify segments. Returns: A renderable for the update, or `None` if no update was required. @@ -1014,7 +1019,7 @@ def render_update( visible_screen_stack.set([] if screen_stack is None else screen_stack) screen_region = self.size.region if full or screen_region in self._dirty_regions: - return self.render_full_update() + return self.render_full_update(simplify=simplify) else: return self.render_partial_update() @@ -1038,9 +1043,12 @@ def render_inline( strips = self.render_strips(size) return InlineUpdate(strips, clear=clear) - def render_full_update(self) -> LayoutUpdate: + def render_full_update(self, simplify: bool = False) -> LayoutUpdate: """Render a full update. + Args: + simplify: Simplify the segments (combine contiguous segments). + Returns: A LayoutUpdate renderable. """ @@ -1048,7 +1056,11 @@ def render_full_update(self) -> LayoutUpdate: self._dirty_regions.clear() crop = screen_region chops = self._render_chops(crop, lambda y: True) - render_strips = [Strip.join(chop.values()) for chop in chops] + if simplify: + render_strips = [Strip.join(chop.values()).simplify() for chop in chops] + else: + render_strips = [Strip.join(chop.values()) for chop in chops] + return LayoutUpdate(render_strips, screen_region) def render_partial_update(self) -> ChopsUpdate | None: diff --git a/src/textual/_event_broker.py b/src/textual/_event_broker.py index 1b63a6cf5e..fe6727e105 100644 --- a/src/textual/_event_broker.py +++ b/src/textual/_event_broker.py @@ -4,15 +4,29 @@ class NoHandler(Exception): - pass + """Raised when handler isn't found in the meta.""" class HandlerArguments(NamedTuple): + """Information for event handler.""" + modifiers: set[str] action: Any def extract_handler_actions(event_name: str, meta: dict[str, Any]) -> HandlerArguments: + """Extract action from meta dict. + + Args: + event_name: Event to check from. + meta: Meta information (stored in Rich Style) + + Raises: + NoHandler: If no handler is found. + + Returns: + Action information. + """ event_path = event_name.split(".") for key, value in meta.items(): if key.startswith("@"): @@ -21,7 +35,3 @@ def extract_handler_actions(event_name: str, meta: dict[str, Any]) -> HandlerArg modifiers = name_args[len(event_path) :] return HandlerArguments(set(modifiers), value) raise NoHandler(f"No handler for {event_name!r}") - - -if __name__ == "__main__": - print(extract_handler_actions("mouse.down", {"@mouse.down.hot": "app.bell()"})) diff --git a/src/textual/_immutable_sequence_view.py b/src/textual/_immutable_sequence_view.py index 2f01ddd2d5..823c73e6a4 100644 --- a/src/textual/_immutable_sequence_view.py +++ b/src/textual/_immutable_sequence_view.py @@ -3,7 +3,7 @@ from __future__ import annotations from sys import maxsize -from typing import Generic, Iterator, Sequence, TypeVar, overload +from typing import TYPE_CHECKING, Generic, Iterator, Sequence, TypeVar, overload T = TypeVar("T") @@ -19,11 +19,13 @@ def __init__(self, wrap: Sequence[T]) -> None: """ self._wrap = wrap - @overload - def __getitem__(self, index: int) -> T: ... + if TYPE_CHECKING: - @overload - def __getitem__(self, index: slice) -> ImmutableSequenceView[T]: ... + @overload + def __getitem__(self, index: int) -> T: ... + + @overload + def __getitem__(self, index: slice) -> ImmutableSequenceView[T]: ... def __getitem__(self, index: int | slice) -> T | ImmutableSequenceView[T]: return ( diff --git a/src/textual/_keyboard_protocol.py b/src/textual/_keyboard_protocol.py new file mode 100644 index 0000000000..d5f364bd0d --- /dev/null +++ b/src/textual/_keyboard_protocol.py @@ -0,0 +1,121 @@ +# https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions +FUNCTIONAL_KEYS = { + "27u": "escape", + "13u": "enter", + "9u": "tab", + "127u": "backspace", + "2~": "insert", + "3~": "delete", + "1D": "left", + "1C": "right", + "1A": "up", + "1B": "down", + "5~": "pageup", + "6~": "pagedown", + "1H": "home", + "7~": "home", + "1F": "end", + "8~": "end", + "57358u": "caps_lock", + "57359u": "scroll_lock", + "57360u": "num_lock", + "57361u": "print_screen", + "57362u": "pause", + "57363u": "menu", + "1P": "f1", + "11~": "f1", + "1Q": "f2", + "12~": "f2", + "13~": "f3", + "1R": "f3", + "1S": "f4", + "14~": "f4", + "15~": "f5", + "17~": "f6", + "18~": "f7", + "19~": "f8", + "20~": "f9", + "21~": "f10", + "23~": "f11", + "24~": "f12", + "57376u": "f13", + "57377u": "f14", + "57378u": "f15", + "57379u": "f16", + "57380u": "f17", + "57381u": "f18", + "57382u": "f19", + "57383u": "f20", + "57384u": "f21", + "57385u": "f22", + "57386u": "f23", + "57387u": "f24", + "57388u": "f25", + "57389u": "f26", + "57390u": "f27", + "57391u": "f28", + "57392u": "f29", + "57393u": "f30", + "57394u": "f31", + "57395u": "f32", + "57396u": "f33", + "57397u": "f34", + "57398u": "f35", + "57399u": "kp_0", + "57400u": "kp_1", + "57401u": "kp_2", + "57402u": "kp_3", + "57403u": "kp_4", + "57404u": "kp_5", + "57405u": "kp_6", + "57406u": "kp_7", + "57407u": "kp_8", + "57408u": "kp_9", + "57409u": "kp_decimal", + "57410u": "kp_divide", + "57411u": "kp_multiply", + "57412u": "kp_subtract", + "57413u": "kp_add", + "57414u": "kp_enter", + "57415u": "kp_equal", + "57416u": "kp_separator", + "57417u": "kp_left", + "57418u": "kp_right", + "57419u": "kp_up", + "57420u": "kp_down", + "57421u": "kp_page_up", + "57422u": "kp_page_down", + "57423u": "kp_home", + "57424u": "kp_end", + "57425u": "kp_insert", + "57426u": "kp_delete", + "1E": "kp_begin", + "57427~": "kp_begin", + "57428u": "media_play", + "57429u": "media_pause", + "57430u": "media_play_pause", + "57431u": "media_reverse", + "57432u": "media_stop", + "57433u": "media_fast_forward", + "57434u": "media_rewind", + "57435u": "media_track_next", + "57436u": "media_track_previous", + "57437u": "media_record", + "57438u": "lower_volume", + "57439u": "raise_volume", + "57440u": "mute_volume", + "57441u": "left_shift", + "57442u": "left_control", + "57443u": "left_alt", + "57444u": "left_super", + "57445u": "left_hyper", + "57446u": "left_meta", + "57447u": "right_shift", + "57448u": "right_control", + "57449u": "right_alt", + "57450u": "right_super", + "57451u": "right_hyper", + "57452u": "right_meta", + "57453u": "iso_level3_shift", + "57454u": "iso_level5_shift", +} diff --git a/src/textual/_node_list.py b/src/textual/_node_list.py index ac9b425f32..198558777d 100644 --- a/src/textual/_node_list.py +++ b/src/textual/_node_list.py @@ -157,11 +157,13 @@ def __iter__(self) -> Iterator[Widget]: def __reversed__(self) -> Iterator[Widget]: return reversed(self._nodes) - @overload - def __getitem__(self, index: int) -> Widget: ... + if TYPE_CHECKING: - @overload - def __getitem__(self, index: slice) -> list[Widget]: ... + @overload + def __getitem__(self, index: int) -> Widget: ... + + @overload + def __getitem__(self, index: slice) -> list[Widget]: ... def __getitem__(self, index: int | slice) -> Widget | list[Widget]: return self._nodes[index] diff --git a/src/textual/_on.py b/src/textual/_on.py index fd4c3d12a0..10e0d80c42 100644 --- a/src/textual/_on.py +++ b/src/textual/_on.py @@ -43,7 +43,7 @@ def quit_button(self) -> None: Example: ```python # Handle the activation of the tab "#home" within the `TabbedContent` "#tabs". - @on(TabbedContent.TabActivated, "#tabs", tab="#home") + @on(TabbedContent.TabActivated, "#tabs", pane="#home") def switch_to_home(self) -> None: self.log("Switching back to the home tab.") ... diff --git a/src/textual/_partition.py b/src/textual/_partition.py index be93fbe1be..09920bf579 100644 --- a/src/textual/_partition.py +++ b/src/textual/_partition.py @@ -21,7 +21,7 @@ def partition( """ result: tuple[list[T], list[T]] = ([], []) - appends = (result[0].append, result[1].append) + appends = (result[1].append, result[0].append) for value in iterable: - appends[1 if predicate(value) else 0](value) + appends[not predicate(value)](value) return result diff --git a/src/textual/_segment_tools.py b/src/textual/_segment_tools.py index 219c6dcadf..5db0f4d70e 100644 --- a/src/textual/_segment_tools.py +++ b/src/textual/_segment_tools.py @@ -4,6 +4,7 @@ from __future__ import annotations +import re from typing import Iterable from rich.segment import Segment @@ -258,3 +259,36 @@ def blank_lines(count: int) -> list[list[Segment]]: if bottom_blank_lines: yield from blank_lines(bottom_blank_lines) + + +_re_spaces = re.compile(r"(\s+|\S+)") + + +def apply_hatch( + segments: Iterable[Segment], + character: str, + hatch_style: Style, + _split=_re_spaces.split, +) -> Iterable[Segment]: + """Replace run of spaces with another character + style. + + Args: + segments: Segments to process. + character: Character to replace spaces. + hatch_style: Style of replacement characters. + + Yields: + Segments. + """ + _Segment = Segment + for segment in segments: + if " " not in segment.text: + yield segment + else: + text, style, _ = segment + for token in _split(text): + if token: + if token.isspace(): + yield _Segment(character * len(token), hatch_style) + else: + yield _Segment(token, style) diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py index 674b2a52bf..7029b6b4f4 100644 --- a/src/textual/_styles_cache.py +++ b/src/textual/_styles_cache.py @@ -14,7 +14,7 @@ from ._border import get_box, render_border_label, render_row from ._context import active_app from ._opacity import _apply_opacity -from ._segment_tools import line_pad, line_trim +from ._segment_tools import apply_hatch, line_pad, line_trim from .color import Color from .constants import DEBUG from .filter import LineFilter @@ -311,6 +311,17 @@ def render_line( inner = from_color(bgcolor=(base_background + background).rich_color) outer = from_color(bgcolor=base_background.rich_color) + def line_post(segments: Iterable[Segment]) -> Iterable[Segment]: + """Apply effects to segments inside the border.""" + if styles.has_rule("hatch"): + character, color = styles.hatch + if character != " " and color.a > 0: + hatch_style = Style.from_color( + (background + color).rich_color, background.rich_color + ) + return apply_hatch(segments, character, hatch_style) + return segments + def post(segments: Iterable[Segment]) -> Iterable[Segment]: """Post process segments to apply opacity and tint. @@ -320,6 +331,7 @@ def post(segments: Iterable[Segment]) -> Iterable[Segment]: Returns: New list of segments """ + try: app = active_app.get() ansi_theme = app.ansi_theme @@ -421,6 +433,7 @@ def post(segments: Iterable[Segment]) -> Iterable[Segment]: line = [make_blank(width - 1, background_style), right] else: line = [make_blank(width, background_style)] + line = line_post(line) else: # Content with border and padding (C) content_y = y - gutter.top @@ -433,7 +446,7 @@ def post(segments: Iterable[Segment]) -> Iterable[Segment]: line = Segment.apply_style(line, inner) if styles.text_opacity != 1.0: line = TextOpacity.process_segments(line, styles.text_opacity) - line = line_pad(line, pad_left, pad_right, inner) + line = line_post(line_pad(line, pad_left, pad_right, inner)) if border_left or border_right: # Add left / right border diff --git a/src/textual/_time.py b/src/textual/_time.py index fea8a569ed..13446dfb84 100644 --- a/src/textual/_time.py +++ b/src/textual/_time.py @@ -1,10 +1,9 @@ import asyncio -import platform +import sys from asyncio import sleep as asyncio_sleep from time import monotonic, perf_counter -PLATFORM = platform.system() -WINDOWS = PLATFORM == "Windows" +WINDOWS = sys.platform == "win32" if WINDOWS: diff --git a/src/textual/_work_decorator.py b/src/textual/_work_decorator.py index 71bd67ac1e..9863d08662 100644 --- a/src/textual/_work_decorator.py +++ b/src/textual/_work_decorator.py @@ -33,42 +33,42 @@ class WorkerDeclarationError(Exception): """An error in the declaration of a worker method.""" -@overload -def work( - method: Callable[FactoryParamSpec, Coroutine[None, None, ReturnType]], - *, - name: str = "", - group: str = "default", - exit_on_error: bool = True, - exclusive: bool = False, - description: str | None = None, - thread: bool = False, -) -> Callable[FactoryParamSpec, "Worker[ReturnType]"]: ... - - -@overload -def work( - method: Callable[FactoryParamSpec, ReturnType], - *, - name: str = "", - group: str = "default", - exit_on_error: bool = True, - exclusive: bool = False, - description: str | None = None, - thread: bool = False, -) -> Callable[FactoryParamSpec, "Worker[ReturnType]"]: ... - +if TYPE_CHECKING: -@overload -def work( - *, - name: str = "", - group: str = "default", - exit_on_error: bool = True, - exclusive: bool = False, - description: str | None = None, - thread: bool = False, -) -> Decorator[..., ReturnType]: ... + @overload + def work( + method: Callable[FactoryParamSpec, Coroutine[None, None, ReturnType]], + *, + name: str = "", + group: str = "default", + exit_on_error: bool = True, + exclusive: bool = False, + description: str | None = None, + thread: bool = False, + ) -> Callable[FactoryParamSpec, "Worker[ReturnType]"]: ... + + @overload + def work( + method: Callable[FactoryParamSpec, ReturnType], + *, + name: str = "", + group: str = "default", + exit_on_error: bool = True, + exclusive: bool = False, + description: str | None = None, + thread: bool = False, + ) -> Callable[FactoryParamSpec, "Worker[ReturnType]"]: ... + + @overload + def work( + *, + name: str = "", + group: str = "default", + exit_on_error: bool = True, + exclusive: bool = False, + description: str | None = None, + thread: bool = False, + ) -> Decorator[..., ReturnType]: ... def work( @@ -103,7 +103,7 @@ def decorator( method: ( Callable[DecoratorParamSpec, ReturnType] | Callable[DecoratorParamSpec, Coroutine[None, None, ReturnType]] - ) + ), ) -> Callable[DecoratorParamSpec, Worker[ReturnType]]: """The decorator.""" diff --git a/src/textual/_worker_manager.py b/src/textual/_worker_manager.py index ab69947718..6c90b9641a 100644 --- a/src/textual/_worker_manager.py +++ b/src/textual/_worker_manager.py @@ -175,5 +175,7 @@ async def wait_for_complete(self, workers: Iterable[Worker] | None = None) -> No Args: workers: An iterable of workers or None to wait for all workers in the manager. """ - - await asyncio.gather(*[worker.wait() for worker in (workers or self)]) + try: + await asyncio.gather(*[worker.wait() for worker in (workers or self)]) + except asyncio.CancelledError: + pass diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index fc84391f39..5b810273fa 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -1,13 +1,13 @@ from __future__ import annotations import re -import unicodedata from typing import Any, Callable, Generator, Iterable from typing_extensions import Final from . import events, messages from ._ansi_sequences import ANSI_SEQUENCES_KEYS, IGNORE_SEQUENCE +from ._keyboard_protocol import FUNCTIONAL_KEYS from ._parser import Awaitable, Parser, TokenCallback from .keys import KEY_NAME_REPLACEMENTS, Keys, _character_to_key @@ -32,6 +32,8 @@ FOCUSOUT: Final[str] = "\x1b[O" """Sequence received when focus is lost from the terminal.""" +_re_extended_key: Final = re.compile(r"\x1b\[(?:(\d+)(?:;(\d+))?)?([u~ABCDEFHPQRS])") + class XTermParser(Parser[events.Event]): _re_sgr_mouse = re.compile(r"\x1b\[<(\d+);(\d+);(\d+)([Mm])") @@ -41,10 +43,12 @@ def __init__(self, more_data: Callable[[], bool], debug: bool = False) -> None: self.last_x = 0 self.last_y = 0 - self._debug_log_file = open("keys.log", "wt") if debug else None + self._debug_log_file = open("keys.log", "at") if debug else None super().__init__() + self.debug_log("---") + def debug_log(self, *args: Any) -> None: # pragma: no cover if self._debug_log_file is not None: self._debug_log_file.write(" ".join(args) + "\n") @@ -73,13 +77,14 @@ def parse_mouse_code(self, code: str) -> events.Event | None: ) button = 0 else: - if buttons & 32: + button = (buttons + 1) & 3 + # XTerm events for mouse movement can look like mouse button down events. But if there is no key pressed, + # it's a mouse move event. + if buttons & 32 or button == 0: event_class = events.MouseMove else: event_class = events.MouseDown if state == "M" else events.MouseUp - button = (buttons + 1) & 3 - event = event_class( x, y, @@ -102,7 +107,7 @@ def parse_mouse_code(self, code: str) -> events.Event | None: the reissued sequence being emitted as key events. """ - def parse(self, on_token: TokenCallback) -> Generator[Awaitable, str, None]: + def parse(self, _on_token: TokenCallback) -> Generator[Awaitable, str, None]: ESC = "\x1b" read1 = self.read1 sequence_to_key_events = self._sequence_to_key_events @@ -111,6 +116,11 @@ def parse(self, on_token: TokenCallback) -> Generator[Awaitable, str, None]: bracketed_paste = False use_prior_escape = False + def on_token(token: events.Event) -> None: + """Hook to log events.""" + self.debug_log(str(token)) + _on_token(token) + def on_key_token(event: events.Key) -> None: """Token callback wrapper for handling keys. @@ -230,6 +240,21 @@ def reissue_sequence_as_keys(reissue_sequence: str) -> None: break if not bracketed_paste: + # Check cursor position report + if ( + cursor_position_match := _re_cursor_position.match(sequence) + ) is not None: + row, column = cursor_position_match.groups() + # Cursor position report conflicts with f3 key + # If it is a keypress, "row" will be 1, so ignore + if int(row) != 1: + on_token( + events.CursorPosition( + x=int(column) - 1, y=int(row) - 1 + ) + ) + break + # Was it a pressed key event that we received? key_events = list(sequence_to_key_events(sequence)) for key_event in key_events: @@ -258,24 +283,16 @@ def reissue_sequence_as_keys(reissue_sequence: str) -> None: on_token(messages.TerminalSupportsSynchronizedOutput()) break - # Or a cursor position query? - if ( - cursor_position_match := _re_cursor_position.match(sequence) - ) is not None: - row, column = cursor_position_match.groups() - on_token( - events.CursorPosition(x=int(column) - 1, y=int(row) - 1) - ) - break - else: if not bracketed_paste: for event in sequence_to_key_events(character): on_key_token(event) - def _sequence_to_key_events( - self, sequence: str, _unicode_name=unicodedata.name - ) -> Iterable[events.Key]: + if self._debug_log_file is not None: + self._debug_log_file.close() + self._debug_log_file = None + + def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]: """Map a sequence of code points on to a sequence of keys. Args: @@ -284,6 +301,37 @@ def _sequence_to_key_events( Returns: Keys """ + + if (match := _re_extended_key.match(sequence)) is not None: + number, modifiers, end = match.groups() + number = number or 1 + if not (key := FUNCTIONAL_KEYS.get(f"{number}{end}", "")): + try: + key = _character_to_key(chr(int(number))) + except Exception: + key = chr(int(number)) + key_tokens: list[str] = [] + if modifiers: + modifier_bits = int(modifiers) - 1 + MODIFIERS = ( + "shift", + "alt", + "ctrl", + "hyper", + "meta", + "caps_lock", + "num_lock", + ) + for bit, modifier in zip(range(8), MODIFIERS): + if modifier_bits & (1 << bit): + key_tokens.append(modifier) + key_tokens.sort() + key_tokens.append(key) + yield events.Key( + f'{"+".join(key_tokens)}', sequence if len(sequence) == 1 else None + ) + return + keys = ANSI_SEQUENCES_KEYS.get(sequence) # If we're being asked to ignore the key... if keys is IGNORE_SEQUENCE: @@ -292,6 +340,7 @@ def _sequence_to_key_events( # to is the ignore key) and the sequence that was ignored as # the character. yield events.Key(Keys.Ignore, sequence) + return if isinstance(keys, tuple): # If the sequence mapped to a tuple, then it's values from the # `Keys` enum. Raise key events from what we find in the tuple. @@ -313,5 +362,5 @@ def _sequence_to_key_events( name = sequence name = KEY_NAME_REPLACEMENTS.get(name, name) yield events.Key(name, sequence) - except: + except Exception: yield events.Key(sequence, sequence) diff --git a/src/textual/actions.py b/src/textual/actions.py index 9365bb268b..a7ff7bbde2 100644 --- a/src/textual/actions.py +++ b/src/textual/actions.py @@ -2,11 +2,12 @@ import ast import re +from functools import lru_cache from typing import Any from typing_extensions import TypeAlias -ActionParseResult: TypeAlias = "tuple[str, tuple[Any, ...]]" +ActionParseResult: TypeAlias = "tuple[str, str, tuple[object, ...]]" """An action is its name and the arbitrary tuple of its arguments.""" @@ -21,6 +22,7 @@ class ActionError(Exception): re_action_args = re.compile(r"([\w\.]+)\((.*)\)") +@lru_cache(maxsize=1024) def parse(action: str) -> ActionParseResult: """Parses an action string. @@ -52,4 +54,6 @@ def parse(action: str) -> ActionParseResult: action_name = action action_args = () - return action_name, action_args + namespace, _, action_name = action_name.rpartition(".") + + return namespace, action_name, action_args diff --git a/src/textual/app.py b/src/textual/app.py index 7ed7275d0a..160e75d55a 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -12,7 +12,6 @@ import inspect import io import os -import platform import signal import sys import threading @@ -27,7 +26,6 @@ ) from datetime import datetime from functools import partial -from pathlib import PurePath from time import perf_counter from typing import ( TYPE_CHECKING, @@ -40,11 +38,9 @@ Generic, Iterable, Iterator, - List, Sequence, Type, TypeVar, - Union, overload, ) from weakref import WeakKeyDictionary, WeakSet @@ -82,6 +78,7 @@ from ._wait import wait_for_idle from ._worker_manager import WorkerManager from .actions import ActionParseResult, SkipAction +from .await_complete import AwaitComplete from .await_remove import AwaitRemove from .binding import Binding, BindingType, _Bindings from .command import CommandPalette, Provider @@ -102,17 +99,19 @@ _get_key_display, _get_unicode_name_from_key, ) -from .messages import CallbackType +from .messages import CallbackType, Prune from .notifications import Notification, Notifications, Notify, SeverityLevel from .reactive import Reactive from .renderables.blank import Blank from .screen import ( + ActiveBinding, Screen, ScreenResultCallbackType, ScreenResultType, - _SystemModalScreen, + SystemModalScreen, ) from .signal import Signal +from .timer import Timer from .widget import AwaitMount, Widget from .widgets._toast import ToastRack from .worker import NoActiveWorker, get_current_worker @@ -131,8 +130,7 @@ from .pilot import Pilot from .widget import MountError # type: ignore # noqa: F401 -PLATFORM = platform.system() -WINDOWS = PLATFORM == "Windows" +WINDOWS = sys.platform == "win32" # asyncio will warn against resources not being cleared if constants.DEBUG: @@ -225,14 +223,6 @@ class SuspendNotSupported(Exception): ReturnType = TypeVar("ReturnType") - -CSSPathType = Union[ - str, - PurePath, - List[Union[str, PurePath]], -] -"""Valid ways of specifying paths to CSS files.""" - CallThreadReturnType = TypeVar("CallThreadReturnType") @@ -364,6 +354,9 @@ class MyApp(App[None]): ENABLE_COMMAND_PALETTE: ClassVar[bool] = True """Should the [command palette][textual.command.CommandPalette] be enabled for the application?""" + NOTIFICATION_TIMEOUT: ClassVar[float] = 5 + """Default number of seconds to show notifications before removing them.""" + COMMANDS: ClassVar[set[type[Provider] | Callable[[], type[Provider]]]] = { get_system_commands } @@ -377,6 +370,9 @@ class MyApp(App[None]): Binding("ctrl+backslash", "command_palette", show=False, priority=True), ] + CLOSE_TIMEOUT: float | None = 5.0 + """Timeout waiting for widget's to close, or `None` for no timeout.""" + title: Reactive[str] = Reactive("", compute=False) sub_title: Reactive[str] = Reactive("", compute=False) @@ -453,7 +449,7 @@ def __init__( soft_wrap=False, ) self._workers = WorkerManager(self) - self.error_console = Console(markup=False, stderr=True) + self.error_console = Console(markup=False, highlight=False, stderr=True) self.driver_class = driver_class or self.get_driver_class() self._screen_stacks: dict[str, list[Screen[Any]]] = {"_default": []} """A stack of screens per mode.""" @@ -466,7 +462,7 @@ def __init__( self._driver: Driver | None = None self._exit_renderables: list[RenderableType] = [] - self._action_targets = {"app", "screen"} + self._action_targets = {"app", "screen", "focused"} self._animator = Animator(self) self._animate = self._animator.bind(self) self.mouse_position = Offset(0, 0) @@ -585,7 +581,6 @@ def __init__( else None ) self._screenshot: str | None = None - self._dom_lock = asyncio.Lock() self._dom_ready = False self._batch_count = 0 self._notifications = Notifications() @@ -603,7 +598,7 @@ def __init__( self._original_stderr = sys.__stderr__ """The original stderr stream (before redirection etc).""" - self.app_suspend_signal = Signal(self, "app-suspend") + self.app_suspend_signal: Signal[App] = Signal(self, "app-suspend") """The signal that is published when the app is suspended. When [`App.suspend`][textual.app.App.suspend] is called this signal @@ -611,7 +606,7 @@ def __init__( [subscribe][textual.signal.Signal.subscribe] to this signal to perform work before the suspension takes place. """ - self.app_resume_signal = Signal(self, "app-resume") + self.app_resume_signal: Signal[App] = Signal(self, "app-resume") """The signal that is published when the app is resumed after a suspend. When the app is resumed after a @@ -645,7 +640,7 @@ def validate_title(self, title: Any) -> str: return str(title) def validate_sub_title(self, sub_title: Any) -> str: - """Make sure the sub-title is set to a string.""" + """Make sure the subtitle is set to a string.""" return str(sub_title) @property @@ -671,7 +666,7 @@ def return_code(self) -> int | None: Non-zero codes indicate errors. A value of 1 means the app exited with a fatal error. - If the app wasn't exited yet, this will be `None`. + If the app hasn't exited yet, this will be `None`. Example: The return code can be used to exit the process via `sys.exit`. @@ -698,7 +693,7 @@ def children(self) -> Sequence["Widget"]: next( screen for screen in reversed(self._screen_stack) - if not isinstance(screen, _SystemModalScreen) + if not isinstance(screen, SystemModalScreen) ), ) except StopIteration: @@ -776,6 +771,16 @@ async def stop_animation(self, attribute: str, complete: bool = True) -> None: """ await self._animator.stop_animation(self, attribute, complete) + @property + def is_dom_root(self) -> bool: + """Is this a root node (i.e. the App)?""" + return True + + @property + def is_attached(self) -> bool: + """Is this node linked to the app through the DOM?""" + return True + @property def debug(self) -> bool: """Is debug mode enabled?""" @@ -855,7 +860,7 @@ def focused(self) -> Widget | None: return focused @property - def namespace_bindings(self) -> dict[str, tuple[DOMNode, Binding]]: + def active_bindings(self) -> dict[str, ActiveBinding]: """Get currently active bindings. If no widget is focused, then app-level bindings are returned. @@ -864,20 +869,22 @@ def namespace_bindings(self) -> dict[str, tuple[DOMNode, Binding]]: This property may be used to inspect current bindings. Returns: - A map of keys to a tuple containing the DOMNode and Binding that key corresponds to. + Active binding information """ + return self.screen.active_bindings - bindings_map: dict[str, tuple[DOMNode, Binding]] = {} - for namespace, bindings in self._binding_chain: - for key, binding in bindings.keys.items(): - if existing_key_and_binding := bindings_map.get(key): - _, existing_binding = existing_key_and_binding - if binding.priority and not existing_binding.priority: - bindings_map[key] = (namespace, binding) - else: - bindings_map[key] = (namespace, binding) + def get_default_screen(self) -> Screen: + """Get the default screen. - return bindings_map + This is called when the App is first composed. The returned screen instance + will be the first screen on the stack. + + Implement this method if you would like to use a custom Screen as the default screen. + + Returns: + A screen instance. + """ + return Screen(id="_default") def _set_active(self) -> None: """Set this app to be the currently active app.""" @@ -1071,7 +1078,7 @@ def _log( ) -> None: """Write to logs or devtools. - Positional args will logged. Keyword args will be prefixed with the key. + Positional args will be logged. Keyword args will be prefixed with the key. Example: ```python @@ -1234,7 +1241,7 @@ def export_screenshot(self, *, title: str | None = None) -> str: safe_box=False, ) screen_render = self.screen._compositor.render_update( - full=True, screen_stack=self.app._background_screens + full=True, screen_stack=self.app._background_screens, simplify=True ) console.print(screen_render) return console.export_svg(title=title or self.title) @@ -1449,15 +1456,24 @@ def on_app_ready() -> None: app_ready_event.set() async def run_app(app: App) -> None: - if message_hook is not None: - message_hook_context_var.set(message_hook) - app._loop = asyncio.get_running_loop() - app._thread_id = threading.get_ident() - await app._process_messages( - ready_callback=on_app_ready, - headless=headless, - terminal_size=size, - ) + """Run the apps message loop. + + Args: + app: App to run. + """ + + try: + if message_hook is not None: + message_hook_context_var.set(message_hook) + app._loop = asyncio.get_running_loop() + app._thread_id = threading.get_ident() + await app._process_messages( + ready_callback=on_app_ready, + headless=headless, + terminal_size=size, + ) + finally: + app_ready_event.set() # Launch the app in the "background" active_message_pump.set(app) @@ -1501,7 +1517,7 @@ async def run_async( mouse: Enable mouse support. size: Force terminal size to `(WIDTH, HEIGHT)`, or None to auto-detect. - auto_pilot: An auto pilot coroutine. + auto_pilot: An autopilot coroutine. Returns: App return value. @@ -1559,7 +1575,10 @@ async def run_auto_pilot( if auto_pilot_task is not None: await auto_pilot_task finally: - await app._shutdown() + try: + await asyncio.shield(app._shutdown()) + except asyncio.CancelledError: + pass return app.return_value @@ -1661,20 +1680,29 @@ async def _on_css_change(self) -> None: self.stylesheet.update(screen) def render(self) -> RenderResult: + """Render method, inherited from widget, to render the screen's background. + + May be overridden to customize background visuals. + + """ return Blank(self.styles.background) ExpectType = TypeVar("ExpectType", bound=Widget) - @overload - def get_child_by_id(self, id: str) -> Widget: ... + if TYPE_CHECKING: + + @overload + def get_child_by_id(self, id: str) -> Widget: ... - @overload - def get_child_by_id(self, id: str, expect_type: type[ExpectType]) -> ExpectType: ... + @overload + def get_child_by_id( + self, id: str, expect_type: type[ExpectType] + ) -> ExpectType: ... def get_child_by_id( self, id: str, expect_type: type[ExpectType] | None = None ) -> ExpectType | Widget: - """Get the first child (immediate descendent) of this DOMNode with the given ID. + """Get the first child (immediate descendant) of this DOMNode with the given ID. Args: id: The ID of the node to search for. @@ -1694,13 +1722,15 @@ def get_child_by_id( else self.screen.get_child_by_id(id, expect_type) ) - @overload - def get_widget_by_id(self, id: str) -> Widget: ... + if TYPE_CHECKING: - @overload - def get_widget_by_id( - self, id: str, expect_type: type[ExpectType] - ) -> ExpectType: ... + @overload + def get_widget_by_id(self, id: str) -> Widget: ... + + @overload + def get_widget_by_id( + self, id: str, expect_type: type[ExpectType] + ) -> ExpectType: ... def get_widget_by_id( self, id: str, expect_type: type[ExpectType] | None = None @@ -1814,7 +1844,7 @@ def mount_all( return self.mount(*widgets, before=before, after=after) def _init_mode(self, mode: str) -> AwaitMount: - """Do internal initialisation of a new screen stack mode. + """Do internal initialization of a new screen stack mode. Args: mode: Name of the mode. @@ -1889,7 +1919,7 @@ def add_mode( self.MODES[mode] = base_screen - def remove_mode(self, mode: str) -> None: + def remove_mode(self, mode: str) -> AwaitComplete: """Removes a mode from the app. Screens that are running in the stack of that mode are scheduled for pruning. @@ -1909,12 +1939,17 @@ def remove_mode(self, mode: str) -> None: del self.MODES[mode] if mode not in self._screen_stacks: - return + return AwaitComplete.nothing() stack = self._screen_stacks[mode] del self._screen_stacks[mode] - for screen in reversed(stack): - self._replace_screen(screen) + + async def remove_screens() -> None: + """Remove screens.""" + for screen in reversed(stack): + await self._replace_screen(screen) + + return AwaitComplete(remove_screens()).call_next(self) def is_screen_installed(self, screen: Screen | str) -> bool: """Check if a given screen has been installed. @@ -2009,7 +2044,7 @@ def _load_screen_css(self, screen: Screen): self.stylesheet.reparse() self.stylesheet.update(self) - def _replace_screen(self, screen: Screen) -> Screen: + async def _replace_screen(self, screen: Screen) -> Screen: """Handle the replaced screen. Args: @@ -2025,25 +2060,27 @@ def _replace_screen(self, screen: Screen) -> Screen: if not self.is_screen_installed(screen) and all( screen not in stack for stack in self._screen_stacks.values() ): - screen.remove() + await screen.remove() self.log.system(f"{screen} REMOVED") return screen - @overload - def push_screen( - self, - screen: Screen[ScreenResultType] | str, - callback: ScreenResultCallbackType[ScreenResultType] | None = None, - wait_for_dismiss: Literal[False] = False, - ) -> AwaitMount: ... + if TYPE_CHECKING: - @overload - def push_screen( - self, - screen: Screen[ScreenResultType] | str, - callback: ScreenResultCallbackType[ScreenResultType] | None = None, - wait_for_dismiss: Literal[True] = True, - ) -> asyncio.Future[ScreenResultType]: ... + @overload + def push_screen( + self, + screen: Screen[ScreenResultType] | str, + callback: ScreenResultCallbackType[ScreenResultType] | None = None, + wait_for_dismiss: Literal[False] = False, + ) -> AwaitMount: ... + + @overload + def push_screen( + self, + screen: Screen[ScreenResultType] | str, + callback: ScreenResultCallbackType[ScreenResultType] | None = None, + wait_for_dismiss: Literal[True] = True, + ) -> asyncio.Future[ScreenResultType]: ... def push_screen( self, @@ -2105,13 +2142,15 @@ def push_screen( else: return await_mount - @overload - async def push_screen_wait( - self, screen: Screen[ScreenResultType] - ) -> ScreenResultType: ... + if TYPE_CHECKING: + + @overload + async def push_screen_wait( + self, screen: Screen[ScreenResultType] + ) -> ScreenResultType: ... - @overload - async def push_screen_wait(self, screen: str) -> Any: ... + @overload + async def push_screen_wait(self, screen: str) -> Any: ... async def push_screen_wait( self, screen: Screen[ScreenResultType] | str @@ -2126,9 +2165,10 @@ async def push_screen_wait( Returns: The screen's result. """ + await self._flush_next_callbacks() return await self.push_screen(screen, wait_for_dismiss=True) - def switch_screen(self, screen: Screen | str) -> AwaitMount: + def switch_screen(self, screen: Screen | str) -> AwaitComplete: """Switch to another [screen](/guide/screens) by replacing the top of the screen stack with a new screen. Args: @@ -2142,16 +2182,24 @@ def switch_screen(self, screen: Screen | str) -> AwaitMount: next_screen, await_mount = self._get_screen(screen) if screen is self.screen or next_screen is self.screen: self.log.system(f"Screen {screen} is already current.") - return AwaitMount(self.screen, []) + return AwaitComplete.nothing() - previous_screen = self._replace_screen(self._screen_stack.pop()) - previous_screen._pop_result_callback() + top_screen = self._screen_stack.pop() + + top_screen._pop_result_callback() self._load_screen_css(next_screen) self._screen_stack.append(next_screen) self.screen.post_message(events.ScreenResume()) self.screen._push_result_callback(self.screen, None) self.log.system(f"{self.screen} is current (SWITCHED)") - return await_mount + + async def do_switch() -> None: + """Task to perform switch.""" + + await await_mount() + await self._replace_screen(top_screen) + + return AwaitComplete(do_switch()).call_next(self) def install_screen(self, screen: Screen, name: str) -> None: """Install a screen. @@ -2182,14 +2230,14 @@ def install_screen(self, screen: Screen, name: str) -> None: def uninstall_screen(self, screen: Screen | str) -> str | None: """Uninstall a screen. - If the screen was not previously installed then this method is a null-op. + If the screen was not previously installed, then this method is a null-op. Uninstalling a screen allows Textual to delete it when it is popped or switched. Note that uninstalling a screen is only required if you have previously installed it with [install_screen][textual.app.App.install_screen]. Textual will also uninstall screens automatically on exit. Args: - screen: The screen to uninstall or the name of a installed screen. + screen: The screen to uninstall or the name of an installed screen. Returns: The name of the screen that was uninstalled, or None if no screen was uninstalled. @@ -2213,22 +2261,29 @@ def uninstall_screen(self, screen: Screen | str) -> str | None: return name return None - def pop_screen(self) -> Screen[object]: + def pop_screen(self) -> AwaitComplete: """Pop the current [screen](/guide/screens) from the stack, and switch to the previous screen. Returns: The screen that was replaced. """ + screen_stack = self._screen_stack if len(screen_stack) <= 1: raise ScreenStackError( "Can't pop screen; there must be at least one screen on the stack" ) - previous_screen = self._replace_screen(screen_stack.pop()) + + previous_screen = screen_stack.pop() previous_screen._pop_result_callback() self.screen.post_message(events.ScreenResume()) self.log.system(f"{self.screen} is active") - return previous_screen + + async def do_pop() -> None: + """Task to pop the screen.""" + await self._replace_screen(previous_screen) + + return AwaitComplete(do_pop()).call_next(self) def set_focus(self, widget: Widget | None, scroll_visible: bool = True) -> None: """Focus (or unfocus) a widget. A focused widget will receive key events first. @@ -2261,6 +2316,29 @@ def _set_mouse_over(self, widget: Widget | None) -> None: finally: self.mouse_over = widget + def _update_mouse_over(self, screen: Screen) -> None: + """Updates the mouse over after the next refresh. + + This method is called whenever a widget is added or removed, which may change + the widget under the mouse. + + """ + + if self.mouse_over is None or not screen.is_active: + return + + async def check_mouse() -> None: + """Check if the mouse over widget has changed.""" + try: + widget, _ = screen.get_widget_at(*self.mouse_position) + except NoWidget: + pass + else: + if widget is not self.mouse_over: + self._set_mouse_over(widget) + + self.call_after_refresh(check_mouse) + def capture_mouse(self, widget: Widget | None) -> None: """Send all mouse events to the given widget or disable mouse capture. @@ -2309,7 +2387,7 @@ def _handle_exception(self, error: Exception) -> None: self._return_code = 1 # If we're running via pilot and this is the first exception encountered, # take note of it so that we can re-raise for test frameworks later. - if self.is_headless and self._exception is None: + if self._exception is None: self._exception = error self._exception_event.set() @@ -2494,8 +2572,7 @@ async def invoke_ready_callback() -> None: try: await self.animator.stop() finally: - for timer in list(self._timers): - timer.stop() + await Timer._stop_all(self._timers) self._running = True try: @@ -2527,6 +2604,7 @@ async def invoke_ready_callback() -> None: console = Console() console.print(self.screen._compositor) console.print() + driver.stop_application_mode() except Exception as error: self._handle_exception(error) @@ -2579,9 +2657,14 @@ async def recompose(self) -> None: Recomposing will remove children and call `self.compose` again to remount. """ - async with self.screen.batch(): - await self.screen.query("*").exclude(".-textual-system").remove() - await self.screen.mount_all(compose(self)) + if self._exit: + return + try: + async with self.screen.batch(): + await self.screen.query("*").exclude(".-textual-system").remove() + await self.screen.mount_all(compose(self)) + except ScreenStackError: + pass def _register_child( self, parent: DOMNode, child: Widget, before: int | None, after: int | None @@ -2615,7 +2698,7 @@ def _register_child( # position (for now) of meaning "okay really what I mean is # do an append, like if I'd asked to add with no before or # after". So... we insert before the next item in the node - # list, iff after isn't -1. + # list, if after isn't -1. parent._nodes._insert(after + 1, child) else: # At this point we appear to not be adding before or after, @@ -2667,6 +2750,9 @@ def _register( apply_stylesheet = self.stylesheet.apply for widget in widget_list: + widget._closing = False + widget._closed = False + widget._pruning = False if not isinstance(widget, Widget): raise AppError(f"Can't register {widget!r}; expected a Widget instance") if widget not in self._registry: @@ -2698,7 +2784,7 @@ async def _disconnect_devtools(self): await self.devtools.disconnect() def _start_widget(self, parent: Widget, widget: Widget) -> None: - """Start a widget (run it's task) so that it can receive messages. + """Start a widget (run its task) so that it can receive messages. Args: parent: The parent of the Widget. @@ -2727,13 +2813,13 @@ async def _close_all(self) -> None: for stack in self._screen_stacks.values(): for stack_screen in reversed(stack): if stack_screen._running: - await self._prune_node(stack_screen) + await self._prune(stack_screen) stack.clear() # Close pre-defined screens. for screen in self.SCREENS.values(): if isinstance(screen, Screen) and screen._running: - await self._prune_node(screen) + await self._prune(screen) # Close any remaining nodes # Should be empty by now @@ -2747,6 +2833,7 @@ async def _shutdown(self) -> None: self._running = False if driver is not None: driver.disable_input() + await self._close_all() await self._close_messages() @@ -2770,7 +2857,7 @@ async def _shutdown(self) -> None: async def _on_exit_app(self) -> None: self._begin_batch() # Prevent repaint / layout while shutting down - await self._message_queue.put(None) + self._message_queue.put_nowait(None) def refresh( self, @@ -2840,16 +2927,23 @@ def _display(self, screen: Screen, renderable: RenderableType | None) -> None: try: try: if isinstance(renderable, CompositorUpdate): - cursor_x, cursor_y = self._previous_cursor_position - terminal_sequence = Control.move( - -cursor_x, -cursor_y - ).segment.text - cursor_x, cursor_y = self.cursor_position - terminal_sequence += renderable.render_segments(console) - terminal_sequence += Control.move( - cursor_x, cursor_y - ).segment.text - self._previous_cursor_position = self.cursor_position + cursor_position = self.screen.outer_size.clamp_offset( + self.cursor_position + ) + if self._driver.is_inline: + terminal_sequence = Control.move( + *(-self._previous_cursor_position) + ).segment.text + terminal_sequence += renderable.render_segments(console) + terminal_sequence += Control.move( + *cursor_position + ).segment.text + else: + terminal_sequence = renderable.render_segments(console) + terminal_sequence += Control.move_to( + *cursor_position + ).segment.text + self._previous_cursor_position = cursor_position else: segments = console.render(renderable) terminal_sequence = console._render_buffer(segments) @@ -2926,20 +3020,20 @@ def _binding_chain(self) -> list[tuple[DOMNode, _Bindings]]: return namespace_bindings - @property - def _modal_binding_chain(self) -> list[tuple[DOMNode, _Bindings]]: - """The binding chain, ignoring everything before the last modal.""" - binding_chain = self._binding_chain - for index, (node, _bindings) in enumerate(binding_chain, 1): - if node.is_modal: - return binding_chain[:index] - return binding_chain - - async def check_bindings(self, key: str, priority: bool = False) -> bool: + def simulate_key(self, key: str) -> None: + """Simulate a key press. + + This will perform the same action as if the user had pressed the key. + + Args: + key: Key to simulate. May also be the name of a key, e.g. "space". + """ + self.call_later(self._check_bindings, key) + + async def _check_bindings(self, key: str, priority: bool = False) -> bool: """Handle a key press. - This method is used internally by the bindings system, but may be called directly - if you wish to *simulate* a key being pressed. + This method is used internally by the bindings system. Args: key: A key. @@ -2949,7 +3043,9 @@ async def check_bindings(self, key: str, priority: bool = False) -> bool: True if the key was handled by a binding, otherwise False """ for namespace, bindings in ( - reversed(self._binding_chain) if priority else self._modal_binding_chain + reversed(self.screen._binding_chain) + if priority + else self.screen._modal_binding_chain ): binding = bindings.keys.get(key) if binding is not None and binding.priority == priority: @@ -2961,7 +3057,7 @@ async def on_event(self, event: events.Event) -> None: # Handle input events that haven't been forwarded # If the event has been forwarded it may have bubbled up back to the App if isinstance(event, events.Compose): - screen: Screen[Any] = Screen(id=f"_default") + screen: Screen[Any] = self.get_default_screen() self._register(self, screen) self._screen_stack.append(screen) screen.post_message(events.ScreenResume()) @@ -3000,7 +3096,12 @@ async def on_event(self, event: events.Event) -> None: pass elif isinstance(event, events.Key): - if not await self.check_bindings(event.key, priority=True): + if self.focused: + try: + self.screen._clear_tooltip() + except NoScreen: + pass + if not await self._check_bindings(event.key, priority=True): forward_target = self.focused or self.screen forward_target._forward_event(event) else: @@ -3014,10 +3115,58 @@ async def on_event(self, event: events.Event) -> None: else: await super().on_event(event) + def _parse_action( + self, action: str | ActionParseResult, default_namespace: DOMNode + ) -> tuple[DOMNode, str, tuple[object, ...]]: + """Parse an action. + + Args: + action: An action string. + + Raises: + ActionError: If there are any errors parsing the action string. + + Returns: + A tuple of (node or None, action name, tuple of parameters). + """ + if isinstance(action, tuple): + destination, action_name, params = action + else: + destination, action_name, params = actions.parse(action) + + action_target: DOMNode | None = None + if destination: + if destination not in self._action_targets: + raise ActionError(f"Action namespace {destination} is not known") + action_target = getattr(self, destination, None) + if action_target is None: + raise ActionError("Action target {destination!r} not available") + return ( + (default_namespace if action_target is None else action_target), + action_name, + params, + ) + + def _check_action_state( + self, action: str, default_namespace: DOMNode + ) -> bool | None: + """Check if an action is enabled. + + Args: + action: An action string. + + Returns: + State of an action. + """ + action_target, action_name, parameters = self._parse_action( + action, default_namespace + ) + return action_target.check_action(action_name, parameters) + async def run_action( self, action: str | ActionParseResult, - default_namespace: object | None = None, + default_namespace: DOMNode | None = None, ) -> bool: """Perform an [action](/guide/actions). @@ -3031,28 +3180,17 @@ async def run_action( Returns: True if the event has been handled. """ - if isinstance(action, str): - target, params = actions.parse(action) - else: - target, params = action - implicit_destination = True - if "." in target: - destination, action_name = target.split(".", 1) - if destination not in self._action_targets: - raise ActionError(f"Action namespace {destination} is not known") - action_target = getattr(self, destination) - implicit_destination = True - else: - action_target = default_namespace if default_namespace is not None else self - action_name = target + action_target, action_name, params = self._parse_action( + action, self if default_namespace is None else default_namespace + ) - handled = await self._dispatch_action(action_target, action_name, params) - if not handled and implicit_destination and not isinstance(action_target, App): - handled = await self.app._dispatch_action(self.app, action_name, params) - return handled + if action_target.check_action(action_name, params): + return await self._dispatch_action(action_target, action_name, params) + else: + return False async def _dispatch_action( - self, namespace: object, action_name: str, params: Any + self, namespace: DOMNode, action_name: str, params: Any ) -> bool: """Dispatch an action to an action method. @@ -3089,10 +3227,11 @@ async def _dispatch_action( except SkipAction: # The action method raised this to explicitly not handle the action log.system(f" {action_name!r} skipped.") + return False async def _broker_event( - self, event_name: str, event: events.Event, default_namespace: object | None + self, event_name: str, event: events.Event, default_namespace: DOMNode ) -> bool: """Allow the app an opportunity to dispatch events to action system. @@ -3114,10 +3253,16 @@ async def _broker_event( return False else: event.stop() - if isinstance(action, str) or (isinstance(action, tuple) and len(action) == 2): - await self.run_action(action, default_namespace=default_namespace) # type: ignore[arg-type] - elif callable(action): - await action() + + if isinstance(action, str): + await self.run_action(action, default_namespace) + elif isinstance(action, tuple) and len(action) == 2: + action_name, action_params = action + namespace, parsed_action, _ = actions.parse(action_name) + await self.run_action( + (namespace, parsed_action, action_params), + default_namespace, + ) else: if isinstance(action, tuple) and self.debug: # It's a tuple and made it this far, which means it'll be a @@ -3136,7 +3281,7 @@ async def _on_layout(self, message: messages.Layout) -> None: message.stop() async def _on_key(self, event: events.Key) -> None: - if not (await self.check_bindings(event.key)): + if not (await self._check_bindings(event.key)): await self.dispatch_key(event) async def _on_shutdown_request(self, event: events.ShutdownRequest) -> None: @@ -3153,168 +3298,61 @@ async def _on_app_focus(self, event: events.AppFocus) -> None: """App has focus.""" # Required by textual-web to manage focus in a web page. self.app_focus = True + self.screen.refresh_bindings() async def _on_app_blur(self, event: events.AppBlur) -> None: """App has lost focus.""" # Required by textual-web to manage focus in a web page. self.app_focus = False + self.screen.refresh_bindings() - def _detach_from_dom(self, widgets: list[Widget]) -> list[Widget]: - """Detach a list of widgets from the DOM. + def _prune(self, *nodes: Widget, parent: DOMNode | None = None) -> AwaitRemove: + """Prune nodes from DOM. Args: - widgets: The list of widgets to detach from the DOM. - - Returns: - The list of widgets that should be pruned. - - Note: - A side-effect of calling this function is that each parent of - each affected widget will be made to forget about the affected - child. - """ - - # We've been given a list of widgets to remove, but removing those - # will also result in other (descendent) widgets being removed. So - # to start with let's get a list of everything that's not going to - # be in the DOM by the time we've finished. Note that, at this - # point, it's entirely possible that there will be duplicates. - everything_to_remove: list[Widget] = [] - for widget in widgets: - everything_to_remove.extend( - widget.walk_children( - Widget, with_self=True, method="depth", reverse=True - ) - ) - - # Next up, let's quickly create a deduped collection of things to - # remove and ensure that, if one of them is the focused widget, - # focus gets moved to somewhere else. - dedupe_to_remove = set(everything_to_remove) - if self.screen.focused in dedupe_to_remove: - self.screen._reset_focus( - self.screen.focused, - [to_remove for to_remove in dedupe_to_remove if to_remove.can_focus], - ) - - # Next, we go through the set of widgets we've been asked to remove - # and try and find the minimal collection of widgets that will - # result in everything else that should be removed, being removed. - # In other words: find the smallest set of ancestors in the DOM that - # will remove the widgets requested for removal, and also ensure - # that all knock-on effects happen too. - request_remove = set(widgets) - pruned_remove = [ - widget for widget in widgets if request_remove.isdisjoint(widget.ancestors) - ] - - # Now that we know that minimal set of widgets, we go through them - # and get their parents to forget about them. This has the effect of - # snipping each affected branch from the DOM. - for widget in pruned_remove: - if widget.parent is not None: - widget.parent._nodes._remove(widget) - - for node in pruned_remove: - node._detach() - - # Return the list of widgets that should end up being sent off in a - # prune event. - return pruned_remove - - def _walk_children(self, root: Widget) -> Iterable[list[Widget]]: - """Walk children depth first, generating widgets and a list of their siblings. + parent: Parent node. Returns: - The child widgets of root. + Optional awaitable. """ - stack: list[Widget] = [root] - pop = stack.pop - push = stack.append - - while stack: - widget = pop() - children = [*widget._nodes, *widget._get_virtual_dom()] - if children: - yield children - for child in widget._nodes: - push(child) + if not nodes: + return AwaitRemove([]) + pruning_nodes: set[Widget] = {*nodes} + for node in nodes: + node.post_message(Prune()) + pruning_nodes.update(node.walk_children(with_self=True)) - def _remove_nodes( - self, widgets: list[Widget], parent: DOMNode | None - ) -> AwaitRemove: - """Remove nodes from DOM, and return an awaitable that awaits cleanup. - - Args: - widgets: List of nodes to remove. - parent: Parent node of widgets, or None for no parent. + try: + screen = nodes[0].screen + except (ScreenStackError, NoScreen): + pass + else: + if screen.focused and screen.focused in pruning_nodes: + screen._reset_focus(screen.focused, list(pruning_nodes)) - Returns: - Awaitable that returns when the nodes have been fully removed. - """ + for node in pruning_nodes: + node._pruning = True - async def prune_widgets_task( - widgets: list[Widget], finished_event: asyncio.Event - ) -> None: - """Prune widgets as a background task. + def post_mount() -> None: + """Called after removing children.""" - Args: - widgets: Widgets to prune. - finished_event: Event to set when complete. - """ - try: - await self._prune_nodes(widgets) - finally: - finished_event.set() - if parent is not None: + if parent is not None: + try: + screen = parent.screen + except (ScreenStackError, NoScreen): + pass + else: + if screen._running: + self._update_mouse_over(screen) + finally: parent.refresh(layout=True) - removed_widgets = self._detach_from_dom(widgets) - - finished_event = asyncio.Event() - remove_task = create_task( - prune_widgets_task(removed_widgets, finished_event), name="prune nodes" + await_complete = AwaitRemove( + [task for node in nodes if (task := node._task) is not None], + post_mount, ) - - await_remove = AwaitRemove(finished_event, remove_task) - self.call_next(await_remove) - return await_remove - - async def _prune_nodes(self, widgets: list[Widget]) -> None: - """Remove nodes and children. - - Args: - widgets: Widgets to remove. - """ - async with self._dom_lock: - for widget in widgets: - await self._prune_node(widget) - - async def _prune_node(self, root: Widget) -> None: - """Remove a node and its children. Children are removed before parents. - - Args: - root: Node to remove. - """ - # Pruning a node that has been removed is a no-op - if root not in self._registry: - return - - node_children = list(self._walk_children(root)) - - for children in reversed(node_children): - # Closing children can be done asynchronously. - close_messages = [ - child._close_messages(wait=True) for child in children if child._running - ] - # TODO: What if a message pump refuses to exit? - if close_messages: - await asyncio.gather(*close_messages) - for child in children: - self._unregister(child) - - await root._close_messages(wait=True) - self._unregister(root) + self.call_next(await_complete) + return await_complete def _watch_app_focus(self, focus: bool) -> None: """Respond to changes in app focus.""" @@ -3329,7 +3367,10 @@ def _watch_app_focus(self, focus: bool) -> None: and self.screen.focused is None ): # ...settle focus back on that widget. - self.screen.set_focus(self._last_focused_on_app_blur) + # Don't scroll the newly focused widget, as this can be quite jarring + self.screen.set_focus( + self._last_focused_on_app_blur, scroll_visible=False + ) except NoScreen: pass # Now that we have focus back on the app and we don't need the @@ -3342,14 +3383,15 @@ def _watch_app_focus(self, focus: bool) -> None: # Remove focus for now. self.screen.set_focus(None) - async def action_check_bindings(self, key: str) -> None: - """An [action](/guide/actions) to handle a key press using the binding system. + async def action_simulate_key(self, key: str) -> None: + """An [action](/guide/actions) to simulate a key press. + + This will invoke the same actions as if the user had pressed the key. Args: key: The key to process. """ - if not await self.check_bindings(key, priority=True): - await self.check_bindings(key, priority=False) + self.simulate_key(key) async def action_quit(self) -> None: """An [action](/guide/actions) to quit the app as soon as possible.""" @@ -3475,7 +3517,7 @@ def _refresh_notifications(self) -> None: # or one will turn up. Things will work out later. return # Update the toast rack. - toast_rack.show(self._notifications) + self.call_later(toast_rack.show, self._notifications) def notify( self, @@ -3483,7 +3525,7 @@ def notify( *, title: str = "", severity: SeverityLevel = "information", - timeout: float = Notification.timeout, + timeout: float | None = None, ) -> None: """Create a notification. @@ -3496,7 +3538,7 @@ def notify( message: The message for the notification. title: The title for the notification. severity: The severity of the notification. - timeout: The timeout (in seconds) for the notification. + timeout: The timeout (in seconds) for the notification, or `None` for default. The `notify` method is used to create an application-wide notification, shown in a [`Toast`][textual.widgets._toast.Toast], @@ -3531,6 +3573,8 @@ def notify( self.notify("It's against my programming to impersonate a deity.", title="") ``` """ + if timeout is None: + timeout = self.NOTIFICATION_TIMEOUT notification = Notification(message, title, severity, timeout) self.post_message(Notify(notification)) @@ -3562,12 +3606,12 @@ def action_command_palette(self) -> None: def _suspend_signal(self) -> None: """Signal that the application is being suspended.""" - self.app_suspend_signal.publish() + self.app_suspend_signal.publish(self) @on(Driver.SignalResume) def _resume_signal(self) -> None: """Signal that the application is being resumed from a suspension.""" - self.app_resume_signal.publish() + self.app_resume_signal.publish(self) @contextmanager def suspend(self) -> Iterator[None]: diff --git a/src/textual/await_complete.py b/src/textual/await_complete.py index 2ff1068862..ebc3c84ea5 100644 --- a/src/textual/await_complete.py +++ b/src/textual/await_complete.py @@ -4,6 +4,9 @@ from typing import Any, Awaitable, Generator import rich.repr +from typing_extensions import Self + +from .message_pump import MessagePump @rich.repr.auto(angular=True) @@ -18,6 +21,15 @@ def __init__(self, *awaitables: Awaitable) -> None: """ self._future: Future[Any] = gather(*awaitables) + def call_next(self, node: MessagePump) -> Self: + """Await after the next message. + + Args: + node: The node which created the object. + """ + node.call_next(self) + return self + async def __call__(self) -> Any: return await self diff --git a/src/textual/await_remove.py b/src/textual/await_remove.py index f02fe5b840..28698c1c94 100644 --- a/src/textual/await_remove.py +++ b/src/textual/await_remove.py @@ -2,33 +2,36 @@ An *optionally* awaitable object returned by methods that remove widgets. """ -from asyncio import Event, Task -from typing import Generator +from __future__ import annotations +import asyncio +from asyncio import Task, gather +from typing import Generator -class AwaitRemove: - """An awaitable returned by a method that removes DOM nodes. +from ._callback import invoke +from ._types import CallbackType - Returned by [Widget.remove][textual.widget.Widget.remove] and - [DOMQuery.remove][textual.css.query.DOMQuery.remove]. - """ - def __init__(self, finished_flag: Event, task: Task) -> None: - """Initialise the instance of ``AwaitRemove``. +class AwaitRemove: + """An awaitable that waits for nodes to be removed.""" - Args: - finished_flag: The asyncio event to wait on. - task: The task which does the remove (required to keep a reference). - """ - self.finished_flag = finished_flag - self._task = task + def __init__( + self, tasks: list[Task], post_remove: CallbackType | None = None + ) -> None: + self._tasks = tasks + self._post_remove = post_remove async def __call__(self) -> None: await self def __await__(self) -> Generator[None, None, None]: + current_task = asyncio.current_task() + tasks = [task for task in self._tasks if task is not current_task] + async def await_prune() -> None: """Wait for the prune operation to finish.""" - await self.finished_flag.wait() + await gather(*tasks) + if self._post_remove is not None: + await invoke(self._post_remove) return await_prune().__await__() diff --git a/src/textual/binding.py b/src/textual/binding.py index 409fe9de56..3cc2828df3 100644 --- a/src/textual/binding.py +++ b/src/textual/binding.py @@ -8,7 +8,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING, Iterable, NamedTuple import rich.repr @@ -17,6 +17,8 @@ if TYPE_CHECKING: from typing_extensions import TypeAlias + from .dom import DOMNode + BindingType: TypeAlias = "Binding | tuple[str, str] | tuple[str, str, str]" @@ -50,6 +52,17 @@ class Binding: """Enable priority binding for this key.""" +class ActiveBinding(NamedTuple): + """Information about an active binding (returned from [active_bindings][textual.screen.active_bindings]).""" + + node: DOMNode + """The node where the binding is defined.""" + binding: Binding + """The binding information.""" + enabled: bool + """Is the binding enabled? (enabled bindings are typically rendered dim)""" + + @rich.repr.auto class _Bindings: """Manage a set of bindings.""" diff --git a/src/textual/cache.py b/src/textual/cache.py index 420d90004f..9186df7cda 100644 --- a/src/textual/cache.py +++ b/src/textual/cache.py @@ -8,7 +8,7 @@ from __future__ import annotations -from typing import Dict, Generic, KeysView, TypeVar, overload +from typing import TYPE_CHECKING, Dict, Generic, KeysView, TypeVar, overload CacheKey = TypeVar("CacheKey") CacheValue = TypeVar("CacheValue") @@ -103,8 +103,7 @@ def set(self, key: CacheKey, value: CacheValue) -> None: key: Key. value: Value. """ - link = self._cache.get(key) - if link is None: + if self._cache.get(key) is None: head = self._head if not head: # First link references itself @@ -128,13 +127,15 @@ def set(self, key: CacheKey, value: CacheValue) -> None: __setitem__ = set - @overload - def get(self, key: CacheKey) -> CacheValue | None: ... + if TYPE_CHECKING: - @overload - def get( - self, key: CacheKey, default: DefaultValue - ) -> CacheValue | DefaultValue: ... + @overload + def get(self, key: CacheKey) -> CacheValue | None: ... + + @overload + def get( + self, key: CacheKey, default: DefaultValue + ) -> CacheValue | DefaultValue: ... def get( self, key: CacheKey, default: DefaultValue | None = None @@ -148,8 +149,8 @@ def get( Returns: Either the value or a default. """ - link = self._cache.get(key) - if link is None: + + if (link := self._cache.get(key)) is None: self.misses += 1 return default if link is not self._head: @@ -166,7 +167,7 @@ def get( def __getitem__(self, key: CacheKey) -> CacheValue: link = self._cache.get(key) - if link is None: + if (link := self._cache.get(key)) is None: self.misses += 1 raise KeyError(key) if link is not self._head: @@ -268,13 +269,15 @@ def set(self, key: CacheKey, value: CacheValue) -> None: __setitem__ = set - @overload - def get(self, key: CacheKey) -> CacheValue | None: ... + if TYPE_CHECKING: - @overload - def get( - self, key: CacheKey, default: DefaultValue - ) -> CacheValue | DefaultValue: ... + @overload + def get(self, key: CacheKey) -> CacheValue | None: ... + + @overload + def get( + self, key: CacheKey, default: DefaultValue + ) -> CacheValue | DefaultValue: ... def get( self, key: CacheKey, default: DefaultValue | None = None diff --git a/src/textual/color.py b/src/textual/color.py index ab5996dda3..31ce703844 100644 --- a/src/textual/color.py +++ b/src/textual/color.py @@ -42,12 +42,11 @@ from rich.color_triplet import ColorTriplet from typing_extensions import Final -from textual.css.scalar import percentage_string_to_float -from textual.css.tokenize import CLOSE_BRACE, COMMA, DECIMAL, OPEN_BRACE, PERCENT -from textual.suggestions import get_suggestion - from ._color_constants import COLOR_NAME_TO_RGB +from .css.scalar import percentage_string_to_float +from .css.tokenize import CLOSE_BRACE, COMMA, DECIMAL, OPEN_BRACE, PERCENT from .geometry import clamp +from .suggestions import get_suggestion _TRUECOLOR = ColorType.TRUECOLOR @@ -224,6 +223,7 @@ def clamped(self) -> Color: return color @property + @lru_cache(1024) def rich_color(self) -> RichColor: """This color encoded in Rich's Color class. @@ -551,25 +551,74 @@ def get_contrast_text(self, alpha: float = 0.95) -> Color: class Gradient: """Defines a color gradient.""" - def __init__(self, *stops: tuple[float, Color]) -> None: + def __init__(self, *stops: tuple[float, Color | str], quality: int = 200) -> None: """Create a color gradient that blends colors to form a spectrum. - A gradient is defined by a sequence of "stops" consisting of a float and a color. - The stop indicate the color at that point on a spectrum between 0 and 1. + A gradient is defined by a sequence of "stops" consisting of a tuple containing a float and a color. + The stop indicates the color at that point on a spectrum between 0 and 1. + Colors may be given as a [Color][textual.color.Color] instance, or a string that + can be parsed into a Color (with [Color.parse][textual.color.Color.parse]). + + The quality of the argument defines the number of _steps_ in the gradient. + 200 was chosen so that there was no obvious banding in [LinearGradient][textual.renderables.gradient.LinearGradient]. + Higher values are unlikely to yield any benefit, but lower values may result in quicker rendering. Args: - stops: A colors stop. + stops: Color stops. + quality: The number of steps in the gradient. Raises: ValueError: If any stops are missing (must be at least a stop for 0 and 1). """ - self._stops = sorted(stops) + parse = Color.parse + self._stops = sorted( + [ + ( + (position, parse(color)) + if isinstance(color, str) + else (position, color) + ) + for position, color in stops + ] + ) if len(stops) < 2: raise ValueError("At least 2 stops required.") if self._stops[0][0] != 0.0: raise ValueError("First stop must be 0.") if self._stops[-1][0] != 1.0: raise ValueError("Last stop must be 1.") + self._quality = quality + self._colors: list[Color] | None = None + self._rich_colors: list[RichColor] | None = None + + @property + def colors(self) -> list[Color]: + """A list of colors in the gradient.""" + position = 0 + quality = self._quality + + if self._colors is None: + colors: list[Color] = [] + add_color = colors.append + (stop1, color1), (stop2, color2) = self._stops[0:2] + for step_position in range(quality): + step = step_position / (quality - 1) + while step > stop2: + position += 1 + (stop1, color1), (stop2, color2) = self._stops[ + position : position + 2 + ] + add_color(color1.blend(color2, (step - stop1) / (stop2 - stop1))) + self._colors = colors + assert len(self._colors) == self._quality + return self._colors + + @property + def rich_colors(self) -> list[RichColor]: + """A list of colors in the gradient (for the Rich library).""" + if self._rich_colors is None: + self._rich_colors = [color.rich_color for color in self.colors] + return self._rich_colors def get_color(self, position: float) -> Color: """Get a color from the gradient at a position between 0 and 1. @@ -580,17 +629,26 @@ def get_color(self, position: float) -> Color: position: A number between 0 and 1, where 0 is the first stop, and 1 is the last. Returns: - A color. + A Textual color. """ - # TODO: consider caching - position = clamp(position, 0.0, 1.0) - for (stop1, color1), (stop2, color2) in zip(self._stops, self._stops[1:]): - if stop2 >= position >= stop1: - return color1.blend( - color2, - (position - stop1) / (stop2 - stop1), - ) - raise AssertionError("Can't get here if `_stops` is valid") + quality = self._quality - 1 + color_index = int(clamp(position * quality, 0, quality)) + return self.colors[color_index] + + def get_rich_color(self, position: float) -> RichColor: + """Get a (Rich) color from the gradient at a position between 0 and 1. + + Positions that are between stops will return a blended color. + + Args: + position: A number between 0 and 1, where 0 is the first stop, and 1 is the last. + + Returns: + A (Rich) color. + """ + quality = self._quality - 1 + color_index = int(clamp(position * quality, 0, quality)) + return self.rich_colors[color_index] # Color constants @@ -598,6 +656,8 @@ def get_color(self, position: float) -> Color: """A constant for pure white.""" BLACK: Final = Color(0, 0, 0) """A constant for pure black.""" +TRANSPARENT: Final = Color.parse("transparent") +"""A constant for transparent.""" def rgb_to_lab(rgb: Color) -> Lab: diff --git a/src/textual/command.py b/src/textual/command.py index 7cf5faf0ea..2cd523767a 100644 --- a/src/textual/command.py +++ b/src/textual/command.py @@ -34,8 +34,9 @@ from .containers import Horizontal, Vertical from .events import Click, Mount from .fuzzy import Matcher +from .message import Message from .reactive import var -from .screen import Screen, _SystemModalScreen +from .screen import Screen, SystemModalScreen from .timer import Timer from .types import CallbackType, IgnoreReturnCallbackType from .widget import Widget @@ -353,7 +354,9 @@ class CommandList(OptionList, can_focus=False): border-right: none; height: auto; max-height: 70vh; - background: $panel; + background: transparent; + padding: 0; + text-style: bold; } CommandList:focus { @@ -369,11 +372,11 @@ class CommandList(OptionList, can_focus=False): } CommandList > .option-list--option-highlighted { - background: $accent; + background: $primary; } CommandList > .option-list--option { - padding-left: 1; + padding-left: 2; } """ @@ -409,13 +412,13 @@ class CommandInput(Input): CommandInput, CommandInput:focus { border: blank; width: 1fr; - background: $panel; + background: transparent; padding-left: 0; } """ -class CommandPalette(_SystemModalScreen[CallbackType]): +class CommandPalette(SystemModalScreen[CallbackType]): """The Textual command palette.""" COMPONENT_CLASSES: ClassVar[set[str]] = { @@ -430,18 +433,19 @@ class CommandPalette(_SystemModalScreen[CallbackType]): """ DEFAULT_CSS = """ + + CommandPalette:inline { /* If the command palette is invoked in inline mode, we may need additional lines. */ min-height: 20; } CommandPalette { - background: $background 30%; - align-horizontal: center; + background: $background 60%; + align-horizontal: center; } - CommandPalette > .command-palette--help-text { - background: transparent; - color: $text-muted; + CommandPalette > .command-palette--help-text { + text-style: dim not bold; } CommandPalette:dark > .command-palette--highlight { @@ -454,17 +458,16 @@ class CommandPalette(_SystemModalScreen[CallbackType]): } CommandPalette > Vertical { - margin-top: 3; - width: 90%; + margin-top: 3; height: 100%; visibility: hidden; + background: $primary 20%; } CommandPalette #--input { height: auto; visibility: visible; border: hkey $primary; - background: $panel; } CommandPalette #--input.--list-visible { @@ -489,7 +492,6 @@ class CommandPalette(_SystemModalScreen[CallbackType]): CommandPalette LoadingIndicator { height: auto; visibility: hidden; - background: $panel; border-bottom: hkey $primary; } @@ -541,6 +543,24 @@ class CommandPalette(_SystemModalScreen[CallbackType]): _PALETTE_ID: Final[str] = "--command-palette" """The internal ID for the command palette.""" + @dataclass + class OptionHighlighted(Message): + """Posted to App when an option is highlighted in the command palette.""" + + highlighted_event: OptionList.OptionHighlighted + """The option highlighted event from the OptionList within the command palette.""" + + @dataclass + class Opened(Message): + """Posted to App when the command palette is opened.""" + + @dataclass + class Closed(Message): + """Posted to App when the command palette is closed.""" + + option_selected: bool + """True if an option was selected, False if the palette was closed without selecting an option.""" + def __init__(self) -> None: """Initialise the command palette.""" super().__init__(id=self._PALETTE_ID) @@ -605,14 +625,14 @@ def compose(self) -> ComposeResult: with Vertical(): with Horizontal(id="--input"): yield SearchIcon() - yield CommandInput(placeholder="Command Palette Search...") + yield CommandInput(placeholder="Search for commands…") if not self.run_on_select: yield Button("\u25b6") with Vertical(id="--results"): yield CommandList() yield LoadingIndicator() - def _on_click(self, event: Click) -> None: + def _on_click(self, event: Click) -> None: # type: ignore[override] """Handle the click event. Args: @@ -623,10 +643,13 @@ def _on_click(self, event: Click) -> None: """ if self.get_widget_at(event.screen_x, event.screen_y)[0] is self: self._cancel_gather_commands() + self.app.post_message(CommandPalette.Closed(option_selected=False)) self.dismiss() def _on_mount(self, _: Mount) -> None: """Configure the command palette once the DOM is ready.""" + + self.app.post_message(CommandPalette.Opened()) self._calling_screen = self.app.screen_stack[-2] match_style = self.get_component_rich_style( @@ -642,7 +665,7 @@ def _on_mount(self, _: Mount) -> None: provider._post_init() self._gather_commands("") - async def _on_unmount(self) -> None: + async def _on_unmount(self) -> None: # type: ignore[override] """Shutdown providers when command palette is closed.""" if self._providers: await wait( @@ -696,7 +719,7 @@ def _show_no_matches() -> None: command_list = self.query_one(CommandList) command_list.add_option( Option( - Align.center(Text("No matches found")), + Align.center(Text("No matches found", style="not bold")), disabled=True, id=self._NO_MATCHES, ) @@ -831,41 +854,6 @@ async def _search_for( for search in searches: search.cancel() - @staticmethod - def _sans_background(style: Style) -> Style: - """Returns the given style minus the background color. - - Args: - style: The style to remove the color from. - - Returns: - The given style, minus its background. - """ - # Here we're pulling out all of the styles *minus* the background. - # This should probably turn into a utility method on Style - # eventually. The reason for this is we want the developer to be - # able to style the help text with a component class, but we want - # the background to always be the background at any given moment in - # the context of an OptionList. At the moment this act of copying - # sans bgcolor seems to be the only way to achieve this. - return Style( - blink2=style.blink2, - blink=style.blink, - bold=style.bold, - color=style.color, - conceal=style.conceal, - dim=style.dim, - encircle=style.encircle, - frame=style.frame, - italic=style.italic, - link=style.link, - overline=style.overline, - reverse=style.reverse, - strike=style.strike, - underline2=style.underline2, - underline=style.underline, - ) - def _refresh_command_list( self, command_list: CommandList, commands: list[Command], clear_current: bool ) -> None: @@ -877,7 +865,7 @@ def _refresh_command_list( clear_current: Should the current content of the list be cleared first? """ # For the moment, this is a fairly naive approach to populating the - # command list with a sorted list of commands. Every time we add a + # command list with a list of commands. Every time we add a # new one we're nuking the list of options and populating them # again. If this turns out to not be a great approach, we may try # and get a lot smarter with this (ideally OptionList will grow a @@ -888,7 +876,7 @@ def _refresh_command_list( if command_list.highlighted is not None and not clear_current else None ) - command_list.clear_options().add_options(sorted(commands, reverse=True)) + command_list.clear_options().add_options(commands) if highlighted is not None and highlighted.id: command_list.highlighted = command_list.get_option_index(highlighted.id) self._list_visible = bool(command_list.option_count) @@ -912,8 +900,8 @@ async def _gather_commands(self, search_value: str) -> None: # We'll potentially use the help text style a lot so let's grab it # the once for use in the loop further down. - help_style = self._sans_background( - self.get_component_rich_style("command-palette--help-text") + help_style = self.get_component_rich_style( + "command-palette--help-text", partial=True ) # The list to hold on to the commands we've gathered from the @@ -974,7 +962,9 @@ async def _gather_commands(self, search_value: str) -> None: # list of commands that have been gathered so far. prompt = hit.prompt if hit.help: - prompt = Group(prompt, Text(hit.help, style=help_style)) + help_text = Text(hit.help) + help_text.stylize(help_style) + prompt = Group(prompt, help_text) gathered_commands.append(Command(prompt, hit, id=str(command_id))) # Before we go making any changes to the UI, we do a quick @@ -1085,12 +1075,14 @@ def _select_or_command( # ...we should return it to the parent screen and let it # decide what to do with it (hopefully it'll run it). self._cancel_gather_commands() + self.app.post_message(CommandPalette.Closed(option_selected=True)) self.dismiss(self._selected_command.command) @on(OptionList.OptionHighlighted) def _stop_event_leak(self, event: OptionList.OptionHighlighted) -> None: """Stop any unused events so they don't leak to the application.""" event.stop() + self.app.post_message(CommandPalette.OptionHighlighted(highlighted_event=event)) def _action_escape(self) -> None: """Handle a request to escape out of the command palette.""" @@ -1098,6 +1090,7 @@ def _action_escape(self) -> None: self._list_visible = False else: self._cancel_gather_commands() + self.app.post_message(CommandPalette.Closed(option_selected=False)) self.dismiss() def _action_command_list(self, action: str) -> None: diff --git a/src/textual/containers.py b/src/textual/containers.py index c8fe96194e..91b64b6a1a 100644 --- a/src/textual/containers.py +++ b/src/textual/containers.py @@ -47,6 +47,8 @@ class ScrollableContainer(Widget, can_focus=True, inherit_bindings=False): Binding("end", "scroll_end", "Scroll End", show=False), Binding("pageup", "page_up", "Page Up", show=False), Binding("pagedown", "page_down", "Page Down", show=False), + Binding("ctrl+pageup", "page_left", "Page Left", show=False), + Binding("ctrl+pagedown", "page_right", "Page Right", show=False), ] """Keyboard bindings for scrollable containers. @@ -60,6 +62,8 @@ class ScrollableContainer(Widget, can_focus=True, inherit_bindings=False): | end | Scroll to the end position, if scrolling is available. | | pageup | Scroll up one page, if vertical scrolling is available. | | pagedown | Scroll down one page, if vertical scrolling is available. | + | ctrl+pageup | Scroll left one page, if horizontal scrolling is available. | + | ctrl+pagedown | Scroll right one page, if horizontal scrolling is available. | """ @@ -102,7 +106,7 @@ class Horizontal(Widget, inherit_bindings=False): class HorizontalScroll(ScrollableContainer): - """A container with horizontal layout and an automatic scrollbar on the Y axis.""" + """A container with horizontal layout and an automatic scrollbar on the X axis.""" DEFAULT_CSS = """ HorizontalScroll { diff --git a/src/textual/css/_help_text.py b/src/textual/css/_help_text.py index fd0788359f..f2ca1564c8 100644 --- a/src/textual/css/_help_text.py +++ b/src/textual/css/_help_text.py @@ -308,7 +308,7 @@ def color_property_help_text( error: Exception | None = None, ) -> HelpText: """Help text to show when the user supplies an invalid value for a color - property. For example, an unparseable color string. + property. For example, an unparsable color string. Args: property_name: The name of the property. diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index efcc309930..361cdfae40 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -17,7 +17,8 @@ from typing_extensions import TypeAlias from .._border import normalize_border_value -from ..color import Color, ColorParseError +from .._cells import cell_len +from ..color import TRANSPARENT, Color, ColorParseError from ..geometry import NULL_SPACING, Spacing, SpacingDimensions, clamp from ._error_tools import friendly_list from ._help_text import ( @@ -31,7 +32,7 @@ string_enum_help_text, style_flags_property_help_text, ) -from .constants import VALID_STYLE_FLAGS +from .constants import HATCHES, VALID_STYLE_FLAGS from .errors import StyleTypeError, StyleValueError from .scalar import ( NULL_SCALAR, @@ -48,6 +49,7 @@ if TYPE_CHECKING: from ..canvas import CanvasLineType from .._layout import Layout + from ..widget import Widget from .styles import StylesBase from .types import AlignHorizontal, AlignVertical, DockEdge, EdgeType @@ -900,7 +902,7 @@ def __get__( """ return cast(Color, obj.get_rule(self.name, self._default_color)) - def __set__(self, obj: StylesBase, color: Color | str | None): + def __set__(self, obj: StylesBase, color: Color | str | None) -> None: """Set the Color. Args: @@ -946,6 +948,27 @@ def __set__(self, obj: StylesBase, color: Color | str | None): raise StyleValueError(f"Invalid color value {color}") +class ScrollbarColorProperty(ColorProperty): + """A descriptor to set scrollbar color(s).""" + + def __set__(self, obj: StylesBase, color: Color | str | None) -> None: + super().__set__(obj, color) + + if obj.node is None: + return + + from ..widget import Widget + + if isinstance(obj.node, Widget): + widget = obj.node + + if widget.show_horizontal_scrollbar: + widget.horizontal_scrollbar.refresh() + + if widget.show_vertical_scrollbar: + widget.vertical_scrollbar.refresh() + + class StyleFlagsProperty: """Descriptor for getting and set style flag properties (e.g. ``bold italic underline``).""" @@ -966,7 +989,7 @@ def __get__( """ return cast(Style, obj.get_rule(self.name, Style.null())) - def __set__(self, obj: StylesBase, style_flags: Style | str | None): + def __set__(self, obj: StylesBase, style_flags: Style | str | None) -> None: """Set the style using a style flag string. Args: @@ -1117,3 +1140,30 @@ def __set__( horizontal, vertical = value setattr(obj, self.horizontal, horizontal) setattr(obj, self.vertical, vertical) + + +class HatchProperty: + """Property to expose hatch style.""" + + def __get__(self, obj: StylesBase, type: type[StylesBase]) -> tuple[str, Color]: + return cast("tuple[str, Color]", obj.get_rule("hatch", (" ", TRANSPARENT))) + + def __set__(self, obj: StylesBase, value: tuple[str, Color | str] | None) -> None: + _rich_traceback_omit = True + if value is None: + obj.clear_rule("hatch") + return + character, color = value + if len(character) != 1: + try: + character = HATCHES[character] + except KeyError: + raise ValueError( + f"Expected a character or hatch value here; found {character!r}" + ) from None + if cell_len(character) != 1: + raise ValueError("Hatch character must have a cell length of 1") + if isinstance(color, str): + color = Color.parse(color) + hatch = (character, color) + obj.set_rule("hatch", hatch) diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index 8b66fec39b..622312a861 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -6,9 +6,10 @@ import rich.repr from .._border import BorderValue, normalize_border_value +from .._cells import cell_len from .._duration import _duration_as_seconds from .._easing import EASING -from ..color import Color, ColorParseError +from ..color import TRANSPARENT, Color, ColorParseError from ..geometry import Spacing, SpacingDimensions, clamp from ..suggestions import get_suggestion from ._error_tools import friendly_list @@ -36,6 +37,7 @@ text_align_help_text, ) from .constants import ( + HATCHES, VALID_ALIGN_HORIZONTAL, VALID_ALIGN_VERTICAL, VALID_BORDER, @@ -43,6 +45,7 @@ VALID_CONSTRAIN, VALID_DISPLAY, VALID_EDGE, + VALID_HATCH, VALID_KEYLINE, VALID_OVERFLOW, VALID_OVERLAY, @@ -1054,6 +1057,75 @@ def process_constrain(self, name: str, tokens: list[Token]) -> None: else: self.styles._rules[name] = value # type: ignore + def process_hatch(self, name: str, tokens: list[Token]) -> None: + if not tokens: + return + character: str | None = None + color = TRANSPARENT + opacity = 1.0 + + if len(tokens) not in (2, 3): + self.error(name, tokens[0], "2 or 3 values expected here") + + character_token, color_token, *opacity_tokens = tokens + + if character_token.name == "token": + if character_token.value not in VALID_HATCH: + self.error( + name, + tokens[0], + string_enum_help_text(name, VALID_HATCH, context="css"), + ) + character = HATCHES[character_token.value] + elif character_token.name == "string": + character = character_token.value[1:-1] + if len(character) != 1: + self.error( + name, + character_token, + f"Hatch type requires a string of length 1; got {character_token.value}", + ) + if cell_len(character) != 1: + self.error( + name, + character_token, + f"Hatch type requires a string with a *cell length* of 1; got {character_token.value}", + ) + + if color_token.name in ("color", "token"): + try: + color = Color.parse(color_token.value) + except Exception as error: + self.error( + name, + color_token, + color_property_help_text(name, context="css", error=error), + ) + else: + self.error( + name, color_token, f"Expected a color; found {color_token.value!r}" + ) + + if opacity_tokens: + opacity_token = opacity_tokens[0] + if opacity_token.name == "scalar": + opacity_scalar = opacity = Scalar.parse(opacity_token.value) + if opacity_scalar.unit != Unit.PERCENT: + self.error( + name, + opacity_token, + "hatch alpha must be given as a percentage.", + ) + opacity = clamp(opacity_scalar.value / 100.0, 0, 1.0) + else: + self.error( + name, + opacity_token, + f"expected a percentage here; found {opacity_token.value!r}", + ) + + self.styles._rules[name] = (character or " ", color.multiply_alpha(opacity)) + def _get_suggested_property_name_for_rule(self, rule_name: str) -> str | None: """ Returns a valid CSS property "Python" name, or None if no close matches could be found. diff --git a/src/textual/css/constants.py b/src/textual/css/constants.py index 27c662dde9..07e2523989 100644 --- a/src/textual/css/constants.py +++ b/src/textual/css/constants.py @@ -74,3 +74,11 @@ VALID_OVERLAY: Final = {"none", "screen"} VALID_CONSTRAIN: Final = {"x", "y", "both", "inflect", "none"} VALID_KEYLINE: Final = {"none", "thin", "heavy", "double"} +VALID_HATCH: Final = {"left", "right", "cross", "vertical", "horizontal"} +HATCHES: Final = { + "left": "╲", + "right": "╱", + "cross": "╳", + "horizontal": "─", + "vertical": "│", +} diff --git a/src/textual/css/query.py b/src/textual/css/query.py index d3cf48b512..6dec06f542 100644 --- a/src/textual/css/query.py +++ b/src/textual/css/query.py @@ -55,12 +55,7 @@ class WrongType(QueryError): @rich.repr.auto(angular=True) class DOMQuery(Generic[QueryType]): - __slots__ = [ - "_node", - "_nodes", - "_filters", - "_excludes", - ] + __slots__ = ["_node", "_nodes", "_filters", "_excludes", "_deep"] def __init__( self, @@ -68,6 +63,7 @@ def __init__( *, filter: str | None = None, exclude: str | None = None, + deep: bool = True, parent: DOMQuery | None = None, ) -> None: """Initialize a query object. @@ -80,6 +76,7 @@ def __init__( node: A DOM node. filter: Query to filter children in the node. exclude: Query to exclude children in the node. + deep: Query should be deep, i.e. recursive. parent: The parent query, if this is the result of filtering another query. Raises: @@ -94,6 +91,7 @@ def __init__( self._excludes: list[tuple[SelectorSet, ...]] = ( parent._excludes.copy() if parent else [] ) + self._deep = deep if filter is not None: try: self._filters.append(parse_selectors(filter)) @@ -118,9 +116,12 @@ def nodes(self) -> list[QueryType]: from ..widget import Widget if self._nodes is None: + initial_nodes = list( + self._node.walk_children(Widget) if self._deep else self._node._nodes + ) nodes = [ node - for node in self._node.walk_children(Widget) + for node in initial_nodes if all(match(selector_set, node) for selector_set in self._filters) ] nodes = [ @@ -144,11 +145,13 @@ def __iter__(self) -> Iterator[QueryType]: def __reversed__(self) -> Iterator[QueryType]: return reversed(self.nodes) - @overload - def __getitem__(self, index: int) -> QueryType: ... + if TYPE_CHECKING: + + @overload + def __getitem__(self, index: int) -> QueryType: ... - @overload - def __getitem__(self, index: slice) -> list[QueryType]: ... + @overload + def __getitem__(self, index: slice) -> list[QueryType]: ... def __getitem__(self, index: int | slice) -> QueryType | list[QueryType]: return self.nodes[index] @@ -156,14 +159,20 @@ def __getitem__(self, index: int | slice) -> QueryType | list[QueryType]: def __rich_repr__(self) -> rich.repr.Result: try: if self._filters: - yield "query", " AND ".join( - ",".join(selector.css for selector in selectors) - for selectors in self._filters + yield ( + "query", + " AND ".join( + ",".join(selector.css for selector in selectors) + for selectors in self._filters + ), ) if self._excludes: - yield "exclude", " OR ".join( - ",".join(selector.css for selector in selectors) - for selectors in self._excludes + yield ( + "exclude", + " OR ".join( + ",".join(selector.css for selector in selectors) + for selectors in self._excludes + ), ) except AttributeError: pass @@ -178,7 +187,12 @@ def filter(self, selector: str) -> DOMQuery[QueryType]: New DOM Query. """ - return DOMQuery(self.node, filter=selector, parent=self) + return DOMQuery( + self.node, + filter=selector, + deep=self._deep, + parent=self, + ) def exclude(self, selector: str) -> DOMQuery[QueryType]: """Exclude nodes that match a given selector. @@ -189,13 +203,20 @@ def exclude(self, selector: str) -> DOMQuery[QueryType]: Returns: New DOM query. """ - return DOMQuery(self.node, exclude=selector, parent=self) + return DOMQuery( + self.node, + exclude=selector, + deep=self._deep, + parent=self, + ) - @overload - def first(self) -> QueryType: ... + if TYPE_CHECKING: - @overload - def first(self, expect_type: type[ExpectType]) -> ExpectType: ... + @overload + def first(self) -> QueryType: ... + + @overload + def first(self, expect_type: type[ExpectType]) -> ExpectType: ... def first( self, expect_type: type[ExpectType] | None = None @@ -225,11 +246,13 @@ def first( else: raise NoMatches(f"No nodes match {self!r} on {self.node!r}") - @overload - def only_one(self) -> QueryType: ... + if TYPE_CHECKING: + + @overload + def only_one(self) -> QueryType: ... - @overload - def only_one(self, expect_type: type[ExpectType]) -> ExpectType: ... + @overload + def only_one(self, expect_type: type[ExpectType]) -> ExpectType: ... def only_one( self, expect_type: type[ExpectType] | None = None @@ -270,11 +293,13 @@ def only_one( pass return the_one - @overload - def last(self) -> QueryType: ... + if TYPE_CHECKING: - @overload - def last(self, expect_type: type[ExpectType]) -> ExpectType: ... + @overload + def last(self) -> QueryType: ... + + @overload + def last(self, expect_type: type[ExpectType]) -> ExpectType: ... def last( self, expect_type: type[ExpectType] | None = None @@ -301,11 +326,13 @@ def last( ) return last - @overload - def results(self) -> Iterator[QueryType]: ... + if TYPE_CHECKING: + + @overload + def results(self) -> Iterator[QueryType]: ... - @overload - def results(self, filter_type: type[ExpectType]) -> Iterator[ExpectType]: ... + @overload + def results(self, filter_type: type[ExpectType]) -> Iterator[ExpectType]: ... def results( self, filter_type: type[ExpectType] | None = None @@ -383,8 +410,7 @@ def remove(self) -> AwaitRemove: An awaitable object that waits for the widgets to be removed. """ app = active_app.get() - await_remove = app._remove_nodes(list(self), self._node) - return await_remove + return app._prune(*self.nodes, parent=self._node) def set_styles( self, css: str | None = None, **update_styles diff --git a/src/textual/css/scalar.py b/src/textual/css/scalar.py index 0ef332c571..849f566732 100644 --- a/src/textual/css/scalar.py +++ b/src/textual/css/scalar.py @@ -12,15 +12,15 @@ class ScalarError(Exception): - pass + """Base class for exceptions raised by the Scalar class.""" class ScalarResolveError(ScalarError): - pass + """Raised for errors resolving scalars (unlikely to occur in practice).""" class ScalarParseError(ScalarError): - pass + """Raised when a scalar couldn't be parsed from a string.""" @unique diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 6566d61847..a157e53801 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -22,6 +22,7 @@ ColorProperty, DockProperty, FractionalProperty, + HatchProperty, IntegerProperty, KeylineProperty, LayoutProperty, @@ -31,6 +32,7 @@ OverflowProperty, ScalarListProperty, ScalarProperty, + ScrollbarColorProperty, SpacingProperty, StringEnumProperty, StyleFlagsProperty, @@ -185,6 +187,8 @@ class RulesMap(TypedDict, total=False): border_subtitle_background: Color border_subtitle_style: Style + hatch: tuple[str, Color] + overlay: Overlay constrain: Constrain @@ -288,15 +292,15 @@ class StylesBase(ABC): transitions = TransitionsProperty() tint = ColorProperty("transparent") - scrollbar_color = ColorProperty("ansi_bright_magenta") - scrollbar_color_hover = ColorProperty("ansi_yellow") - scrollbar_color_active = ColorProperty("ansi_bright_yellow") + scrollbar_color = ScrollbarColorProperty("ansi_bright_magenta") + scrollbar_color_hover = ScrollbarColorProperty("ansi_yellow") + scrollbar_color_active = ScrollbarColorProperty("ansi_bright_yellow") - scrollbar_corner_color = ColorProperty("#666666") + scrollbar_corner_color = ScrollbarColorProperty("#666666") - scrollbar_background = ColorProperty("#555555") - scrollbar_background_hover = ColorProperty("#444444") - scrollbar_background_active = ColorProperty("black") + scrollbar_background = ScrollbarColorProperty("#555555") + scrollbar_background_hover = ScrollbarColorProperty("#444444") + scrollbar_background_active = ScrollbarColorProperty("black") scrollbar_gutter = StringEnumProperty( VALID_SCROLLBAR_GUTTER, "auto", layout=True, refresh_children=True @@ -354,6 +358,8 @@ class StylesBase(ABC): border_subtitle_background = ColorProperty(Color(0, 0, 0, 0)) border_subtitle_style = StyleFlagsProperty() + hatch = HatchProperty() + overlay = StringEnumProperty( VALID_OVERLAY, "none", layout=True, refresh_parent=True ) @@ -440,6 +446,18 @@ def is_relative_height(self) -> bool: height = self.height return height is not None and height.unit in (Unit.FRACTION, Unit.PERCENT) + @property + def is_auto_width(self) -> bool: + """Does the node have automatic width?""" + width = self.width + return width is not None and width.unit == Unit.AUTO + + @property + def is_auto_height(self) -> bool: + """Does the node have automatic height?""" + height = self.height + return height is not None and height.unit == Unit.AUTO + @abstractmethod def has_rule(self, rule: str) -> bool: """Check if a rule is set on this Styles object. @@ -642,9 +660,15 @@ def partial_rich_style(self) -> Style: Rich Style object. """ style = Style( - color=(self.color.rich_color if self.has_rule("color") else None), + color=( + self.color.rich_color + if self.has_rule("color") and self.color.a > 0 + else None + ), bgcolor=( - self.background.rich_color if self.has_rule("background") else None + self.background.rich_color + if self.has_rule("background") and self.background.a > 0 + else None ), ) style += self.text_style @@ -1063,6 +1087,9 @@ def append_declaration(name: str, value: str) -> None: keyline_type, keyline_color = self.keyline if keyline_type != "none": append_declaration("keyline", f"{keyline_type}, {keyline_color.css}") + if "hatch" in rules: + hatch_character, hatch_color = self.hatch + append_declaration("hatch", f'"{hatch_character}" {hatch_color.css}') lines.sort() return lines @@ -1176,6 +1203,7 @@ def animate( ) def __rich_repr__(self) -> rich.repr.Result: + yield self.node for rule_name in RULE_NAMES: if self.has_rule(rule_name): yield rule_name, getattr(self, rule_name) diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 0728f7584d..19d2b6a6ce 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -138,7 +138,7 @@ class CssSource(NamedTuple): @rich.repr.auto(angular=True) class Stylesheet: - """A Stylsheet generated from Textual CSS.""" + """A Stylesheet generated from Textual CSS.""" def __init__(self, *, variables: dict[str, str] | None = None) -> None: self._rules: list[RuleSet] = [] diff --git a/src/textual/document/_document.py b/src/textual/document/_document.py index ca850285b5..386a6a7713 100644 --- a/src/textual/document/_document.py +++ b/src/textual/document/_document.py @@ -182,11 +182,13 @@ def start(self) -> Location: def end(self) -> Location: """Returns the location of the end of the document.""" - @overload - def __getitem__(self, line_index: int) -> str: ... + if TYPE_CHECKING: - @overload - def __getitem__(self, line_index: slice) -> list[str]: ... + @overload + def __getitem__(self, line_index: int) -> str: ... + + @overload + def __getitem__(self, line_index: slice) -> list[str]: ... @abstractmethod def __getitem__(self, line_index: int | slice) -> str | list[str]: @@ -351,7 +353,7 @@ def start(self) -> Location: def end(self) -> Location: """Returns the location of the end of the document.""" last_line = self._lines[-1] - return (self.line_count, len(last_line)) + return (self.line_count - 1, len(last_line)) def get_index_from_location(self, location: Location) -> int: """Given a location, returns the index from the document's text. diff --git a/src/textual/document/_edit.py b/src/textual/document/_edit.py index d0bd5ad83d..dc9c04aeba 100644 --- a/src/textual/document/_edit.py +++ b/src/textual/document/_edit.py @@ -81,8 +81,16 @@ def do(self, text_area: TextArea, record_selection: bool = True) -> EditResult: ) row_offset = new_edit_to_row - edit_bottom_row - target_selection_start_row = selection_start_row + row_offset - target_selection_end_row = selection_end_row + row_offset + target_selection_start_row = ( + selection_start_row + row_offset + if edit_bottom_row <= selection_start_row + else selection_start_row + ) + target_selection_end_row = ( + selection_end_row + row_offset + if edit_bottom_row <= selection_end_row + else selection_end_row + ) if self.maintain_selection_offset: self._updated_selection = Selection( diff --git a/src/textual/dom.py b/src/textual/dom.py index 1a6d969cf3..bc75cadd86 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -206,6 +206,8 @@ def __init__( dict[str, tuple[MessagePump, Reactive | object]] | None ) = None + self._pruning = False + super().__init__() def set_reactive( @@ -219,7 +221,7 @@ def set_reactive( ``` Args: - name: Name of reactive attribute. + reactive: A reactive property (use the class scope syntax, i.e. `MyClass.my_reactive`). value: New value of reactive. Raises: @@ -235,6 +237,29 @@ def set_reactive( ) setattr(self, f"_reactive_{reactive.name}", value) + def mutate_reactive(self, reactive: Reactive[ReactiveType]) -> None: + """Force an update to a mutable reactive. + + Example: + ```python + self.reactive_name_list.append("Jessica") + self.mutate_reactive(MyClass.reactive_name_list) + ``` + + Textual will automatically detect when a reactive is set to a new value, but it is unable + to detect if a value is _mutated_ (such as updating a list, dict, or attribute of an object). + If you do wish to use a collection or other mutable object in a reactive, then you can call + this method after your reactive is updated. This will ensure that all the reactive _superpowers_ + work. + + Args: + reactive: A reactive property (use the class scope syntax, i.e. `MyClass.my_reactive`). + """ + + internal_name = f"_reactive_{reactive.name}" + value = getattr(self, internal_name) + reactive._set(self, value, always=True) + def data_bind( self, *reactives: Reactive[Any], @@ -484,7 +509,7 @@ def __init_subclass__( ] ) - def get_component_styles(self, name: str) -> RenderStyles: + def get_component_styles(self, *names: str) -> RenderStyles: """Get a "component" styles object (must be defined in COMPONENT_CLASSES classvar). Args: @@ -496,9 +521,16 @@ def get_component_styles(self, name: str) -> RenderStyles: Returns: A Styles object. """ - if name not in self._component_styles: - raise KeyError(f"No {name!r} key in COMPONENT_CLASSES") - styles = self._component_styles[name] + styles = RenderStyles(self, Styles(), Styles()) + for name in names: + if name not in self._component_styles: + raise KeyError(f"No {name!r} key in COMPONENT_CLASSES") + component_styles = self._component_styles[name] + styles.node = component_styles.node + styles.base.merge(component_styles.base) + styles.inline.merge(component_styles.inline) + styles._updates += 1 + return styles def _post_mount(self): @@ -746,8 +778,7 @@ def css_path_nodes(self) -> list[DOMNode]: append = result.append node: DOMNode = self - while isinstance(node._parent, DOMNode): - node = node._parent + while isinstance((node := node._parent), DOMNode): append(node) return result[::-1] @@ -778,7 +809,9 @@ def display(self) -> bool: my_widget.display = False # Hide my_widget ``` """ - return self.styles.display != "none" and not (self._closing or self._closed) + return self.styles.display != "none" and not ( + self._closing or self._closed or self._pruning + ) @display.setter def display(self, new_val: bool | str) -> None: @@ -1078,12 +1111,11 @@ def ancestors_with_self(self) -> list[DOMNode]: Returns: A list of nodes. """ - nodes: list[MessagePump | None] = [] + nodes: list[MessagePump | None] = [self] add_node = nodes.append node: MessagePump | None = self - while node is not None: + while (node := node._parent) is not None: add_node(node) - node = node._parent return cast("list[DOMNode]", nodes) @property @@ -1093,7 +1125,12 @@ def ancestors(self) -> list[DOMNode]: Returns: A list of nodes. """ - return self.ancestors_with_self[1:] + nodes: list[MessagePump | None] = [] + add_node = nodes.append + node: MessagePump | None = self + while (node := node._parent) is not None: + add_node(node) + return cast("list[DOMNode]", nodes) @property def displayed_children(self) -> list[Widget]: @@ -1180,24 +1217,26 @@ def _add_children(self, *nodes: Widget) -> None: WalkType = TypeVar("WalkType", bound="DOMNode") - @overload - def walk_children( - self, - filter_type: type[WalkType], - *, - with_self: bool = False, - method: WalkMethod = "depth", - reverse: bool = False, - ) -> list[WalkType]: ... + if TYPE_CHECKING: - @overload - def walk_children( - self, - *, - with_self: bool = False, - method: WalkMethod = "depth", - reverse: bool = False, - ) -> list[DOMNode]: ... + @overload + def walk_children( + self, + filter_type: type[WalkType], + *, + with_self: bool = False, + method: WalkMethod = "depth", + reverse: bool = False, + ) -> list[WalkType]: ... + + @overload + def walk_children( + self, + *, + with_self: bool = False, + method: WalkMethod = "depth", + reverse: bool = False, + ) -> list[DOMNode]: ... def walk_children( self, @@ -1233,11 +1272,13 @@ def walk_children( nodes.reverse() return cast("list[DOMNode]", nodes) - @overload - def query(self, selector: str | None) -> DOMQuery[Widget]: ... + if TYPE_CHECKING: + + @overload + def query(self, selector: str | None = None) -> DOMQuery[Widget]: ... - @overload - def query(self, selector: type[QueryType]) -> DOMQuery[QueryType]: ... + @overload + def query(self, selector: type[QueryType]) -> DOMQuery[QueryType]: ... def query( self, selector: str | type[QueryType] | None = None @@ -1258,14 +1299,49 @@ def query( else: return DOMQuery[QueryType](self, filter=selector.__name__) - @overload - def query_one(self, selector: str) -> Widget: ... + if TYPE_CHECKING: + + @overload + def query_children(self, selector: str | None = None) -> DOMQuery[Widget]: ... - @overload - def query_one(self, selector: type[QueryType]) -> QueryType: ... + @overload + def query_children(self, selector: type[QueryType]) -> DOMQuery[QueryType]: ... - @overload - def query_one(self, selector: str, expect_type: type[QueryType]) -> QueryType: ... + def query_children( + self, selector: str | type[QueryType] | None = None + ) -> DOMQuery[Widget] | DOMQuery[QueryType]: + """Query the DOM for the immediate children that match a selector or widget type. + + Note that this will not return child widgets more than a single level deep. + If you want to a query to potentially match all children in the widget tree, + see [query][textual.dom.DOMNode.query]. + + Args: + selector: A CSS selector, widget type, or `None` for all nodes. + + Returns: + A query object. + """ + from .css.query import DOMQuery, QueryType + from .widget import Widget + + if isinstance(selector, str) or selector is None: + return DOMQuery[Widget](self, deep=False, filter=selector) + else: + return DOMQuery[QueryType](self, deep=False, filter=selector.__name__) + + if TYPE_CHECKING: + + @overload + def query_one(self, selector: str) -> Widget: ... + + @overload + def query_one(self, selector: type[QueryType]) -> QueryType: ... + + @overload + def query_one( + self, selector: str, expect_type: type[QueryType] + ) -> QueryType: ... def query_one( self, @@ -1453,6 +1529,31 @@ def refresh( ) -> Self: return self + def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None: + """Check whether an action is enabled. + + Implement this method to add logic for [dynamic actions](/guide/actions#dynamic-actions) / bindings. + + Args: + action: The name of an action. + action_parameters: A tuple of any action parameters. + + Returns: + `True` if the action is enabled+visible, + `False` if the action is disabled+hidden, + `None` if the action is disabled+visible (grayed out in footer) + """ + return True + + def refresh_bindings(self) -> None: + """Call to prompt widgets such as the [Footer][textual.widgets.Footer] to update + the display of key bindings. + + See [actions](/guide/actions#dynamic-actions) for how to use this method. + + """ + self.screen.refresh_bindings() + async def action_toggle(self, attribute_name: str) -> None: """Toggle an attribute on the node. diff --git a/src/textual/driver.py b/src/textual/driver.py index f0ad89c98b..2165711a54 100644 --- a/src/textual/driver.py +++ b/src/textual/driver.py @@ -99,7 +99,6 @@ def process_event(self, event: events.Event) -> None: and not event.button and self._last_move_event is not None ): - # Deduplicate self._down_buttons while preserving order. buttons = list(dict.fromkeys(self._down_buttons).keys()) self._down_buttons.clear() diff --git a/src/textual/drivers/_input_reader.py b/src/textual/drivers/_input_reader.py index 84c72d3633..30f652284b 100644 --- a/src/textual/drivers/_input_reader.py +++ b/src/textual/drivers/_input_reader.py @@ -1,8 +1,8 @@ -import platform +import sys __all__ = ["InputReader"] -WINDOWS = platform.system() == "Windows" +WINDOWS = sys.platform == "win32" if WINDOWS: from ._input_reader_windows import InputReader diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py index 061b732bda..41d19911ed 100644 --- a/src/textual/drivers/linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -14,6 +14,8 @@ import rich.repr from .. import events +from .._loop import loop_last +from .._parser import ParseError from .._xterm_parser import XTermParser from ..driver import Driver from ..geometry import Size @@ -46,6 +48,7 @@ def __init__( super().__init__(app, debug=debug, mouse=mouse, size=size) self._file = sys.__stderr__ self.fileno = sys.__stdin__.fileno() + self.input_tty = sys.__stdin__.isatty() self.attrs_before: list[Any] | None = None self.exit_event = Event() self._key_thread: Thread | None = None @@ -115,6 +118,7 @@ def _enable_mouse_support(self) -> None: """Enable reporting of mouse events.""" if not self._mouse: return + write = self.write write("\x1b[?1000h") # SET_VT200_MOUSE write("\x1b[?1003h") # SET_ANY_EVENT_MOUSE @@ -192,7 +196,7 @@ def _stop_again(*_) -> None: loop = asyncio.get_running_loop() - def send_size_event(): + def send_size_event() -> None: terminal_size = self._get_terminal_size() width, height = terminal_size textual_size = Size(width, height) @@ -234,10 +238,14 @@ def on_terminal_resize(signum, stack) -> None: # defaults to ASCII EOT = Ctrl-D = 4.) newattr[tty.CC][termios.VMIN] = 1 - termios.tcsetattr(self.fileno, termios.TCSANOW, newattr) + try: + termios.tcsetattr(self.fileno, termios.TCSANOW, newattr) + except termios.error: + pass self.write("\x1b[?25l") # Hide cursor - self.write("\033[?1004h") # Enable FocusIn/FocusOut. + self.write("\x1b[?1004h") # Enable FocusIn/FocusOut. + self.write("\x1b[>1u") # https://sw.kovidgoyal.net/kitty/keyboard-protocol/ self.flush() self._key_thread = Thread(target=self._run_input_thread) send_size_event() @@ -245,6 +253,9 @@ def on_terminal_resize(signum, stack) -> None: self._request_terminal_sync_mode_support() self._enable_bracketed_paste() + # Appears to fix an issue enabling mouse support in iTerm 3.5.0 + self._enable_mouse_support() + # If we need to ask the app to signal that we've come back from a # SIGTSTP... if self._must_signal_resume: @@ -259,6 +270,8 @@ def _request_terminal_sync_mode_support(self) -> None: # Terminals should ignore this sequence if not supported. # Apple terminal doesn't, and writes a single 'p' in to the terminal, # so we will make a special case for Apple terminal (which doesn't support sync anyway). + if not self.input_tty: + return if os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal": self.write("\033[?2026$p") self.flush() @@ -304,8 +317,11 @@ def disable_input(self) -> None: if self._key_thread is not None: self._key_thread.join() self.exit_event.clear() - termios.tcflush(self.fileno, termios.TCIFLUSH) - except Exception as error: + try: + termios.tcflush(self.fileno, termios.TCIFLUSH) + except termios.error: + pass + except Exception: # TODO: log this pass @@ -320,10 +336,14 @@ def stop_application_mode(self) -> None: except termios.error: pass - # Alt screen false, show cursor - self.write("\x1b[?1049l" + "\x1b[?25h") - self.write("\033[?1004l") # Disable FocusIn/FocusOut. - self.flush() + # Alt screen false, show cursor + self.write("\x1b[?1049l") + self.write("\x1b[?25h") + self.write("\x1b[?1004l") # Disable FocusIn/FocusOut. + self.write( + "\x1b[ None: """Perform cleanup.""" @@ -356,8 +376,8 @@ def run_input_thread(self) -> None: def more_data() -> bool: """Check if there is more data to parse.""" - for _key, events in selector.select(0.01): - if events & EVENT_READ: + for _key, selector_events in selector.select(0.1): + if selector_events & EVENT_READ: return True return False @@ -368,15 +388,36 @@ def more_data() -> bool: decode = utf8_decoder read = os.read + def process_selector_events( + selector_events: list[tuple[selectors.SelectorKey, int]], + final: bool = False, + ) -> None: + """Process events from selector. + + Args: + selector_events: List of selector events. + final: True if this is the last call. + + """ + for last, (_selector_key, mask) in loop_last(selector_events): + if mask & EVENT_READ: + unicode_data = decode(read(fileno, 1024 * 4), final=final and last) + if not unicode_data: + # This can occur if the stdin is piped + break + for event in feed(unicode_data): + self.process_event(event) + try: while not self.exit_event.is_set(): - selector_events = selector.select(0.1) - for _selector_key, mask in selector_events: - if mask & EVENT_READ: - unicode_data = decode( - read(fileno, 1024), final=self.exit_event.is_set() - ) - for event in feed(unicode_data): - self.process_event(event) + process_selector_events(selector.select(0.1)) + + process_selector_events(selector.select(0.1), final=True) + finally: selector.close() + try: + for event in feed(""): + pass + except ParseError: + pass diff --git a/src/textual/drivers/linux_inline_driver.py b/src/textual/drivers/linux_inline_driver.py index 01fc44b0df..66bde69260 100644 --- a/src/textual/drivers/linux_inline_driver.py +++ b/src/textual/drivers/linux_inline_driver.py @@ -108,7 +108,7 @@ def _run_input_thread(self) -> None: """ try: self.run_input_thread() - except BaseException as error: + except BaseException: import rich.traceback self._app.call_later( @@ -127,7 +127,7 @@ def run_input_thread(self) -> None: def more_data() -> bool: """Check if there is more data to parse.""" - for _key, events in selector.select(0.01): + for _key, events in selector.select(0.1): if events & EVENT_READ: return True return False @@ -158,22 +158,32 @@ def more_data() -> bool: def start_application_mode(self) -> None: loop = asyncio.get_running_loop() - def send_size_event() -> None: + def send_size_event(clear: bool = False) -> None: + """Send the resize event, optionally clearing the screen. + + Args: + clear: Clear the screen. + """ terminal_size = self._get_terminal_size() width, height = terminal_size textual_size = Size(width, height) event = events.Resize(textual_size, textual_size) + + async def update_size() -> None: + """Update the screen size.""" + if clear: + self.write("\x1b[2J") + await self._app._post_message(event) + asyncio.run_coroutine_threadsafe( - self._app._post_message(event), + update_size(), loop=loop, ) - def on_terminal_resize(signum, stack) -> None: - self.write("\x1b[2J") - self.flush() - send_size_event() + def on_terminal_resize(signum, stack) -> None: + send_size_event(clear=True) - signal.signal(signal.SIGWINCH, on_terminal_resize) + signal.signal(signal.SIGWINCH, on_terminal_resize) self.write("\x1b[?25l") # Hide cursor self.write("\033[?1004h") # Enable FocusIn/FocusOut. @@ -266,6 +276,10 @@ def disable_input(self) -> None: # TODO: log this pass + def flush(self): + """Flush any buffered data.""" + self._file.flush() + def stop_application_mode(self) -> None: """Stop application mode, restore state.""" self._disable_bracketed_paste() diff --git a/src/textual/drivers/web_driver.py b/src/textual/drivers/web_driver.py index 1a3055eb06..f4536639c7 100644 --- a/src/textual/drivers/web_driver.py +++ b/src/textual/drivers/web_driver.py @@ -14,7 +14,6 @@ import asyncio import json import os -import platform import signal import sys from codecs import getincrementaldecoder @@ -29,7 +28,7 @@ from ._byte_stream import ByteStream from ._input_reader import InputReader -WINDOWS = platform.system() == "Windows" +WINDOWS = sys.platform == "win32" class _ExitInput(Exception): @@ -147,7 +146,7 @@ def do_exit() -> None: self._enable_bracketed_paste() self.flush() self._key_thread.start() - self._app.post_message(events.AppBlur()) + self._app.call_later(self._app.post_message, events.AppBlur()) def disable_input(self) -> None: """Disable further input.""" diff --git a/src/textual/events.py b/src/textual/events.py index ce5ecac4e3..17e3e3e046 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -560,7 +560,8 @@ class Enter(Event, bubble=False, verbose=True): class Leave(Event, bubble=False, verbose=True): - """Sent when the mouse is moved away from a widget. + """Sent when the mouse is moved away from a widget, or if a widget is + programmatically disabled while hovered. - [ ] Bubbles - [X] Verbose diff --git a/src/textual/expand_tabs.py b/src/textual/expand_tabs.py index 9026a6fabe..a019aa11c0 100644 --- a/src/textual/expand_tabs.py +++ b/src/textual/expand_tabs.py @@ -67,7 +67,7 @@ def expand_text_tabs_from_widths(line: Text, tab_widths: list[int]) -> Text: This will return a new Text instance with tab characters expanded into a number of spaces. Each time a tab is encountered, it's expanded into the next integer encountered in the `tab_widths` list. Consequently, the length - of `tab_widths` should match the number of tab chracters in `line`. + of `tab_widths` should match the number of tab characters in `line`. Args: line: The `Text` instance to expand tabs in. diff --git a/src/textual/fuzzy.py b/src/textual/fuzzy.py index 236f83e32c..b09f8e1604 100644 --- a/src/textual/fuzzy.py +++ b/src/textual/fuzzy.py @@ -114,52 +114,3 @@ def highlight(self, candidate: str) -> Text: text.stylize(self._match_style, offset, offset + 1) return text - - -if __name__ == "__main__": - from itertools import permutations - from string import ascii_lowercase - from time import monotonic - - from rich import print - from rich.rule import Rule - - matcher = Matcher("foo.bar") - print(Rule()) - print("Query is:", matcher.query) - print("Rule is:", matcher.query_pattern) - print(Rule()) - candidates = ( - "foo.bar", - " foo.bar ", - "Hello foo.bar world", - "f o o . b a r", - "f o o .bar", - "foo. b a r", - "Lots of text before the foo.bar", - "foo.bar up front and then lots of text afterwards", - "This has an o in it but does not have a match", - "Let's find one obvious match. But blat around always roughly.", - ) - results = sorted( - [ - (matcher.match(candidate), matcher.highlight(candidate)) - for candidate in candidates - ], - key=lambda pair: pair[0], - reverse=True, - ) - for score, highlight in results: - print(f"{score:.15f} '", highlight, "'", sep="") - print(Rule()) - - RUNS = 5 - candidates = [ - "".join(permutation) for permutation in permutations(ascii_lowercase[:10]) - ] - matcher = Matcher(ascii_lowercase[:10]) - start = monotonic() - for _ in range(RUNS): - for candidate in candidates: - _ = matcher.match(candidate) - print(f"{RUNS * len(candidates)} matches in {monotonic() - start:.5f} seconds") diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 2a7879a983..b754b335ea 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -151,6 +151,19 @@ def get_distance_to(self, other: Offset) -> float: distance: float = ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) ** 0.5 return distance + def clamp(self, width: int, height: int) -> Offset: + """Clamp the offset to fit within a rectangle of width x height. + + Args: + width: Width to clamp. + height: Height to clamp. + + Returns: + A new offset. + """ + x, y = self + return Offset(clamp(x, 0, width - 1), clamp(y, 0, height - 1)) + class Size(NamedTuple): """The dimensions (width and height) of a rectangular region. @@ -268,6 +281,17 @@ def __contains__(self, other: Any) -> bool: width, height = self return width > x >= 0 and height > y >= 0 + def clamp_offset(self, offset: Offset) -> Offset: + """Clamp an offset to fit within the width x height. + + Args: + offset: An offset. + + Returns: + A new offset that will fit inside the dimensions defined in the Size. + """ + return offset.clamp(self.width, self.height) + class Region(NamedTuple): """Defines a rectangular region. @@ -982,7 +1006,7 @@ def inflect( class Spacing(NamedTuple): - """The spacing around a renderable, such as padding and border. + """Stores spacing around a widget, such as padding and border. Spacing is defined by four integers for the space at the top, right, bottom, and left of a region. diff --git a/src/textual/keys.py b/src/textual/keys.py index 36da438d24..0006b9a645 100644 --- a/src/textual/keys.py +++ b/src/textual/keys.py @@ -271,7 +271,7 @@ def _get_unicode_name_from_key(key: str) -> str: This function can be seen as a pseudo-inverse of the function `_character_to_key`. """ - return KEY_TO_UNICODE_NAME.get(key, key.upper()) + return KEY_TO_UNICODE_NAME.get(key, key) def _get_key_aliases(key: str) -> list[str]: diff --git a/src/textual/layouts/grid.py b/src/textual/layouts/grid.py index 077b905074..ad9d40ff66 100644 --- a/src/textual/layouts/grid.py +++ b/src/textual/layouts/grid.py @@ -6,7 +6,7 @@ from .._layout import ArrangeResult, Layout, WidgetPlacement from .._resolve import resolve from ..css.scalar import Scalar -from ..geometry import Region, Size +from ..geometry import Region, Size, Spacing if TYPE_CHECKING: from ..widget import Widget @@ -32,9 +32,18 @@ def arrange( viewport = parent.screen.size keyline_style, keyline_color = styles.keyline offset = (0, 0) - if keyline_style != "none": + gutter_spacing: Spacing | None + if keyline_style == "none": + gutter_spacing = None + else: size -= (2, 2) offset = (1, 1) + gutter_spacing = Spacing( + gutter_vertical, + gutter_horizontal, + gutter_vertical, + gutter_horizontal, + ) def cell_coords(column_count: int) -> Iterable[tuple[int, int]]: """Iterate over table coordinates ad infinitum. @@ -206,14 +215,15 @@ def apply_height_limits(widget: Widget, height: int) -> int: if widget.styles.row_span != 1: continue column_width = columns[column][1] + gutter_width, gutter_height = widget.styles.gutter.totals widget_height = apply_height_limits( widget, widget.get_content_height( size, viewport, - column_width, + column_width - gutter_width, ) - + widget.styles.gutter.height, + + gutter_height, ) height = max(height, widget_height) row_scalars[row] = Scalar.from_number(height) @@ -226,6 +236,7 @@ def apply_height_limits(widget: Widget, height: int) -> int: add_widget = widgets.append max_column = len(columns) - 1 max_row = len(rows) - 1 + for widget, (column, row, column_span, row_span) in cell_size_map.items(): x = columns[column][0] if row > max_row: @@ -245,7 +256,17 @@ def apply_height_limits(widget: Widget, height: int) -> int: .clip_size(cell_size) .shrink(margin) ) - add_placement(WidgetPlacement(region + offset, margin, widget)) + add_placement( + WidgetPlacement( + region + offset, + ( + margin + if gutter_spacing is None + else margin.grow_maximum(gutter_spacing) + ), + widget, + ) + ) add_widget(widget) return placements diff --git a/src/textual/layouts/horizontal.py b/src/textual/layouts/horizontal.py index 02672ae879..e4581f5734 100644 --- a/src/textual/layouts/horizontal.py +++ b/src/textual/layouts/horizontal.py @@ -38,7 +38,7 @@ def arrange( ] ) + (box_margins[0].left + box_margins[-1].right), - min( + max( [ margin_top + margin_bottom for margin_top, _, margin_bottom, _ in box_margins diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py index 995b135439..a81518f1d2 100644 --- a/src/textual/layouts/vertical.py +++ b/src/textual/layouts/vertical.py @@ -29,7 +29,7 @@ def arrange( ] if box_margins: resolve_margin = Size( - min( + max( [ margin_right + margin_left for _, margin_right, _, margin_left in box_margins diff --git a/src/textual/logging.py b/src/textual/logging.py index 106a4d318b..2389b9443b 100644 --- a/src/textual/logging.py +++ b/src/textual/logging.py @@ -20,7 +20,7 @@ def __init__(self, stderr: bool = True, stdout: bool = False) -> None: Args: stderr: Log to stderr when there is no active app. - stdout: Log to stdout when there is not active app. + stdout: Log to stdout when there is no active app. """ super().__init__() self._stderr = stderr diff --git a/src/textual/message.py b/src/textual/message.py index 84c840480d..383eb34d33 100644 --- a/src/textual/message.py +++ b/src/textual/message.py @@ -154,4 +154,5 @@ def _bubble_to(self, widget: MessagePump) -> None: Args: widget: Target of bubble. """ + self._no_default_action = False widget.post_message(self) diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index a7514f4625..108c51b4cf 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -40,6 +40,7 @@ from .events import Event from .message import Message from .reactive import Reactive, TooManyComputesError +from .signal import Signal from .timer import Timer, TimerCallback if TYPE_CHECKING: @@ -134,6 +135,12 @@ def __init__(self, parent: MessagePump | None = None) -> None: self._next_callbacks: list[events.Callback] = [] self._thread_id: int = threading.get_ident() self._prevented_messages_on_mount = self._prevent_message_types_stack[-1] + self.message_signal: Signal[Message] = Signal(self, "messages") + """Subscribe to this signal to be notified of all messages sent to this widget. + + This is a fairly low-level mechanism, and shouldn't replace regular message handling. + + """ @property def _prevent_message_types_stack(self) -> list[set[type[Message]]]: @@ -197,6 +204,11 @@ def message_queue_size(self) -> int: """The current size of the message queue.""" return self._message_queue.qsize() + @property + def is_dom_root(self): + """Is this a root node (i.e. the App)?""" + return False + @property def app(self) -> "App[object]": """ @@ -221,12 +233,22 @@ def app(self) -> "App[object]": active_app.set(node) return node + @property + def is_attached(self) -> bool: + """Is this node linked to the app through the DOM?""" + if self.app._exit: + return False + node: MessagePump | None = self + while (node := node._parent) is not None: + if node.is_dom_root: + return True + return False + @property def is_parent_active(self) -> bool: """Is the parent active?""" - return bool( - self._parent and not self._parent._closed and not self._parent._closing - ) + parent = self._parent + return bool(parent is not None and not parent._closed and not parent._closing) @property def is_running(self) -> bool: @@ -242,19 +264,6 @@ def log(self) -> Logger: """ return self.app._logger - @property - def is_attached(self) -> bool: - """Is the node attached to the app via the DOM?""" - from .app import App - - node = self - - while not isinstance(node, App): - if node._parent is None: - return False - node = node._parent - return True - def _attach(self, parent: MessagePump) -> None: """Set the parent, and therefore attach this node to the tree. @@ -276,6 +285,7 @@ def check_message_enabled(self, message: Message) -> bool: Returns: `True` if the message will be sent, or `False` if it is disabled. """ + return type(message) not in self._disabled_messages def disable_messages(self, *messages: type[Message]) -> None: @@ -348,11 +358,12 @@ def set_timer( Returns: A timer object. """ + timer = Timer( self, delay, name=name or f"set_timer#{Timer._timer_count}", - callback=callback, + callback=None if callback is None else partial(self.call_next, callback), repeat=0, pause=pause, ) @@ -443,29 +454,21 @@ def call_next(self, callback: Callback, *args: Any, **kwargs: Any) -> None: def _on_invoke_later(self, message: messages.InvokeLater) -> None: # Forward InvokeLater message to the Screen - self.app.screen._invoke_later( - message.callback, message._sender or active_message_pump.get() - ) - - def _close_messages_no_wait(self) -> None: - """Request the message queue to immediately exit.""" - self._message_queue.put_nowait(messages.CloseMessages()) - - async def _on_close_messages(self, message: messages.CloseMessages) -> None: - await self._close_messages() + if self.app._running: + self.app.screen._invoke_later( + message.callback, message._sender or active_message_pump.get() + ) async def _close_messages(self, wait: bool = True) -> None: """Close message queue, and optionally wait for queue to finish processing.""" if self._closed or self._closing: return self._closing = True - stop_timers = list(self._timers) - for timer in stop_timers: - timer.stop() - self._timers.clear() - await self._message_queue.put(events.Unmount()) + if self._timers: + await Timer._stop_all(self._timers) + self._timers.clear() Reactive._reset_object(self) - await self._message_queue.put(None) + self._message_queue.put_nowait(None) if wait and self._task is not None and asyncio.current_task() != self._task: try: running_widget = active_message_pump.get() @@ -473,7 +476,10 @@ async def _close_messages(self, wait: bool = True) -> None: running_widget = None if running_widget is None or running_widget is not self: - await self._task + try: + await self._task + except CancelledError: + pass def _start_messages(self) -> None: """Start messages task.""" @@ -499,8 +505,15 @@ async def _process_messages(self) -> None: pass finally: self._running = False - for timer in list(self._timers): - timer.stop() + try: + if self._timers: + await Timer._stop_all(self._timers) + self._timers.clear() + finally: + await self._message_loop_exit() + + async def _message_loop_exit(self) -> None: + """Called when the message loop has completed.""" async def _pre_process(self) -> bool: """Procedure to run before processing messages. @@ -533,6 +546,13 @@ async def _pre_process(self) -> bool: def _post_mount(self): """Called after the object has been mounted.""" + def _close_messages_no_wait(self) -> None: + """Request the message queue to immediately exit.""" + self._message_queue.put_nowait(messages.CloseMessages()) + + async def _on_close_messages(self, message: messages.CloseMessages) -> None: + await self._close_messages() + async def _process_messages_loop(self) -> None: """Process messages until the queue is closed.""" _rich_traceback_guard = True @@ -571,6 +591,7 @@ async def _process_messages_loop(self) -> None: self.app._handle_exception(error) break finally: + self.message_signal.publish(message) self._message_queue.task_done() current_time = time() @@ -724,7 +745,7 @@ async def _on_message(self, message: Message) -> None: if message._sender is not None and message._sender == self._parent: # parent is sender, so we stop propagation after parent message.stop() - if self.is_parent_active and not self._parent._closing: + if self.is_parent_active and self.is_attached: message._bubble_to(self._parent) def check_idle(self) -> None: @@ -777,6 +798,8 @@ def post_message(self, message: Message) -> bool: return True async def on_callback(self, event: events.Callback) -> None: + if self.app._closing: + return await invoke(event.callback) # TODO: Does dispatch_key belong on message pump? @@ -828,6 +851,8 @@ def get_key_handler(pump: MessagePump, key: str) -> Callable | None: return handled async def on_timer(self, event: events.Timer) -> None: + if not self.app._running: + return event.prevent_default() event.stop() if event.callback is not None: diff --git a/src/textual/messages.py b/src/textual/messages.py index 91a54b2811..efa1fb673a 100644 --- a/src/textual/messages.py +++ b/src/textual/messages.py @@ -17,6 +17,11 @@ class CloseMessages(Message, verbose=True): """Requests message pump to close.""" +@rich.repr.auto +class Prune(Message, verbose=True, bubble=False): + """Ask the node to prune (remove from DOM).""" + + @rich.repr.auto class ExitApp(Message, verbose=True): """Exit the app.""" diff --git a/src/textual/pilot.py b/src/textual/pilot.py index 8226eb97ee..739a66591e 100644 --- a/src/textual/pilot.py +++ b/src/textual/pilot.py @@ -366,7 +366,11 @@ async def _wait_for_screen(self, timeout: float = 30.0) -> bool: Raises: WaitForScreenTimeout: If the screen and its children didn't finish processing within the timeout. """ - children = [self.app, *self.app.screen.walk_children(with_self=True)] + try: + screen = self.app.screen + except Exception: + return False + children = [self.app, *screen.walk_children(with_self=True)] count = 0 count_zero_event = asyncio.Event() diff --git a/src/textual/reactive.py b/src/textual/reactive.py index 41b240c1b2..2507b14ab5 100644 --- a/src/textual/reactive.py +++ b/src/textual/reactive.py @@ -73,6 +73,7 @@ def invoke_watcher( value: The new value of the attribute. """ _rich_traceback_omit = True + param_count = count_parameters(watch_function) reset_token = active_message_pump.set(watcher_object) try: @@ -105,6 +106,7 @@ class Reactive(Generic[ReactiveType]): always_update: Call watchers even when the new value equals the old value. compute: Run compute methods when attribute is changed. recompose: Compose the widget again when the attribute changes. + bindings: Refresh bindings when the reactive changes. """ _reactives: ClassVar[dict[str, object]] = {} @@ -119,6 +121,7 @@ def __init__( always_update: bool = False, compute: bool = True, recompose: bool = False, + bindings: bool = False, ) -> None: self._default = default self._layout = layout @@ -127,6 +130,7 @@ def __init__( self._always_update = always_update self._run_compute = compute self._recompose = recompose + self._bindings = bindings self._owner: Type[MessageTarget] | None = None def __rich_repr__(self) -> rich.repr.Result: @@ -217,15 +221,19 @@ def __set_name__(self, owner: Type[MessageTarget], name: str) -> None: default = self._default setattr(owner, f"_default_{name}", default) - @overload - def __get__( - self: Reactive[ReactiveType], obj: ReactableType, obj_type: type[ReactableType] - ) -> ReactiveType: ... + if TYPE_CHECKING: - @overload - def __get__( - self: Reactive[ReactiveType], obj: None, obj_type: type[ReactableType] - ) -> Reactive[ReactiveType]: ... + @overload + def __get__( + self: Reactive[ReactiveType], + obj: ReactableType, + obj_type: type[ReactableType], + ) -> ReactiveType: ... + + @overload + def __get__( + self: Reactive[ReactiveType], obj: None, obj_type: type[ReactableType] + ) -> Reactive[ReactiveType]: ... def __get__( self: Reactive[ReactiveType], @@ -254,7 +262,7 @@ def __get__( else: return getattr(obj, internal_name) - def __set__(self, obj: Reactable, value: ReactiveType) -> None: + def _set(self, obj: Reactable, value: ReactiveType, always: bool = False) -> None: _rich_traceback_omit = True if not hasattr(obj, "_id"): @@ -279,7 +287,7 @@ def __set__(self, obj: Reactable, value: ReactiveType) -> None: if callable(public_validate_function): value = public_validate_function(value) # If the value has changed, or this is the first time setting the value - if current_value != value or self._always_update: + if always or self._always_update or current_value != value: # Store the internal value setattr(obj, self.internal_name, value) @@ -289,6 +297,9 @@ def __set__(self, obj: Reactable, value: ReactiveType) -> None: if self._run_compute: self._compute(obj) + if self._bindings: + obj.refresh_bindings() + # Refresh according to descriptor flags if self._layout or self._repaint or self._recompose: obj.refresh( @@ -297,6 +308,11 @@ def __set__(self, obj: Reactable, value: ReactiveType) -> None: recompose=self._recompose, ) + def __set__(self, obj: Reactable, value: ReactiveType) -> None: + _rich_traceback_omit = True + + self._set(obj, value) + @classmethod def _check_watchers(cls, obj: Reactable, name: str, old_value: Any) -> None: """Check watchers, and call watch methods / computes @@ -367,6 +383,7 @@ class reactive(Reactive[ReactiveType]): repaint: Perform a repaint on change. init: Call watchers on initialize (post mount). always_update: Call watchers even when the new value equals the old value. + bindings: Refresh bindings when the reactive changes. """ def __init__( @@ -378,6 +395,7 @@ def __init__( init: bool = True, always_update: bool = False, recompose: bool = False, + bindings: bool = False, ) -> None: super().__init__( default, @@ -386,6 +404,7 @@ def __init__( init=init, always_update=always_update, recompose=recompose, + bindings=bindings, ) @@ -396,6 +415,7 @@ class var(Reactive[ReactiveType]): default: A default value or callable that returns a default. init: Call watchers on initialize (post mount). always_update: Call watchers even when the new value equals the old value. + bindings: Refresh bindings when the reactive changes. """ def __init__( @@ -403,6 +423,7 @@ def __init__( default: ReactiveType | Callable[[], ReactiveType], init: bool = True, always_update: bool = False, + bindings: bool = False, ) -> None: super().__init__( default, @@ -410,6 +431,7 @@ def __init__( repaint=False, init=init, always_update=always_update, + bindings=bindings, ) diff --git a/src/textual/renderables/gradient.py b/src/textual/renderables/gradient.py index 7dd359f7a7..82534b8f7f 100644 --- a/src/textual/renderables/gradient.py +++ b/src/textual/renderables/gradient.py @@ -1,10 +1,8 @@ from __future__ import annotations -from functools import lru_cache from math import cos, pi, sin from typing import Sequence -from rich.color import Color as RichColor from rich.console import Console, ConsoleOptions, RenderResult from rich.segment import Segment from rich.style import Style @@ -59,6 +57,7 @@ def __init__( (stop, Color.parse(color) if isinstance(color, str) else color) for stop, color in stops ] + self._color_gradient = Gradient(*self._stops) def __rich_console__( self, console: Console, options: ConsoleOptions @@ -75,38 +74,20 @@ def __rich_console__( new_line = Segment.line() - color_gradient = Gradient(*self._stops) - _Segment = Segment - get_color = color_gradient.get_color + get_color = self._color_gradient.get_rich_color from_color = Style.from_color - @lru_cache(maxsize=1024) - def get_rich_color(color_offset: int) -> RichColor: - """Get a Rich color in the gradient. - - Args: - color_index: A offset within the color gradient normalized between 0 and 255. - - Returns: - A Rich color. - """ - return get_color(color_offset / 255).rich_color - for line_y in range(height): point_y = float(line_y) * 2 - center_y point_x = 0 - center_x - x1 = (center_x + (point_x * cos_angle - point_y * sin_angle)) / width * 255 + x1 = (center_x + (point_x * cos_angle - point_y * sin_angle)) / width x2 = ( - (center_x + (point_x * cos_angle - (point_y + 1.0) * sin_angle)) - / width - * 255 - ) + center_x + (point_x * cos_angle - (point_y + 1.0) * sin_angle) + ) / width point_x = width - center_x - end_x1 = ( - (center_x + (point_x * cos_angle - point_y * sin_angle)) / width * 255 - ) + end_x1 = (center_x + (point_x * cos_angle - point_y * sin_angle)) / width delta_x = (end_x1 - x1) / width if abs(delta_x) < 0.0001: @@ -114,8 +95,8 @@ def get_rich_color(color_offset: int) -> RichColor: yield _Segment( "▀" * width, from_color( - get_rich_color(int(x1)), - get_rich_color(int(x2)), + get_color(x1), + get_color(x2), ), ) @@ -124,8 +105,8 @@ def get_rich_color(color_offset: int) -> RichColor: _Segment( "▀", from_color( - get_rich_color(int(x1 + x * delta_x)), - get_rich_color(int(x2 + x * delta_x)), + get_color(x1 + x * delta_x), + get_color(x2 + x * delta_x), ), ) for x in range(width) diff --git a/src/textual/rlock.py b/src/textual/rlock.py new file mode 100644 index 0000000000..d7a6af2d5e --- /dev/null +++ b/src/textual/rlock.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from asyncio import Lock, Task, current_task + + +class RLock: + """A re-entrant asyncio lock.""" + + def __init__(self) -> None: + self._owner: Task | None = None + self._count = 0 + self._lock = Lock() + + async def acquire(self) -> None: + """Wait until the lock can be acquired.""" + task = current_task() + assert task is not None + if self._owner is None or self._owner is not task: + await self._lock.acquire() + self._owner = task + self._count += 1 + + def release(self) -> None: + """Release a previously acquired lock.""" + task = current_task() + assert task is not None + self._count -= 1 + if self._count < 0: + # Should not occur if every acquire as a release + raise RuntimeError("RLock.release called too many times") + if self._owner is task: + if not self._count: + self._owner = None + self._lock.release() + + @property + def is_locked(self): + """Return True if lock is acquired.""" + return self._lock.locked() + + async def __aenter__(self) -> None: + """Asynchronous context manager to acquire and release lock.""" + await self.acquire() + + async def __aexit__(self, _type, _value, _traceback) -> None: + """Exit the context manager.""" + self.release() + + +if __name__ == "__main__": + from asyncio import Lock + + async def locks(): + lock = RLock() + async with lock: + async with lock: + print("Hello") + + import asyncio + + asyncio.run(locks()) diff --git a/src/textual/screen.py b/src/textual/screen.py index 861a298976..cf233fbc8a 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -33,7 +33,8 @@ from ._context import active_message_pump, visible_screen_stack from ._path import CSSPathType, _css_path_type_as_list, _make_path_object_relative from ._types import CallbackType -from .binding import Binding +from .await_complete import AwaitComplete +from .binding import ActiveBinding, Binding, _Bindings from .css.match import match from .css.parse import parse_selectors from .css.query import NoMatches, QueryType @@ -69,6 +70,7 @@ """Type of a screen result callback function.""" +@rich.repr.auto class ResultCallback(Generic[ScreenResultType]): """Holds the details of a callback.""" @@ -136,7 +138,7 @@ class Screen(Generic[ScreenResultType], Widget): Screen { layout: vertical; overflow-y: auto; - background: $surface; + background: $surface; &:inline { height: auto; @@ -164,7 +166,8 @@ class Screen(Generic[ScreenResultType], Widget): """ focused: Reactive[Widget | None] = Reactive(None) - """The focused [widget][textual.widget.Widget] or `None` for no focus.""" + """The focused [widget][textual.widget.Widget] or `None` for no focus. + To set focus, do not update this value directly. Use [set_focus][textual.screen.Screen.set_focus] instead.""" stack_updates: Reactive[int] = Reactive(0, repaint=False) """An integer that updates when the screen is resumed.""" sub_title: Reactive[str | None] = Reactive(None, compute=False) @@ -179,8 +182,8 @@ class Screen(Generic[ScreenResultType], Widget): """ BINDINGS = [ - Binding("tab", "focus_next", "Focus Next", show=False), - Binding("shift+tab", "focus_previous", "Focus Previous", show=False), + Binding("tab", "app.focus_next", "Focus Next", show=False), + Binding("shift+tab", "app.focus_previous", "Focus Previous", show=False), ] def __init__( @@ -221,9 +224,16 @@ def __init__( self.title = self.TITLE self.sub_title = self.SUB_TITLE - self.screen_layout_refresh_signal = Signal(self, "layout-refresh") + self.screen_layout_refresh_signal: Signal[Screen] = Signal( + self, "layout-refresh" + ) """The signal that is published when the screen's layout is refreshed.""" + self._bindings_updated = False + """Indicates that a binding update was requested.""" + self.bindings_updated_signal: Signal[Screen] = Signal(self, "bindings_updated") + """A signal published when the bindings have been updated""" + @property def is_modal(self) -> bool: """Is the screen modal?""" @@ -262,7 +272,93 @@ def layers(self) -> tuple[str, ...]: extras.append("_tooltips") return (*super().layers, *extras) + def _watch_focused(self): + self.refresh_bindings() + + def _watch_stack_updates(self): + self.refresh_bindings() + + def refresh_bindings(self) -> None: + """Call to request a refresh of bindings.""" + self.log.debug("Bindings updated") + self._bindings_updated = True + self.check_idle() + + @property + def _binding_chain(self) -> list[tuple[DOMNode, _Bindings]]: + """Binding chain from this screen.""" + focused = self.focused + if focused is not None and focused.loading: + focused = None + namespace_bindings: list[tuple[DOMNode, _Bindings]] + + if focused is None: + namespace_bindings = [ + (self, self._bindings), + (self.app, self.app._bindings), + ] + else: + namespace_bindings = [ + (node, node._bindings) for node in focused.ancestors_with_self + ] + + return namespace_bindings + + @property + def _modal_binding_chain(self) -> list[tuple[DOMNode, _Bindings]]: + """The binding chain, ignoring everything before the last modal.""" + binding_chain = self._binding_chain + for index, (node, _bindings) in enumerate(binding_chain, 1): + if node.is_modal: + return binding_chain[:index] + return binding_chain + + @property + def active_bindings(self) -> dict[str, ActiveBinding]: + """Get currently active bindings for this screen. + + If no widget is focused, then app-level bindings are returned. + If a widget is focused, then any bindings present in the screen and app are merged and returned. + + This property may be used to inspect current bindings. + + Returns: + A map of keys to a tuple containing (namespace, binding, enabled boolean). + """ + + bindings_map: dict[str, ActiveBinding] = {} + for namespace, bindings in self._modal_binding_chain: + for key, binding in bindings.keys.items(): + action_state = self.app._check_action_state(binding.action, namespace) + if action_state is False: + continue + if existing_key_and_binding := bindings_map.get(key): + _, existing_binding, _ = existing_key_and_binding + if binding.priority and not existing_binding.priority: + bindings_map[key] = ActiveBinding( + namespace, binding, bool(action_state) + ) + else: + bindings_map[key] = ActiveBinding( + namespace, binding, bool(action_state) + ) + + return bindings_map + + @property + def is_active(self) -> bool: + """Is the screen active (i.e. visible and top of the stack)?""" + try: + return self.app.screen is self + except Exception: + return False + def render(self) -> RenderableType: + """Render method inherited from widget, used to render the screen's background. + + Returns: + Background renderable. + """ background = self.styles.background try: base_screen = visible_screen_stack.get().pop() @@ -270,10 +366,13 @@ def render(self) -> RenderableType: base_screen = None if base_screen is not None and background.a < 1: + # If background is translucent, render a background screen return BackgroundScreen(base_screen, background) if background.is_transparent: + # If the background is transparent, defer to App.render return self.app.render() + # Render a screen of a solid color. return Blank(background) def get_offset(self, widget: Widget) -> Offset: @@ -618,21 +717,22 @@ def set_focus(self, widget: Widget | None, scroll_visible: bool = True) -> None: # Change focus self.focused = widget # Send focus event + widget.post_message(events.Focus()) + focused = widget + if scroll_visible: def scroll_to_center(widget: Widget) -> None: """Scroll to center (after a refresh).""" - if widget.has_focus and not self.screen.can_view(widget): - self.screen.scroll_to_center(widget, origin_visible=True) + if self.focused is widget and not self.can_view(widget): + self.scroll_to_center(widget, origin_visible=True) - self.call_after_refresh(scroll_to_center, widget) - - widget.post_message(events.Focus()) - focused = widget + self.call_later(scroll_to_center, widget) self.log.debug(widget, "was focused") self._update_focus_styles(focused, blurred) + self.refresh_bindings() def _extend_compose(self, widgets: list[Widget]) -> None: """Insert Textual's own internal widgets. @@ -650,24 +750,32 @@ def _extend_compose(self, widgets: list[Widget]) -> None: def _on_mount(self, event: events.Mount) -> None: """Set up the tooltip-clearing signal when we mount.""" - self.screen_layout_refresh_signal.subscribe(self, self._maybe_clear_tooltip) + self.screen_layout_refresh_signal.subscribe( + self, self._maybe_clear_tooltip, immediate=True + ) + self.refresh_bindings() async def _on_idle(self, event: events.Idle) -> None: # Check for any widgets marked as 'dirty' (needs a repaint) event.prevent_default() - if not self.app._batch_count and self.is_current: - if ( - self._layout_required - or self._scroll_required - or self._repaint_required - or self._recompose_required - or self._dirty_widgets - ): - self._update_timer.resume() - return - - await self._invoke_and_clear_callbacks() + try: + if not self.app._batch_count and self.is_current: + if ( + self._layout_required + or self._scroll_required + or self._repaint_required + or self._recompose_required + or self._dirty_widgets + ): + self._update_timer.resume() + return + + await self._invoke_and_clear_callbacks() + finally: + if self._bindings_updated: + self._bindings_updated = False + self.app.call_later(self.bindings_updated_signal.publish, self) def _compositor_refresh(self) -> None: """Perform a compositor refresh.""" @@ -714,6 +822,7 @@ def _compositor_refresh(self) -> None: app.screen.refresh(*self._compositor._dirty_regions) self._compositor._dirty_regions.clear() self._dirty_widgets.clear() + app._update_mouse_over(self) def _on_timer_update(self) -> None: """Called by the _update_timer.""" @@ -861,7 +970,7 @@ def _refresh_layout(self, size: Size | None = None, scroll: bool = False) -> Non self._compositor_refresh() if self.app._dom_ready: - self.screen_layout_refresh_signal.publish() + self.screen_layout_refresh_signal.publish(self.screen) else: self.app.post_message(events.Ready()) self.app._dom_ready = True @@ -966,7 +1075,7 @@ def _clear_tooltip(self) -> None: self._tooltip_timer.stop() tooltip.display = False - def _maybe_clear_tooltip(self) -> None: + def _maybe_clear_tooltip(self, _) -> None: """Check if the widget under the mouse cursor still pertains to the tooltip. If they differ, the tooltip will be removed. @@ -1083,6 +1192,7 @@ def _forward_event(self, event: events.Event) -> None: if event.is_forwarded: return event._set_forwarded() + if isinstance(event, (events.Enter, events.Leave)): self.post_message(event) @@ -1117,9 +1227,16 @@ def _forward_event(self, event: events.Event) -> None: class _NoResult: """Class used to mark that there is no result.""" - def dismiss(self, result: ScreenResultType | Type[_NoResult] = _NoResult) -> None: + def dismiss( + self, result: ScreenResultType | Type[_NoResult] = _NoResult + ) -> AwaitComplete: """Dismiss the screen, optionally with a result. + !!! note + + Only the active screen may be dismissed. If you try to dismiss a screen that isn't active, + this method will raise a `ScreenError`. + If `result` is provided and a callback was set when the screen was [pushed][textual.app.App.push_screen], then the callback will be invoked with `result`. @@ -1127,21 +1244,21 @@ def dismiss(self, result: ScreenResultType | Type[_NoResult] = _NoResult) -> Non result: The optional result to be passed to the result callback. Raises: + ScreenError: If the screen being dismissed is not active. ScreenStackError: If trying to dismiss a screen that is not at the top of the stack. """ - if self is not self.app.screen: - from .app import ScreenStackError + if not self.is_active: + from .app import ScreenError - raise ScreenStackError( - f"Can't dismiss screen {self} that's not at the top of the stack." - ) + raise ScreenError("Screen is not active") if result is not self._NoResult and self._result_callbacks: self._result_callbacks[-1](cast(ScreenResultType, result)) - self.app.pop_screen() + await_pop = self.app.pop_screen() + return await_pop - def action_dismiss( + async def action_dismiss( self, result: ScreenResultType | Type[_NoResult] = _NoResult ) -> None: """A wrapper around [`dismiss`][textual.screen.Screen.dismiss] that can be called as an action. @@ -1149,6 +1266,7 @@ def action_dismiss( Args: result: The optional result to be passed to the result callback. """ + await self._flush_next_callbacks() self.dismiss(result) def can_view(self, widget: Widget) -> bool: @@ -1204,7 +1322,7 @@ def __init__( self._modal = True -class _SystemModalScreen(ModalScreen[ScreenResultType], inherit_css=False): +class SystemModalScreen(ModalScreen[ScreenResultType], inherit_css=False): """A variant of `ModalScreen` for internal use. This version of `ModalScreen` allows us to build system-level screens; diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index 2700419192..a105aa8370 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -70,6 +70,13 @@ def __rich_repr__(self) -> rich.repr.Result: class ScrollBarRender: + VERTICAL_BARS: ClassVar[list[str]] = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", " "] + """Glyphs used for vertical scrollbar ends, for smoother display.""" + HORIZONTAL_BARS: ClassVar[list[str]] = ["▉", "▊", "▋", "▌", "▍", "▎", "▏", " "] + """Glyphs used for horizontal scrollbar ends, for smoother display.""" + BLANK_GLYPH: ClassVar[str] = " " + """Glyph used for the main body of the scrollbar""" + def __init__( self, virtual_size: int = 100, @@ -99,9 +106,9 @@ def render_bar( bar_color: Color = Color.parse("bright_magenta"), ) -> Segments: if vertical: - bars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", " "] + bars = cls.VERTICAL_BARS else: - bars = ["▉", "▊", "▋", "▌", "▍", "▎", "▏", " "] + bars = cls.HORIZONTAL_BARS back = back_color bar = bar_color @@ -112,7 +119,7 @@ def render_bar( _Segment = Segment _Style = Style - blank = " " * width_thickness + blank = cls.BLANK_GLYPH * width_thickness foreground_meta = {"@mouse.down": "grab"} if window_size and size and virtual_size and size != virtual_size: diff --git a/src/textual/signal.py b/src/textual/signal.py index a1a1d80b8a..5960ea687d 100644 --- a/src/textual/signal.py +++ b/src/textual/signal.py @@ -9,7 +9,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Awaitable, Callable, Generic, TypeVar, Union from weakref import WeakKeyDictionary import rich.repr @@ -17,8 +17,13 @@ from textual import log if TYPE_CHECKING: - from ._types import IgnoreReturnCallbackType from .dom import DOMNode + from .message_pump import MessagePump +SignalT = TypeVar("SignalT") + +SignalCallbackType = Union[ + Callable[[SignalT], Awaitable[Any]], Callable[[SignalT], Any] +] class SignalError(Exception): @@ -26,7 +31,7 @@ class SignalError(Exception): @rich.repr.auto(angular=True) -class Signal: +class Signal(Generic[SignalT]): """A signal that a widget may subscribe to, in order to invoke callbacks when an associated event occurs.""" def __init__(self, owner: DOMNode, name: str) -> None: @@ -39,7 +44,7 @@ def __init__(self, owner: DOMNode, name: str) -> None: self._owner = owner self._name = name self._subscriptions: WeakKeyDictionary[ - DOMNode, list[IgnoreReturnCallbackType] + MessagePump, list[SignalCallbackType] ] = WeakKeyDictionary() def __rich_repr__(self) -> rich.repr.Result: @@ -47,27 +52,47 @@ def __rich_repr__(self) -> rich.repr.Result: yield "name", self._name yield "subscriptions", list(self._subscriptions.keys()) - def subscribe(self, node: DOMNode, callback: IgnoreReturnCallbackType) -> None: + def subscribe( + self, + node: MessagePump, + callback: SignalCallbackType, + immediate: bool = False, + ) -> None: """Subscribe a node to this signal. When the signal is published, the callback will be invoked. Args: node: Node to subscribe. - callback: A callback function which takes no arguments, and returns anything (return type ignored). + callback: A callback function which takes a single argument and returns anything (return type ignored). + immediate: Invoke the callback immediately on publish if `True`, otherwise post it to the DOM node to be + called once existing messages have been processed. Raises: SignalError: Raised when subscribing a non-mounted widget. """ + if not node.is_running: raise SignalError( f"Node must be running to subscribe to a signal (has {node} been mounted)?" ) + + if immediate: + + def signal_callback(data: object) -> None: + """Invoke the callback immediately.""" + callback(data) + + else: + + def signal_callback(data: object) -> None: + """Post the callback to the node, to call at the next opertunity.""" + node.call_next(callback, data) + callbacks = self._subscriptions.setdefault(node, []) - if callback not in callbacks: - callbacks.append(callback) + callbacks.append(signal_callback) - def unsubscribe(self, node: DOMNode) -> None: + def unsubscribe(self, node: MessagePump) -> None: """Unsubscribe a node from this signal. Args: @@ -75,18 +100,29 @@ def unsubscribe(self, node: DOMNode) -> None: """ self._subscriptions.pop(node, None) - def publish(self) -> None: - """Publish the signal (invoke subscribed callbacks).""" + def publish(self, data: SignalT) -> None: + """Publish the signal (invoke subscribed callbacks). + + Args: + data: An argument to pass to the callbacks. + + """ + # Don't publish if the DOM is not ready or shutting down + if not self._owner.is_attached or self._owner._pruning: + return + for ancestor_node in self._owner.ancestors_with_self: + if not ancestor_node.is_running: + return for node, callbacks in list(self._subscriptions.items()): - if not node.is_running: + if not (node.is_running and node.is_attached) or node._pruning: # Removed nodes that are no longer running self._subscriptions.pop(node) else: # Call callbacks for callback in callbacks: try: - callback() + callback(data) except Exception as error: log.error( f"error publishing signal to {node} ignored (callback={callback}); {error}" diff --git a/src/textual/timer.py b/src/textual/timer.py index 1d31af4b14..1cc8dcbdaa 100644 --- a/src/textual/timer.py +++ b/src/textual/timer.py @@ -7,8 +7,8 @@ from __future__ import annotations import weakref -from asyncio import CancelledError, Event, Task, create_task -from typing import Any, Awaitable, Callable, Union +from asyncio import CancelledError, Event, Task, create_task, gather +from typing import Any, Awaitable, Callable, Iterable, Union from rich.repr import Result, rich_repr @@ -23,7 +23,7 @@ class EventTargetGone(Exception): - pass + """Raised if the timer event target has been deleted prior to the timer event being sent.""" @rich_repr @@ -83,12 +83,47 @@ def _start(self) -> None: """Start the timer.""" self._task = create_task(self._run_timer(), name=self.name) - def stop(self) -> None: - """Stop the timer.""" - if self._task is not None: - self._active.set() - self._task.cancel() - self._task = None + def stop(self) -> Task: + """Stop the timer. + + Returns: + A Task object. Await this to wait until the timer has completed. + + """ + if self._task is None: + + async def noop() -> None: + """A dummy task.""" + + return create_task(noop()) + + self._active.set() + self._task.cancel() + return self._task + + @classmethod + async def _stop_all(cls, timers: Iterable[Timer]) -> None: + """Stop a number of timers, and await their completion. + + Args: + timers: A number of timers. + """ + + async def stop_timer(timer: Timer) -> None: + """Stop a timer and wait for it to finish. + + Args: + timer: A Timer instance. + """ + if timer._task is not None: + timer._active.set() + timer._task.cancel() + try: + await timer._task + except CancelledError: + pass + + await gather(*[stop_timer(timer) for timer in list(timers)]) def pause(self) -> None: """Pause the timer. diff --git a/src/textual/walk.py b/src/textual/walk.py index a58bf0110f..0f6790c882 100644 --- a/src/textual/walk.py +++ b/src/textual/walk.py @@ -17,21 +17,22 @@ WalkType = TypeVar("WalkType", bound=DOMNode) -@overload -def walk_depth_first( - root: DOMNode, - *, - with_root: bool = True, -) -> Iterable[DOMNode]: ... +if TYPE_CHECKING: + @overload + def walk_depth_first( + root: DOMNode, + *, + with_root: bool = True, + ) -> Iterable[DOMNode]: ... -@overload -def walk_depth_first( - root: WalkType, - filter_type: type[WalkType], - *, - with_root: bool = True, -) -> Iterable[WalkType]: ... + @overload + def walk_depth_first( + root: WalkType, + filter_type: type[WalkType], + *, + with_root: bool = True, + ) -> Iterable[WalkType]: ... def walk_depth_first( @@ -65,31 +66,31 @@ def walk_depth_first( if with_root and isinstance(root, check_type): yield root while stack: - node = next(stack[-1], None) - if node is None: + if (node := next(stack[-1], None)) is None: pop() else: if isinstance(node, check_type): yield node - if node.children: - push(iter(node.children)) + if children := node._nodes: + push(iter(children)) -@overload -def walk_breadth_first( - root: DOMNode, - *, - with_root: bool = True, -) -> Iterable[DOMNode]: ... +if TYPE_CHECKING: + @overload + def walk_breadth_first( + root: DOMNode, + *, + with_root: bool = True, + ) -> Iterable[DOMNode]: ... -@overload -def walk_breadth_first( - root: WalkType, - filter_type: type[WalkType], - *, - with_root: bool = True, -) -> Iterable[WalkType]: ... + @overload + def walk_breadth_first( + root: WalkType, + filter_type: type[WalkType], + *, + with_root: bool = True, + ) -> Iterable[WalkType]: ... def walk_breadth_first( @@ -127,4 +128,4 @@ def walk_breadth_first( node = popleft() if isinstance(node, check_type): yield node - extend(node.children) + extend(node._nodes) diff --git a/src/textual/widget.py b/src/textual/widget.py index e334c18ecf..b7296f6117 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -4,7 +4,7 @@ from __future__ import annotations -from asyncio import Lock, create_task, wait +from asyncio import create_task, gather, wait from collections import Counter from contextlib import asynccontextmanager from fractions import Fraction @@ -55,9 +55,11 @@ from ._styles_cache import StylesCache from ._types import AnimationLevel from .actions import SkipAction +from .await_complete import AwaitComplete from .await_remove import AwaitRemove from .box_model import BoxModel from .cache import FIFOCache +from .color import Color from .css.match import match from .css.parse import parse_selectors from .css.query import NoMatches, WrongType @@ -75,11 +77,12 @@ ) from .layouts.vertical import VerticalLayout from .message import Message -from .messages import CallbackType -from .notifications import Notification, SeverityLevel +from .messages import CallbackType, Prune +from .notifications import SeverityLevel from .reactive import Reactive from .render import measure from .renderables.blank import Blank +from .rlock import RLock from .strip import Strip from .walk import walk_depth_first @@ -105,6 +108,8 @@ _NULL_STYLE = Style() +_MOUSE_EVENTS_DISALLOW_IF_DISABLED = (events.MouseEvent, events.Enter, events.Leave) +_MOUSE_EVENTS_ALLOW_IF_DISABLED = (events.MouseScrollDown, events.MouseScrollUp) class AwaitMount: @@ -134,6 +139,10 @@ async def await_mount() -> None: if aws: await wait(aws) self._parent.refresh(layout=True) + try: + self._parent.app._update_mouse_over(self._parent.screen) + except NoScreen: + pass return await_mount().__await__() @@ -360,7 +369,7 @@ def __init__( ) self._styles_cache = StylesCache() - self._rich_style_cache: dict[str, tuple[Style, Style]] = {} + self._rich_style_cache: dict[tuple[str, ...], tuple[Style, Style]] = {} self._tooltip: RenderableType | None = None """The tooltip content.""" @@ -391,13 +400,17 @@ def __init__( if self.BORDER_SUBTITLE: self.border_subtitle = self.BORDER_SUBTITLE - self.lock = Lock() + self.lock = RLock() """`asyncio` lock to be used to synchronize the state of the widget. Two different tasks might call methods on a widget at the same time, which might result in a race condition. This can be fixed by adding `async with widget.lock:` around the method calls. """ + self._anchored: Widget | None = None + """An anchored child widget, or `None` if no child is anchored.""" + self._anchor_animate: bool = False + """Flag to enable animation when scrolling anchored widgets.""" virtual_size: Reactive[Size] = Reactive(Size(0, 0), layout=True) """The virtual (scrollable) [size][textual.geometry.Size] of the widget.""" @@ -514,6 +527,40 @@ def opacity(self) -> float: break return opacity + @property + def is_anchored(self) -> bool: + """Is this widget anchored?""" + return self._parent is not None and self._parent is self + + def anchor(self, *, animate: bool = False) -> None: + """Anchor the widget, which scrolls it into view (like [scroll_visible][textual.widget.Widget.scroll_visible]), + but also keeps it in view if the widget's size changes, or the size of its container changes. + + !!! note + + Anchored widgets will be un-anchored if the users scrolls the container. + + Args: + animate: `True` if the scroll should animate, or `False` if it shouldn't. + """ + if self._parent is not None and isinstance(self._parent, Widget): + self._parent._anchored = self + self._parent._anchor_animate = animate + self.check_idle() + + def clear_anchor(self) -> None: + """Stop anchoring this widget (a no-op if this widget is not anchored).""" + if ( + self._parent is not None + and isinstance(self._parent, Widget) + and self._parent._anchored is self + ): + self._parent._anchored = None + + def _clear_anchor(self) -> None: + """Clear an anchored child.""" + self._anchored = None + def _check_disabled(self) -> bool: """Check if the widget is disabled either explicitly by setting `disabled`, or implicitly by setting `loading`. @@ -625,15 +672,16 @@ def set_loading(self, loading: bool) -> Awaitable: Returns: An optional awaitable. """ - + LOADING_INDICATOR_CLASS = "-textual-loading-indicator" + LOADING_INDICATOR_QUERY = f".{LOADING_INDICATOR_CLASS}" + remove_indicator = self.query_children(LOADING_INDICATOR_QUERY).remove() if loading: loading_indicator = self.get_loading_widget() - loading_indicator.add_class("-textual-loading-indicator") + loading_indicator.add_class(LOADING_INDICATOR_CLASS) await_mount = self.mount(loading_indicator) - return await_mount + return AwaitComplete(remove_indicator, await_mount).call_next(self) else: - await_remove = self.query(".-textual-loading-indicator").remove() - return await_remove + return remove_indicator async def _watch_loading(self, loading: bool) -> None: """Called when the 'loading' reactive is changed.""" @@ -641,11 +689,15 @@ async def _watch_loading(self, loading: bool) -> None: ExpectType = TypeVar("ExpectType", bound="Widget") - @overload - def get_child_by_id(self, id: str) -> Widget: ... + if TYPE_CHECKING: + + @overload + def get_child_by_id(self, id: str) -> Widget: ... - @overload - def get_child_by_id(self, id: str, expect_type: type[ExpectType]) -> ExpectType: ... + @overload + def get_child_by_id( + self, id: str, expect_type: type[ExpectType] + ) -> ExpectType: ... def get_child_by_id( self, id: str, expect_type: type[ExpectType] | None = None @@ -675,13 +727,15 @@ def get_child_by_id( ) return child - @overload - def get_widget_by_id(self, id: str) -> Widget: ... + if TYPE_CHECKING: - @overload - def get_widget_by_id( - self, id: str, expect_type: type[ExpectType] - ) -> ExpectType: ... + @overload + def get_widget_by_id(self, id: str) -> Widget: ... + + @overload + def get_widget_by_id( + self, id: str, expect_type: type[ExpectType] + ) -> ExpectType: ... def get_widget_by_id( self, id: str, expect_type: type[ExpectType] | None = None @@ -738,7 +792,7 @@ def get_child_by_type(self, expect_type: type[ExpectType]) -> ExpectType: return child raise NoMatches(f"No immediate child of type {expect_type}; {self._nodes}") - def get_component_rich_style(self, name: str, *, partial: bool = False) -> Style: + def get_component_rich_style(self, *names: str, partial: bool = False) -> Style: """Get a *Rich* style for a component. Args: @@ -749,13 +803,21 @@ def get_component_rich_style(self, name: str, *, partial: bool = False) -> Style A Rich style object. """ - if name not in self._rich_style_cache: - component_styles = self.get_component_styles(name) + if names not in self._rich_style_cache: + component_styles = self.get_component_styles(*names) style = component_styles.rich_style + text_opacity = component_styles.text_opacity + if text_opacity < 1 and style.bgcolor is not None: + style += Style.from_color( + ( + Color.from_rich_color(style.bgcolor) + + component_styles.color.multiply_alpha(text_opacity) + ).rich_color + ) partial_style = component_styles.partial_rich_style - self._rich_style_cache[name] = (style, partial_style) + self._rich_style_cache[names] = (style, partial_style) - style, partial_style = self._rich_style_cache[name] + style, partial_style = self._rich_style_cache[names] return partial_style if partial else style @@ -890,13 +952,16 @@ def mount( Only one of ``before`` or ``after`` can be provided. If both are provided a ``MountError`` will be raised. """ + if self._closing or self._pruning: + return AwaitMount(self, []) + if not self.is_attached: + raise MountError(f"Can't mount widget(s) before {self!r} is mounted") # Check for duplicate IDs in the incoming widgets - ids_to_mount = [widget.id for widget in widgets if widget.id is not None] - unique_ids = set(ids_to_mount) - num_unique_ids = len(unique_ids) - num_widgets_with_ids = len(ids_to_mount) - if num_unique_ids != num_widgets_with_ids: - counter = Counter(widget.id for widget in widgets) + ids_to_mount = [ + widget_id for widget in widgets if (widget_id := widget.id) is not None + ] + if len(set(ids_to_mount)) != len(ids_to_mount): + counter = Counter(ids_to_mount) for widget_id, count in counter.items(): if count > 1: raise MountError( @@ -957,26 +1022,30 @@ def mount_all( Only one of ``before`` or ``after`` can be provided. If both are provided a ``MountError`` will be raised. """ + if self.app._exit: + return AwaitMount(self, []) await_mount = self.mount(*widgets, before=before, after=after) return await_mount - @overload - def move_child( - self, - child: int | Widget, - *, - before: int | Widget, - after: None = None, - ) -> None: ... - - @overload - def move_child( - self, - child: int | Widget, - *, - after: int | Widget, - before: None = None, - ) -> None: ... + if TYPE_CHECKING: + + @overload + def move_child( + self, + child: int | Widget, + *, + before: int | Widget, + after: None = None, + ) -> None: ... + + @overload + def move_child( + self, + child: int | Widget, + *, + after: int | Widget, + before: None = None, + ) -> None: ... def move_child( self, @@ -1077,9 +1146,12 @@ async def recompose(self) -> None: Recomposing will remove children and call `self.compose` again to remount. """ - if self._parent is not None: - async with self.batch(): - await self.query("*").exclude(".-textual-system").remove() + if not self.is_attached or self._pruning: + return + + async with self.batch(): + await self.query("*").exclude(".-textual-system").remove() + if self.is_attached: await self.mount_all(compose(self)) def _post_register(self, app: App) -> None: @@ -1163,13 +1235,19 @@ def _get_box_model( min_width -= gutter.width content_width = max(content_width, min_width, Fraction(0)) - if styles.max_width is not None: + if styles.max_width is not None and not ( + container.width == 0 + and not styles.max_width.is_cells + and self._parent is not None + and self._parent.styles.is_auto_width + ): # Restrict to maximum width, if set max_width = styles.max_width.resolve( container - margin.totals, viewport, width_fraction ) if is_border_box: max_width -= gutter.width + content_width = min(content_width, max_width) content_width = max(Fraction(0), content_width) @@ -1207,7 +1285,12 @@ def _get_box_model( min_height -= gutter.height content_height = max(content_height, min_height, Fraction(0)) - if styles.max_height is not None: + if styles.max_height is not None and not ( + container.height == 0 + and not styles.max_height.is_cells + and self._parent is not None + and self._parent.styles.is_auto_height + ): # Restrict maximum height, if set max_height = styles.max_height.resolve( container - margin.totals, viewport, height_fraction @@ -1233,9 +1316,11 @@ def get_content_width(self, container: Size, viewport: Size) -> int: Returns: The optimal width of the content. """ + if self.is_container: assert self._layout is not None - return self._layout.get_content_width(self, container, viewport) + width = self._layout.get_content_width(self, container, viewport) + return width cache_key = container.width if self._content_width_cache[0] == cache_key: @@ -1282,13 +1367,17 @@ def get_content_height(self, container: Size, viewport: Size, width: int) -> int renderable = self.render() if isinstance(renderable, Text): - height = len( - renderable.wrap( - self._console, - width, - no_wrap=renderable.no_wrap, - tab_size=renderable.tab_size or 8, + height = ( + len( + renderable.wrap( + self._console, + width, + no_wrap=renderable.no_wrap, + tab_size=renderable.tab_size or 8, + ) ) + if renderable + else 0 ) else: options = self._console.options.update_width(width).update( @@ -1557,6 +1646,15 @@ def size(self) -> Size: """ return self.content_region.size + @property + def scrollable_size(self) -> Size: + """The size of the scrollable content. + + Returns: + Scrollable content size. + """ + return self.scrollable_content_region.size + @property def outer_size(self) -> Size: """The size of the widget (including padding and border). @@ -1695,11 +1793,13 @@ def virtual_region_with_margin(self) -> Region: @property def _self_or_ancestors_disabled(self) -> bool: """Is this widget or any of its ancestors disabled?""" - return any( - node.disabled - for node in self.ancestors_with_self - if isinstance(node, Widget) - ) + + node: Widget | None = self + while isinstance(node, Widget) and not node.is_dom_root: + if node.disabled: + return True + node = node._parent # type:ignore[assignment] + return False @property def focusable(self) -> bool: @@ -2662,7 +2762,6 @@ def scroll_to_widget( Returns: `True` if any scrolling has occurred in any descendant, otherwise `False`. """ - # Grow the region by the margin so to keep the margin in view. region = widget.virtual_region_with_margin scrolled = False @@ -2674,7 +2773,7 @@ def scroll_to_widget( else: scroll_offset = container.scroll_to_region( region, - spacing=widget.gutter + widget.dock_gutter, + spacing=widget.dock_gutter, animate=animate, speed=speed, duration=duration, @@ -2695,6 +2794,8 @@ def scroll_to_widget( region = ( ( region.translate(-scroll_offset) + .translate(container.styles.margin.top_left) + .translate(container.styles.border.spacing.top_left) .translate(-widget.scroll_offset) .translate(container.virtual_region_with_margin.offset) ) @@ -2749,29 +2850,40 @@ def scroll_to_region( if window in region and not (top or center): return Offset() + def clamp_delta(delta: Offset) -> Offset: + """Clamp the delta to avoid scrolling out of range.""" + scroll_x, scroll_y = self.scroll_offset + delta = Offset( + clamp(scroll_x + delta.x, 0, self.max_scroll_x) - scroll_x, + clamp(scroll_y + delta.y, 0, self.max_scroll_y) - scroll_y, + ) + return delta + if center: region_center_x, region_center_y = region.center window_center_x, window_center_y = window.center - center_delta = Offset( - round(region_center_x - window_center_x), - round(region_center_y - window_center_y), + + delta = clamp_delta( + Offset( + int(region_center_x - window_center_x + 0.5), + int(region_center_y - window_center_y + 0.5), + ) ) - if origin_visible and region.offset not in window.translate(center_delta): - center_delta = Region.get_scroll_to_visible(window, region, top=True) - delta_x, delta_y = center_delta + if origin_visible and (region.offset not in window.translate(delta)): + delta = clamp_delta( + Region.get_scroll_to_visible(window, region, top=True) + ) else: - delta_x, delta_y = Region.get_scroll_to_visible(window, region, top=top) - scroll_x, scroll_y = self.scroll_offset + delta = clamp_delta( + Region.get_scroll_to_visible(window, region, top=top), + ) if not self.allow_horizontal_scroll and not force: - delta_x = 0 + delta = Offset(0, delta.y) + if not self.allow_vertical_scroll and not force: - delta_y = 0 + delta = Offset(delta.x, 0) - delta = Offset( - clamp(scroll_x + delta_x, 0, self.max_scroll_x) - scroll_x, - clamp(scroll_y + delta_y, 0, self.max_scroll_y) - scroll_y, - ) if delta: if speed is None and duration is None: duration = 0.2 @@ -2855,7 +2967,6 @@ def scroll_to_center( on_complete: A callable to invoke when the animation is finished. level: Minimum level required for the animation to take place (inclusive). """ - self.call_after_refresh( self.scroll_to_widget, widget=widget, @@ -3099,6 +3210,9 @@ def _get_rich_justify(self) -> JustifyMethod | None: def post_render(self, renderable: RenderableType) -> ConsoleRenderable: """Applies style attributes to the default renderable. + This method is called by Textual itself. + It is unlikely you will need to call or implement this method. + Returns: A new renderable. """ @@ -3133,19 +3247,24 @@ def watch_has_focus(self, value: bool) -> None: """Update from CSS if has focus state changes.""" self._update_styles() - def watch_disabled(self) -> None: + def watch_disabled(self, disabled: bool) -> None: """Update the styles of the widget and its children when disabled is toggled.""" from .app import ScreenStackError + if disabled and self.mouse_over: + # Ensure widget gets a Leave if it is disabled while hovered + self._message_queue.put_nowait(events.Leave()) try: + screen = self.screen if ( - self.disabled - and self.app.focused is not None - and self in self.app.focused.ancestors_with_self + disabled + and screen.focused is not None + and self in screen.focused.ancestors_with_self ): - self.app.focused.blur() - except (ScreenStackError, NoActiveAppError): + screen.focused.blur() + except (ScreenStackError, NoActiveAppError, NoScreen): pass + self._update_styles() def _size_updated( @@ -3162,6 +3281,7 @@ def _size_updated( Returns: True if anything changed, or False if nothing changed. """ + if ( self._size != size or self.virtual_size != virtual_size @@ -3288,6 +3408,15 @@ def get_style_at(self, x: int, y: int) -> Style: return Style() return self.screen.get_style_at(*screen_offset) + def suppress_click(self) -> None: + """Suppress a click event. + + This will prevent a [Click][textual.events.Click] event being sent, + if called after a mouse down event and before the click itself. + + """ + self.app._mouse_down_widget = None + def _forward_event(self, event: events.Event) -> None: event._set_forwarded() self.post_message(event) @@ -3326,8 +3455,7 @@ def refresh( Returns: The `Widget` instance. """ - if not self._is_mounted: - return self + if layout: self._layout_required = True for ancestor in self.ancestors: @@ -3335,6 +3463,11 @@ def refresh( break ancestor._clear_arrangement_cache() + if not self._is_mounted: + self._repaint_required = True + self.check_idle() + return self + if recompose: self._recompose_required = True self.call_next(self._check_recompose) @@ -3355,26 +3488,36 @@ def remove(self) -> AwaitRemove: Returns: An awaitable object that waits for the widget to be removed. """ - - await_remove = self.app._remove_nodes([self], self.parent) + await_remove = self.app._prune(self, parent=self._parent) return await_remove - def remove_children(self, selector: str | type[QueryType] = "*") -> AwaitRemove: + def remove_children( + self, selector: str | type[QueryType] | Iterable[Widget] = "*" + ) -> AwaitRemove: """Remove the immediate children of this Widget from the DOM. Args: - selector: A CSS selector to specify which direct children to remove. + selector: A CSS selector or iterable of widgets to remove. Returns: An awaitable object that waits for the direct children to be removed. """ - if not isinstance(selector, str): + + if callable(selector) and issubclass(selector, Widget): selector = selector.__name__ - parsed_selectors = parse_selectors(selector) - children_to_remove = [ - child for child in self.children if match(parsed_selectors, child) - ] - await_remove = self.app._remove_nodes(children_to_remove, self) + + children_to_remove: Iterable[Widget] + + if isinstance(selector, str): + parsed_selectors = parse_selectors(selector) + children_to_remove = [ + child for child in self.children if match(parsed_selectors, child) + ] + else: + children_to_remove = selector + await_remove = self.app._prune( + *children_to_remove, parent=cast(DOMNode, self._parent) + ) return await_remove @asynccontextmanager @@ -3462,9 +3605,30 @@ def post_message(self, message: Message) -> bool: self.log.warning(self, f"IS NOT RUNNING, {message!r} not sent") except NoActiveAppError: pass - return super().post_message(message) + async def on_prune(self, event: messages.Prune) -> None: + """Close message loop when asked to prune.""" + await self._close_messages(wait=False) + + async def _message_loop_exit(self) -> None: + """Clean up DOM tree.""" + parent = self._parent + # Post messages to children, asking them to prune + children = [*self.children, *self._get_virtual_dom()] + for node in children: + node.post_message(Prune()) + + # Wait for child nodes to exit + await gather(*[node._task for node in children if node._task is not None]) + # Send unmount event + await self._dispatch_message(events.Unmount()) + assert isinstance(parent, DOMNode) + # Finalize removal from DOM + parent._nodes._remove(self) + self.app._registry.discard(self) + self._detach() + async def _on_idle(self, event: events.Idle) -> None: """Called when there are no more events on the queue. @@ -3473,6 +3637,11 @@ async def _on_idle(self, event: events.Idle) -> None: """ self._check_refresh() + if self.is_anchored: + self.scroll_visible(animate=self._anchor_animate) + if self._anchored: + self._anchored.scroll_visible(animate=self._anchor_animate) + def _check_refresh(self) -> None: """Check if a refresh was requested.""" if self._parent is not None and not self._closing: @@ -3501,7 +3670,7 @@ def focus(self, scroll_visible: bool = True) -> Self: The `Widget` instance. """ - def set_focus(widget: Widget): + def set_focus(widget: Widget) -> None: """Callback to set the focus.""" try: widget.screen.set_focus(self, scroll_visible=scroll_visible) @@ -3569,20 +3738,20 @@ def check_message_enabled(self, message: Message) -> bool: `True` if the message will be sent, or `False` if it is disabled. """ # Do the normal checking and get out if that fails. - if not super().check_message_enabled(message): - return False - message_type = type(message) - if self._is_prevented(message_type): + if not super().check_message_enabled(message) or self._is_prevented( + type(message) + ): return False + # Mouse scroll events should always go through, this allows mouse # wheel scrolling to pass through disabled widgets. - if isinstance(message, (events.MouseScrollDown, events.MouseScrollUp)): + if isinstance(message, _MOUSE_EVENTS_ALLOW_IF_DISABLED): return True # Otherwise, if this is any other mouse event, the widget receiving # the event must not be disabled at this moment. return ( not self._self_or_ancestors_disabled - if isinstance(message, (events.MouseEvent, events.Enter, events.Leave)) + if isinstance(message, _MOUSE_EVENTS_DISALLOW_IF_DISABLED) else True ) @@ -3636,7 +3805,8 @@ async def mount_composed_widgets(self, widgets: list[Widget]) -> None: Args: widgets: A list of child widgets. """ - await self.mount_all(widgets) + if widgets: + await self.mount_all(widgets) def _extend_compose(self, widgets: list[Widget]) -> None: """Hook to extend composed widgets. @@ -3673,45 +3843,54 @@ def _on_blur(self, event: events.Blur) -> None: def _on_mouse_scroll_down(self, event: events.MouseScrollDown) -> None: if event.ctrl or event.shift: if self.allow_horizontal_scroll: + self._clear_anchor() if self._scroll_right_for_pointer(animate=False): event.stop() else: if self.allow_vertical_scroll: + self._clear_anchor() if self._scroll_down_for_pointer(animate=False): event.stop() def _on_mouse_scroll_up(self, event: events.MouseScrollUp) -> None: if event.ctrl or event.shift: if self.allow_horizontal_scroll: + self._clear_anchor() if self._scroll_left_for_pointer(animate=False): event.stop() else: if self.allow_vertical_scroll: + self._clear_anchor() if self._scroll_up_for_pointer(animate=False): event.stop() def _on_scroll_to(self, message: ScrollTo) -> None: if self._allow_scroll: + self._clear_anchor() self.scroll_to(message.x, message.y, animate=message.animate, duration=0.1) message.stop() def _on_scroll_up(self, event: ScrollUp) -> None: if self.allow_vertical_scroll: + self._clear_anchor() self.scroll_page_up() event.stop() def _on_scroll_down(self, event: ScrollDown) -> None: if self.allow_vertical_scroll: + self._clear_anchor() self.scroll_page_down() event.stop() def _on_scroll_left(self, event: ScrollLeft) -> None: if self.allow_horizontal_scroll: + self._clear_anchor() self.scroll_page_left() event.stop() def _on_scroll_right(self, event: ScrollRight) -> None: if self.allow_horizontal_scroll: + self._clear_anchor() self.scroll_page_right() event.stop() @@ -3738,50 +3917,70 @@ def _on_unmount(self) -> None: def action_scroll_home(self) -> None: if not self._allow_scroll: raise SkipAction() + self._clear_anchor() self.scroll_home() def action_scroll_end(self) -> None: if not self._allow_scroll: raise SkipAction() + self._clear_anchor() self.scroll_end() def action_scroll_left(self) -> None: if not self.allow_horizontal_scroll: raise SkipAction() + self._clear_anchor() self.scroll_left() def action_scroll_right(self) -> None: if not self.allow_horizontal_scroll: raise SkipAction() + self._clear_anchor() self.scroll_right() def action_scroll_up(self) -> None: if not self.allow_vertical_scroll: raise SkipAction() + self._clear_anchor() self.scroll_up() def action_scroll_down(self) -> None: if not self.allow_vertical_scroll: raise SkipAction() + self._clear_anchor() self.scroll_down() def action_page_down(self) -> None: if not self.allow_vertical_scroll: raise SkipAction() + self._clear_anchor() self.scroll_page_down() def action_page_up(self) -> None: if not self.allow_vertical_scroll: raise SkipAction() + self._clear_anchor() self.scroll_page_up() + def action_page_left(self) -> None: + if not self.allow_horizontal_scroll: + raise SkipAction() + self._clear_anchor() + self.scroll_page_left() + + def action_page_right(self) -> None: + if not self.allow_horizontal_scroll: + raise SkipAction() + self._clear_anchor() + self.scroll_page_right() + def notify( self, message: str, *, title: str = "", severity: SeverityLevel = "information", - timeout: float = Notification.timeout, + timeout: float | None = None, ) -> None: """Create a notification. @@ -3793,9 +3992,21 @@ def notify( message: The message for the notification. title: The title for the notification. severity: The severity of the notification. - timeout: The timeout (in seconds) for the notification. + timeout: The timeout (in seconds) for the notification, or `None` for default. See [`App.notify`][textual.app.App.notify] for the full documentation for this method. """ - return self.app.notify(message, title=title, severity=severity, timeout=timeout) + if timeout is None: + return self.app.notify( + message, + title=title, + severity=severity, + ) + else: + return self.app.notify( + message, + title=title, + severity=severity, + timeout=timeout, + ) diff --git a/src/textual/widgets/__init__.py b/src/textual/widgets/__init__.py index cd6e21f13b..a9505aa0dc 100644 --- a/src/textual/widgets/__init__.py +++ b/src/textual/widgets/__init__.py @@ -12,6 +12,7 @@ from ..widget import Widget from ._button import Button from ._checkbox import Checkbox + from ._classic_footer import ClassicFooter from ._collapsible import Collapsible from ._content_switcher import ContentSwitcher from ._data_table import DataTable @@ -49,6 +50,7 @@ __all__ = [ "Button", "Checkbox", + "ClassicFooter", "Collapsible", "ContentSwitcher", "DataTable", @@ -70,6 +72,7 @@ "ProgressBar", "RadioButton", "RadioSet", + "RichLog", "Rule", "Select", "SelectionList", @@ -81,7 +84,6 @@ "TabPane", "Tabs", "TextArea", - "RichLog", "Tooltip", "Tree", "Welcome", diff --git a/src/textual/widgets/__init__.pyi b/src/textual/widgets/__init__.pyi index d4db2f8f52..93b3af4d66 100644 --- a/src/textual/widgets/__init__.pyi +++ b/src/textual/widgets/__init__.pyi @@ -1,6 +1,7 @@ # This stub file must re-export every classes exposed in the __init__.py's `__all__` list: from ._button import Button as Button from ._checkbox import Checkbox as Checkbox +from ._classic_footer import ClassicFooter as ClassicFooter from ._collapsible import Collapsible as Collapsible from ._content_switcher import ContentSwitcher as ContentSwitcher from ._data_table import DataTable as DataTable diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 532076c9f8..7024f2bac6 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -141,9 +141,7 @@ class Button(Widget, can_focus=True): border-bottom: tall $error-lighten-2; border-top: tall $error-darken-2; } - } - } """ @@ -184,6 +182,7 @@ def __init__( id: str | None = None, classes: str | None = None, disabled: bool = False, + tooltip: RenderableType | None = None, ): """Create a Button widget. @@ -194,6 +193,7 @@ def __init__( id: The ID of the button in the DOM. classes: The CSS classes of the button. disabled: Whether the button is disabled or not. + tooltip: Optional tooltip. """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) @@ -202,8 +202,10 @@ def __init__( self.label = label self.variant = variant - self.active_effect_duration = 0.3 + self.active_effect_duration = 0.2 """Amount of time in seconds the button 'press' animation lasts.""" + if tooltip is not None: + self.tooltip = tooltip def get_content_width(self, container: Size, viewport: Size) -> int: try: @@ -250,13 +252,17 @@ def post_render(self, renderable: RenderableType) -> ConsoleRenderable: async def _on_click(self, event: events.Click) -> None: event.stop() - self.press() + if not self.has_class("-active"): + self.press() def press(self) -> Self: - """Respond to a button press. + """Animate the button and send the [Pressed][textual.widgets.Button.Pressed] message. + + Can be used to simulate the button being pressed by a user. Returns: - The button instance.""" + The button instance. + """ if self.disabled or not self.display: return self # Manage the "active" effect: @@ -275,7 +281,8 @@ def _start_active_affect(self) -> None: def action_press(self) -> None: """Activate a press of the button.""" - self.press() + if not self.has_class("-active"): + self.press() @classmethod def success( diff --git a/src/textual/widgets/_classic_footer.py b/src/textual/widgets/_classic_footer.py new file mode 100644 index 0000000000..d8df547300 --- /dev/null +++ b/src/textual/widgets/_classic_footer.py @@ -0,0 +1,161 @@ +from __future__ import annotations + +from collections import defaultdict +from typing import TYPE_CHECKING, ClassVar, Optional + +import rich.repr +from rich.text import Text + +from .. import events +from ..binding import Binding +from ..reactive import reactive +from ..widget import Widget + +if TYPE_CHECKING: + from ..app import RenderResult + from ..screen import Screen + + +@rich.repr.auto +class ClassicFooter(Widget): + """A simple footer widget which docks itself to the bottom of the parent container.""" + + COMPONENT_CLASSES: ClassVar[set[str]] = { + "footer--description", + "footer--key", + "footer--highlight", + "footer--highlight-key", + } + """ + | Class | Description | + | :- | :- | + | `classic-footer--description` | Targets the descriptions of the key bindings. | + | `classic-footer--highlight` | Targets the highlighted key binding. | + | `classic-footer--highlight-key` | Targets the key portion of the highlighted key binding. | + | `classic-footer--key` | Targets the key portions of the key bindings. | + """ + + __name__ = "Footer" + + DEFAULT_CSS = """ + ClassicFooter { + background: $accent; + color: $text; + dock: bottom; + height: 1; + } + ClassicFooter > .footer--highlight { + background: $accent-darken-1; + } + + ClassicFooter > .footer--highlight-key { + background: $secondary; + text-style: bold; + } + + ClassicFooter > .footer--key { + text-style: bold; + background: $accent-darken-2; + } + """ + + highlight_key: reactive[str | None] = reactive[Optional[str]](None) + + def __init__(self) -> None: + super().__init__() + self._key_text: Text | None = None + self.auto_links = False + + async def watch_highlight_key(self) -> None: + """If highlight key changes we need to regenerate the text.""" + self._key_text = None + self.refresh() + + def _on_mount(self, _: events.Mount) -> None: + self.screen.bindings_updated_signal.subscribe(self, self._bindings_changed) + self.log.warning( + "ClassicFooter is deprecated and will be removed in Textual 1.0" + ) + + def _bindings_changed(self, _screen: Screen) -> None: + self._key_text = None + self.refresh() + + def _on_mouse_move(self, event: events.MouseMove) -> None: + """Store any key we are moving over.""" + self.highlight_key = event.style.meta.get("key") + + def _on_leave(self, _: events.Leave) -> None: + """Clear any highlight when the mouse leaves the widget""" + self.highlight_key = None + + def __rich_repr__(self) -> rich.repr.Result: + yield from super().__rich_repr__() + + def _make_key_text(self) -> Text: + """Create text containing all the keys.""" + base_style = self.rich_style + text = Text( + style=self.rich_style, + no_wrap=True, + overflow="ellipsis", + justify="left", + end="", + ) + highlight_style = self.get_component_rich_style("footer--highlight") + highlight_key_style = self.get_component_rich_style("footer--highlight-key") + key_style = self.get_component_rich_style("footer--key") + description_style = self.get_component_rich_style("footer--description") + + bindings = [ + (binding, enabled) + for (_, binding, enabled) in self.screen.active_bindings.values() + if binding.show + ] + + action_to_bindings: defaultdict[str, list[tuple[Binding, bool]]] = defaultdict( + list + ) + for binding, enabled in bindings: + action_to_bindings[binding.action].append((binding, enabled)) + + app_focus = self.app.app_focus + for _, _bindings in action_to_bindings.items(): + binding, enabled = _bindings[0] + if binding.key_display is None: + key_display = self.app.get_key_display(binding.key) + if key_display is None: + key_display = binding.key.upper() + else: + key_display = binding.key_display + hovered = self.highlight_key == binding.key + key_text = Text.assemble( + (f" {key_display} ", highlight_key_style if hovered else key_style), + ( + f" {binding.description} ", + highlight_style if hovered else base_style + description_style, + ), + meta=( + { + "@click": f"app.simulate_key('{binding.key}')", + "key": binding.key, + } + if enabled and app_focus + else {} + ), + ) + if not enabled or not app_focus: + key_text.stylize("dim") + text.append_text(key_text) + return text + + def notify_style_update(self) -> None: + self._key_text = None + + def post_render(self, renderable): + return renderable + + def render(self) -> RenderResult: + if self._key_text is None: + self._key_text = self._make_key_text() + return self._key_text diff --git a/src/textual/widgets/_content_switcher.py b/src/textual/widgets/_content_switcher.py index 8e63e7a502..7077232233 100644 --- a/src/textual/widgets/_content_switcher.py +++ b/src/textual/widgets/_content_switcher.py @@ -4,6 +4,7 @@ from typing import Optional +from ..await_complete import AwaitComplete from ..containers import Container from ..css.query import NoMatches from ..events import Mount @@ -98,3 +99,34 @@ def watch_current(self, old: str | None, new: str | None) -> None: pass if new: self.get_child_by_id(new).display = True + + def add_content( + self, widget: Widget, *, id: str | None = None, set_current: bool = False + ) -> AwaitComplete: + """Add new content to the `ContentSwitcher`. + + Args: + widget: A Widget to add. + id: ID for the widget, or `None` if the widget already has an ID. + set_current: Set the new widget as current (which will cause it to display). + + Returns: + An awaitable to wait for the new content to be mounted. + """ + if id is not None and widget.id != id: + widget.id = id + + if not widget.id: + raise ValueError( + "Widget must have an ID (or set id parameter when calling add_content)" + ) + + async def _add_content() -> None: + """Add new widget and potentially change the current widget.""" + widget.display = False + with self.app.batch_update(): + await self.mount(widget) + if set_current: + self.current = widget.id + + return AwaitComplete(_add_content()) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index b695c83529..107635e9e9 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from itertools import chain, zip_longest from operator import itemgetter -from typing import Any, Callable, ClassVar, Generic, Iterable, NamedTuple, TypeVar, cast +from typing import Any, Callable, ClassVar, Generic, Iterable, NamedTuple, TypeVar import rich.repr from rich.console import RenderableType @@ -49,6 +49,8 @@ _DEFAULT_CELL_X_PADDING = 1 """Default padding to use on each side of a column in the data table.""" +_EMPTY_TEXT = Text(no_wrap=True, end="") + class CellDoesNotExist(Exception): """The cell key/index was invalid. @@ -152,22 +154,68 @@ def __rich_repr__(self): yield "column_key", self.column_key -def default_cell_formatter(obj: object) -> RenderableType: +def _find_newline(string: str, number: int) -> int: + """Find newline number n (the nth newline) in a string. + + Args: + string: The string to search. + number: The nth newline character to find. + + Returns: + The index of the nth newline character, or -1 if not found. + """ + if not string or number < 1: + return -1 + + pos = -1 + for _ in range(number): + pos = string.find("\n", pos + 1) + if pos == -1: + break + return pos + + +def default_cell_formatter( + obj: object, wrap: bool = True, height: int = 0 +) -> RenderableType: """Convert a cell into a Rich renderable for display. Args: obj: Data for a cell. + wrap: Enable or disable wrapping inside the cell. + height: The height of the cell, or `None` to render the entire cell. + This can be used to short-circuit rendering. e.g. If we know the cell + has a height of 1, we can render the cell as a single line of text + without any wrapping. Returns: A renderable to be displayed which represents the data. """ + # Get the string which will be displayed in the cell. + possible_markup = False if isinstance(obj, str): - return Text.from_markup(obj) - if isinstance(obj, float): - return f"{obj:.2f}" - if not is_renderable(obj): - return str(obj) - return cast(RenderableType, obj) + possible_markup = True + content = obj + elif isinstance(obj, float): + content = f"{obj:.2f}" + elif not is_renderable(obj): + content = str(obj) + else: + return obj + + if height: + # Let's throw away lines which definitely won't appear in the cell + # after wrapping using the height constraint. A cell can only grow + # vertically after wrapping occurs, so this is a safe operation. + trim_position = _find_newline(content, height) + if trim_position != -1 and trim_position != len(content) - 1: + content = content[:trim_position] + + if possible_markup: + text = Text.from_markup(content, end="") + text.no_wrap = not wrap + return text + return Text(content, no_wrap=not wrap, end="") @dataclass @@ -222,6 +270,10 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): Binding("left", "cursor_left", "Cursor Left", show=False), Binding("pageup", "page_up", "Page Up", show=False), Binding("pagedown", "page_down", "Page Down", show=False), + Binding("ctrl+home", "scroll_top", "Top", show=False), + Binding("ctrl+end", "scroll_bottom", "Bottom", show=False), + Binding("home", "scroll_home", "Home", show=False), + Binding("end", "scroll_end", "End", show=False), ] """ | Key(s) | Description | @@ -231,6 +283,12 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): | down | Move the cursor down. | | right | Move the cursor right. | | left | Move the cursor left. | + | pageup | Move one page up. | + | pagedown | Move one page down. | + | ctrl+home | Move to the top. | + | ctrl+end | Move to the bottom. | + | home | Move to the home position (leftmost column). | + | end | Move to the end position (rightmost column). | """ COMPONENT_CLASSES: ClassVar[set[str]] = { @@ -254,8 +312,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): | `datatable--header` | Target the header of the data table. | | `datatable--header-cursor` | Target cells highlighted by the cursor. | | `datatable--header-hover` | Target hovered header or row label cells. | - | `datatable--even-row` | Target even rows (row indices start at 0). | - | `datatable--odd-row` | Target odd rows (row indices start at 0). | + | `datatable--even-row` | Target even rows (row indices start at 0) if zebra_stripes. | + | `datatable--odd-row` | Target odd rows (row indices start at 0) if zebra_stripes. | """ DEFAULT_CSS = """ @@ -710,7 +768,8 @@ def __init__( self.fixed_columns = fixed_columns """The number of columns to fix (prevented from scrolling).""" self.zebra_stripes = zebra_stripes - """Apply zebra effect on row backgrounds (light, dark, light, dark, ...).""" + """Apply alternating styles, datatable--even-row and datatable-odd-row, to create a zebra effect, e.g., + alternating light and dark backgrounds.""" self.show_cursor = show_cursor """Show/hide both the keyboard and hover cursor.""" self.cursor_foreground_priority = cursor_foreground_priority @@ -755,7 +814,7 @@ def _y_offsets(self) -> list[tuple[RowKey, int]]: y-coordinate, we can index into this list to find which row that y-coordinate lands on, and the y-offset *within* that row. The length of the returned list is therefore the total height of all rows within the DataTable.""" - y_offsets = [] + y_offsets: list[tuple[RowKey, int]] = [] if self._update_count in self._offset_cache: y_offsets = self._offset_cache[self._update_count] else: @@ -1021,7 +1080,11 @@ def get_row_height(self, row_key: RowKey) -> int: return self.rows[row_key].height def notify_style_update(self) -> None: - self._clear_caches() + self._row_render_cache.clear() + self._cell_render_cache.clear() + self._line_cache.clear() + self._styles_cache.clear() + self._get_styles_to_render_cell.cache_clear() self.refresh() def _on_resize(self, _: events.Resize) -> None: @@ -1096,9 +1159,11 @@ def watch_cursor_coordinate( elif self.cursor_type == "column": self.refresh_column(old_coordinate.column) self._highlight_column(new_coordinate.column) - # If the coordinate was changed via `move_cursor`, give priority to its - # scrolling because it may be animated. - self.call_after_refresh(self._scroll_cursor_into_view) + + if self._require_update_dimensions: + self.call_after_refresh(self._scroll_cursor_into_view) + else: + self._scroll_cursor_into_view() def move_cursor( self, @@ -1106,6 +1171,7 @@ def move_cursor( row: int | None = None, column: int | None = None, animate: bool = False, + scroll: bool = True, ) -> None: """Move the cursor to the given position. @@ -1122,6 +1188,7 @@ def move_cursor( row: The new row to move the cursor to. column: The new column to move the cursor to. animate: Whether to animate the change of coordinates. + scroll: Scroll the cursor into view after moving. """ cursor_row, cursor_column = self.cursor_coordinate @@ -1137,7 +1204,11 @@ def move_cursor( # of rows then tried to immediately move the cursor. # We do this before setting `cursor_coordinate` because its watcher will also # schedule a call to `_scroll_cursor_into_view` without optionally animating. - self.call_after_refresh(self._scroll_cursor_into_view, animate=animate) + if scroll: + if self._require_update_dimensions: + self.call_after_refresh(self._scroll_cursor_into_view, animate=animate) + else: + self._scroll_cursor_into_view(animate=animate) self.cursor_coordinate = destination @@ -1249,19 +1320,37 @@ def _update_column_widths(self, updated_cells: set[CellKey]) -> None: """Update the widths of the columns based on the newly updated cell widths.""" for row_key, column_key in updated_cells: column = self.columns.get(column_key) - if column is None: + row = self.rows.get(row_key) + if column is None or row is None: continue console = self.app.console label_width = measure(console, column.label, 1) content_width = column.content_width cell_value = self._data[row_key][column_key] - new_content_width = measure(console, default_cell_formatter(cell_value), 1) + render_height = row.height + new_content_width = measure( + console, + default_cell_formatter( + cell_value, + wrap=row.height != 1, + height=render_height, + ), + 1, + ) if new_content_width < content_width: cells_in_column = self.get_column(column_key) cell_widths = [ - measure(console, default_cell_formatter(cell), 1) + measure( + console, + default_cell_formatter( + cell, + wrap=row.height != 1, + height=render_height, + ), + 1, + ) for cell in cells_in_column ] column.content_width = max([*cell_widths, label_width]) @@ -1522,6 +1611,7 @@ def add_column( self._updated_cells.add(CellKey(row_key, column_key)) self._require_update_dimensions = True + self._update_count += 1 self.check_idle() return column_key @@ -1556,6 +1646,9 @@ def add_row( # If we don't do this, users will be required to call add_column(s) # Before they call add_row. + if len(cells) > len(self.ordered_columns): + raise ValueError("More values provided than there are columns.") + row_index = self.row_count # Map the key of this row to its current index self._row_locations[row_key] = row_index @@ -1563,7 +1656,9 @@ def add_row( column.key: cell for column, cell in zip_longest(self.ordered_columns, cells) } - label = Text.from_markup(label) if isinstance(label, str) else label + + label = Text.from_markup(label, end="") if isinstance(label, str) else label + # Rows with auto-height get a height of 0 because 1) we need an integer height # to do some intermediate computations and 2) because 0 doesn't impact the data # table while we don't figure out how tall this row is. @@ -1873,17 +1968,35 @@ def _get_row_renderables(self, row_index: int) -> RowRenderables: return RowRenderables(None, header_row) ordered_row = self.get_row_at(row_index) - empty = Text() - - formatted_row_cells = [ - Text() if datum is None else default_cell_formatter(datum) or empty + row_key = self._row_locations.get_key(row_index) + if row_key is None: + return RowRenderables(None, []) + row_metadata = self.rows.get(row_key) + if row_metadata is None: + return RowRenderables(None, []) + + formatted_row_cells: list[RenderableType] = [ + ( + _EMPTY_TEXT + if datum is None + else default_cell_formatter( + datum, + wrap=row_metadata.height != 1, + height=row_metadata.height, + ) + or _EMPTY_TEXT + ) for datum, _ in zip_longest(ordered_row, range(len(self.columns))) ] + label = None if self._should_render_row_labels: - row_metadata = self.rows.get(self._row_locations.get_key(row_index)) label = ( - default_cell_formatter(row_metadata.label) + default_cell_formatter( + row_metadata.label, + wrap=row_metadata.height != 1, + height=row_metadata.height, + ) if row_metadata.label else None ) @@ -1959,19 +2072,25 @@ def _render_cell( ) if is_header_cell: - options = self.app.console.options.update_dimensions( - width, self.header_height - ) + row_height = self.header_height + options = self.app.console.options.update_dimensions(width, row_height) else: - row = self.rows[row_key] # If an auto-height row hasn't had its height calculated, we don't fix # the value for `height` so that we can measure the height of the cell. + row = self.rows[row_key] if row.auto_height and row.height == 0: + row_height = 0 options = self.app.console.options.update_width(width) else: + row_height = row.height options = self.app.console.options.update_dimensions( - width, row.height + width, row_height ) + + # If the row height is explicitly set to 1, then we don't wrap. + if row_height == 1: + options = options.update(no_wrap=True) + lines = self.app.console.render_lines( Styled( Padding(cell, (0, self.cell_padding)), @@ -2418,7 +2537,7 @@ def _scroll_cursor_into_view(self, animate: bool = False) -> None: else: region = self._get_cell_region(self.cursor_coordinate) - self.scroll_to_region(region, animate=animate, spacing=fixed_offset) + self.scroll_to_region(region, animate=animate, spacing=fixed_offset, force=True) def _set_hover_cursor(self, active: bool) -> None: """Set whether the hover cursor (the faint cursor you see when you @@ -2441,7 +2560,7 @@ def _set_hover_cursor(self, active: bool) -> None: async def _on_click(self, event: events.Click) -> None: self._set_hover_cursor(True) meta = event.style.meta - if not "row" in meta or not "column" in meta: + if "row" not in meta or "column" not in meta: return row_index = meta["row"] @@ -2472,21 +2591,23 @@ def action_page_down(self) -> None: """Move the cursor one page down.""" self._set_hover_cursor(False) if self.show_cursor and self.cursor_type in ("cell", "row"): - height = self.size.height - (self.header_height if self.show_header else 0) + height = self.scrollable_content_region.height - ( + self.header_height if self.show_header else 0 + ) # Determine how many rows constitutes a "page" offset = 0 rows_to_scroll = 0 - row_index, column_index = self.cursor_coordinate + row_index, _ = self.cursor_coordinate for ordered_row in self.ordered_rows[row_index:]: offset += ordered_row.height + rows_to_scroll += 1 if offset > height: break - rows_to_scroll += 1 - self.cursor_coordinate = Coordinate( - row_index + rows_to_scroll - 1, column_index - ) + target_row = row_index + rows_to_scroll - 1 + self.scroll_relative(y=height, animate=False, force=True) + self.move_cursor(row=target_row, scroll=False) else: super().action_page_down() @@ -2494,44 +2615,74 @@ def action_page_up(self) -> None: """Move the cursor one page up.""" self._set_hover_cursor(False) if self.show_cursor and self.cursor_type in ("cell", "row"): - height = self.size.height - (self.header_height if self.show_header else 0) + height = self.scrollable_content_region.height - ( + self.header_height if self.show_header else 0 + ) # Determine how many rows constitutes a "page" offset = 0 rows_to_scroll = 0 - row_index, column_index = self.cursor_coordinate + row_index, _ = self.cursor_coordinate for ordered_row in self.ordered_rows[: row_index + 1]: offset += ordered_row.height + rows_to_scroll += 1 if offset > height: break - rows_to_scroll += 1 - self.cursor_coordinate = Coordinate( - row_index - rows_to_scroll + 1, column_index - ) + target_row = row_index - rows_to_scroll + 1 + self.scroll_relative(y=-height, animate=False) + self.move_cursor(row=target_row, scroll=False) else: super().action_page_up() - def action_scroll_home(self) -> None: - """Scroll to the top of the data table.""" + def action_page_left(self) -> None: + """Move the cursor one page left.""" + self._set_hover_cursor(False) + super().scroll_page_left() + + def action_page_right(self) -> None: + """Move the cursor one page right.""" + self._set_hover_cursor(False) + super().scroll_page_right() + + def action_scroll_top(self) -> None: + """Move the cursor and scroll to the top.""" self._set_hover_cursor(False) cursor_type = self.cursor_type if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"): - row_index, column_index = self.cursor_coordinate + _, column_index = self.cursor_coordinate self.cursor_coordinate = Coordinate(0, column_index) else: super().action_scroll_home() - def action_scroll_end(self) -> None: - """Scroll to the bottom of the data table.""" + def action_scroll_bottom(self) -> None: + """Move the cursor and scroll to the bottom.""" self._set_hover_cursor(False) cursor_type = self.cursor_type if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"): - row_index, column_index = self.cursor_coordinate + _, column_index = self.cursor_coordinate self.cursor_coordinate = Coordinate(self.row_count - 1, column_index) else: super().action_scroll_end() + def action_scroll_home(self) -> None: + """Move the cursor and scroll to the leftmost column.""" + self._set_hover_cursor(False) + cursor_type = self.cursor_type + if self.show_cursor and (cursor_type == "cell" or cursor_type == "column"): + self.move_cursor(column=0) + else: + self.scroll_x = 0 + + def action_scroll_end(self) -> None: + """Move the cursor and scroll to the rightmost column.""" + self._set_hover_cursor(False) + cursor_type = self.cursor_type + if self.show_cursor and (cursor_type == "cell" or cursor_type == "column"): + self.move_cursor(column=len(self.columns) - 1) + else: + self.scroll_x = self.max_scroll_x + def action_cursor_up(self) -> None: self._set_hover_cursor(False) cursor_type = self.cursor_type diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py index 5f3c60d925..09a743ac48 100644 --- a/src/textual/widgets/_footer.py +++ b/src/textual/widgets/_footer.py @@ -1,147 +1,200 @@ from __future__ import annotations from collections import defaultdict -from typing import TYPE_CHECKING, ClassVar, Optional +from typing import TYPE_CHECKING import rich.repr from rich.text import Text -from .. import events - -if TYPE_CHECKING: - from ..app import RenderResult - +from ..app import ComposeResult +from ..binding import Binding +from ..containers import ScrollableContainer from ..reactive import reactive from ..widget import Widget +if TYPE_CHECKING: + from ..screen import Screen + @rich.repr.auto -class Footer(Widget): - """A simple footer widget which docks itself to the bottom of the parent container.""" - - COMPONENT_CLASSES: ClassVar[set[str]] = { - "footer--description", - "footer--key", - "footer--highlight", - "footer--highlight-key", +class FooterKey(Widget): + COMPONENT_CLASSES = { + "footer-key--key", + "footer-key--description", + } + + DEFAULT_CSS = """ + FooterKey { + width: auto; + height: 1; + background: $panel; + color: $text-muted; + .footer-key--key { + color: $secondary; + background: $panel; + text-style: bold; + padding: 0 1; + } + + .footer-key--description { + padding: 0 1 0 0; + } + + &:light .footer-key--key { + color: $primary; + } + + &:hover { + background: $panel-darken-2; + color: $text; + .footer-key--key { + background: $panel-darken-2; + } + } + + &.-disabled { + text-style: dim; + background: $panel; + &:hover { + .footer-key--key { + background: $panel; + } + } + } + + &.-compact { + .footer-key--key { + padding: 0; + } + .footer-key--description { + padding: 0 0 0 1; + } + } } """ - | Class | Description | - | :- | :- | - | `footer--description` | Targets the descriptions of the key bindings. | - | `footer--highlight` | Targets the highlighted key binding. | - | `footer--highlight-key` | Targets the key portion of the highlighted key binding. | - | `footer--key` | Targets the key portions of the key bindings. | - """ + upper_case_keys = reactive(False) + ctrl_to_caret = reactive(True) + compact = reactive(True) + + def __init__( + self, + key: str, + key_display: str, + description: str, + action: str, + disabled: bool = False, + ) -> None: + self.key = key + self.key_display = key_display + self.description = description + self.action = action + self._disabled = disabled + super().__init__(classes="-disabled" if disabled else "") + + def render(self) -> Text: + key_style = self.get_component_rich_style("footer-key--key") + description_style = self.get_component_rich_style("footer-key--description") + key_display = self.key_display + key_padding = self.get_component_styles("footer-key--key").padding + description_padding = self.get_component_styles( + "footer-key--description" + ).padding + if self.upper_case_keys: + key_display = key_display.upper() + if self.ctrl_to_caret and key_display.lower().startswith("ctrl+"): + key_display = "^" + key_display.split("+", 1)[1] + description = self.description + label_text = Text.assemble( + ( + " " * key_padding.left + key_display + " " * key_padding.right, + key_style, + ), + ( + " " * description_padding.left + + description + + " " * description_padding.right, + description_style, + ), + ) + label_text.stylize_before(self.rich_style) + return label_text + + async def on_mouse_down(self) -> None: + if self._disabled: + self.app.bell() + else: + self.app.simulate_key(self.key) + + def _watch_compact(self, compact: bool) -> None: + self.set_class(compact, "-compact") + + +@rich.repr.auto +class Footer(ScrollableContainer, can_focus=False, can_focus_children=False): DEFAULT_CSS = """ Footer { - background: $accent; + layout: grid; + grid-columns: auto; + background: $panel; color: $text; dock: bottom; height: 1; - } - Footer > .footer--highlight { - background: $accent-darken-1; - } - - Footer > .footer--highlight-key { - background: $secondary; - text-style: bold; - } - - Footer > .footer--key { - text-style: bold; - background: $accent-darken-2; + scrollbar-size: 0 0; + &.-compact { + grid-gutter: 1; + } } """ - highlight_key: reactive[str | None] = reactive[Optional[str]](None) - - def __init__(self) -> None: - super().__init__() - self._key_text: Text | None = None - self.auto_links = False - - async def watch_highlight_key(self) -> None: - """If highlight key changes we need to regenerate the text.""" - self._key_text = None - self.refresh() - - def _on_mount(self, _: events.Mount) -> None: - self.watch(self.screen, "focused", self._bindings_changed) - self.watch(self.screen, "stack_updates", self._bindings_changed) - - def _bindings_changed(self, _: Widget | None) -> None: - self._key_text = None - self.refresh() - - def _on_mouse_move(self, event: events.MouseMove) -> None: - """Store any key we are moving over.""" - self.highlight_key = event.style.meta.get("key") - - def _on_leave(self, _: events.Leave) -> None: - """Clear any highlight when the mouse leaves the widget""" - self.highlight_key = None - - def __rich_repr__(self) -> rich.repr.Result: - yield from super().__rich_repr__() - - def _make_key_text(self) -> Text: - """Create text containing all the keys.""" - base_style = self.rich_style - text = Text( - style=self.rich_style, - no_wrap=True, - overflow="ellipsis", - justify="left", - end="", - ) - highlight_style = self.get_component_rich_style("footer--highlight") - highlight_key_style = self.get_component_rich_style("footer--highlight-key") - key_style = self.get_component_rich_style("footer--key") - description_style = self.get_component_rich_style("footer--description") - + upper_case_keys = reactive(False) + """Upper case key display.""" + ctrl_to_caret = reactive(True) + """Convert 'ctrl+' prefix to '^'.""" + compact = reactive(False) + """Display in compact style.""" + _bindings_ready = reactive(False, repaint=False) + """True if the bindings are ready to be displayed.""" + + def compose(self) -> ComposeResult: + if not self._bindings_ready: + return bindings = [ - binding - for (_, binding) in self.app.namespace_bindings.values() + (binding, enabled) + for (_, binding, enabled) in self.screen.active_bindings.values() if binding.show ] - - action_to_bindings = defaultdict(list) - for binding in bindings: - action_to_bindings[binding.action].append(binding) - - for _, bindings in action_to_bindings.items(): - binding = bindings[0] - if binding.key_display is None: - key_display = self.app.get_key_display(binding.key) - if key_display is None: - key_display = binding.key.upper() - else: - key_display = binding.key_display - hovered = self.highlight_key == binding.key - key_text = Text.assemble( - (f" {key_display} ", highlight_key_style if hovered else key_style), - ( - f" {binding.description} ", - highlight_style if hovered else base_style + description_style, - ), - meta={ - "@click": f"app.check_bindings('{binding.key}')", - "key": binding.key, - }, + action_to_bindings: defaultdict[str, list[tuple[Binding, bool]]] = defaultdict( + list + ) + for binding, enabled in bindings: + action_to_bindings[binding.action].append((binding, enabled)) + + self.styles.grid_size_columns = len(action_to_bindings) + for multi_bindings in action_to_bindings.values(): + binding, enabled = multi_bindings[0] + yield FooterKey( + binding.key, + binding.key_display or self.app.get_key_display(binding.key), + binding.description, + binding.action, + disabled=not enabled, + ).data_bind( + Footer.upper_case_keys, + Footer.ctrl_to_caret, + Footer.compact, ) - text.append_text(key_text) - return text - def notify_style_update(self) -> None: - self._key_text = None + def on_mount(self) -> None: + async def bindings_changed(screen: Screen) -> None: + self._bindings_ready = True + if self.is_attached and screen is self.screen: + await self.recompose() + + self.screen.bindings_updated_signal.subscribe(self, bindings_changed) - def post_render(self, renderable): - return renderable + def on_unmount(self) -> None: + self.screen.bindings_updated_signal.unsubscribe(self) - def render(self) -> RenderResult: - if self._key_text is None: - self._key_text = self._make_key_text() - return self._key_text + def watch_compact(self, compact: bool) -> None: + self.set_class(compact, "-compact") diff --git a/src/textual/widgets/_header.py b/src/textual/widgets/_header.py index 62ebe8879c..3505514813 100644 --- a/src/textual/widgets/_header.py +++ b/src/textual/widgets/_header.py @@ -7,6 +7,7 @@ from rich.text import Text from ..app import RenderResult +from ..dom import NoScreen from ..events import Click, Mount from ..reactive import Reactive from ..widget import Widget @@ -34,7 +35,7 @@ class HeaderIcon(Widget): async def on_click(self, event: Click) -> None: """Launch the command palette when icon is clicked.""" event.stop() - await self.run_action("command_palette") + await self.run_action("app.command_palette") def render(self) -> RenderResult: """Render the header icon. @@ -77,6 +78,8 @@ class HeaderClock(HeaderClockSpace): } """ + time_format: Reactive[str] = Reactive("%X") + def _on_mount(self, _: Mount) -> None: self.set_interval(1, callback=self.refresh, name=f"update header clock") @@ -86,7 +89,7 @@ def render(self) -> RenderResult: Returns: The rendered clock. """ - return Text(datetime.now().time().strftime("%X")) + return Text(datetime.now().time().strftime(self.time_format)) class HeaderTitle(Widget): @@ -139,6 +142,12 @@ class Header(Widget): tall: Reactive[bool] = Reactive(False) """Set to `True` for a taller header or `False` for a single line header.""" + icon: Reactive[str] = Reactive("⭘") + """A character for the icon at the top left.""" + + time_format: Reactive[str] = Reactive("%X") + """Time format of the clock.""" + def __init__( self, show_clock: bool = False, @@ -146,6 +155,8 @@ def __init__( name: str | None = None, id: str | None = None, classes: str | None = None, + icon: str | None = None, + time_format: str | None = None, ): """Initialise the header widget. @@ -154,14 +165,24 @@ def __init__( name: The name of the header widget. id: The ID of the header widget in the DOM. classes: The CSS classes of the header widget. + icon: Single character to use as an icon, or `None` for default. + time_format: Time format (used by strftime) for clock, or `None` for default. """ super().__init__(name=name, id=id, classes=classes) self._show_clock = show_clock + if icon is not None: + self.icon = icon + if time_format is not None: + self.time_format = time_format def compose(self): - yield HeaderIcon() + yield HeaderIcon().data_bind(Header.icon) yield HeaderTitle() - yield HeaderClock() if self._show_clock else HeaderClockSpace() + yield ( + HeaderClock().data_bind(Header.time_format) + if self._show_clock + else HeaderClockSpace() + ) def watch_tall(self, tall: bool) -> None: self.set_class(tall, "-tall") @@ -193,10 +214,16 @@ def screen_sub_title(self) -> str: def _on_mount(self, _: Mount) -> None: async def set_title() -> None: - self.query_one(HeaderTitle).text = self.screen_title + try: + self.query_one(HeaderTitle).text = self.screen_title + except NoScreen: + pass async def set_sub_title() -> None: - self.query_one(HeaderTitle).sub_text = self.screen_sub_title + try: + self.query_one(HeaderTitle).sub_text = self.screen_sub_title + except NoScreen: + pass self.watch(self.app, "title", set_title) self.watch(self.app, "sub_title", set_sub_title) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 0b3ae57ee1..8e7ba91381 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, ClassVar, Iterable from rich.cells import cell_len, get_character_cell_size -from rich.console import Console, ConsoleOptions +from rich.console import Console, ConsoleOptions, RenderableType from rich.console import RenderResult as RichRenderResult from rich.highlighter import Highlighter from rich.segment import Segment @@ -257,6 +257,7 @@ def __init__( id: str | None = None, classes: str | None = None, disabled: bool = False, + tooltip: RenderableType | None = None, ) -> None: """Initialise the `Input` widget. @@ -279,6 +280,7 @@ def __init__( id: Optional ID for the widget. classes: Optional initial classes for the widget. disabled: Whether the input is disabled or not. + tooltip: Optional tooltip. """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) @@ -335,6 +337,8 @@ def __init__( if value is not None: self.value = value + if tooltip is not None: + self.tooltip = tooltip def _position_to_cell(self, position: int) -> int: """Convert an index within the value to cell position.""" diff --git a/src/textual/widgets/_label.py b/src/textual/widgets/_label.py index d890d333bd..50ef2d28ab 100644 --- a/src/textual/widgets/_label.py +++ b/src/textual/widgets/_label.py @@ -10,5 +10,6 @@ class Label(Static): Label { width: auto; height: auto; + min-height: 1; } """ diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index 69b1039bb3..a4243cdc87 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -119,9 +119,10 @@ def __init__( ) # Set the index to the given initial index, or the first available index after. self._index = _widget_navigation.find_next_enabled( - self._nodes, - anchor=initial_index - 1 if initial_index is not None else None, + children, + anchor=initial_index if initial_index is not None else None, direction=1, + with_anchor=True, ) def _on_mount(self, _: Mount) -> None: @@ -216,6 +217,51 @@ def clear(self) -> AwaitRemove: self.index = None return await_remove + def insert(self, index: int, items: Iterable[ListItem]) -> AwaitMount: + """Insert new ListItem(s) to specified index. + + Args: + index: index to insert new ListItem. + items: The ListItems to insert. + + Returns: + An awaitable that yields control to the event loop + until the DOM has been updated with the new child item. + """ + await_mount = self.mount(*items, before=index) + return await_mount + + def pop(self, index: Optional[int] = None) -> AwaitRemove: + """Remove last ListItem from ListView or + Remove ListItem from ListView by index + + Args: + index: index of ListItem to remove from ListView + + Returns: + An awaitable that yields control to the event loop until + the DOM has been updated to reflect item being removed. + """ + if index is None: + await_remove = self.query("ListItem").last().remove() + else: + await_remove = self.query("ListItem")[index].remove() + return await_remove + + def remove_items(self, indices: Iterable[int]) -> AwaitRemove: + """Remove ListItems from ListView by indices + + Args: + indices: index(s) of ListItems to remove from ListView + + Returns: + An awaitable object that waits for the direct children to be removed. + """ + items = self.query("ListItem") + items_to_remove = [items[index] for index in indices] + await_remove = self.remove_children(items_to_remove) + return await_remove + def action_select_cursor(self) -> None: """Select the current item in the list.""" selected_child = self.highlighted_child diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index 849e291bd4..6ff61de4c2 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -1,8 +1,11 @@ from __future__ import annotations +import asyncio import re +from functools import partial from pathlib import Path, PurePath from typing import Callable, Iterable, Optional +from urllib.parse import unquote from markdown_it import MarkdownIt from markdown_it.token import Token @@ -48,6 +51,16 @@ def location(self) -> Path: return Path(".") return self.stack[self.index] + @property + def start(self) -> bool: + """Is the current location at the start of the stack?""" + return self.index == 0 + + @property + def end(self) -> bool: + """Is the current location at the end of the stack?""" + return self.index >= len(self.stack) - 1 + def go(self, path: str | PurePath) -> Path: """Go to a new document. @@ -221,6 +234,8 @@ class MarkdownHeader(MarkdownBlock): DEFAULT_CSS = """ MarkdownHeader { color: $text; + margin: 2 0 1 0; + } """ @@ -231,13 +246,10 @@ class MarkdownH1(MarkdownHeader): DEFAULT_CSS = """ MarkdownH1 { - background: $accent-darken-2; - border: wide $background; content-align: center middle; - - padding: 1; text-style: bold; - color: $text; + color: $success; + &:light {color: $primary;} } """ @@ -248,13 +260,9 @@ class MarkdownH2(MarkdownHeader): DEFAULT_CSS = """ MarkdownH2 { - background: $panel; - border: wide $background; - text-align: center; text-style: underline; - color: $text; - padding: 1; - text-style: bold; + color: $success; + &:light {color: $primary;} } """ @@ -264,11 +272,11 @@ class MarkdownH3(MarkdownHeader): DEFAULT_CSS = """ MarkdownH3 { - background: $surface; text-style: bold; - color: $text; - border-bottom: wide $foreground; + color: $success; + margin: 1 0; width: auto; + &:light {color: $primary;} } """ @@ -278,8 +286,9 @@ class MarkdownH4(MarkdownHeader): DEFAULT_CSS = """ MarkdownH4 { - text-style: underline; + text-style: bold underline; margin: 1 0; + color: $text; } """ @@ -292,6 +301,7 @@ class MarkdownH5(MarkdownHeader): text-style: bold; color: $text; margin: 1 0; + } """ @@ -338,10 +348,13 @@ class MarkdownBlockQuote(MarkdownBlock): DEFAULT_CSS = """ MarkdownBlockQuote { background: $boost; - border-left: outer $success; + border-left: outer $success-darken-2; margin: 1 0; padding: 0 1; } + MarkdownBlockQuote:light { + border-left: outer $primary; + } MarkdownBlockQuote > BlockQuote { margin-left: 2; margin-top: 1; @@ -475,6 +488,11 @@ def render(self) -> Table: table.add_row(*row) return table + async def action_link(self, href: str) -> None: + """Pass a link action on to the MarkdownTable parent.""" + if isinstance(self.parent, MarkdownTable): + await self.parent.action_link(href) + class MarkdownTable(MarkdownBlock): """A Table markdown Block.""" @@ -482,9 +500,7 @@ class MarkdownTable(MarkdownBlock): DEFAULT_CSS = """ MarkdownTable { width: 100%; - margin: 1 0; background: $panel; - border: wide $background; } """ @@ -537,10 +553,13 @@ class MarkdownBullet(Widget): width: auto; color: $success; text-style: bold; + &:light { + color: $primary; + } } """ - symbol = reactive("\u25CF") + symbol = reactive("\u25cf") """The symbol for the bullet.""" def render(self) -> Text: @@ -587,6 +606,8 @@ class MarkdownFence(MarkdownBlock): height: auto; max-height: 20; color: rgb(210,210,210); + + } MarkdownFence > * { @@ -651,8 +672,10 @@ class Markdown(Widget): DEFAULT_CSS = """ Markdown { height: auto; - margin: 0 4 1 4; + margin: 0 2 1 2; layout: vertical; + color: $text; + overflow-y: auto; } .em { text-style: italic; @@ -681,7 +704,7 @@ class Markdown(Widget): | `strong` | Target text that is styled inline with strong. | """ - BULLETS = ["\u25CF ", "▪ ", "‣ ", "• ", "⭑ "] + BULLETS = ["\u25cf ", "▪ ", "‣ ", "• ", "⭑ "] code_dark_theme: reactive[str] = reactive("material") """The theme to use for code blocks when in [dark mode][textual.app.App.dark].""" @@ -759,7 +782,7 @@ def __init__(self, markdown: Markdown, href: str) -> None: super().__init__() self.markdown: Markdown = markdown """The `Markdown` widget containing the link clicked.""" - self.href: str = href + self.href: str = unquote(href) """The link that was selected.""" @property @@ -771,9 +794,9 @@ def control(self) -> Markdown: """ return self.markdown - def _on_mount(self, _: Mount) -> None: + async def _on_mount(self, _: Mount) -> None: if self._markdown is not None: - self.update(self._markdown) + await self.update(self._markdown) def _watch_code_dark_theme(self) -> None: """React to the dark theme being changed.""" @@ -823,7 +846,7 @@ def goto_anchor(self, anchor: str) -> bool: unique = TrackedSlugs() for _, title, header_id in self._table_of_contents: if unique.slug(title) == anchor: - self.parent.scroll_to_widget(self.query_one(f"#{header_id}"), top=True) + self.query_one(f"#{header_id}").scroll_visible(top=True) return True return False @@ -841,7 +864,10 @@ async def load(self, path: Path) -> None: those that can be raised by calling [`Path.read_text`][pathlib.Path.read_text]. """ path, anchor = self.sanitize_location(str(path)) - await self.update(path.read_text(encoding="utf-8")) + data = await asyncio.get_running_loop().run_in_executor( + None, partial(path.read_text, encoding="utf-8") + ) + await self.update(data) if anchor: self.goto_anchor(anchor) @@ -865,93 +891,143 @@ def update(self, markdown: str) -> AwaitComplete: Returns: An optionally awaitable object. Await this to ensure that all children have been mounted. """ - output: list[MarkdownBlock] = [] - stack: list[MarkdownBlock] = [] parser = ( MarkdownIt("gfm-like") if self._parser_factory is None else self._parser_factory() ) - block_id: int = 0 - self._table_of_contents = [] - - for token in parser.parse(markdown): - if token.type == "heading_open": - block_id += 1 - stack.append(HEADINGS[token.tag](self, id=f"block{block_id}")) - elif token.type == "hr": - output.append(MarkdownHorizontalRule(self)) - elif token.type == "paragraph_open": - stack.append(MarkdownParagraph(self)) - elif token.type == "blockquote_open": - stack.append(MarkdownBlockQuote(self)) - elif token.type == "bullet_list_open": - stack.append(MarkdownBulletList(self)) - elif token.type == "ordered_list_open": - stack.append(MarkdownOrderedList(self)) - elif token.type == "list_item_open": - if token.info: - stack.append(MarkdownOrderedListItem(self, token.info)) - else: - item_count = sum( - 1 - for block in stack - if isinstance(block, MarkdownUnorderedListItem) - ) - stack.append( - MarkdownUnorderedListItem( - self, - self.BULLETS[item_count % len(self.BULLETS)], - ) - ) + table_of_contents = [] + + def parse_markdown(tokens) -> Iterable[MarkdownBlock]: + """Create a stream of MarkdownBlock widgets from markdown. + + Args: + tokens: List of tokens - elif token.type == "table_open": - stack.append(MarkdownTable(self)) - elif token.type == "tbody_open": - stack.append(MarkdownTBody(self)) - elif token.type == "thead_open": - stack.append(MarkdownTHead(self)) - elif token.type == "tr_open": - stack.append(MarkdownTR(self)) - elif token.type == "th_open": - stack.append(MarkdownTH(self)) - elif token.type == "td_open": - stack.append(MarkdownTD(self)) - elif token.type.endswith("_close"): - block = stack.pop() - if token.type == "heading_close": - heading = block._text.plain - level = int(token.tag[1:]) - self._table_of_contents.append((level, heading, block.id)) - if stack: - stack[-1]._blocks.append(block) + Yields: + Widgets for mounting. + """ + + stack: list[MarkdownBlock] = [] + stack_append = stack.append + block_id: int = 0 + + for token in tokens: + token_type = token.type + if token_type == "heading_open": + block_id += 1 + stack_append(HEADINGS[token.tag](self, id=f"block{block_id}")) + elif token_type == "hr": + yield MarkdownHorizontalRule(self) + elif token_type == "paragraph_open": + stack_append(MarkdownParagraph(self)) + elif token_type == "blockquote_open": + stack_append(MarkdownBlockQuote(self)) + elif token_type == "bullet_list_open": + stack_append(MarkdownBulletList(self)) + elif token_type == "ordered_list_open": + stack_append(MarkdownOrderedList(self)) + elif token_type == "list_item_open": + if token.info: + stack_append(MarkdownOrderedListItem(self, token.info)) + else: + item_count = sum( + 1 + for block in stack + if isinstance(block, MarkdownUnorderedListItem) + ) + stack_append( + MarkdownUnorderedListItem( + self, + self.BULLETS[item_count % len(self.BULLETS)], + ) + ) + elif token_type == "table_open": + stack_append(MarkdownTable(self)) + elif token_type == "tbody_open": + stack_append(MarkdownTBody(self)) + elif token_type == "thead_open": + stack_append(MarkdownTHead(self)) + elif token_type == "tr_open": + stack_append(MarkdownTR(self)) + elif token_type == "th_open": + stack_append(MarkdownTH(self)) + elif token_type == "td_open": + stack_append(MarkdownTD(self)) + elif token_type.endswith("_close"): + block = stack.pop() + if token.type == "heading_close": + heading = block._text.plain + level = int(token.tag[1:]) + table_of_contents.append((level, heading, block.id)) + if stack: + stack[-1]._blocks.append(block) + else: + yield block + elif token_type == "inline": + stack[-1].build_from_token(token) + elif token_type in ("fence", "code_block"): + fence = MarkdownFence(self, token.content.rstrip(), token.info) + if stack: + stack[-1]._blocks.append(fence) + else: + yield fence else: - output.append(block) - elif token.type == "inline": - stack[-1].build_from_token(token) - elif token.type in ("fence", "code_block"): - (stack[-1]._blocks if stack else output).append( - MarkdownFence(self, token.content.rstrip(), token.info) - ) - else: - external = self.unhandled_token(token) - if external is not None: - (stack[-1]._blocks if stack else output).append(external) - - self.post_message( - Markdown.TableOfContentsUpdated(self, self._table_of_contents).set_sender( - self - ) - ) + external = self.unhandled_token(token) + if external is not None: + if stack: + stack[-1]._blocks.append(external) + else: + yield external + markdown_block = self.query("MarkdownBlock") async def await_update() -> None: - """Update in a single batch.""" + """Update in batches.""" + BATCH_SIZE = 200 + batch: list[MarkdownBlock] = [] + tokens = await asyncio.get_running_loop().run_in_executor( + None, parser.parse, markdown + ) - with self.app.batch_update(): - await markdown_block.remove() - await self.mount_all(output) + # Lock so that you can't update with more than one document simultaneously + async with self.lock: + # Remove existing blocks for the first batch only + removed: bool = False + + async def mount_batch(batch: list[MarkdownBlock]) -> None: + """Mount a single match of blocks. + + Args: + batch: A list of blocks to mount. + """ + nonlocal removed + if removed: + await self.mount_all(batch) + else: + with self.app.batch_update(): + await markdown_block.remove() + await self.mount_all(batch) + removed = True + + for block in parse_markdown(tokens): + batch.append(block) + if len(batch) == BATCH_SIZE: + await mount_batch(batch) + batch.clear() + if batch: + await mount_batch(batch) + if not removed: + await markdown_block.remove() + + self._table_of_contents = table_of_contents + + self.post_message( + Markdown.TableOfContentsUpdated( + self, self._table_of_contents + ).set_sender(self) + ) return AwaitComplete(await_update()) @@ -1066,6 +1142,9 @@ class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True): navigator: var[Navigator] = var(Navigator) + class NavigatorUpdated(Message): + """Navigator has been changed (clicked link etc).""" + def __init__( self, markdown: str | None = None, @@ -1101,9 +1180,9 @@ def table_of_contents(self) -> MarkdownTableOfContents: """The [table of contents][textual.widgets.markdown.MarkdownTableOfContents] widget.""" return self.query_one(MarkdownTableOfContents) - def _on_mount(self, _: Mount) -> None: + async def _on_mount(self, _: Mount) -> None: if self._markdown is not None: - self.document.update(self._markdown) + await self.document.update(self._markdown) async def go(self, location: str | PurePath) -> None: """Navigate to a new document path.""" @@ -1114,16 +1193,19 @@ async def go(self, location: str | PurePath) -> None: else: # We've been asked to go to a file, optionally with an anchor. await self.document.load(self.navigator.go(location)) + self.post_message(self.NavigatorUpdated()) async def back(self) -> None: """Go back one level in the history.""" if self.navigator.back(): await self.document.load(self.navigator.location) + self.post_message(self.NavigatorUpdated()) async def forward(self) -> None: """Go forward one level in the history.""" if self.navigator.forward(): await self.document.load(self.navigator.location) + self.post_message(self.NavigatorUpdated()) async def _on_markdown_link_clicked(self, message: Markdown.LinkClicked) -> None: message.stop() diff --git a/src/textual/widgets/_option_list.py b/src/textual/widgets/_option_list.py index c59320afda..25fbaee6d4 100644 --- a/src/textual/widgets/_option_list.py +++ b/src/textual/widgets/_option_list.py @@ -1,31 +1,27 @@ -"""Provides the core of a classic vertical bounce-bar option list. - -Useful as a lightweight list view (not to be confused with ListView, which -is much richer but uses widgets for the items) and as the base for various -forms of bounce-bar menu. -""" - from __future__ import annotations -from typing import ClassVar, Iterable, NamedTuple +from typing import TYPE_CHECKING, ClassVar, Iterable, NamedTuple +import rich.repr from rich.console import RenderableType +from rich.measure import Measurement from rich.padding import Padding -from rich.repr import Result from rich.rule import Rule -from rich.style import Style -from typing_extensions import Self, TypeAlias +from rich.style import NULL_STYLE, Style -from .. import _widget_navigation +from .. import _widget_navigation, events from .._widget_navigation import Direction from ..binding import Binding, BindingType -from ..events import Click, Idle, Leave, MouseMove +from ..cache import LRUCache from ..geometry import Region, Size from ..message import Message from ..reactive import reactive from ..scroll_view import ScrollView from ..strip import Strip +if TYPE_CHECKING: + from typing_extensions import Self, TypeAlias + class DuplicateID(Exception): """Raised if a duplicate ID is used when adding options to an option list.""" @@ -35,6 +31,11 @@ class OptionDoesNotExist(Exception): """Raised when a request has been made for an option that doesn't exist.""" +class Separator: + """Class used to add a separator to an [OptionList][textual.widgets.OptionList].""" + + +@rich.repr.auto class Option: """Class that holds the details of an individual option.""" @@ -70,27 +71,13 @@ def id(self) -> str | None: """The optional ID for the option.""" return self.__id - def __rich_repr__(self) -> Result: + def __rich_repr__(self) -> rich.repr.Result: yield "prompt", self.prompt yield "id", self.id, None yield "disabled", self.disabled, False - -class Separator: - """Class used to add a separator to an [OptionList][textual.widgets.OptionList].""" - - -class Line(NamedTuple): - """Class that holds a list of segments for the line of a option.""" - - segments: Strip - """The strip of segments that make up the line.""" - - option_index: int | None = None - """The index of the [Option][textual.widgets.option_list.Option] that this line is related to. - - If the line isn't related to an option this will be `None`. - """ + def __rich__(self) -> RenderableType: + return self.__prompt class OptionLineSpan(NamedTuple): @@ -108,14 +95,6 @@ class OptionLineSpan(NamedTuple): line_count: int """The count of lines that make up the option.""" - def __contains__(self, line: object) -> bool: - # For this named tuple `in` will have a very specific meaning; but - # to keep mypy and friends happy we need to accept an object as the - # parameter. So, let's keep the type checkers happy but only accept - # an int. - assert isinstance(line, int) - return line >= self.first and line < (self.first + self.line_count) - OptionListContent: TypeAlias = "Option | Separator" """The type of an item of content in the option list. @@ -156,24 +135,6 @@ class OptionList(ScrollView, can_focus=True): | up | Move the highlight up. | """ - COMPONENT_CLASSES: ClassVar[set[str]] = { - "option-list--option", - "option-list--option-disabled", - "option-list--option-highlighted", - "option-list--option-hover", - "option-list--option-hover-highlighted", - "option-list--separator", - } - """ - | Class | Description | - | :- | :- | - | `option-list--option-disabled` | Target disabled options. | - | `option-list--option-highlighted` | Target the highlighted option. | - | `option-list--option-hover` | Target an option that has the mouse over it. | - | `option-list--option-hover-highlighted` | Target a highlighted option that has the mouse over it. | - | `option-list--separator` | Target the separators. | - """ - DEFAULT_CSS = """ OptionList { height: auto; @@ -224,6 +185,24 @@ class OptionList(ScrollView, can_focus=True): } """ + COMPONENT_CLASSES: ClassVar[set[str]] = { + "option-list--option", + "option-list--option-disabled", + "option-list--option-highlighted", + "option-list--option-hover", + "option-list--option-hover-highlighted", + "option-list--separator", + } + """ + | Class | Description | + | :- | :- | + | `option-list--option-disabled` | Target disabled options. | + | `option-list--option-highlighted` | Target the highlighted option. | + | `option-list--option-hover` | Target an option that has the mouse over it. | + | `option-list--option-hover-highlighted` | Target a highlighted option that has the mouse over it. | + | `option-list--separator` | Target the separators. | + """ + highlighted: reactive[int | None] = reactive["int | None"](None) """The index of the currently-highlighted option, or `None` if no option is highlighted.""" @@ -256,7 +235,7 @@ def control(self) -> OptionList: """ return self.option_list - def __rich_repr__(self) -> Result: + def __rich_repr__(self) -> rich.repr.Result: yield "option_list", self.option_list yield "option", self.option yield "option_id", self.option_id @@ -284,23 +263,10 @@ def __init__( classes: str | None = None, disabled: bool = False, wrap: bool = True, + tooltip: RenderableType | None = None, ): - """Initialise the option list. - - Args: - *content: The content for the option list. - name: The name of the option list. - id: The ID of the option list in the DOM. - classes: The CSS classes of the option list. - disabled: Whether the option list is disabled or not. - wrap: Should prompts be auto-wrapped? - """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) - # Internal refresh trackers. For things driven from on_idle. - self._needs_refresh_content_tracking = False - self._needs_to_scroll_to_highlight = False - self._wrap = wrap """Should we auto-wrap options? @@ -330,73 +296,109 @@ def __init__( """ self._option_ids: dict[str, int] = { - option.id: index for index, option in enumerate(self._options) if option.id + option.id: index + for index, option in enumerate(self._options) + if option.id is not None } """A dictionary of option IDs and the option indexes they relate to.""" - self._lines: list[Line] = [] - """A list of all of the individual lines that make up the option list. + self._content_render_cache: LRUCache[tuple[int, Style, int], list[Strip]] + self._content_render_cache = LRUCache(256) - Note that the size of this list will be at least the same as the number - of options, and actually greater if any prompt of any option is - multiple lines. - """ + self._lines: list[tuple[int, int]] | None = None + self._spans: list[OptionLineSpan] | None = None - self._spans: list[OptionLineSpan] = [] - """A list of the locations and sizes of all options in the option list. + self._mouse_hovering_over: int | None = None + """Used to track what the mouse is hovering over.""" + + if tooltip is not None: + self.tooltip = tooltip + + if self._options: + self.action_first() + + def _left_gutter_width(self) -> int: + """Returns the size of any left gutter that should be taken into account. - This will be the same size as the number of prompts; each entry in - the list contains the line offset of the start of the prompt, and - the count of the lines in the prompt. + Returns: + The width of the left gutter. """ + return 0 - # Initial calculation of the content tracking. - self._request_content_tracking_refresh() + def _on_mount(self): + self._populate() - self._mouse_hovering_over: int | None = None - """Used to track what the mouse is hovering over.""" + def _refresh_lines(self) -> None: + self._lines = None + self._spans = None + self._content_render_cache.clear() + self.check_idle() + + def notify_style_update(self) -> None: + self._content_render_cache.clear() + + def _on_resize(self): + self._refresh_lines() - # Finally, cause the highlighted property to settle down based on - # the state of the option list in regard to its available options. - self.action_first() + def on_idle(self): + if self._lines is None: + self._populate() - def _request_content_tracking_refresh( - self, rescroll_to_highlight: bool = False + def _add_lines( + self, new_content: list[OptionListContent], width: int, option_index=0 ) -> None: - """Request that the content tracking information gets refreshed. + """Add new lines. Args: - rescroll_to_highlight: Should the widget ensure the highlight is visible? - - Calling this method sets a flag to say the refresh should happen, - and books the refresh call in for the next idle moment. + new_content: New content to add. + width: Width to render content. + option_index: Starting option index. """ - self._needs_refresh_content_tracking = True - self._needs_to_scroll_to_highlight = rescroll_to_highlight - self.check_idle() + assert self._lines is not None + assert self._spans is not None + style = NULL_STYLE - async def _on_idle(self, _: Idle) -> None: - """Perform content tracking data refresh when idle.""" - self._refresh_content_tracking() - if self._needs_to_scroll_to_highlight: - self._needs_to_scroll_to_highlight = False - self.scroll_to_highlight() + for index, content in enumerate(new_content, len(self._lines)): + if isinstance(content, Option): + height = len( + self._render_option_content( + index, content, style, width - self._left_gutter_width() + ) + ) + + self._spans.append(OptionLineSpan(len(self._lines), height)) + self._lines.extend( + (option_index, y_offset) for y_offset in range(height) + ) + option_index += 1 + else: + self._lines.append(OptionLineSpan(-1, 0)) - def watch_show_vertical_scrollbar(self) -> None: - """Handle the vertical scrollbar visibility status changing. + self.virtual_size = Size(width, len(self._lines)) - `show_vertical_scrollbar` is watched because it has an impact on the - available width in which to render the renderables that make up the - options in the list. If a vertical scrollbar appears or disappears - we need to recalculate all the lines that make up the list. - """ - self._request_content_tracking_refresh() + def _populate(self) -> None: + """Populate the lines data-structure.""" + if self._lines is not None: + return + self._lines = [] + self._spans = [] + + self._add_lines( + self._contents, + self.scrollable_content_region.width - self._left_gutter_width(), + ) + self.refresh() - def _on_resize(self) -> None: - """Refresh the layout of the renderables in the list when resized.""" - self._request_content_tracking_refresh(rescroll_to_highlight=True) + def get_content_width(self, container: Size, viewport: Size) -> int: + """Get maximum width of options.""" + console = self.app.console + options = console.options + return max( + Measurement.get(console, options, option.prompt).maximum + for option in self._options + ) - def _on_mouse_move(self, event: MouseMove) -> None: + def _on_mouse_move(self, event: events.MouseMove) -> None: """React to the mouse moving. Args: @@ -404,18 +406,22 @@ def _on_mouse_move(self, event: MouseMove) -> None: """ self._mouse_hovering_over = event.style.meta.get("option") - def _on_leave(self, _: Leave) -> None: + def _on_leave(self, _: events.Leave) -> None: """React to the mouse leaving the widget.""" self._mouse_hovering_over = None - async def _on_click(self, event: Click) -> None: + async def _on_click(self, event: events.Click) -> None: """React to the mouse being clicked on an item. Args: event: The click event. """ clicked_option: int | None = event.style.meta.get("option") - if clicked_option is not None and not self._options[clicked_option].disabled: + if ( + clicked_option is not None + and clicked_option >= 0 + and not self._options[clicked_option].disabled + ): self.highlighted = clicked_option self.action_select() @@ -434,97 +440,38 @@ def _make_content(self, content: NewOptionListContent) -> OptionListContent: return Separator() return Option(content) - def _clear_content_tracking(self) -> None: - """Clear down the content tracking information.""" - self._lines.clear() - self._spans.clear() - - def _left_gutter_width(self) -> int: - """Returns the size of any left gutter that should be taken into account. - - Returns: - The width of the left gutter. - """ - return 0 - - def _refresh_content_tracking(self, force: bool = False) -> None: - """Refresh the various forms of option list content tracking. + def _render_option_content( + self, option_index: int, renderable: RenderableType, style: Style, width: int + ) -> list[Strip]: + """Render content for option and style. Args: - force: Optionally force the refresh. - - Raises: - DuplicateID: If there is an attempt to use a duplicate ID. + option_index: Option index to render. + renderable: The Option renderable. + style: The Rich style to render with. + width: The width of the renderable. - Without a `force` the refresh will only take place if it has been - requested via `_refresh_content_tracking`. + Returns: + A list of strips. """ + cache_key = (option_index, style, width) + if (strips := self._content_render_cache.get(cache_key, None)) is not None: + return strips - # If we don't need to refresh, don't bother. - if not self._needs_refresh_content_tracking and not force: - return - - # If we don't know our own width yet, we can't sensibly work out the - # heights of the prompts of the options yet, so let's shortcut that - # work. We'll be back here once we know our height. - if not self.size.width: - return - - self._clear_content_tracking() - self._needs_refresh_content_tracking = False - - # Set up for doing less property access work inside the loop. - lines_from = self.app.console.render_lines - add_span = self._spans.append - add_lines = self._lines.extend - - # Adjust the options for our purposes. - options = self.app.console.options.update_width( - self.scrollable_content_region.width - self._left_gutter_width() - ) - options.no_wrap = not self._wrap + padding = self.get_component_styles("option-list--option").padding + console = self.app.console + options = console.options.update_width(width) if not self._wrap: - options.overflow = "ellipsis" + options = options.update(no_wrap=True, overflow="ellipsis") + if padding: + renderable = Padding(renderable, padding) + lines = self.app.console.render_lines(renderable, options, style=style) - # Create a rule that can be used as a separator. - separator = Strip(lines_from(Rule(style=""))[0]) + style_meta = Style.from_meta({"option": option_index}) + strips = [Strip(line, width).apply_style(style_meta) for line in lines] - # Work through each item that makes up the content of the list, - # break out the individual lines that will be used to draw it, and - # also set up the tracking of the actual options. - line = 0 - option_index = 0 - padding = self.get_component_styles("option-list--option").padding - for content in self._contents: - if isinstance(content, Option): - # The content is an option, so render out the prompt and - # work out the lines needed to show it. - new_lines = [ - Line( - Strip(prompt_line).apply_style( - Style(meta={"option": option_index}) - ), - option_index, - ) - for prompt_line in lines_from( - Padding(content.prompt, padding) if padding else content.prompt, - options, - ) - ] - # Record the span information for the option. - add_span(OptionLineSpan(line, len(new_lines))) - option_index += 1 - else: - # The content isn't an option, so it must be a separator (if - # there were to be other non-option content for an option - # list it's in this if/else where we'd process it). - new_lines = [Line(separator)] - add_lines(new_lines) - line += len(new_lines) - - # Now that we know how many lines make up the whole content of the - # list, set the virtual size. - self.virtual_size = Size(self.scrollable_content_region.width, len(self._lines)) + self._content_render_cache[cache_key] = strips + return strips def _duplicate_id_check(self, candidate_items: list[OptionListContent]) -> None: """Check the items to be added for any duplicates. @@ -569,9 +516,15 @@ def add_options(self, items: Iterable[NewOptionListContent]) -> Self: """ # Only work if we have items to add; but don't make a fuss out of # zero items to add, just carry on like nothing happened. - if items: + if self._lines is None: + self._lines = [] + if self._spans is None: + self._spans = [] + new_items = list(items) + if new_items: + option_index = len(self._options) # Turn any incoming values into valid content for the list. - content = [self._make_content(item) for item in items] + content = [self._make_content(item) for item in new_items] self._duplicate_id_check(content) self._contents.extend(content) # Pull out the content that is genuine options, create any new @@ -585,7 +538,11 @@ def add_options(self, items: Iterable[NewOptionListContent]) -> Self: self._option_ids[new_option.id] = new_option_index self._options.extend(new_options) - self._refresh_content_tracking(force=True) + self._add_lines( + content, + self.scrollable_content_region.width - self._left_gutter_width(), + option_index=option_index, + ) self.refresh() return self @@ -621,11 +578,10 @@ def _remove_option(self, index: int) -> None: for option_id, option_index in self._option_ids.items() if option_index != index } - self._refresh_content_tracking(force=True) + self._refresh_lines() # Force a re-validation of the highlight. self.highlighted = self.highlighted self._mouse_hovering_over = None - self.refresh() def remove_option(self, option_id: str) -> Self: """Remove the option with the given ID. @@ -673,8 +629,7 @@ def _replace_option_prompt(self, index: int, prompt: RenderableType) -> None: OptionDoesNotExist: If there is no option with the given index. """ self.get_option_at_index(index).set_prompt(prompt) - self._refresh_content_tracking(force=True) - self.refresh() + self._refresh_lines() def replace_option_prompt(self, option_id: str, prompt: RenderableType) -> Self: """Replace the prompt of the option with the given ID. @@ -721,8 +676,7 @@ def clear_options(self) -> Self: self._option_ids.clear() self.highlighted = None self._mouse_hovering_over = None - self.virtual_size = Size(self.scrollable_content_region.width, 0) - self._refresh_content_tracking(force=True) + self._refresh_lines() return self def _set_option_disabled(self, index: int, disabled: bool) -> Self: @@ -862,80 +816,59 @@ def get_option_index(self, option_id: str) -> int: ) from None def render_line(self, y: int) -> Strip: - """Render a single line in the option list. - - Args: - y: The Y offset of the line to render. - - Returns: - A `Strip` instance for the caller to render. - """ - - scroll_x, scroll_y = self.scroll_offset + self._populate() + assert self._lines is not None - # First off, work out which line we're working on, based off the - # current scroll offset plus the line we're being asked to render. + _scroll_x, scroll_y = self.scroll_offset line_number = scroll_y + y + try: - line = self._lines[line_number] + option_index, y_offset = self._lines[line_number] except IndexError: - # An IndexError means we're drawing in an option list where - # there's more list than there are options. return Strip([]) - # Now that we know which line we're on, pull out the option index so - # we have a "local" copy to refer to rather than needing to do a - # property access multiple times. - option_index = line.option_index - - # Knowing which line we're going to be drawing, we can now go pull - # the relevant segments for the line of that particular prompt. - strip = line.segments - - # If the line we're looking at isn't associated with an option, it - # will be a separator, so let's exit early with that. - if option_index is None: - return strip.apply_style( - self.get_component_rich_style("option-list--separator") - ) - - # At this point we know we're drawing actual content. To allow for - # horizontal scrolling, let's crop the strip at the right locations. - strip = strip.crop(scroll_x, scroll_x + self.scrollable_content_region.width) + renderable = ( + Rule(style=self.get_component_rich_style("option-list--separator")) + if option_index == -1 + else self._options[option_index] + ) - highlighted = self.highlighted - mouse_over = self._mouse_hovering_over - spans = self._spans + mouse_over = self._mouse_hovering_over == option_index - # Handle drawing a disabled option. - if self._options[option_index].disabled: - return strip.apply_style( - self.get_component_rich_style("option-list--option-disabled") - ) + component_class: str | None = None - # Handle drawing a highlighted option. - if highlighted is not None and line_number in spans[highlighted]: - # Highlighted with the mouse over it? - if option_index == mouse_over: - return strip.apply_style( - self.get_component_rich_style( - "option-list--option-hover-highlighted" - ) - ) - # Just a normal highlight. - return strip.apply_style( - self.get_component_rich_style("option-list--option-highlighted") - ) - - # Perhaps the line is within an otherwise-uninteresting option that - # has the mouse hovering over it? - if mouse_over is not None and line_number in spans[mouse_over]: - return strip.apply_style( - self.get_component_rich_style("option-list--option-hover") - ) + if option_index == -1: + component_class = "option-list--separator" + else: + try: + option = self._options[option_index] + except IndexError: + pass + else: + if option.disabled: + component_class = "option-list--option-disabled" + elif self.highlighted == option_index: + component_class = "option-list--option-highlighted" + elif mouse_over: + component_class = "option-list--option-hover" + + style = ( + self.get_component_rich_style(component_class) + if component_class + else self.rich_style + ) - # It's a normal option line. - return strip.apply_style(self.rich_style) + strips = self._render_option_content( + option_index, + renderable, + style, + self.scrollable_content_region.width - self._left_gutter_width(), + ) + try: + strip = strips[y_offset] + except IndexError: + return Strip([]) + return strip def scroll_to_highlight(self, top: bool = False) -> None: """Ensure that the highlighted option is in view. @@ -944,19 +877,18 @@ def scroll_to_highlight(self, top: bool = False) -> None: top: Scroll highlight to top of the list. """ highlighted = self.highlighted - if highlighted is None: + if highlighted is None or self._spans is None: return + try: - span = self._spans[highlighted] + y, height = self._spans[highlighted] except IndexError: # Index error means we're being asked to scroll to a highlight # before all the tracking information has been worked out. # That's fine; let's just NoP that. return self.scroll_to_region( - Region( - 0, span.first, self.scrollable_content_region.width, span.line_count - ), + Region(0, y, self.scrollable_content_region.width, height), force=True, animate=False, top=top, @@ -1019,6 +951,10 @@ def _page(self, direction: Direction) -> None: # If we find ourselves in a position where we don't know where we're # going, we need a fallback location. Where we go will depend on the # direction. + self._populate() + assert self._spans is not None + assert self._lines is not None + fallback = self.action_first if direction == -1 else self.action_last highlighted = self.highlighted @@ -1037,7 +973,7 @@ def _page(self, direction: Direction) -> None: try: # Now that we've got a target line, let's figure out the # index of the target option. - target_option = self._lines[target_line].option_index + target_option: int | None = self._lines[target_line][0] except IndexError: # An index error suggests we've gone out of bounds, let's # settle on whatever the call thinks is a good place to wrap @@ -1079,3 +1015,14 @@ def action_select(self) -> None: highlighted = self.highlighted if highlighted is not None and not self._options[highlighted].disabled: self.post_message(self.OptionSelected(self, highlighted)) + + +if __name__ == "__main__": + from textual.app import App, ComposeResult + + class OptionApp(App): + def compose(self) -> ComposeResult: + yield OptionList("Foo", "Bar", "Baz") + + app = OptionApp() + app.run() diff --git a/src/textual/widgets/_radio_set.py b/src/textual/widgets/_radio_set.py index 7ea1fea4f0..99439cade7 100644 --- a/src/textual/widgets/_radio_set.py +++ b/src/textual/widgets/_radio_set.py @@ -5,6 +5,7 @@ from typing import ClassVar, Optional import rich.repr +from rich.console import RenderableType from .. import _widget_navigation from ..binding import Binding, BindingType @@ -121,6 +122,7 @@ def __init__( id: str | None = None, classes: str | None = None, disabled: bool = False, + tooltip: RenderableType | None = None, ) -> None: """Initialise the radio set. @@ -130,6 +132,7 @@ def __init__( id: The ID of the radio set in the DOM. classes: The CSS classes of the radio set. disabled: Whether the radio set is disabled or not. + tooltip: Optional tooltip. Note: When a `str` label is provided, a @@ -148,6 +151,8 @@ def __init__( classes=classes, disabled=disabled, ) + if tooltip is not None: + self.tooltip = tooltip def _on_mount(self, _: Mount) -> None: """Perform some processing once mounted in the DOM.""" diff --git a/src/textual/widgets/_rule.py b/src/textual/widgets/_rule.py index f172c0bda5..cb17595a05 100644 --- a/src/textual/widgets/_rule.py +++ b/src/textual/widgets/_rule.py @@ -1,8 +1,14 @@ from __future__ import annotations -from rich.text import Text +from typing import Iterable + +from rich.console import Console, ConsoleOptions +from rich.segment import Segment +from rich.style import Style from typing_extensions import Literal +from textual.geometry import Size + from ..app import RenderResult from ..css._error_tools import friendly_list from ..reactive import Reactive, reactive @@ -72,6 +78,36 @@ class InvalidLineStyle(Exception): """Exception raised for an invalid rule line style.""" +class HorizontalRuleRenderable: + """Renders a horizontal rule.""" + + def __init__(self, character: str, style: Style, width: int): + self.character = character + self.style = style + self.width = width + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> Iterable[Segment]: + yield Segment(self.width * self.character, self.style) + + +class VerticalRuleRenderable: + """Renders a vertical rule.""" + + def __init__(self, character: str, style: Style, height: int): + self.character = character + self.style = style + self.height = height + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> Iterable[Segment]: + segment = Segment(self.character, self.style) + new_line = Segment.line() + return ([segment, new_line] * self.height)[:-1] + + class Rule(Widget, can_focus=False): """A rule widget to separate content, similar to a `
` HTML tag.""" @@ -81,15 +117,15 @@ class Rule(Widget, can_focus=False): } Rule.-horizontal { - min-height: 1; - max-height: 1; + height: 1; margin: 1 0; + width: 1fr; } Rule.-vertical { - min-width: 1; - max-width: 1; + width: 1; margin: 0 2; + height: 1fr; } """ @@ -122,15 +158,21 @@ def __init__( super().__init__(name=name, id=id, classes=classes, disabled=disabled) self.orientation = orientation self.line_style = line_style + self.expand = True def render(self) -> RenderResult: - rule_char: str + rule_character: str + style = self.rich_style if self.orientation == "vertical": - rule_char = _VERTICAL_LINE_CHARS[self.line_style] - return Text(rule_char * self.size.height) + rule_character = _VERTICAL_LINE_CHARS[self.line_style] + return VerticalRuleRenderable( + rule_character, style, self.content_size.height + ) elif self.orientation == "horizontal": - rule_char = _HORIZONTAL_LINE_CHARS[self.line_style] - return Text(rule_char * self.size.width) + rule_character = _HORIZONTAL_LINE_CHARS[self.line_style] + return HorizontalRuleRenderable( + rule_character, style, self.content_size.width + ) else: raise InvalidRuleOrientation( f"Valid rule orientations are {friendly_list(_VALID_RULE_ORIENTATIONS)}" @@ -156,6 +198,16 @@ def validate_line_style(self, style: LineStyle) -> LineStyle: ) return style + def get_content_width(self, container: Size, viewport: Size) -> int: + if self.orientation == "horizontal": + return container.width + return 1 + + def get_content_height(self, container: Size, viewport: Size, width: int) -> int: + if self.orientation == "horizontal": + return 1 + return container.height + @classmethod def horizontal( cls, diff --git a/src/textual/widgets/_select.py b/src/textual/widgets/_select.py index 43875e8fe8..6049dd3227 100644 --- a/src/textual/widgets/_select.py +++ b/src/textual/widgets/_select.py @@ -90,6 +90,7 @@ def action_dismiss(self) -> None: def _on_blur(self, _event: events.Blur) -> None: """On blur we want to dismiss the overlay.""" self.post_message(self.Dismiss(lost_focus=True)) + self.suppress_click() def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None: """Inform parent when an option is selected.""" @@ -177,6 +178,7 @@ def _watch_has_value(self, has_value: bool) -> None: async def _on_click(self, event: events.Click) -> None: """Inform ancestor we want to toggle.""" + event.stop() self.post_message(self.Toggle()) @@ -296,6 +298,7 @@ def __init__( id: str | None = None, classes: str | None = None, disabled: bool = False, + tooltip: RenderableType | None = None, ): """Initialize the Select control. @@ -313,6 +316,7 @@ def __init__( id: The ID of the control in the DOM. classes: The CSS classes of the control. disabled: Whether the control is disabled or not. + tooltip: Optional tooltip. Raises: EmptySelectError: If no options are provided and `allow_blank` is `False`. @@ -322,6 +326,8 @@ def __init__( self.prompt = prompt self._value = value self._setup_variables_for_options(options) + if tooltip is not None: + self.tooltip = tooltip @classmethod def from_values( diff --git a/src/textual/widgets/_selection_list.py b/src/textual/widgets/_selection_list.py index 85f95b1326..3f9f961f22 100644 --- a/src/textual/widgets/_selection_list.py +++ b/src/textual/widgets/_selection_list.py @@ -646,6 +646,11 @@ def _remove_option(self, index: int) -> None: option = self.get_option_at_index(index) self._deselect(option.value) del self._values[option.value] + # Decrement index of options after the one we just removed. + self._values = { + option_value: option_index - 1 if option_index > index else option_index + for option_value, option_index in self._values.items() + } return super()._remove_option(index) def add_options( diff --git a/src/textual/widgets/_switch.py b/src/textual/widgets/_switch.py index b6dbe5f6ea..257e56ebac 100644 --- a/src/textual/widgets/_switch.py +++ b/src/textual/widgets/_switch.py @@ -2,8 +2,12 @@ from typing import TYPE_CHECKING, ClassVar +from rich.console import RenderableType + if TYPE_CHECKING: from ..app import RenderResult + from typing_extensions import Self + from ..binding import Binding, BindingType from ..events import Click from ..geometry import Size @@ -12,9 +16,6 @@ from ..scrollbar import ScrollBarRender from ..widget import Widget -if TYPE_CHECKING: - from typing_extensions import Self - class Switch(Widget, can_focus=True): """A switch widget that represents a boolean value. @@ -110,6 +111,7 @@ def __init__( id: str | None = None, classes: str | None = None, disabled: bool = False, + tooltip: RenderableType | None = None, ): """Initialise the switch. @@ -120,12 +122,15 @@ def __init__( id: The ID of the switch in the DOM. classes: The CSS classes of the switch. disabled: Whether the switch is disabled or not. + tooltip: Optional tooltip. """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) if value: self.slider_pos = 1.0 self.set_reactive(Switch.value, value) self._should_animate = animate + if tooltip is not None: + self.tooltip = tooltip def watch_value(self, value: bool) -> None: target_slider_pos = 1.0 if value else 0.0 diff --git a/src/textual/widgets/_tabbed_content.py b/src/textual/widgets/_tabbed_content.py index 54b0c38799..b69e1f24c7 100644 --- a/src/textual/widgets/_tabbed_content.py +++ b/src/textual/widgets/_tabbed_content.py @@ -9,6 +9,7 @@ from rich.text import Text, TextType from typing_extensions import Final +from .. import events from ..app import ComposeResult from ..await_complete import AwaitComplete from ..css.query import NoMatches @@ -196,6 +197,10 @@ class Disabled(TabPaneMessage): class Enabled(TabPaneMessage): """Sent when a tab pane is enabled via its reactive `disabled`.""" + @dataclass + class Focused(TabPaneMessage): + """Sent when a child widget is focused.""" + def __init__( self, title: TextType, @@ -224,6 +229,10 @@ def _watch_disabled(self, disabled: bool) -> None: """Notify the parent `TabbedContent` that a tab pane was enabled/disabled.""" self.post_message(self.Disabled(self) if disabled else self.Enabled(self)) + def _on_descendant_focus(self, event: events.DescendantFocus): + """Tell TabbedContent parent something is focused in this pane.""" + self.post_message(self.Focused(self)) + class TabbedContent(Widget): """A container with associated tabs to toggle content visibility.""" @@ -507,6 +516,12 @@ def _on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None: ) ) + def _on_tab_pane_focused(self, event: TabPane.Focused) -> None: + """One of the panes contains a widget that was programmatically focused.""" + event.stop() + if event.tab_pane.id is not None: + self.active = event.tab_pane.id + def _on_tabs_cleared(self, event: Tabs.Cleared) -> None: """Called when there are no active tabs. The tabs may have been cleared, or they may all be hidden.""" diff --git a/src/textual/widgets/_tabs.py b/src/textual/widgets/_tabs.py index 96d9c7c554..dce53e0e81 100644 --- a/src/textual/widgets/_tabs.py +++ b/src/textual/widgets/_tabs.py @@ -42,7 +42,7 @@ class Underline(Widget): """ | Class | Description | | :- | :- | - | `underline-bar` | Style of the bar (may be used to change the color). | + | `underline--bar` | Style of the bar (may be used to change the color). | """ highlight_start = reactive(0) @@ -510,7 +510,7 @@ def remove_tab(self, tab_or_id: Tab | str | None) -> AwaitComplete: An optionally awaitable object that waits for the tab to be removed. """ if not tab_or_id: - return AwaitComplete(self.app._remove_nodes([], None)) + return AwaitComplete() if isinstance(tab_or_id, Tab): remove_tab = tab_or_id @@ -518,7 +518,7 @@ def remove_tab(self, tab_or_id: Tab | str | None) -> AwaitComplete: try: remove_tab = self.query_one(f"#tabs-list > #{tab_or_id}", Tab) except NoMatches: - return AwaitComplete(self.app._remove_nodes([], None)) + return AwaitComplete() removing_active_tab = remove_tab.has_class("-active") next_tab = self._next_active @@ -673,7 +673,7 @@ def _on_underline_clicked(self, event: Underline.Clicked) -> None: offset = event.offset + (0, -1) self.focus() for tab in self.query(Tab): - if offset in tab.region: + if offset in tab.region and not tab.disabled: self._activate_tab(tab) break diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 37231b21aa..7b1c10cb6b 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import TYPE_CHECKING, ClassVar, Iterable, Optional, Sequence, Tuple +from rich.console import RenderableType from rich.style import Style from rich.text import Text from typing_extensions import Literal @@ -300,6 +301,9 @@ class TextArea(ScrollView): Changing this value will immediately re-render the `TextArea`.""" + line_number_start: Reactive[int] = reactive(1, init=False) + """The line number the first line should be.""" + indent_width: Reactive[int] = reactive(4, init=False) """The width of tabs or the multiple of spaces to align to on pressing the `tab` key. @@ -369,11 +373,13 @@ def __init__( tab_behavior: Literal["focus", "indent"] = "focus", read_only: bool = False, show_line_numbers: bool = False, + line_number_start: int = 1, max_checkpoints: int = 50, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, + tooltip: RenderableType | None = None, ) -> None: """Construct a new `TextArea`. @@ -385,11 +391,13 @@ def __init__( tab_behavior: If 'focus', pressing tab will switch focus. If 'indent', pressing tab will insert a tab. read_only: Enable read-only mode. This prevents edits using the keyboard. show_line_numbers: Show line numbers on the left edge. + line_number_start: What line number to start on. max_checkpoints: The maximum number of undo history checkpoints to retain. name: The name of the `TextArea` widget. id: The ID of the widget, used to refer to it from Textual CSS. classes: One or more Textual CSS compatible class names separated by spaces. disabled: True if the widget is disabled. + tooltip: Optional tooltip. """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) @@ -452,12 +460,16 @@ def __init__( self.set_reactive(TextArea.soft_wrap, soft_wrap) self.set_reactive(TextArea.read_only, read_only) self.set_reactive(TextArea.show_line_numbers, show_line_numbers) + self.set_reactive(TextArea.line_number_start, line_number_start) self.tab_behavior = tab_behavior # When `app.dark` is toggled, reset the theme (since it caches values). self.watch(self.app, "dark", self._app_dark_toggled, init=False) + if tooltip is not None: + self.tooltip = tooltip + @classmethod def code_editor( cls, @@ -469,11 +481,13 @@ def code_editor( tab_behavior: Literal["focus", "indent"] = "indent", read_only: bool = False, show_line_numbers: bool = True, + line_number_start: int = 1, max_checkpoints: int = 50, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, + tooltip: RenderableType | None = None, ) -> TextArea: """Construct a new `TextArea` with sensible defaults for editing code. @@ -487,10 +501,12 @@ def code_editor( soft_wrap: Enable soft wrapping. tab_behavior: If 'focus', pressing tab will switch focus. If 'indent', pressing tab will insert a tab. show_line_numbers: Show line numbers on the left edge. + line_number_start: What line number to start on. name: The name of the `TextArea` widget. id: The ID of the widget, used to refer to it from Textual CSS. classes: One or more Textual CSS compatible class names separated by spaces. disabled: True if the widget is disabled. + tooltip: Optional tooltip """ return cls( text, @@ -500,11 +516,13 @@ def code_editor( tab_behavior=tab_behavior, read_only=read_only, show_line_numbers=show_line_numbers, + line_number_start=line_number_start, max_checkpoints=max_checkpoints, name=name, id=id, classes=classes, disabled=disabled, + tooltip=tooltip, ) @staticmethod @@ -682,6 +700,11 @@ def _watch_show_line_numbers(self) -> None: self._rewrap_and_refresh_virtual_size() self.scroll_cursor_visible() + def _watch_line_number_start(self) -> None: + """The line number gutter max size might change and contributes to virtual size, so recalculate.""" + self._rewrap_and_refresh_virtual_size() + self.scroll_cursor_visible() + def _watch_indent_width(self) -> None: """Changing width of tabs will change the document display width.""" self._rewrap_and_refresh_virtual_size() @@ -969,6 +992,21 @@ def _refresh_size(self) -> None: width, height = self.document.get_size(self.indent_width) self.virtual_size = Size(width + self.gutter_width + 1, height) + def get_line(self, line_index: int) -> Text: + """Retrieve the line at the given line index. + + You can stylize the Text object returned here to apply additional + styling to TextArea content. + + Args: + line_index: The index of the line. + + Returns: + A `rich.Text` object containing the requested line. + """ + line_string = self.document.get_line(line_index) + return Text(line_string, end="") + def render_line(self, y: int) -> Strip: """Render a single line of the TextArea. Called by Textual. @@ -982,7 +1020,6 @@ def render_line(self, y: int) -> Strip: if theme: theme.apply_css(self) - document = self.document wrapped_document = self.wrapped_document scroll_x, scroll_y = self.scroll_offset @@ -1006,9 +1043,7 @@ def render_line(self, y: int) -> Strip: line_index, section_offset = line_info - # Get the line from the Document. - line_string = document.get_line(line_index) - line = Text(line_string, end="") + line = self.get_line(line_index) line_character_count = len(line) line.tab_size = self.indent_width line.set_length(line_character_count + 1) # space at end for cursor @@ -1058,7 +1093,7 @@ def render_line(self, y: int) -> Strip: highlights = self._highlights if highlights and theme: - line_bytes = _utf8_encode(line_string) + line_bytes = _utf8_encode(line.plain) byte_to_codepoint = build_byte_to_codepoint_dict(line_bytes) get_highlight_from_theme = theme.syntax_styles.get line_highlights = highlights[line_index] @@ -1121,7 +1156,9 @@ def render_line(self, y: int) -> Strip: gutter_style = theme.gutter_style gutter_width_no_margin = gutter_width - 2 - gutter_content = str(line_index + 1) if section_offset == 0 else "" + gutter_content = ( + str(line_index + self.line_number_start) if section_offset == 0 else "" + ) gutter = Text( f"{gutter_content:>{gutter_width_no_margin}} ", style=gutter_style or "", @@ -1446,7 +1483,8 @@ def gutter_width(self) -> int: # The longest number in the gutter plus two extra characters: `│ `. gutter_margin = 2 gutter_width = ( - len(str(self.document.line_count)) + gutter_margin + len(str(self.document.line_count - 1 + self.line_number_start)) + + gutter_margin if self.show_line_numbers else 0 ) @@ -1501,17 +1539,18 @@ async def _on_mouse_move(self, event: events.MouseMove) -> None: def _end_mouse_selection(self) -> None: """Finalize the selection that has been made using the mouse.""" - self._selecting = False - self.release_mouse() - self.record_cursor_width() - self._restart_blink() + if self._selecting: + self._selecting = False + self.release_mouse() + self.record_cursor_width() + self._restart_blink() async def _on_mouse_up(self, event: events.MouseUp) -> None: """Finalize the selection that has been made using the mouse.""" self._end_mouse_selection() async def _on_hide(self, event: events.Hide) -> None: - """Finalize the selection that has been made using the mouse when thew widget is hidden.""" + """Finalize the selection that has been made using the mouse when the widget is hidden.""" self._end_mouse_selection() async def _on_paste(self, event: events.Paste) -> None: diff --git a/src/textual/widgets/_toast.py b/src/textual/widgets/_toast.py index a8198f4b53..0a057523f7 100644 --- a/src/textual/widgets/_toast.py +++ b/src/textual/widgets/_toast.py @@ -183,7 +183,6 @@ def show(self, notifications: Notifications) -> None: Args: notifications: The notifications to show. """ - # Look for any stale toasts and remove them. for toast in self.query(Toast): if toast._notification not in notifications: diff --git a/src/textual/widgets/_toggle_button.py b/src/textual/widgets/_toggle_button.py index 6af1ea6a82..dca89cdbd0 100644 --- a/src/textual/widgets/_toggle_button.py +++ b/src/textual/widgets/_toggle_button.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, ClassVar +from rich.console import RenderableType from rich.style import Style from rich.text import Text, TextType @@ -130,6 +131,7 @@ def __init__( id: str | None = None, classes: str | None = None, disabled: bool = False, + tooltip: RenderableType | None = None, ) -> None: """Initialise the toggle. @@ -141,6 +143,7 @@ def __init__( id: The ID of the toggle in the DOM. classes: The CSS classes of the toggle. disabled: Whether the button is disabled or not. + tooltip: RenderableType | None = None, """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) self._button_first = button_first @@ -148,6 +151,8 @@ def __init__( with self.prevent(self.Changed): self.value = value self._label = self._make_label(label) + if tooltip is not None: + self.tooltip = tooltip def _make_label(self, label: TextType) -> Text: """Make a `Text` label from a `TextType` value. @@ -164,6 +169,7 @@ def _make_label(self, label: TextType) -> Text: label = label.split()[0] except IndexError: pass + return label @property @@ -211,7 +217,9 @@ def render(self) -> RenderResult: """ button = self._button label = self._label.copy() - label.stylize(self.get_component_rich_style("toggle--label", partial=True)) + label.stylize_before( + self.get_component_rich_style("toggle--label", partial=True) + ) spacer = " " if label else "" return Text.assemble( *( diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 686382433d..9ee5227444 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -9,7 +9,7 @@ from rich.style import NULL_STYLE, Style from rich.text import Text, TextType -from .. import events +from .. import events, on from .._immutable_sequence_view import ImmutableSequenceView from .._loop import loop_last from .._segment_tools import line_pad @@ -70,13 +70,13 @@ def _get_guide_width(self, guide_depth: int, show_root: bool) -> int: Width in cells. """ if show_root: - return 2 + (max(0, len(self.path) - 1)) * guide_depth + width = (max(0, len(self.path) - 1)) * guide_depth else: - guides = 2 + width = 0 if len(self.path) > 1: - guides += (len(self.path) - 1) * guide_depth + width += (len(self.path) - 1) * guide_depth - return guides + return width class TreeNodes(ImmutableSequenceView["TreeNode[TreeDataType]"]): @@ -495,7 +495,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): guide_depth = reactive(4, init=False) """The indent depth of tree nodes.""" auto_expand = var(True) - """Auto expand tree nodes when clicked.""" + """Auto expand tree nodes when they are selected.""" LINES: dict[str, tuple[str, str, str, str]] = { "default": ( @@ -745,7 +745,7 @@ def reset(self, label: TextType, data: TreeDataType | None = None) -> Self: self.root.data = data return self - def select_node(self, node: TreeNode[TreeDataType] | None) -> None: + def move_cursor(self, node: TreeNode[TreeDataType] | None) -> None: """Move the cursor to the given node, or reset cursor. Args: @@ -753,6 +753,23 @@ def select_node(self, node: TreeNode[TreeDataType] | None) -> None: """ self.cursor_line = -1 if node is None else node._line + def select_node(self, node: TreeNode[TreeDataType] | None) -> None: + """Move the cursor to the given node and select it, or reset cursor. + + Args: + node: A tree node to move the cursor to and select, or None to reset cursor. + """ + self.move_cursor(node) + if node is not None: + self.post_message(Tree.NodeSelected(node)) + + @on(NodeSelected) + def _expand_node_on_select(self, event: NodeSelected[TreeDataType]) -> None: + """When the node is selected, expand the node if `auto_expand` is True.""" + node = event.node + if self.auto_expand: + self._toggle_node(node) + def get_node_at_line(self, line_no: int) -> TreeNode[TreeDataType] | None: """Get the node for a given line. @@ -1089,6 +1106,8 @@ def get_guides(style: Style) -> tuple[str, str, str, str]: else: line_style = base_style + line_style += Style(meta={"line": y}) + guides = Text(style=line_style) guides_append = guides.append @@ -1124,7 +1143,7 @@ def get_guides(style: Style) -> tuple[str, str, str, str]: ) label = self.render_label(line.path[-1], line_style, label_style).copy() - label.stylize(Style(meta={"node": line.node._id, "line": y})) + label.stylize(Style(meta={"node": line.node._id})) guides.append(label) segments = list(guides.render(self.app.console)) @@ -1231,6 +1250,4 @@ def action_select_cursor(self) -> None: pass else: node = line.path[-1] - if self.auto_expand: - self._toggle_node(node) - self.post_message(self.NodeSelected(node)) + self.post_message(Tree.NodeSelected(node)) diff --git a/src/textual/worker.py b/src/textual/worker.py index f7f10b60ae..d3455b9a61 100644 --- a/src/textual/worker.py +++ b/src/textual/worker.py @@ -293,7 +293,7 @@ async def do_work() -> ResultType: return asyncio.run(do_work()) def run_coroutine( - work: Callable[[], Coroutine[None, None, ResultType]] + work: Callable[[], Coroutine[None, None, ResultType]], ) -> ResultType: """Set the active worker and await coroutine.""" return run_awaitable(work()) diff --git a/tests/command_palette/test_events.py b/tests/command_palette/test_events.py new file mode 100644 index 0000000000..47f5b8eee1 --- /dev/null +++ b/tests/command_palette/test_events.py @@ -0,0 +1,75 @@ +from typing import Union +from unittest import mock + +from textual import on +from textual.app import App +from textual.command import CommandPalette, Hit, Hits, Provider + +CommandPaletteEvent = Union[ + CommandPalette.Opened, CommandPalette.Closed, CommandPalette.OptionHighlighted +] + + +class SimpleSource(Provider): + async def search(self, query: str) -> Hits: + def goes_nowhere_does_nothing() -> None: + pass + + yield Hit(1, query, goes_nowhere_does_nothing, query) + + +class AppWithActiveCommandPalette(App[None]): + COMMANDS = {SimpleSource} + + def __init__(self) -> None: + super().__init__() + self.events: list[CommandPaletteEvent] = [] + + def on_mount(self) -> None: + self.action_command_palette() + + @on(CommandPalette.Opened) + @on(CommandPalette.Closed) + @on(CommandPalette.OptionHighlighted) + def record_event( + self, + event: CommandPaletteEvent, + ) -> None: + self.events.append(event) + + +async def test_command_palette_opened_event(): + app = AppWithActiveCommandPalette() + async with app.run_test(): + assert app.events == [CommandPalette.Opened()] + + +async def test_command_palette_closed_event(): + app = AppWithActiveCommandPalette() + async with app.run_test() as pilot: + await pilot.press("escape") + assert app.events == [CommandPalette.Opened(), CommandPalette.Closed(False)] + + +async def test_command_palette_closed_event_value(): + app = AppWithActiveCommandPalette() + async with app.run_test() as pilot: + await pilot.press("a") + await pilot.press("down") + await pilot.press("enter") + assert app.events == [ + CommandPalette.Opened(), + CommandPalette.OptionHighlighted(mock.ANY), + CommandPalette.Closed(True), + ] + + +async def test_command_palette_option_highlighted_event(): + app = AppWithActiveCommandPalette() + async with app.run_test() as pilot: + await pilot.press("a") + await pilot.press("down") + assert app.events == [ + CommandPalette.Opened(), + CommandPalette.OptionHighlighted(mock.ANY), + ] diff --git a/tests/css/test_screen_css.py b/tests/css/test_screen_css.py index 54138fb8a5..61d26a6a38 100644 --- a/tests/css/test_screen_css.py +++ b/tests/css/test_screen_css.py @@ -210,7 +210,7 @@ async def test_screen_css_switch_screen_type_by_name(): class MyApp(SwitchBaseApp): SCREENS = {"screenwithcss": ScreenWithCSS} - def key_p(self): + async def key_p(self): self.switch_screen("screenwithcss") def key_o(self): diff --git a/tests/deadlock.py b/tests/deadlock.py new file mode 100644 index 0000000000..b27472b1df --- /dev/null +++ b/tests/deadlock.py @@ -0,0 +1,21 @@ +""" +Called by test_pipe.py + +""" + +from textual.app import App +from textual.binding import Binding +from textual.widgets import Footer + + +class MyApp(App[None]): + BINDINGS = [ + Binding(key="q", action="quit", description="Quit the app"), + ] + + def compose(self): + yield Footer() + + +app = MyApp() +app.run() diff --git a/tests/document/test_document.py b/tests/document/test_document.py index ad1c82b834..ebf8474c8e 100644 --- a/tests/document/test_document.py +++ b/tests/document/test_document.py @@ -137,3 +137,16 @@ def test_location_from_index(text): len(lines) - 1, len(lines[-1]), ) + + +@pytest.mark.parametrize( + "text", [TEXT, TEXT_NEWLINE, TEXT_WINDOWS, TEXT_WINDOWS_NEWLINE] +) +def test_document_end(text): + """The location is always what we expect.""" + document = Document(text) + expected_line_number = ( + len(text.splitlines()) if text.endswith("\n") else len(text.splitlines()) - 1 + ) + expected_pos = 0 if text.endswith("\n") else (len(text.splitlines()[-1])) + assert document.end == (expected_line_number, expected_pos) diff --git a/tests/listview/test_listview_initial_index.py b/tests/listview/test_listview_initial_index.py new file mode 100644 index 0000000000..515de4f044 --- /dev/null +++ b/tests/listview/test_listview_initial_index.py @@ -0,0 +1,42 @@ +import pytest + +from textual.app import App, ComposeResult +from textual.widgets import Label, ListItem, ListView + + +@pytest.mark.parametrize( + "initial_index,expected_index", + [ + (0, 1), + (1, 1), + (2, 4), + (3, 4), + (4, 4), + (5, 5), + (6, 7), + (7, 7), + (8, 1), + ], +) +async def test_listview_initial_index(initial_index, expected_index) -> None: + """Regression test for https://github.com/Textualize/textual/issues/4449""" + + class ListViewDisabledItemsApp(App[None]): + def compose(self) -> ComposeResult: + yield ListView( + ListItem(Label("0"), disabled=True), + ListItem(Label("1")), + ListItem(Label("2"), disabled=True), + ListItem(Label("3"), disabled=True), + ListItem(Label("4")), + ListItem(Label("5")), + ListItem(Label("6"), disabled=True), + ListItem(Label("7")), + ListItem(Label("8"), disabled=True), + initial_index=initial_index, + ) + + app = ListViewDisabledItemsApp() + async with app.run_test() as pilot: + list_view = pilot.app.query_one(ListView) + assert list_view._index == expected_index diff --git a/tests/listview/test_listview_navigation.py b/tests/listview/test_listview_navigation.py index 4a93bb2695..680b8ced68 100644 --- a/tests/listview/test_listview_navigation.py +++ b/tests/listview/test_listview_navigation.py @@ -35,7 +35,6 @@ async def test_keyboard_navigation_with_disabled_items() -> None: await pilot.press("up") assert app.highlighted == [ - None, "1", "4", "5", diff --git a/tests/listview/test_listview_remove_items.py b/tests/listview/test_listview_remove_items.py new file mode 100644 index 0000000000..43501cdf19 --- /dev/null +++ b/tests/listview/test_listview_remove_items.py @@ -0,0 +1,32 @@ +from textual.app import App, ComposeResult +from textual.widgets import ListView, ListItem, Label + + +class ListViewApp(App[None]): + def compose(self) -> ComposeResult: + yield ListView( + ListItem(Label("0")), + ListItem(Label("1")), + ListItem(Label("2")), + ListItem(Label("3")), + ListItem(Label("4")), + ListItem(Label("5")), + ListItem(Label("6")), + ListItem(Label("7")), + ListItem(Label("8")), + ) + + +async def test_listview_remove_items() -> None: + """Regression test for https://github.com/Textualize/textual/issues/4735""" + app = ListViewApp() + async with app.run_test() as pilot: + listview = pilot.app.query_one(ListView) + assert len(listview) == 9 + await listview.remove_items(range(4, 9)) + assert len(listview) == 4 + + +if __name__ == "__main__": + app = ListViewApp() + app.run() diff --git a/tests/option_list/test_option_list_mouse_click.py b/tests/option_list/test_option_list_mouse_click.py new file mode 100644 index 0000000000..ec0182c470 --- /dev/null +++ b/tests/option_list/test_option_list_mouse_click.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from textual import on +from textual.app import App, ComposeResult +from textual.widgets import OptionList +from textual.widgets.option_list import Option, Separator + + +class OptionListApp(App[None]): + messages: list[str] = [] + + def compose(self) -> ComposeResult: + yield OptionList( + Option("0"), + Separator(), + Option("1"), + ) + + @on(OptionList.OptionMessage) + def log_option_message( + self, + event: OptionList.OptionMessage, + ) -> None: + self.messages.append(event.__class__.__name__) + + +async def test_option_list_clicking_separator() -> None: + """Regression test for https://github.com/Textualize/textual/issues/4710""" + app = OptionListApp() + async with app.run_test() as pilot: + option_list = pilot.app.query_one(OptionList) + expected_messages = ["OptionHighlighted"] # Initial highlight + assert option_list.highlighted == 0 + assert app.messages == expected_messages + + # Select the second option with the mouse + await pilot.click(OptionList, offset=(3, 3)) + expected_messages.extend(["OptionHighlighted", "OptionSelected"]) + assert option_list.highlighted == 1 + assert app.messages == expected_messages + + # Click the separator - there should be no change + await pilot.click(OptionList, offset=(3, 2)) + assert option_list.highlighted == 1 + assert app.messages == expected_messages diff --git a/tests/selection_list/test_selection_list_create.py b/tests/selection_list/test_selection_list_create.py index e189a3c9d5..114bb69bd3 100644 --- a/tests/selection_list/test_selection_list_create.py +++ b/tests/selection_list/test_selection_list_create.py @@ -114,3 +114,12 @@ async def test_options_are_available_soon() -> None: selection = Selection("", 0, id="some_id") selection_list = SelectionList[int](selection) assert selection_list.get_option("some_id") is selection + + +async def test_removing_option_updates_indexes() -> None: + async with SelectionListApp().run_test() as pilot: + selections = pilot.app.query_one(SelectionList) + assert selections._values == {n: n for n in range(5)} + + selections.remove_option_at_index(0) + assert selections._values == {n + 1: n for n in range(4)} diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 5941436dd7..5efd95785d 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -21,139 +21,139 @@ font-weight: 700; } - .terminal-904862513-matrix { + .terminal-1827861245-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-904862513-title { + .terminal-1827861245-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-904862513-r1 { fill: #cad1d6 } - .terminal-904862513-r2 { fill: #7ae998 } - .terminal-904862513-r3 { fill: #c5c8c6 } - .terminal-904862513-r4 { fill: #4ebf71;font-weight: bold } - .terminal-904862513-r5 { fill: #008139 } - .terminal-904862513-r6 { fill: #e3dbce } - .terminal-904862513-r7 { fill: #e1e1e1 } - .terminal-904862513-r8 { fill: #e76580 } - .terminal-904862513-r9 { fill: #f5e5e9;font-weight: bold } - .terminal-904862513-r10 { fill: #780028 } + .terminal-1827861245-r1 { fill: #cad1d6 } + .terminal-1827861245-r2 { fill: #7ae998 } + .terminal-1827861245-r3 { fill: #c5c8c6 } + .terminal-1827861245-r4 { fill: #4ebf71;font-weight: bold } + .terminal-1827861245-r5 { fill: #008139 } + .terminal-1827861245-r6 { fill: #e3dbce } + .terminal-1827861245-r7 { fill: #e1e1e1 } + .terminal-1827861245-r8 { fill: #e76580 } + .terminal-1827861245-r9 { fill: #f5e5e9;font-weight: bold } + .terminal-1827861245-r10 { fill: #780028 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AlignContainersApp + AlignContainersApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - center - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - middle - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  center  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  middle  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + @@ -184,147 +184,147 @@ font-weight: 700; } - .terminal-1605463770-matrix { + .terminal-4256246556-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1605463770-title { + .terminal-4256246556-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1605463770-r1 { fill: #1f1f1f } - .terminal-1605463770-r2 { fill: #c5c8c6 } - .terminal-1605463770-r3 { fill: #aa3731 } - .terminal-1605463770-r4 { fill: #c8837f } - .terminal-1605463770-r5 { fill: #448c27 } - .terminal-1605463770-r6 { fill: #8ab679 } - .terminal-1605463770-r7 { fill: #cb9000 } - .terminal-1605463770-r8 { fill: #dbb862 } - .terminal-1605463770-r9 { fill: #325cc0 } - .terminal-1605463770-r10 { fill: #8099d5 } - .terminal-1605463770-r11 { fill: #7a3e9d } - .terminal-1605463770-r12 { fill: #ab87c0 } - .terminal-1605463770-r13 { fill: #0083b2 } - .terminal-1605463770-r14 { fill: #62b0cc } - .terminal-1605463770-r15 { fill: #f7f7f7 } - .terminal-1605463770-r16 { fill: #f6f6f6 } - .terminal-1605463770-r17 { fill: #000000 } - .terminal-1605463770-r18 { fill: #626262 } + .terminal-4256246556-r1 { fill: #1f1f1f } + .terminal-4256246556-r2 { fill: #c5c8c6 } + .terminal-4256246556-r3 { fill: #aa3731 } + .terminal-4256246556-r4 { fill: #c8837f } + .terminal-4256246556-r5 { fill: #448c27 } + .terminal-4256246556-r6 { fill: #8ab679 } + .terminal-4256246556-r7 { fill: #cb9000 } + .terminal-4256246556-r8 { fill: #dbb862 } + .terminal-4256246556-r9 { fill: #325cc0 } + .terminal-4256246556-r10 { fill: #8099d5 } + .terminal-4256246556-r11 { fill: #7a3e9d } + .terminal-4256246556-r12 { fill: #ab87c0 } + .terminal-4256246556-r13 { fill: #0083b2 } + .terminal-4256246556-r14 { fill: #62b0cc } + .terminal-4256246556-r15 { fill: #f7f7f7 } + .terminal-4256246556-r16 { fill: #f6f6f6 } + .terminal-4256246556-r17 { fill: #000000 } + .terminal-4256246556-r18 { fill: #626262 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AnsiMappingApp + AnsiMappingApp - - - - Foreground & background - red - dim red - green - dim green - yellow - dim yellow - blue - dim blue - magenta - dim magenta - cyan - dim cyan - white - dim white - black - dim black - - - - - - + + + + Foreground & background                                                          + red + dim red + green + dim green + yellow + dim yellow + blue + dim blue + magenta + dim magenta + cyan + dim cyan + white + dim white + black + dim black + + + + + + @@ -355,145 +355,145 @@ font-weight: 700; } - .terminal-747464051-matrix { + .terminal-1802641872-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-747464051-title { + .terminal-1802641872-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-747464051-r1 { fill: #e1e1e1 } - .terminal-747464051-r2 { fill: #c5c8c6 } - .terminal-747464051-r3 { fill: #f4005f } - .terminal-747464051-r4 { fill: #9e0c45 } - .terminal-747464051-r5 { fill: #98e024 } - .terminal-747464051-r6 { fill: #679221 } - .terminal-747464051-r7 { fill: #fd971f } - .terminal-747464051-r8 { fill: #a3661e } - .terminal-747464051-r9 { fill: #9d65ff } - .terminal-747464051-r10 { fill: #6a48a5 } - .terminal-747464051-r11 { fill: #58d1eb } - .terminal-747464051-r12 { fill: #408999 } - .terminal-747464051-r13 { fill: #c4c5b5 } - .terminal-747464051-r14 { fill: #818278 } - .terminal-747464051-r15 { fill: #1a1a1a } - .terminal-747464051-r16 { fill: #1b1b1b } + .terminal-1802641872-r1 { fill: #e1e1e1 } + .terminal-1802641872-r2 { fill: #c5c8c6 } + .terminal-1802641872-r3 { fill: #f4005f } + .terminal-1802641872-r4 { fill: #9e0c45 } + .terminal-1802641872-r5 { fill: #98e024 } + .terminal-1802641872-r6 { fill: #679221 } + .terminal-1802641872-r7 { fill: #fd971f } + .terminal-1802641872-r8 { fill: #a3661e } + .terminal-1802641872-r9 { fill: #9d65ff } + .terminal-1802641872-r10 { fill: #6a48a5 } + .terminal-1802641872-r11 { fill: #58d1eb } + .terminal-1802641872-r12 { fill: #408999 } + .terminal-1802641872-r13 { fill: #c4c5b5 } + .terminal-1802641872-r14 { fill: #818278 } + .terminal-1802641872-r15 { fill: #1a1a1a } + .terminal-1802641872-r16 { fill: #1b1b1b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AnsiMappingApp + AnsiMappingApp - - - - Foreground & background - red - dim red - green - dim green - yellow - dim yellow - blue - dim blue - magenta - dim magenta - cyan - dim cyan - white - dim white - black - dim black - - - - - - + + + + Foreground & background                                                          + red + dim red + green + dim green + yellow + dim yellow + blue + dim blue + magenta + dim magenta + cyan + dim cyan + white + dim white + black + dim black + + + + + + @@ -524,134 +524,134 @@ font-weight: 700; } - .terminal-530450620-matrix { + .terminal-2159330678-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-530450620-title { + .terminal-2159330678-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-530450620-r1 { fill: #e1e1e1 } - .terminal-530450620-r2 { fill: #c5c8c6 } - .terminal-530450620-r3 { fill: #1e1e1e } - .terminal-530450620-r4 { fill: #121212 } - .terminal-530450620-r5 { fill: #e2e2e2 } + .terminal-2159330678-r1 { fill: #e1e1e1 } + .terminal-2159330678-r2 { fill: #c5c8c6 } + .terminal-2159330678-r3 { fill: #1e1e1e } + .terminal-2159330678-r4 { fill: #121212 } + .terminal-2159330678-r5 { fill: #e2e2e2 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AppBlurApp + AppBlurApp - - - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - This should be the blur style - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - This should also be the blur style - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - + + + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + This should be the blur style      + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + This should also be the blur style + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + @@ -682,141 +682,141 @@ font-weight: 700; } - .terminal-3685857257-matrix { + .terminal-3141096165-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3685857257-title { + .terminal-3141096165-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3685857257-r1 { fill: #00ffff } - .terminal-3685857257-r2 { fill: #c5c8c6 } - .terminal-3685857257-r3 { fill: #e1e1e1 } - .terminal-3685857257-r4 { fill: #008000 } - .terminal-3685857257-r5 { fill: #ff0000 } - .terminal-3685857257-r6 { fill: #e1e1e1;font-weight: bold } - .terminal-3685857257-r7 { fill: #dde6ed } + .terminal-3141096165-r1 { fill: #00ffff } + .terminal-3141096165-r2 { fill: #c5c8c6 } + .terminal-3141096165-r3 { fill: #e1e1e1 } + .terminal-3141096165-r4 { fill: #008000 } + .terminal-3141096165-r5 { fill: #ff0000 } + .terminal-3141096165-r6 { fill: #e1e1e1;font-weight: bold } + .terminal-3141096165-r7 { fill: #dde6ed } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - FRApp + FRApp - - - - ────────────────────────────────────────────────────────────────────────────── - ──────────────────────────── - Hello one line - ────────────────────────── - Widget#child - - - - - - - - - - - - - - ────────────────────────── - - Two - Lines with 1x2 margin - - ──────────────────────────── - ────────────────────────────────────────────────────────────────────────────── + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + ┌────────────────────────────┐ + Hello one line               + ┌──────────────────────────┐ + Widget#child + + + + + + + + + + + + + + └──────────────────────────┘ + + Two + Lines with 1x2 margin + + └────────────────────────────┘ + └──────────────────────────────────────────────────────────────────────────────┘ @@ -846,136 +846,136 @@ font-weight: 700; } - .terminal-2376794324-matrix { + .terminal-2558549670-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2376794324-title { + .terminal-2558549670-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2376794324-r1 { fill: #008000 } - .terminal-2376794324-r2 { fill: #c5c8c6 } - .terminal-2376794324-r3 { fill: #e1e1e1 } - .terminal-2376794324-r4 { fill: #1e1e1e } - .terminal-2376794324-r5 { fill: #121212 } - .terminal-2376794324-r6 { fill: #e2e2e2 } + .terminal-2558549670-r1 { fill: #008000 } + .terminal-2558549670-r2 { fill: #c5c8c6 } + .terminal-2558549670-r3 { fill: #e1e1e1 } + .terminal-2558549670-r4 { fill: #1e1e1e } + .terminal-2558549670-r5 { fill: #121212 } + .terminal-2558549670-r6 { fill: #e2e2e2 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - GridApp + GridApp - - - - ────────────────────────────────────────────────────────────────────────────── - foo▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - Longer label▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - foo▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - Longer label▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - foo bar foo bar foo bar foo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - bar foo bar foo bar foo bar  - foo bar foo bar foo bar ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - Longer label▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ────────────────────────────────────────────────────────────────────────────── + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + foo         ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + Longer label▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + └──────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────┐ + foo▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + Longer label▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + └──────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────┐ + foo bar foo bar foo bar foo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + bar foo bar foo bar foo bar  + foo bar foo bar foo bar ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + Longer label                  ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + └──────────────────────────────────────────────────────────────────────────────┘ @@ -1005,136 +1005,303 @@ font-weight: 700; } - .terminal-3669917786-matrix { + .terminal-3513309286-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3669917786-title { + .terminal-3513309286-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3669917786-r1 { fill: #c5c8c6 } - .terminal-3669917786-r2 { fill: #e3e3e3 } - .terminal-3669917786-r3 { fill: #e1e1e1 } - .terminal-3669917786-r4 { fill: #ff0000 } - .terminal-3669917786-r5 { fill: #dde8f3;font-weight: bold } - .terminal-3669917786-r6 { fill: #ddedf9 } + .terminal-3513309286-r1 { fill: #c5c8c6 } + .terminal-3513309286-r2 { fill: #e3e3e3 } + .terminal-3513309286-r3 { fill: #e1e1e1 } + .terminal-3513309286-r4 { fill: #ff0000 } + .terminal-3513309286-r5 { fill: #fea62b;font-weight: bold } + .terminal-3513309286-r6 { fill: #a7a9ab } + .terminal-3513309286-r7 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - GridHeightAuto + GridHeightAuto - - - - GridHeightAuto - Here is some text before the grid - ────────────────────────────────────────────────────────────────────────────── - Cell #0Cell #1Cell #2 - Cell #3Cell #4Cell #5 - Cell #6Cell #7Cell #8 - ────────────────────────────────────────────────────────────────────────────── - Here is some text after the grid - - - - - - - - - - - - - - - -  G  Grid  V  Vertical  H  Horizontal  C  Container  + + + + GridHeightAuto + Here is some text before the grid                                                + ┌──────────────────────────────────────────────────────────────────────────────┐ + Cell #0                   Cell #1                   Cell #2                    + Cell #3                   Cell #4                   Cell #5                    + Cell #6                   Cell #7                   Cell #8                    + └──────────────────────────────────────────────────────────────────────────────┘ + Here is some text after the grid                                                 + + + + + + + + + + + + + + + +  g Grid  v Vertical  h Horizontal  c Container  + + + + + ''' +# --- +# name: test_auto_tab_active + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ExampleApp + + + + + + + + + + + Parent 1Parent 2 + ━━━━━━━━━━━━╸━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + Child 2.1Child 2.2 + ━━━━━━━━━━━━━╸━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button 2.2  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + +  SPACE Focus button 2.2  @@ -1164,202 +1331,202 @@ font-weight: 700; } - .terminal-3752476664-matrix { + .terminal-1765028414-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3752476664-title { + .terminal-1765028414-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3752476664-r1 { fill: #c5c8c6 } - .terminal-3752476664-r2 { fill: #e3e3e3 } - .terminal-3752476664-r3 { fill: #004578 } - .terminal-3752476664-r4 { fill: #e1e1e1 } - .terminal-3752476664-r5 { fill: #632ca6 } - .terminal-3752476664-r6 { fill: #dde6ed;font-weight: bold } - .terminal-3752476664-r7 { fill: #14191f } - .terminal-3752476664-r8 { fill: #23568b } + .terminal-1765028414-r1 { fill: #c5c8c6 } + .terminal-1765028414-r2 { fill: #e3e3e3 } + .terminal-1765028414-r3 { fill: #004578 } + .terminal-1765028414-r4 { fill: #e1e1e1 } + .terminal-1765028414-r5 { fill: #632ca6 } + .terminal-1765028414-r6 { fill: #dde6ed;font-weight: bold } + .terminal-1765028414-r7 { fill: #14191f } + .terminal-1765028414-r8 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - MyApp - ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - oktest - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ -  0 ────────────────────────────────────── 1 ────────────────────────────────────── 2 ───── - -  Foo       Bar         Baz               Foo       Bar         Baz               Foo      -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY▁▁ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY▁▁ ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH - ───────────────────────────────────────────────────────────────────────────────────────────── - - ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + + + MyApp + ╭──────────────────╮╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ + ok                ││test                                                                                               + ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍││╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ + ││╭─ 0 ──────────────────────────────────────╮╭─ 1 ──────────────────────────────────────╮╭─ 2 ─────│ + │││││││ + │││ Foo       Bar         Baz              ││ Foo       Bar         Baz              ││ Foo      + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY▁▁││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY▁▁││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + │││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY││ ABCDEFGH + ││╰──────────────────────────────────────────╯╰──────────────────────────────────────────╯╰─────────│ + ││ + ╰──────────────────╯╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ @@ -1389,136 +1556,136 @@ font-weight: 700; } - .terminal-1625062503-matrix { + .terminal-2582506136-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1625062503-title { + .terminal-2582506136-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1625062503-r1 { fill: #c5c8c6 } - .terminal-1625062503-r2 { fill: #e3e3e3 } - .terminal-1625062503-r3 { fill: #1e1e1e } - .terminal-1625062503-r4 { fill: #0178d4 } - .terminal-1625062503-r5 { fill: #e1e1e1 } - .terminal-1625062503-r6 { fill: #e2e2e2 } - .terminal-1625062503-r7 { fill: #ddedf9 } + .terminal-2582506136-r1 { fill: #c5c8c6 } + .terminal-2582506136-r2 { fill: #e3e3e3 } + .terminal-2582506136-r3 { fill: #1e1e1e } + .terminal-2582506136-r4 { fill: #0178d4 } + .terminal-2582506136-r5 { fill: #e1e1e1 } + .terminal-2582506136-r6 { fill: #e2e2e2 } + .terminal-2582506136-r7 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - InputWidthAutoApp + InputWidthAutoApp - - - - InputWidthAutoApp - ▔▔▔▔▔▔▔▔▔▔ - Hello - ▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - - - + + + + InputWidthAutoApp + ▔▔▔▔▔▔▔▔▔▔ + Hello + ▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + + + @@ -1549,135 +1716,135 @@ font-weight: 700; } - .terminal-1381838495-matrix { + .terminal-4093147427-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1381838495-title { + .terminal-4093147427-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1381838495-r1 { fill: #454a50 } - .terminal-1381838495-r2 { fill: #e1e1e1 } - .terminal-1381838495-r3 { fill: #c5c8c6 } - .terminal-1381838495-r4 { fill: #24292f;font-weight: bold } - .terminal-1381838495-r5 { fill: #000000 } - .terminal-1381838495-r6 { fill: #e2e3e3;font-weight: bold } + .terminal-4093147427-r1 { fill: #454a50 } + .terminal-4093147427-r2 { fill: #e1e1e1 } + .terminal-4093147427-r3 { fill: #c5c8c6 } + .terminal-4093147427-r4 { fill: #24292f;font-weight: bold } + .terminal-4093147427-r5 { fill: #000000 } + .terminal-4093147427-r6 { fill: #e2e3e3;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ButtonApp + ButtonApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - - Hello - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - Hello - World !! - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + +  Hello  + + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + +  Hello  +  World !!  + + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + @@ -1685,6 +1852,164 @@ ''' # --- +# name: test_bindings_screen_overrides_show + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HideBindingApp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  p Binding shown  + + + + + ''' +# --- # name: test_blur_on_disabled ''' @@ -1708,134 +2033,134 @@ font-weight: 700; } - .terminal-3271357708-matrix { + .terminal-3770003934-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3271357708-title { + .terminal-3770003934-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3271357708-r1 { fill: #1e1e1e } - .terminal-3271357708-r2 { fill: #171717 } - .terminal-3271357708-r3 { fill: #c5c8c6 } - .terminal-3271357708-r4 { fill: #a7a7a7 } - .terminal-3271357708-r5 { fill: #e1e1e1 } + .terminal-3770003934-r1 { fill: #1e1e1e } + .terminal-3770003934-r2 { fill: #171717 } + .terminal-3770003934-r3 { fill: #c5c8c6 } + .terminal-3770003934-r4 { fill: #a7a7a7 } + .terminal-3770003934-r5 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BlurApp + BlurApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - foo - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + foo                                                                        + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + + + + @@ -1866,137 +2191,137 @@ font-weight: 700; } - .terminal-812768284-matrix { + .terminal-29072690-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-812768284-title { + .terminal-29072690-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-812768284-r1 { fill: #1e1e1e } - .terminal-812768284-r2 { fill: #c5c8c6 } - .terminal-812768284-r3 { fill: #e1e1e1 } - .terminal-812768284-r4 { fill: #183118 } - .terminal-812768284-r5 { fill: #124512 } - .terminal-812768284-r6 { fill: #0c580c } - .terminal-812768284-r7 { fill: #066c06 } - .terminal-812768284-r8 { fill: #008000 } + .terminal-29072690-r1 { fill: #1e1e1e } + .terminal-29072690-r2 { fill: #c5c8c6 } + .terminal-29072690-r3 { fill: #e1e1e1 } + .terminal-29072690-r4 { fill: #183118 } + .terminal-29072690-r5 { fill: #124512 } + .terminal-29072690-r6 { fill: #0c580c } + .terminal-29072690-r7 { fill: #066c06 } + .terminal-29072690-r8 { fill: #008000 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderAlphaApp + BorderAlphaApp - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + + + + @@ -2027,133 +2352,133 @@ font-weight: 700; } - .terminal-1480993901-matrix { + .terminal-1229229535-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1480993901-title { + .terminal-1229229535-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1480993901-r1 { fill: #ffffff } - .terminal-1480993901-r2 { fill: #e1e1e1 } - .terminal-1480993901-r3 { fill: #c5c8c6 } - .terminal-1480993901-r4 { fill: #e2e3e3;font-weight: bold } + .terminal-1229229535-r1 { fill: #ffffff } + .terminal-1229229535-r2 { fill: #e1e1e1 } + .terminal-1229229535-r3 { fill: #c5c8c6 } + .terminal-1229229535-r4 { fill: #e2e3e3;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ButtonIssue + ButtonIssue - - - - ────────────── - Test - ────────────── - - - - - - - - - - - - - - - - - - - - + + + + ┌──────────────┐ +  Test  + └──────────────┘ + + + + + + + + + + + + + + + + + + + + @@ -2184,136 +2509,136 @@ font-weight: 700; } - .terminal-898715-matrix { + .terminal-3701416180-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-898715-title { + .terminal-3701416180-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-898715-r1 { fill: #ff0000 } - .terminal-898715-r2 { fill: #e1e1e1 } - .terminal-898715-r3 { fill: #c5c8c6 } - .terminal-898715-r4 { fill: #454a50 } - .terminal-898715-r5 { fill: #24292f;font-weight: bold } - .terminal-898715-r6 { fill: #000000 } - .terminal-898715-r7 { fill: #e2e3e3;font-weight: bold } + .terminal-3701416180-r1 { fill: #ff0000 } + .terminal-3701416180-r2 { fill: #e1e1e1 } + .terminal-3701416180-r3 { fill: #c5c8c6 } + .terminal-3701416180-r4 { fill: #454a50 } + .terminal-3701416180-r5 { fill: #24292f;font-weight: bold } + .terminal-3701416180-r6 { fill: #000000 } + .terminal-3701416180-r7 { fill: #e2e3e3;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HorizontalWidthAutoApp + HorizontalWidthAutoApp - - - - ──────────────────────────── - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - This is a very wide button - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ──────────────────────────── - ──────────────────────────────────────────────────────── - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - This is a very wide buttonThis is a very wide button - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ──────────────────────────────────────────────────────── - - - - - - - - - - - - - + + + + ┌────────────────────────────┐ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  This is a very wide button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + └────────────────────────────┘ + ┌────────────────────────────────────────────────────────┐ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  This is a very wide button  This is a very wide button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + └────────────────────────────────────────────────────────┘ + + + + + + + + + + + + + @@ -2344,141 +2669,141 @@ font-weight: 700; } - .terminal-2679207537-matrix { + .terminal-3060571111-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2679207537-title { + .terminal-3060571111-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2679207537-r1 { fill: #454a50 } - .terminal-2679207537-r2 { fill: #e1e1e1 } - .terminal-2679207537-r3 { fill: #c5c8c6 } - .terminal-2679207537-r4 { fill: #24292f;font-weight: bold } - .terminal-2679207537-r5 { fill: #24292f;font-weight: bold;font-style: italic; } - .terminal-2679207537-r6 { fill: #000000 } - .terminal-2679207537-r7 { fill: #e2e3e3;font-weight: bold } - .terminal-2679207537-r8 { fill: #f4005f;font-weight: bold;font-style: italic; } - .terminal-2679207537-r9 { fill: #303336 } - .terminal-2679207537-r10 { fill: #a7a7a7;font-weight: bold } - .terminal-2679207537-r11 { fill: #620909;font-weight: bold;font-style: italic; } - .terminal-2679207537-r12 { fill: #0f0f0f } + .terminal-3060571111-r1 { fill: #454a50 } + .terminal-3060571111-r2 { fill: #e1e1e1 } + .terminal-3060571111-r3 { fill: #c5c8c6 } + .terminal-3060571111-r4 { fill: #24292f;font-weight: bold } + .terminal-3060571111-r5 { fill: #24292f;font-weight: bold;font-style: italic; } + .terminal-3060571111-r6 { fill: #000000 } + .terminal-3060571111-r7 { fill: #e2e3e3;font-weight: bold } + .terminal-3060571111-r8 { fill: #f4005f;font-weight: bold;font-style: italic; } + .terminal-3060571111-r9 { fill: #303336 } + .terminal-3060571111-r10 { fill: #a7a7a7;font-weight: bold } + .terminal-3060571111-r11 { fill: #620909;font-weight: bold;font-style: italic; } + .terminal-3060571111-r12 { fill: #0f0f0f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ButtonsWithMarkupApp + ButtonsWithMarkupApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Focused Button - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Blurred Button - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Disabled Button - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Focused Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Blurred Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Disabled Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + @@ -2509,134 +2834,134 @@ font-weight: 700; } - .terminal-3403690919-matrix { + .terminal-4065186018-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3403690919-title { + .terminal-4065186018-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3403690919-r1 { fill: #454a50 } - .terminal-3403690919-r2 { fill: #e1e1e1 } - .terminal-3403690919-r3 { fill: #c5c8c6 } - .terminal-3403690919-r4 { fill: #24292f;font-weight: bold } - .terminal-3403690919-r5 { fill: #000000 } + .terminal-4065186018-r1 { fill: #454a50 } + .terminal-4065186018-r2 { fill: #e1e1e1 } + .terminal-4065186018-r3 { fill: #c5c8c6 } + .terminal-4065186018-r4 { fill: #24292f;font-weight: bold } + .terminal-4065186018-r5 { fill: #000000 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ButtonWithMultilineLabelApp + ButtonWithMultilineLabelApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button - with - multi-line - label - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  +  with  +  multi-line  +  label  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + @@ -2667,162 +2992,162 @@ font-weight: 700; } - .terminal-3236763676-matrix { + .terminal-1520326498-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3236763676-title { + .terminal-1520326498-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3236763676-r1 { fill: #e1e1e1 } - .terminal-3236763676-r2 { fill: #c5c8c6 } - .terminal-3236763676-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-3236763676-r4 { fill: #454a50 } - .terminal-3236763676-r5 { fill: #303336 } - .terminal-3236763676-r6 { fill: #24292f;font-weight: bold } - .terminal-3236763676-r7 { fill: #a7a7a7;font-weight: bold } - .terminal-3236763676-r8 { fill: #000000 } - .terminal-3236763676-r9 { fill: #0f0f0f } - .terminal-3236763676-r10 { fill: #507bb3 } - .terminal-3236763676-r11 { fill: #364b66 } - .terminal-3236763676-r12 { fill: #dde6ed;font-weight: bold } - .terminal-3236763676-r13 { fill: #a5a9ac;font-weight: bold } - .terminal-3236763676-r14 { fill: #001541 } - .terminal-3236763676-r15 { fill: #0f192e } - .terminal-3236763676-r16 { fill: #7ae998 } - .terminal-3236763676-r17 { fill: #4a8159 } - .terminal-3236763676-r18 { fill: #0a180e;font-weight: bold } - .terminal-3236763676-r19 { fill: #0e1510;font-weight: bold } - .terminal-3236763676-r20 { fill: #008139 } - .terminal-3236763676-r21 { fill: #0f4e2a } - .terminal-3236763676-r22 { fill: #ffcf56 } - .terminal-3236763676-r23 { fill: #8b7439 } - .terminal-3236763676-r24 { fill: #211505;font-weight: bold } - .terminal-3236763676-r25 { fill: #19140c;font-weight: bold } - .terminal-3236763676-r26 { fill: #b86b00 } - .terminal-3236763676-r27 { fill: #68430f } - .terminal-3236763676-r28 { fill: #e76580 } - .terminal-3236763676-r29 { fill: #80404d } - .terminal-3236763676-r30 { fill: #f5e5e9;font-weight: bold } - .terminal-3236763676-r31 { fill: #b0a8aa;font-weight: bold } - .terminal-3236763676-r32 { fill: #780028 } - .terminal-3236763676-r33 { fill: #4a0f22 } + .terminal-1520326498-r1 { fill: #e1e1e1 } + .terminal-1520326498-r2 { fill: #c5c8c6 } + .terminal-1520326498-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-1520326498-r4 { fill: #454a50 } + .terminal-1520326498-r5 { fill: #303336 } + .terminal-1520326498-r6 { fill: #24292f;font-weight: bold } + .terminal-1520326498-r7 { fill: #a7a7a7;font-weight: bold } + .terminal-1520326498-r8 { fill: #000000 } + .terminal-1520326498-r9 { fill: #0f0f0f } + .terminal-1520326498-r10 { fill: #507bb3 } + .terminal-1520326498-r11 { fill: #364b66 } + .terminal-1520326498-r12 { fill: #dde6ed;font-weight: bold } + .terminal-1520326498-r13 { fill: #a5a9ac;font-weight: bold } + .terminal-1520326498-r14 { fill: #001541 } + .terminal-1520326498-r15 { fill: #0f192e } + .terminal-1520326498-r16 { fill: #7ae998 } + .terminal-1520326498-r17 { fill: #4a8159 } + .terminal-1520326498-r18 { fill: #0a180e;font-weight: bold } + .terminal-1520326498-r19 { fill: #0e1510;font-weight: bold } + .terminal-1520326498-r20 { fill: #008139 } + .terminal-1520326498-r21 { fill: #0f4e2a } + .terminal-1520326498-r22 { fill: #ffcf56 } + .terminal-1520326498-r23 { fill: #8b7439 } + .terminal-1520326498-r24 { fill: #211505;font-weight: bold } + .terminal-1520326498-r25 { fill: #19140c;font-weight: bold } + .terminal-1520326498-r26 { fill: #b86b00 } + .terminal-1520326498-r27 { fill: #68430f } + .terminal-1520326498-r28 { fill: #e76580 } + .terminal-1520326498-r29 { fill: #80404d } + .terminal-1520326498-r30 { fill: #f5e5e9;font-weight: bold } + .terminal-1520326498-r31 { fill: #b0a8aa;font-weight: bold } + .terminal-1520326498-r32 { fill: #780028 } + .terminal-1520326498-r33 { fill: #4a0f22 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ButtonsApp + ButtonsApp - - - - - Standard ButtonsDisabled Buttons - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - DefaultDefault - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Primary!Primary! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Success!Success! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Warning!Warning! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Error!Error! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - + + + + + Standard ButtonsDisabled Buttons + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Default  Default  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Primary!  Primary!  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Success!  Success!  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Warning!  Warning!  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Error!  Error!  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + @@ -3020,136 +3345,138 @@ font-weight: 700; } - .terminal-4171601622-matrix { + .terminal-4248372996-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4171601622-title { + .terminal-4248372996-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4171601622-r1 { fill: #121212 } - .terminal-4171601622-r2 { fill: #c5c8c6 } - .terminal-4171601622-r3 { fill: #ddedf9 } - .terminal-4171601622-r4 { fill: #e2e2e2 } - .terminal-4171601622-r5 { fill: #e1e1e1 } - .terminal-4171601622-r6 { fill: #dde8f3;font-weight: bold } + .terminal-4248372996-r1 { fill: #121212 } + .terminal-4248372996-r2 { fill: #c5c8c6 } + .terminal-4248372996-r3 { fill: #ddedf9 } + .terminal-4248372996-r4 { fill: #e2e2e2 } + .terminal-4248372996-r5 { fill: #e1e1e1 } + .terminal-4248372996-r6 { fill: #fea62b;font-weight: bold } + .terminal-4248372996-r7 { fill: #a7a9ab } + .terminal-4248372996-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CollapsibleApp + CollapsibleApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▶ Leto - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▶ Jessica - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▶ Paul - - - - - - - - - - - - - - - -  C  Collapse All  E  Expand All  + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▶ Leto + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▶ Jessica + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▶ Paul + + + + + + + + + + + + + + + +  c Collapse All  e Expand All  @@ -3179,134 +3506,134 @@ font-weight: 700; } - .terminal-3510718873-matrix { + .terminal-693385943-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3510718873-title { + .terminal-693385943-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3510718873-r1 { fill: #121212 } - .terminal-3510718873-r2 { fill: #c5c8c6 } - .terminal-3510718873-r3 { fill: #ddedf9 } - .terminal-3510718873-r4 { fill: #e2e2e2 } - .terminal-3510718873-r5 { fill: #e1e1e1 } + .terminal-693385943-r1 { fill: #121212 } + .terminal-693385943-r2 { fill: #c5c8c6 } + .terminal-693385943-r3 { fill: #ddedf9 } + .terminal-693385943-r4 { fill: #e2e2e2 } + .terminal-693385943-r5 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CollapsibleApp + CollapsibleApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - >>> Togglev Toggle - - Hello, world. - - - - - - - - - - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + >>> Togglev Toggle + + Hello, world.                        + + + + + + + + + + + + + + + + + + + @@ -3337,137 +3664,140 @@ font-weight: 700; } - .terminal-4078801769-matrix { + .terminal-1549040424-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4078801769-title { + .terminal-1549040424-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4078801769-r1 { fill: #121212 } - .terminal-4078801769-r2 { fill: #e1e1e1 } - .terminal-4078801769-r3 { fill: #c5c8c6 } - .terminal-4078801769-r4 { fill: #ddedf9 } - .terminal-4078801769-r5 { fill: #e2e2e2 } - .terminal-4078801769-r6 { fill: #0053aa } - .terminal-4078801769-r7 { fill: #dde8f3;font-weight: bold } + .terminal-1549040424-r1 { fill: #121212 } + .terminal-1549040424-r2 { fill: #e1e1e1 } + .terminal-1549040424-r3 { fill: #c5c8c6 } + .terminal-1549040424-r4 { fill: #ddedf9 } + .terminal-1549040424-r5 { fill: #e2e2e2 } + .terminal-1549040424-r6 { fill: #4ebf71;font-weight: bold } + .terminal-1549040424-r7 { fill: #14191f } + .terminal-1549040424-r8 { fill: #fea62b;font-weight: bold } + .terminal-1549040424-r9 { fill: #a7a9ab } + .terminal-1549040424-r10 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CollapsibleApp + CollapsibleApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▼ Leto - - # Duke Leto I Atreides - - Head of House Atreides. - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▼ Jessica - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - Lady Jessica - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Bene Gesserit and concubine of Leto, and mother of Paul and Alia. - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▼ Paul - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -  Collapse All  E  Expand All  + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▼ Leto + + # Duke Leto I Atreides + + Head of House Atreides.                                                    + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▼ Jessica + + + + Lady Jessica + +   Bene Gesserit and concubine of Leto, and mother of Paul and Alia. + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▼ Paul▆▆ + + + +  c Collapse All  e Expand All  @@ -3497,135 +3827,135 @@ font-weight: 700; } - .terminal-3576423522-matrix { + .terminal-2781425159-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3576423522-title { + .terminal-2781425159-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3576423522-r1 { fill: #121212 } - .terminal-3576423522-r2 { fill: #c5c8c6 } - .terminal-3576423522-r3 { fill: #ddedf9 } - .terminal-3576423522-r4 { fill: #e2e2e2 } - .terminal-3576423522-r5 { fill: #e3e3e3 } - .terminal-3576423522-r6 { fill: #e1e1e1 } + .terminal-2781425159-r1 { fill: #121212 } + .terminal-2781425159-r2 { fill: #c5c8c6 } + .terminal-2781425159-r3 { fill: #ddedf9 } + .terminal-2781425159-r4 { fill: #e2e2e2 } + .terminal-2781425159-r5 { fill: #e3e3e3 } + .terminal-2781425159-r6 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CollapsibleApp + CollapsibleApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▼ Toggle - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▶ Toggle - - - - - - - - - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▼ Toggle + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▶ Toggle + + + + + + + + + + + + + + + + + + @@ -3656,137 +3986,139 @@ font-weight: 700; } - .terminal-4121784704-matrix { + .terminal-844098826-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4121784704-title { + .terminal-844098826-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4121784704-r1 { fill: #121212 } - .terminal-4121784704-r2 { fill: #c5c8c6 } - .terminal-4121784704-r3 { fill: #ddedf9 } - .terminal-4121784704-r4 { fill: #e2e2e2 } - .terminal-4121784704-r5 { fill: #0053aa } - .terminal-4121784704-r6 { fill: #dde8f3;font-weight: bold } - .terminal-4121784704-r7 { fill: #e1e1e1 } + .terminal-844098826-r1 { fill: #121212 } + .terminal-844098826-r2 { fill: #c5c8c6 } + .terminal-844098826-r3 { fill: #ddedf9 } + .terminal-844098826-r4 { fill: #e2e2e2 } + .terminal-844098826-r5 { fill: #4ebf71;font-weight: bold } + .terminal-844098826-r6 { fill: #e1e1e1 } + .terminal-844098826-r7 { fill: #fea62b;font-weight: bold } + .terminal-844098826-r8 { fill: #a7a9ab } + .terminal-844098826-r9 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CollapsibleApp + CollapsibleApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▼ Leto - - # Duke Leto I Atreides - - Head of House Atreides. - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▼ Jessica - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - Lady Jessica - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Bene Gesserit and concubine of Leto, and mother of Paul and Alia. - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▶ Paul - - -  C  Collapse All  E  Expand All  + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▼ Leto + + # Duke Leto I Atreides + + Head of House Atreides.                                                      + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▼ Jessica + + + + Lady Jessica + +   Bene Gesserit and concubine of Leto, and mother of Paul and Alia. + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▶ Paul + + + +  c Collapse All  e Expand All  @@ -3816,133 +4148,133 @@ font-weight: 700; } - .terminal-2207022363-matrix { + .terminal-4204346594-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2207022363-title { + .terminal-4204346594-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2207022363-r1 { fill: #ff0000 } - .terminal-2207022363-r2 { fill: #c5c8c6 } - .terminal-2207022363-r3 { fill: #008000 } - .terminal-2207022363-r4 { fill: #e1e1e1 } + .terminal-4204346594-r1 { fill: #ff0000 } + .terminal-4204346594-r2 { fill: #c5c8c6 } + .terminal-4204346594-r3 { fill: #008000 } + .terminal-4204346594-r4 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HeightApp + HeightApp - - - - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────── - As tall as containerThis has defaultI have a static height - height - but a - few lines - ──────────────── - - - - - - - - - - ────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - - - - - + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + ┌────────────────────┐┌────────────────┐┌──────────────────────┐ + As tall as container││This has default││I have a static height + ││height││ + ││but a││ + ││few lines││ + │└────────────────┘│ + + + + + + + + + + └────────────────────┘└──────────────────────┘ + └──────────────────────────────────────────────────────────────────────────────┘ + + + + + @@ -3973,137 +4305,138 @@ font-weight: 700; } - .terminal-454793765-matrix { + .terminal-1919115509-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-454793765-title { + .terminal-1919115509-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-454793765-r1 { fill: #a2a2a2 } - .terminal-454793765-r2 { fill: #c5c8c6 } - .terminal-454793765-r3 { fill: #004578 } - .terminal-454793765-r4 { fill: #e2e3e3 } - .terminal-454793765-r5 { fill: #00ff00 } - .terminal-454793765-r6 { fill: #000000 } - .terminal-454793765-r7 { fill: #1e1e1e } - .terminal-454793765-r8 { fill: #fea62b;font-weight: bold } + .terminal-1919115509-r1 { fill: #646464 } + .terminal-1919115509-r2 { fill: #c5c8c6 } + .terminal-1919115509-r3 { fill: #004578 } + .terminal-1919115509-r4 { fill: #dfe1e2 } + .terminal-1919115509-r5 { fill: #00ff00 } + .terminal-1919115509-r6 { fill: #000000 } + .terminal-1919115509-r7 { fill: #1e1e1e } + .terminal-1919115509-r8 { fill: #dfe1e2;font-weight: bold } + .terminal-1919115509-r9 { fill: #fea62b;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CommandPaletteApp + CommandPaletteApp - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - 🔎A - - - This is a test of this code 9 - This is a test of this code 8 - This is a test of this code 7 - This is a test of this code 6 - This is a test of this code 5 - This is a test of this code 4 - This is a test of this code 3 - This is a test of this code 2 - This is a test of this code 1 - This is a test of this code 0 - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + 🔎A + + +   This is a test of this code 0                                                  +   This is a test of this code 1                                                  +   This is a test of this code 2                                                  +   This is a test of this code 3                                                  +   This is a test of this code 4                                                  +   This is a test of this code 5                                                  +   This is a test of this code 6                                                  +   This is a test of this code 7                                                  +   This is a test of this code 8                                                  +   This is a test of this code 9                                                  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + @@ -4134,137 +4467,138 @@ font-weight: 700; } - .terminal-929804574-matrix { + .terminal-1425100236-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-929804574-title { + .terminal-1425100236-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-929804574-r1 { fill: #a2a2a2 } - .terminal-929804574-r2 { fill: #c5c8c6 } - .terminal-929804574-r3 { fill: #004578 } - .terminal-929804574-r4 { fill: #e2e3e3 } - .terminal-929804574-r5 { fill: #00ff00 } - .terminal-929804574-r6 { fill: #000000 } - .terminal-929804574-r7 { fill: #1e1e1e } - .terminal-929804574-r8 { fill: #777a7e } + .terminal-1425100236-r1 { fill: #646464 } + .terminal-1425100236-r2 { fill: #c5c8c6 } + .terminal-1425100236-r3 { fill: #004578 } + .terminal-1425100236-r4 { fill: #dfe1e2 } + .terminal-1425100236-r5 { fill: #00ff00 } + .terminal-1425100236-r6 { fill: #000000 } + .terminal-1425100236-r7 { fill: #1e1e1e } + .terminal-1425100236-r8 { fill: #697278 } + .terminal-1425100236-r9 { fill: #dfe1e2;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CommandPaletteApp + CommandPaletteApp - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - 🔎Command Palette Search... - - - This is a test of this code 0 - This is a test of this code 1 - This is a test of this code 2 - This is a test of this code 3 - This is a test of this code 4 - This is a test of this code 5 - This is a test of this code 6 - This is a test of this code 7 - This is a test of this code 8 - This is a test of this code 9 - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + 🔎Search for commands… + + +   This is a test of this code 0                                                  +   This is a test of this code 1                                                  +   This is a test of this code 2                                                  +   This is a test of this code 3                                                  +   This is a test of this code 4                                                  +   This is a test of this code 5                                                  +   This is a test of this code 6                                                  +   This is a test of this code 7                                                  +   This is a test of this code 8                                                  +   This is a test of this code 9                                                  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + @@ -4272,7 +4606,7 @@ ''' # --- -# name: test_content_switcher_example_initial +# name: test_component_text_opacity ''' @@ -4295,150 +4629,141 @@ font-weight: 700; } - .terminal-3026307999-matrix { + .terminal-2090419717-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3026307999-title { + .terminal-2090419717-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3026307999-r1 { fill: #c5c8c6 } - .terminal-3026307999-r2 { fill: #e1e1e1 } - .terminal-3026307999-r3 { fill: #454a50 } - .terminal-3026307999-r4 { fill: #24292f;font-weight: bold } - .terminal-3026307999-r5 { fill: #e2e3e3;font-weight: bold } - .terminal-3026307999-r6 { fill: #000000 } - .terminal-3026307999-r7 { fill: #004578 } - .terminal-3026307999-r8 { fill: #dde6ed;font-weight: bold } - .terminal-3026307999-r9 { fill: #dde6ed } - .terminal-3026307999-r10 { fill: #211505 } - .terminal-3026307999-r11 { fill: #e2e3e3 } + .terminal-2090419717-r1 { fill: #7f7fff } + .terminal-2090419717-r2 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ContentSwitcherApp + TestApp - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - DataTableMarkdown - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ──────────────────────────────────────────────────────────────────── -  Book                                 Year  -  Dune                                 1965  -  Dune Messiah                         1969  -  Children of Dune                     1976  -  God Emperor of Dune                  1981  -  Heretics of Dune                     1984  -  Chapterhouse: Dune                   1985  - - - - - - - - - - - ──────────────────────────────────────────────────────────────────── - + + + + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW + WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW ''' # --- -# name: test_content_switcher_example_switch +# name: test_content_switcher_example_initial ''' - + - - + + - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + + + ContentSwitcherApp + + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  DataTable  Markdown  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ╭────────────────────────────────────────────────────────────────────╮ +  Book                                 Year  +  Dune                                 1965  +  Dune Messiah                         1969  +  Children of Dune                     1976  +  God Emperor of Dune                  1981  +  Heretics of Dune                     1984  +  Chapterhouse: Dune                   1985  + + + + + + + + + + + ╰────────────────────────────────────────────────────────────────────╯ + + + + + + ''' +# --- +# name: test_content_switcher_example_switch + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ContentSwitcherApp - - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - DataTableMarkdown - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ───────────────────────────────────────── - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - Three Flavours Cornetto - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - The Three Flavours Cornetto  - trilogy is an anthology series  - of British comedic genre films  - directed by Edgar Wright. - -        Shaun of the Dead        - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - UK       - Release  - Flavour Date    Director  -  ━━━━━━━━━━━━━━━━━━━━━━━━━━━  - Strawbe…2004-04…Edgar     - Wright    - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -            Hot Fuzz             - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - UK       - Release  - Flavour Date    Director  -  ━━━━━━━━━━━━━━━━━━━━━━━━━━━  - Classico2007-02…Edgar     - Wright    - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -         The World's End         - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - UK        - Release   - FlavourDate     Director  - ───────────────────────────────────────── + ContentSwitcherApp + + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  DataTable  Markdown  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ╭─────────────────────────────────────────╮ + + + Three Flavours Cornetto + +   The Three Flavours Cornetto trilogy  +   is an anthology series of British  +   comedic genre films directed by Edgar   +   Wright. + + + Shaun of the Dead + + + UK Release   + Flavour   Date        Director    +    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━    +    Strawberry 2004-04-09   Edgar          +                            Wright         + + + + Hot Fuzz + + + UK Release    + Flavour Date         Director     +    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━    +    Classico 2007-02-17    Edgar Wright    + + + + The World's End + + + UK Release     + FlavourDate          Director     +    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━    +    Mint    2013-07-19     Edgar Wright    + + + + + + ╰─────────────────────────────────────────╯ @@ -4729,131 +5215,131 @@ font-weight: 700; } - .terminal-1055651203-matrix { + .terminal-3958682080-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1055651203-title { + .terminal-3958682080-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1055651203-r1 { fill: #e1e1e1 } - .terminal-1055651203-r2 { fill: #c5c8c6 } + .terminal-3958682080-r1 { fill: #e1e1e1 } + .terminal-3958682080-r2 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HotReloadingApp + HotReloadingApp - - - - Hello, world! - - - - - - - - - - - - - - - - - - - - - - + + + + Hello, world!                                                                    + + + + + + + + + + + + + + + + + + + + + + @@ -4884,131 +5370,131 @@ font-weight: 700; } - .terminal-1055651203-matrix { + .terminal-3958682080-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1055651203-title { + .terminal-3958682080-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1055651203-r1 { fill: #e1e1e1 } - .terminal-1055651203-r2 { fill: #c5c8c6 } + .terminal-3958682080-r1 { fill: #e1e1e1 } + .terminal-3958682080-r2 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HotReloadingApp + HotReloadingApp - - - - Hello, world! - - - - - - - - - - - - - - - - - - - - - - + + + + Hello, world!                                                                    + + + + + + + + + + + + + + + + + + + + + + @@ -5039,134 +5525,134 @@ font-weight: 700; } - .terminal-1567237307-matrix { + .terminal-4107518032-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1567237307-title { + .terminal-4107518032-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1567237307-r1 { fill: #e1e1e1 } - .terminal-1567237307-r2 { fill: #c5c8c6 } - .terminal-1567237307-r3 { fill: #ffffff } - .terminal-1567237307-r4 { fill: #e5f2e5 } - .terminal-1567237307-r5 { fill: #e5f2e5;font-weight: bold } + .terminal-4107518032-r1 { fill: #e1e1e1 } + .terminal-4107518032-r2 { fill: #c5c8c6 } + .terminal-4107518032-r3 { fill: #ffffff } + .terminal-4107518032-r4 { fill: #e5f2e5 } + .terminal-4107518032-r5 { fill: #e5f2e5;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AlignApp + AlignApp - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - Vertical alignment with Textual - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - Take note, browsers. - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - - + + + + + + + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + Vertical alignment with Textual + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + Take note, browsers. + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + + + + + @@ -5197,135 +5683,135 @@ font-weight: 700; } - .terminal-1945469710-matrix { + .terminal-800662067-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1945469710-title { + .terminal-800662067-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1945469710-r1 { fill: #808080 } - .terminal-1945469710-r2 { fill: #e1e1e1 } - .terminal-1945469710-r3 { fill: #c5c8c6 } - .terminal-1945469710-r4 { fill: #ddedf9 } - .terminal-1945469710-r5 { fill: #e2e2e2 } + .terminal-800662067-r1 { fill: #808080 } + .terminal-800662067-r2 { fill: #e1e1e1 } + .terminal-800662067-r3 { fill: #c5c8c6 } + .terminal-800662067-r4 { fill: #ddedf9 } + .terminal-800662067-r5 { fill: #e2e2e2 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AlignAllApp + AlignAllApp - - - - ──────────────────────────────────────────────────────────────────────── - left topcenter topright top - - - - - ──────────────────────────────────────────────────────────────────────── - - ──────────────────────────────────────────────────────────────────────── - - - left middlecenter middleright middle - - - ──────────────────────────────────────────────────────────────────────── - - ──────────────────────────────────────────────────────────────────────── - - - - - - left bottomcenter bottomright bottom - ──────────────────────────────────────────────────────────────────────── + + + + ┌────────────────────────┐┌────────────────────────┐┌────────────────────────┐ + left topcenter topright top + + + + + └────────────────────────┘└────────────────────────┘└────────────────────────┘ + + ┌────────────────────────┐┌────────────────────────┐┌────────────────────────┐ + + + left middlecenter middleright middle + + + └────────────────────────┘└────────────────────────┘└────────────────────────┘ + + ┌────────────────────────┐┌────────────────────────┐┌────────────────────────┐ + + + + + + left bottomcenter bottomright bottom + └────────────────────────┘└────────────────────────┘└────────────────────────┘ @@ -5674,134 +6160,134 @@ font-weight: 700; } - .terminal-1839441138-matrix { + .terminal-3021441172-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1839441138-title { + .terminal-3021441172-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1839441138-r1 { fill: #ffffff } - .terminal-1839441138-r2 { fill: #c5c8c6 } - .terminal-1839441138-r3 { fill: #ff0000 } - .terminal-1839441138-r4 { fill: #008000 } - .terminal-1839441138-r5 { fill: #0000ff } + .terminal-3021441172-r1 { fill: #ffffff } + .terminal-3021441172-r2 { fill: #c5c8c6 } + .terminal-3021441172-r3 { fill: #ff0000 } + .terminal-3021441172-r4 { fill: #008000 } + .terminal-3021441172-r5 { fill: #0000ff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderApp + BorderApp - - - - - ──────────────────────────────────────────────────────────────────────────── - - My border is solid red - - ──────────────────────────────────────────────────────────────────────────── - - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - - My border is dashed green - - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - My border is tall blue - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - + + + + + ┌────────────────────────────────────────────────────────────────────────────┐ + + My border is solid red + + └────────────────────────────────────────────────────────────────────────────┘ + + ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ + + My border is dashed green + + ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + My border is tall blue + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + @@ -5832,133 +6318,133 @@ font-weight: 700; } - .terminal-2091190527-matrix { + .terminal-251619606-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2091190527-title { + .terminal-251619606-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2091190527-r1 { fill: #e1e1e1 } - .terminal-2091190527-r2 { fill: #c5c8c6 } - .terminal-2091190527-r3 { fill: #0178d4 } - .terminal-2091190527-r4 { fill: #1e1e1e } + .terminal-251619606-r1 { fill: #e1e1e1 } + .terminal-251619606-r2 { fill: #c5c8c6 } + .terminal-251619606-r3 { fill: #0178d4 } + .terminal-251619606-r4 { fill: #1e1e1e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AllBordersApp + AllBordersApp - - - - - +----------------+╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍═════════════════ - |ascii|blankdasheddouble - +----------------+╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍═════════════════ - - - - ━━━━━━━━━━━━━━━━▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - heavyhidden/nonehkeyinner - ━━━━━━━━━━━━━━━━▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - - - - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█████████████████───────────────────────────────── - outerpanelroundsolid - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁───────────────────────────────── - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - tallthickvkeywide - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - + + + + + +----------------+┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓╔═════════════════╗ + |ascii|blankdasheddouble + +----------------+┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛╚═════════════════╝ + + + + ┏━━━━━━━━━━━━━━━━┓▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▗▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▖ + heavyhidden/nonehkeyinner + ┗━━━━━━━━━━━━━━━━┛▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘ + + + + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜█████████████████▎╭────────────────╮┌─────────────────┐ + outerpanelroundsolid + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎╰────────────────╯└─────────────────┘ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█▏                ▕▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + tallthickvkeywide + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▏                ▕▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + @@ -5989,141 +6475,141 @@ font-weight: 700; } - .terminal-2586053582-matrix { + .terminal-2407668749-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2586053582-title { + .terminal-2407668749-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2586053582-r1 { fill: #e1e1e1 } - .terminal-2586053582-r2 { fill: #c5c8c6 } - .terminal-2586053582-r3 { fill: #fea62b } - .terminal-2586053582-r4 { fill: #fea62b;font-weight: bold } - .terminal-2586053582-r5 { fill: #fea62b;font-weight: bold;font-style: italic; } - .terminal-2586053582-r6 { fill: #f4005f;font-weight: bold } - .terminal-2586053582-r7 { fill: #1e1e1e } - .terminal-2586053582-r8 { fill: #1e1e1e;text-decoration: underline; } - .terminal-2586053582-r9 { fill: #fea62b;text-decoration: underline; } - .terminal-2586053582-r10 { fill: #1a1a1a;text-decoration: underline; } - .terminal-2586053582-r11 { fill: #4ebf71 } - .terminal-2586053582-r12 { fill: #b93c5b } + .terminal-2407668749-r1 { fill: #e1e1e1 } + .terminal-2407668749-r2 { fill: #c5c8c6 } + .terminal-2407668749-r3 { fill: #fea62b } + .terminal-2407668749-r4 { fill: #fea62b;font-weight: bold } + .terminal-2407668749-r5 { fill: #fea62b;font-weight: bold;font-style: italic; } + .terminal-2407668749-r6 { fill: #f4005f;font-weight: bold } + .terminal-2407668749-r7 { fill: #1e1e1e } + .terminal-2407668749-r8 { fill: #1e1e1e;text-decoration: underline; } + .terminal-2407668749-r9 { fill: #fea62b;text-decoration: underline; } + .terminal-2407668749-r10 { fill: #1a1a1a;text-decoration: underline; } + .terminal-2407668749-r11 { fill: #4ebf71 } + .terminal-2407668749-r12 { fill: #b93c5b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderSubTitleAlignAll + BorderSubTitleAlignAll - - - - - - Border titleLef…▁▁▁▁Left▁▁▁▁ - This is the story ofa Pythondeveloper that - Border subtitleCen…▔▔▔▔@@@▔▔▔▔▔ - - - - - - +--------------+Title───────────────── - |had to fill up|nine labelsand ended up redoing it - +-Left-------+──────────────Subtitle - - - - - Title, but really looo… - Title, but r…Title, but reall… - because the first tryhad some labelsthat were too long. - Subtitle, bu…Subtitle, but re… - Subtitle, but really l… - + + + + + + ▏  Border title      ▕╭─ Lef… ─╮▁▁▁▁▁ Left ▁▁▁▁▁ + This is the story ofa Pythondeveloper that + ▏   Border subtitle  ▕╰─ Cen… ─╯▔▔▔▔▔ @@@ ▔▔▔▔▔▔ + + + + + + +--------------+─Title───────────────── + |had to fill up|             nine labels          and ended up redoing it   + +- Left -------+──────────────Subtitle─ + + + + + ─Title, but really looo…─ + ─Title, but r…──Title, but reall…─ + because the first try       had some labels          that were too long.     + ─Subtitle, bu…──Subtitle, but re…─ + ─Subtitle, but really l…─ + @@ -6154,134 +6640,134 @@ font-weight: 700; } - .terminal-1601354540-matrix { + .terminal-1369800768-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1601354540-title { + .terminal-1369800768-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1601354540-r1 { fill: #e1e1e1 } - .terminal-1601354540-r2 { fill: #c5c8c6 } - .terminal-1601354540-r3 { fill: #fea62b } - .terminal-1601354540-r4 { fill: #ffffff } - .terminal-1601354540-r5 { fill: #1e1e1e } + .terminal-1369800768-r1 { fill: #e1e1e1 } + .terminal-1369800768-r2 { fill: #c5c8c6 } + .terminal-1369800768-r3 { fill: #fea62b } + .terminal-1369800768-r4 { fill: #ffffff } + .terminal-1369800768-r5 { fill: #1e1e1e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderSubtitleAlignApp + BorderSubtitleAlignApp - - - - - ──────────────────────────────────────────────────────────────────────────── - - My subtitle is on the left. - -  < Left ─────────────────────────────────────────────────────────────────── - - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - - My subtitle is centered - - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ Centered! ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - My subtitle is on the right - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ Right >  - - - - - + + + + + ┌────────────────────────────────────────────────────────────────────────────┐ + + My subtitle is on the left. + + └─ < Left ───────────────────────────────────────────────────────────────────┘ + + ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ + + My subtitle is centered + + ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ Centered! ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎ + + My subtitle is on the right + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ Right > ▁▎ + + + + + @@ -6312,134 +6798,134 @@ font-weight: 700; } - .terminal-2047325817-matrix { + .terminal-3570826125-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2047325817-title { + .terminal-3570826125-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2047325817-r1 { fill: #e1e1e1 } - .terminal-2047325817-r2 { fill: #c5c8c6 } - .terminal-2047325817-r3 { fill: #fea62b } - .terminal-2047325817-r4 { fill: #ffffff } - .terminal-2047325817-r5 { fill: #1e1e1e } + .terminal-3570826125-r1 { fill: #e1e1e1 } + .terminal-3570826125-r2 { fill: #c5c8c6 } + .terminal-3570826125-r3 { fill: #fea62b } + .terminal-3570826125-r4 { fill: #ffffff } + .terminal-3570826125-r5 { fill: #1e1e1e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderTitleAlignApp + BorderTitleAlignApp - - - - -  < Left ─────────────────────────────────────────────────────────────────── - - My title is on the left. - - ──────────────────────────────────────────────────────────────────────────── - - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ Centered! ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - - My title is centered - - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Right >  - - My title is on the right - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - + + + + + ┌─ < Left ───────────────────────────────────────────────────────────────────┐ + + My title is on the left. + + └────────────────────────────────────────────────────────────────────────────┘ + + ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ Centered! ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ + + My title is centered + + ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Right > ▔▎ + + My title is on the right + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎ + + + + + @@ -6470,134 +6956,134 @@ font-weight: 700; } - .terminal-2286355719-matrix { + .terminal-2865494641-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2286355719-title { + .terminal-2865494641-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2286355719-r1 { fill: #e1e1e1 } - .terminal-2286355719-r2 { fill: #c5c8c6 } - .terminal-2286355719-r3 { fill: #ff0000 } - .terminal-2286355719-r4 { fill: #008000;font-weight: bold } - .terminal-2286355719-r5 { fill: #ff00ff;font-style: italic; } + .terminal-2865494641-r1 { fill: #e1e1e1 } + .terminal-2865494641-r2 { fill: #c5c8c6 } + .terminal-2865494641-r3 { fill: #ff0000 } + .terminal-2865494641-r4 { fill: #008000;font-weight: bold } + .terminal-2865494641-r5 { fill: #ff00ff;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderTitleApp + BorderTitleApp - - - - - - - - - -  Textual Rocks ━━━━━━━━━━━━━ - - - - - Hello, World! - - - - - ━━━━━━━━━━━━━ Textual Rocks  - - - - - - + + + + + + + + + + ┏━ Textual Rocks ━━━━━━━━━━━━━┓ + + + + + Hello, World! + + + + + ┗━━━━━━━━━━━━━ Textual Rocks ━┛ + + + + + + @@ -6628,132 +7114,132 @@ font-weight: 700; } - .terminal-3266307003-matrix { + .terminal-467154456-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3266307003-title { + .terminal-467154456-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3266307003-r1 { fill: #000000 } - .terminal-3266307003-r2 { fill: #c5c8c6 } - .terminal-3266307003-r3 { fill: #ccccff } + .terminal-467154456-r1 { fill: #000000 } + .terminal-467154456-r2 { fill: #c5c8c6 } + .terminal-467154456-r3 { fill: #ccccff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BoxSizingApp + BoxSizingApp - - - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - I'm using border-box! - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - I'm using content-box! - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - - - + + + + + +   ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁   + + I'm using border-box! + +   ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔   + + +   ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁   + + I'm using content-box! + + + + + +   ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔   + + + + + @@ -7262,133 +7748,133 @@ font-weight: 700; } - .terminal-1585086532-matrix { + .terminal-2684005981-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1585086532-title { + .terminal-2684005981-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1585086532-r1 { fill: #c5c8c6 } - .terminal-1585086532-r2 { fill: #ffffff } - .terminal-1585086532-r3 { fill: #ffffff;font-style: italic; } - .terminal-1585086532-r4 { fill: #ffffff;font-weight: bold } + .terminal-2684005981-r1 { fill: #c5c8c6 } + .terminal-2684005981-r2 { fill: #ffffff } + .terminal-2684005981-r3 { fill: #ffffff;font-style: italic; } + .terminal-2684005981-r4 { fill: #ffffff;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ContentAlignApp + ContentAlignApp - - - - - With content-align you can... - - - - - - - - - - ...Easily align content... - - - - - - - - - - - ...Horizontally and vertically! + + + + + With content-align you can... + + + + + + + + + + ...Easily align content... + + + + + + + + + + + ...Horizontally and vertically! @@ -7575,132 +8061,132 @@ font-weight: 700; } - .terminal-3544266701-matrix { + .terminal-2110623858-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3544266701-title { + .terminal-2110623858-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3544266701-r1 { fill: #0000ff } - .terminal-3544266701-r2 { fill: #c5c8c6 } - .terminal-3544266701-r3 { fill: #ddeedd } + .terminal-2110623858-r1 { fill: #0000ff } + .terminal-2110623858-r2 { fill: #c5c8c6 } + .terminal-2110623858-r3 { fill: #ddeedd } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DisplayApp + DisplayApp - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Widget 1 - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Widget 3 - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - - - - - - - - - + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + ┃Widget 1 + + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + ┃Widget 3 + + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + + + + + + + + + + + + @@ -7731,132 +8217,132 @@ font-weight: 700; } - .terminal-1440050744-matrix { + .terminal-766040431-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1440050744-title { + .terminal-766040431-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1440050744-r1 { fill: #e1e1e1 } - .terminal-1440050744-r2 { fill: #c5c8c6 } - .terminal-1440050744-r3 { fill: #ffffff } + .terminal-766040431-r1 { fill: #e1e1e1 } + .terminal-766040431-r2 { fill: #c5c8c6 } + .terminal-766040431-r3 { fill: #ffffff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DockAllApp + DockAllApp - - - - - - - ────────────────────────────────────────────────────────── - top - - - - - - - leftright - - - - - - - - bottom - ────────────────────────────────────────────────────────── - - + + + + + + + ╭──────────────────────────────────────────────────────────╮ +                            top                             + + + + + + + left                                                 right + + + + + + + +                           bottom                           + ╰──────────────────────────────────────────────────────────╯ + + @@ -7887,133 +8373,133 @@ font-weight: 700; } - .terminal-2927206876-matrix { + .terminal-3791676016-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2927206876-title { + .terminal-3791676016-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2927206876-r1 { fill: #c5c8c6 } - .terminal-2927206876-r2 { fill: #e1e1e1 } - .terminal-2927206876-r3 { fill: #731077 } - .terminal-2927206876-r4 { fill: #161c1d } + .terminal-3791676016-r1 { fill: #c5c8c6 } + .terminal-3791676016-r2 { fill: #e1e1e1 } + .terminal-3791676016-r3 { fill: #731077 } + .terminal-3791676016-r4 { fill: #161c1d } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - GridApp + GridApp - - - - - Grid cell 1Grid cell 2 - - row-span: 3; - column-span: 2; - - - Grid cell 3 - - - - - - Grid cell 4 - - - - - - Grid cell 5Grid cell 6Grid cell 7 - - - + + + + + Grid cell 1Grid cell 2 + + row-span: 3; + column-span: 2; + + + Grid cell 3 + + + + + + Grid cell 4 + + + + + + Grid cell 5Grid cell 6Grid cell 7 + + + @@ -8044,133 +8530,133 @@ font-weight: 700; } - .terminal-3216047084-matrix { + .terminal-3072634976-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3216047084-title { + .terminal-3072634976-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3216047084-r1 { fill: #ffffff } - .terminal-3216047084-r2 { fill: #c5c8c6 } - .terminal-3216047084-r3 { fill: #e1e1e1 } + .terminal-3072634976-r1 { fill: #ffffff } + .terminal-3072634976-r2 { fill: #c5c8c6 } + .terminal-3072634976-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - ────────────────────────────────────────────────────────────────────── - 1frwidth = 162fr1frwidth = 16 - - - - - - - - - - ────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────── - 1frwidth = 162fr1frwidth = 16 - - - - - - - - - - ────────────────────────────────────────────────────────────────────── + + + + ╭──────────╮╭──────────────╮╭──────────────────────╮╭──────────╮╭──────────────╮ + 1fr││width = 16││2fr││1fr││width = 16 + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ╰──────────╯╰──────────────╯╰──────────────────────╯╰──────────╯╰──────────────╯ + ╭──────────╮╭──────────────╮╭──────────────────────╮╭──────────╮╭──────────────╮ + 1fr││width = 16││2fr││1fr││width = 16 + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ││││││││ + ╰──────────╯╰──────────────╯╰──────────────────────╯╰──────────╯╰──────────────╯ @@ -8200,133 +8686,133 @@ font-weight: 700; } - .terminal-1194212456-matrix { + .terminal-3574968865-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1194212456-title { + .terminal-3574968865-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1194212456-r1 { fill: #ffffff } - .terminal-1194212456-r2 { fill: #e1e1e1 } - .terminal-1194212456-r3 { fill: #c5c8c6 } + .terminal-3574968865-r1 { fill: #ffffff } + .terminal-3574968865-r2 { fill: #e1e1e1 } + .terminal-3574968865-r3 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - ────────────────────────────────────────────────────────────────────────── - - 12 - - ────────────────────────────────────────────────────────────────────────── - - ────────────────────────────────────────────────────────────────────────── - - 34 - - ────────────────────────────────────────────────────────────────────────── - - ────────────────────────────────────────────────────────────────────────── - - 56 - - ────────────────────────────────────────────────────────────────────────── - - ────────────────────────────────────────────────────────────────────────── - - 78 - - - ────────────────────────────────────────────────────────────────────────── + + + + ╭─────────────────────────────────────╮╭─────────────────────────────────────╮ + + 12 + + ╰─────────────────────────────────────╯╰─────────────────────────────────────╯ + + ╭─────────────────────────────────────╮╭─────────────────────────────────────╮ + + 34 + + ╰─────────────────────────────────────╯╰─────────────────────────────────────╯ + + ╭─────────────────────────────────────╮╭─────────────────────────────────────╮ + + 56 + + ╰─────────────────────────────────────╯╰─────────────────────────────────────╯ + + ╭─────────────────────────────────────╮╭─────────────────────────────────────╮ + + 78 + + + ╰─────────────────────────────────────╯╰─────────────────────────────────────╯ @@ -8356,133 +8842,133 @@ font-weight: 700; } - .terminal-986618502-matrix { + .terminal-3421668871-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-986618502-title { + .terminal-3421668871-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-986618502-r1 { fill: #ffffff } - .terminal-986618502-r2 { fill: #c5c8c6 } - .terminal-986618502-r3 { fill: #e1e1e1 } + .terminal-3421668871-r1 { fill: #ffffff } + .terminal-3421668871-r2 { fill: #c5c8c6 } + .terminal-3421668871-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - ──────────────────────────────────────────────────────────────────────────── - 1fr1fr - ──────────────────────────────────────────────────────────────────────────── - ──────────────────────────────────────────────────────────────────────────── - - height = 6height = 6 - - - ──────────────────────────────────────────────────────────────────────────── - ──────────────────────────────────────────────────────────────────────────── - - 25%25% - - - ──────────────────────────────────────────────────────────────────────────── - ──────────────────────────────────────────────────────────────────────────── - 1fr1fr - ──────────────────────────────────────────────────────────────────────────── - ──────────────────────────────────────────────────────────────────────────── - - height = 6height = 6 - - - ──────────────────────────────────────────────────────────────────────────── + + + + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + 1fr││1fr + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + ││ + height = 6││height = 6 + ││ + ││ + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + ││ + 25%││25% + ││ + ││ + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + 1fr││1fr + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + ││ + height = 6││height = 6 + ││ + ││ + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ @@ -8512,132 +8998,132 @@ font-weight: 700; } - .terminal-3270198448-matrix { + .terminal-4060527209-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3270198448-title { + .terminal-4060527209-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3270198448-r1 { fill: #ffffff } - .terminal-3270198448-r2 { fill: #c5c8c6 } - .terminal-3270198448-r3 { fill: #e1e1e1 } + .terminal-4060527209-r1 { fill: #ffffff } + .terminal-4060527209-r2 { fill: #c5c8c6 } + .terminal-4060527209-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - ──────────────────────────────────────────────────────────────────────────── - - 12 - - - ──────────────────────────────────────────────────────────────────────────── - ──────────────────────────────────────────────────────────────────────────── - - 34 - - - ──────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────── - - 5 - - - ────────────────────────────────────── - - - - - + + + + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + ││ + 1││2 + ││ + ││ + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + ││ + 3││4 + ││ + ││ + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ + ╭──────────────────────────────────────╮ + + 5 + + + ╰──────────────────────────────────────╯ + + + + + @@ -8668,133 +9154,294 @@ font-weight: 700; } - .terminal-4208206220-matrix { + .terminal-1640924670-matrix { + font-family: Fira Code, monospace; + font-size: 20px; + line-height: 24.4px; + font-variant-east-asian: full-width; + } + + .terminal-1640924670-title { + font-size: 18px; + font-weight: bold; + font-family: arial; + } + + .terminal-1640924670-r1 { fill: #ffffff } + .terminal-1640924670-r2 { fill: #c5c8c6 } + .terminal-1640924670-r3 { fill: #e1e1e1 } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MyApp + + + + + + + + + + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + ││ + ││ + 1││2 + ││ + ││ + ││ + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ + ╭──────────────────────────────────────╮╭──────────────────────────────────────╮ + ││ + ││ + 3││4 + ││ + ││ + ││ + ╰──────────────────────────────────────╯╰──────────────────────────────────────╯ + ╭──────────────────────────────────────╮ + + + 5 + + + + ╰──────────────────────────────────────╯ + + + + + ''' +# --- +# name: test_css_property[hatch.py] + ''' + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + HatchApp - - - - ──────────────────────────────────────────────────────────────────────────── - - - 12 - - - - ──────────────────────────────────────────────────────────────────────────── - ──────────────────────────────────────────────────────────────────────────── - - - 34 - - - - ──────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────── - - - 5 - - - - ────────────────────────────────────── + + + + ┌─ cross ──────┐┌─ horizontal ─┐┌─ custom ─────┐┌─ left ───────┐┌─ right ──────┐ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╳╳╳╳╳╳╳╳╳╳╳╳╳╳││──────────────││TTTTTTTTTTTTTT││╲╲╲╲╲╲╲╲╲╲╲╲╲╲││╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + └──────────────┘└──────────────┘└──────────────┘└──────────────┘└──────────────┘ @@ -9144,136 +9791,136 @@ font-weight: 700; } - .terminal-3762053931-matrix { + .terminal-1885421407-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3762053931-title { + .terminal-1885421407-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3762053931-r1 { fill: #c5c8c6 } - .terminal-3762053931-r2 { fill: #008000 } - .terminal-3762053931-r3 { fill: #e8e0e7 } - .terminal-3762053931-r4 { fill: #eae3e5 } - .terminal-3762053931-r5 { fill: #1e1e1e } - .terminal-3762053931-r6 { fill: #ede6e6 } - .terminal-3762053931-r7 { fill: #efeedf } + .terminal-1885421407-r1 { fill: #c5c8c6 } + .terminal-1885421407-r2 { fill: #008000 } + .terminal-1885421407-r3 { fill: #e8e0e7 } + .terminal-1885421407-r4 { fill: #eae3e5 } + .terminal-1885421407-r5 { fill: #1e1e1e } + .terminal-1885421407-r6 { fill: #ede6e6 } + .terminal-1885421407-r7 { fill: #efeedf } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - KeylineApp + KeylineApp - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - #foo - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━#bar - - - Placeholder - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - #baz - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - + + + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ + + + #foo + + + ┣━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┫#bar + + + Placeholder + + + ┣━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┫ + + + #baz + + + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + @@ -9304,135 +9951,135 @@ font-weight: 700; } - .terminal-1481425640-matrix { + .terminal-1481807543-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1481425640-title { + .terminal-1481807543-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1481425640-r1 { fill: #fea62b } - .terminal-1481425640-r2 { fill: #c5c8c6 } - .terminal-1481425640-r3 { fill: #e8e0e7 } - .terminal-1481425640-r4 { fill: #eae3e5 } - .terminal-1481425640-r5 { fill: #ede6e6 } + .terminal-1481807543-r1 { fill: #fea62b } + .terminal-1481807543-r2 { fill: #c5c8c6 } + .terminal-1481807543-r3 { fill: #e8e0e7 } + .terminal-1481807543-r4 { fill: #eae3e5 } + .terminal-1481807543-r5 { fill: #ede6e6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - KeylineApp + KeylineApp - - - - ──────────────────────────────────────────────────────────────────────────── - - - - - - - - - - - PlaceholderPlaceholderPlaceholder - - - - - - - - - - - - ──────────────────────────────────────────────────────────────────────────── + + + + ┌─────────────────────────┬─────────────────────────┬──────────────────────────┐ + + + + + + + + + + + PlaceholderPlaceholderPlaceholder + + + + + + + + + + + + └─────────────────────────┴─────────────────────────┴──────────────────────────┘ @@ -9620,134 +10267,134 @@ font-weight: 700; } - .terminal-687058265-matrix { + .terminal-3791839841-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-687058265-title { + .terminal-3791839841-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-687058265-r1 { fill: #e1e1e1 } - .terminal-687058265-r2 { fill: #c5c8c6 } - .terminal-687058265-r3 { fill: #ffdddd;text-decoration: underline; } - .terminal-687058265-r4 { fill: #121201;text-decoration: underline; } - .terminal-687058265-r5 { fill: #ddedf9;text-decoration: underline; } + .terminal-3791839841-r1 { fill: #e1e1e1 } + .terminal-3791839841-r2 { fill: #c5c8c6 } + .terminal-3791839841-r3 { fill: #ffdddd;text-decoration: underline; } + .terminal-3791839841-r4 { fill: #121201;text-decoration: underline; } + .terminal-3791839841-r5 { fill: #ddedf9;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LinkBackgroundApp + LinkBackgroundApp - - - - Visit the Textualize website. - Click here for the bell sound. - You can also click here for the bell sound. - Exit this application. - - - - - - - - - - - - - - - - - - - + + + + Visit the Textualize website.                                                    + Click here for the bell sound.                                                   + You can also click here for the bell sound.                                      + Exit this application. + + + + + + + + + + + + + + + + + + + @@ -9778,132 +10425,132 @@ font-weight: 700; } - .terminal-1076604876-matrix { + .terminal-213509332-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1076604876-title { + .terminal-213509332-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1076604876-r1 { fill: #e1e1e1 } - .terminal-1076604876-r2 { fill: #c5c8c6 } - .terminal-1076604876-r3 { fill: #e1e1e1;text-decoration: underline; } + .terminal-213509332-r1 { fill: #e1e1e1 } + .terminal-213509332-r2 { fill: #c5c8c6 } + .terminal-213509332-r3 { fill: #e1e1e1;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LinkHoverBackgroundApp + LinkHoverBackgroundApp - - - - Visit the Textualize website. - Click here for the bell sound. - You can also click here for the bell sound. - Exit this application. - - - - - - - - - - - - - - - - - - - + + + + Visit the Textualize website.                                                    + Click here for the bell sound.                                                   + You can also click here for the bell sound.                                      + Exit this application. + + + + + + + + + + + + + + + + + + + @@ -9934,134 +10581,134 @@ font-weight: 700; } - .terminal-3021056461-matrix { + .terminal-1781128917-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3021056461-title { + .terminal-1781128917-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3021056461-r1 { fill: #e1e1e1 } - .terminal-3021056461-r2 { fill: #c5c8c6 } - .terminal-3021056461-r3 { fill: #ff0000;text-decoration: underline; } - .terminal-3021056461-r4 { fill: #8e8e0f;text-decoration: underline; } - .terminal-3021056461-r5 { fill: #0178d4;text-decoration: underline; } + .terminal-1781128917-r1 { fill: #e1e1e1 } + .terminal-1781128917-r2 { fill: #c5c8c6 } + .terminal-1781128917-r3 { fill: #ff0000;text-decoration: underline; } + .terminal-1781128917-r4 { fill: #8e8e0f;text-decoration: underline; } + .terminal-1781128917-r5 { fill: #0178d4;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LinkColorApp + LinkColorApp - - - - Visit the Textualize website. - Click here for the bell sound. - You can also click here for the bell sound. - Exit this application. - - - - - - - - - - - - - - - - - - - + + + + Visit the Textualize website.                                                    + Click here for the bell sound.                                                   + You can also click here for the bell sound.                                      + Exit this application. + + + + + + + + + + + + + + + + + + + @@ -10092,132 +10739,132 @@ font-weight: 700; } - .terminal-3576933835-matrix { + .terminal-2559238867-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3576933835-title { + .terminal-2559238867-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3576933835-r1 { fill: #e1e1e1 } - .terminal-3576933835-r2 { fill: #c5c8c6 } - .terminal-3576933835-r3 { fill: #e1e1e1;text-decoration: underline; } + .terminal-2559238867-r1 { fill: #e1e1e1 } + .terminal-2559238867-r2 { fill: #c5c8c6 } + .terminal-2559238867-r3 { fill: #e1e1e1;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LinkHoverColorApp + LinkHoverColorApp - - - - Visit the Textualize website. - Click here for the bell sound. - You can also click here for the bell sound. - Exit this application. - - - - - - - - - - - - - - - - - - - + + + + Visit the Textualize website.                                                    + Click here for the bell sound.                                                   + You can also click here for the bell sound.                                      + Exit this application. + + + + + + + + + + + + + + + + + + + @@ -10248,134 +10895,134 @@ font-weight: 700; } - .terminal-2410786847-matrix { + .terminal-4258391335-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2410786847-title { + .terminal-4258391335-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2410786847-r1 { fill: #e1e1e1 } - .terminal-2410786847-r2 { fill: #c5c8c6 } - .terminal-2410786847-r3 { fill: #e1e1e1;font-weight: bold;font-style: italic; } - .terminal-2410786847-r4 { fill: #1e1e1e;text-decoration: line-through; } - .terminal-2410786847-r5 { fill: #e1e1e1;font-weight: bold } + .terminal-4258391335-r1 { fill: #e1e1e1 } + .terminal-4258391335-r2 { fill: #c5c8c6 } + .terminal-4258391335-r3 { fill: #e1e1e1;font-weight: bold;font-style: italic; } + .terminal-4258391335-r4 { fill: #1e1e1e;text-decoration: line-through; } + .terminal-4258391335-r5 { fill: #e1e1e1;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LinkStyleApp + LinkStyleApp - - - - Visit the Textualize website. - Click here for the bell sound. - You can also click here for the bell sound. - Exit this application. - - - - - - - - - - - - - - - - - - - + + + + Visit the Textualize website.                                                    + Click here for the bell sound.                                                   + You can also click here for the bell sound.                                      + Exit this application. + + + + + + + + + + + + + + + + + + + @@ -10406,132 +11053,132 @@ font-weight: 700; } - .terminal-3588337117-matrix { + .terminal-2570642149-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3588337117-title { + .terminal-2570642149-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3588337117-r1 { fill: #e1e1e1 } - .terminal-3588337117-r2 { fill: #c5c8c6 } - .terminal-3588337117-r3 { fill: #e1e1e1;text-decoration: underline; } + .terminal-2570642149-r1 { fill: #e1e1e1 } + .terminal-2570642149-r2 { fill: #c5c8c6 } + .terminal-2570642149-r3 { fill: #e1e1e1;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LinkHoverStyleApp + LinkHoverStyleApp - - - - Visit the Textualize website. - Click here for the bell sound. - You can also click here for the bell sound. - Exit this application. - - - - - - - - - - - - - - - - - - - + + + + Visit the Textualize website.                                                    + Click here for the bell sound.                                                   + You can also click here for the bell sound.                                      + Exit this application. + + + + + + + + + + + + + + + + + + + @@ -10719,133 +11366,133 @@ font-weight: 700; } - .terminal-3234129636-matrix { + .terminal-1776342825-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3234129636-title { + .terminal-1776342825-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3234129636-r1 { fill: #000000 } - .terminal-3234129636-r2 { fill: #c5c8c6 } - .terminal-3234129636-r3 { fill: #0000ff } - .terminal-3234129636-r4 { fill: #ccccff } + .terminal-1776342825-r1 { fill: #000000 } + .terminal-1776342825-r2 { fill: #c5c8c6 } + .terminal-1776342825-r3 { fill: #0000ff } + .terminal-1776342825-r4 { fill: #ccccff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarginApp + MarginApp - - - - - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see  - its path. - Where the fear has gone there will be nothing. Only I will  - remain. - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - - - - - - + + + + + + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see  + its path. + Where the fear has gone there will be nothing. Only I will  + remain. + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + + + + + + @@ -10876,141 +11523,141 @@ font-weight: 700; } - .terminal-670958431-matrix { + .terminal-2957478392-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-670958431-title { + .terminal-2957478392-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-670958431-r1 { fill: #ffffff } - .terminal-670958431-r2 { fill: #e0e0e0 } - .terminal-670958431-r3 { fill: #c5c8c6 } - .terminal-670958431-r4 { fill: #ece5e5 } - .terminal-670958431-r5 { fill: #eee8e3 } - .terminal-670958431-r6 { fill: #e7e0e6 } - .terminal-670958431-r7 { fill: #eae2e4 } - .terminal-670958431-r8 { fill: #e3ede7 } - .terminal-670958431-r9 { fill: #e8ede4 } - .terminal-670958431-r10 { fill: #e1eceb } - .terminal-670958431-r11 { fill: #eeeddf } + .terminal-2957478392-r1 { fill: #ffffff } + .terminal-2957478392-r2 { fill: #e0e0e0 } + .terminal-2957478392-r3 { fill: #c5c8c6 } + .terminal-2957478392-r4 { fill: #ece5e5 } + .terminal-2957478392-r5 { fill: #eee8e3 } + .terminal-2957478392-r6 { fill: #e7e0e6 } + .terminal-2957478392-r7 { fill: #eae2e4 } + .terminal-2957478392-r8 { fill: #e3ede7 } + .terminal-2957478392-r9 { fill: #e8ede4 } + .terminal-2957478392-r10 { fill: #e1eceb } + .terminal-2957478392-r11 { fill: #eeeddf } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarginAllApp + MarginAllApp - - - - ────────────────────────────────────────────────────────────────── - - - - marginmargin: 1  - no marginmargin: 1: 1 51 2 6 - - - - - ────────────────────────────────────────────────────────────────── - - ────────────────────────────────────────────────────────────────── - - - margin-bottom: 4 - - margin-right: margin-left: 3 - 3 - margin-top: 4 - - - - ────────────────────────────────────────────────────────────────── + + + + ╭────────────────╮╭─────────────────╮╭────────────────╮╭─────────────────╮ + + + + marginmargin: 1  + no marginmargin: 1: 1 51 2 6 + + + + + ╰────────────────╯╰─────────────────╯╰────────────────╯╰─────────────────╯ + + ╭────────────────╮╭─────────────────╮╭────────────────╮╭─────────────────╮ + + + margin-bottom: 4 + + margin-right: margin-left: 3 + 3 + margin-top: 4 + + + + ╰────────────────╯╰─────────────────╯╰────────────────╯╰─────────────────╯ @@ -11518,135 +12165,135 @@ font-weight: 700; } - .terminal-1031378205-matrix { + .terminal-438685870-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1031378205-title { + .terminal-438685870-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1031378205-r1 { fill: #c5c8c6 } - .terminal-1031378205-r2 { fill: #e1e1e1 } - .terminal-1031378205-r3 { fill: #e8e0e7 } - .terminal-1031378205-r4 { fill: #eae3e5 } - .terminal-1031378205-r5 { fill: #ede6e6 } - .terminal-1031378205-r6 { fill: #efe9e4 } + .terminal-438685870-r1 { fill: #c5c8c6 } + .terminal-438685870-r2 { fill: #e1e1e1 } + .terminal-438685870-r3 { fill: #e8e0e7 } + .terminal-438685870-r4 { fill: #eae3e5 } + .terminal-438685870-r5 { fill: #ede6e6 } + .terminal-438685870-r6 { fill: #efe9e4 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MinWidthApp + MinWidthApp - - - - - - min-width: 25% - - - - - min-width: 75% - - - - - - min-width: 100 - - - - - - min-width: 400h - - - + + + + + + min-width: 25% + + + + + min-width: 75% + + + + + + min-width: 100 + + + + + + min-width: 400h + + + @@ -11677,134 +12324,134 @@ font-weight: 700; } - .terminal-3520697079-matrix { + .terminal-3730833227-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3520697079-title { + .terminal-3730833227-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3520697079-r1 { fill: #000000 } - .terminal-3520697079-r2 { fill: #0000ff } - .terminal-3520697079-r3 { fill: #c5c8c6 } - .terminal-3520697079-r4 { fill: #ff0000 } - .terminal-3520697079-r5 { fill: #008000 } + .terminal-3730833227-r1 { fill: #000000 } + .terminal-3730833227-r2 { fill: #0000ff } + .terminal-3730833227-r3 { fill: #c5c8c6 } + .terminal-3730833227-r4 { fill: #ff0000 } + .terminal-3730833227-r5 { fill: #008000 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OffsetApp + OffsetApp - - - - - Chani (offset 0  - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀-3) - - - - Paul (offset 8 2)▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - - - - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - - - Duncan (offset 4  - 10) - - - - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - - - + + + + + Chani (offset 0  + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜-3) + + + + ▌Paul (offset 8 2)▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ + + + + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + + + ▌Duncan (offset 4  + ▌10) + + + + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ + + + @@ -11835,141 +12482,141 @@ font-weight: 700; } - .terminal-2483518667-matrix { + .terminal-618549268-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2483518667-title { + .terminal-618549268-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2483518667-r1 { fill: #000000 } - .terminal-2483518667-r2 { fill: #c5c8c6 } - .terminal-2483518667-r3 { fill: #000000;font-weight: bold } - .terminal-2483518667-r4 { fill: #01090f } - .terminal-2483518667-r5 { fill: #373838;font-weight: bold } - .terminal-2483518667-r6 { fill: #07243f } - .terminal-2483518667-r7 { fill: #6f7474;font-weight: bold } - .terminal-2483518667-r8 { fill: #10518f } - .terminal-2483518667-r9 { fill: #a8b3b2;font-weight: bold } - .terminal-2483518667-r10 { fill: #1e90ff } - .terminal-2483518667-r11 { fill: #e2f4f3;font-weight: bold } + .terminal-618549268-r1 { fill: #000000 } + .terminal-618549268-r2 { fill: #c5c8c6 } + .terminal-618549268-r3 { fill: #000000;font-weight: bold } + .terminal-618549268-r4 { fill: #01090f } + .terminal-618549268-r5 { fill: #373838;font-weight: bold } + .terminal-618549268-r6 { fill: #07243f } + .terminal-618549268-r7 { fill: #6f7474;font-weight: bold } + .terminal-618549268-r8 { fill: #10518f } + .terminal-618549268-r9 { fill: #a8b3b2;font-weight: bold } + .terminal-618549268-r10 { fill: #1e90ff } + .terminal-618549268-r11 { fill: #e2f4f3;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OpacityApp + OpacityApp - - - - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - opacity: 0% - - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - - opacity: 25% - - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - - opacity: 50% - - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - - opacity: 75% - - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - - opacity: 100% - - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + + + + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ + opacity: 0% + + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ + + opacity: 25% + + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ + + opacity: 50% + + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ + + opacity: 75% + + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ + + opacity: 100% + + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ @@ -11999,133 +12646,133 @@ font-weight: 700; } - .terminal-3556982422-matrix { + .terminal-2596745327-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3556982422-title { + .terminal-2596745327-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3556982422-r1 { fill: #000000 } - .terminal-3556982422-r2 { fill: #c5c8c6 } - .terminal-3556982422-r3 { fill: #008000 } - .terminal-3556982422-r4 { fill: #cce5cc } + .terminal-2596745327-r1 { fill: #000000 } + .terminal-2596745327-r2 { fill: #c5c8c6 } + .terminal-2596745327-r3 { fill: #008000 } + .terminal-2596745327-r4 { fill: #cce5cc } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OutlineApp + OutlineApp - - - - - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ear is the mind-killer. - ear is the little-death that brings total obliteration. -  will face my fear. -  will permit it to pass over me and through me. - nd when it has gone past, I will turn the inner eye to see its - ath. - here the fear has gone there will be nothing. Only I will  - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - - - - - - - - + + + + + + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ear is the mind-killer. + ear is the little-death that brings total obliteration. +  will face my fear. +  will permit it to pass over me and through me. + nd when it has gone past, I will turn the inner eye to see its + ath. + here the fear has gone there will be nothing. Only I will  + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + + + + + + + + @@ -12156,133 +12803,133 @@ font-weight: 700; } - .terminal-2040973868-matrix { + .terminal-119716715-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2040973868-title { + .terminal-119716715-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2040973868-r1 { fill: #e1e1e1 } - .terminal-2040973868-r2 { fill: #0178d4 } - .terminal-2040973868-r3 { fill: #c5c8c6 } - .terminal-2040973868-r4 { fill: #1e1e1e } + .terminal-119716715-r1 { fill: #e1e1e1 } + .terminal-119716715-r2 { fill: #0178d4 } + .terminal-119716715-r3 { fill: #c5c8c6 } + .terminal-119716715-r4 { fill: #1e1e1e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AllOutlinesApp + AllOutlinesApp - - - - +------------------+╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - |ascii|blankdashed - +------------------+╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - - - ══════════════════━━━━━━━━━━━━━━━━━━ - doubleheavyhidden/none - ══════════════════━━━━━━━━━━━━━━━━━━ - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - hkeyinnernone - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - - - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀──────────────────────────────────── - outerroundsolid - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄──────────────────────────────────── - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - tallvkeywide - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + + +------------------+┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ + |ascii|blankdashed + +------------------+┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛ + + + ╔══════════════════╗┏━━━━━━━━━━━━━━━━━━┓ + doubleheavyhidden/none + ╚══════════════════╝┗━━━━━━━━━━━━━━━━━━┛ + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▗▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▖ + hkeyinnernone + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘ + + + ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜╭──────────────────╮┌──────────────────┐ + outerroundsolid + ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟╰──────────────────╯└──────────────────┘ + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▏                  ▕▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + tallvkeywide + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎▏                  ▕▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ @@ -12313,134 +12960,134 @@ font-weight: 700; } - .terminal-1454950069-matrix { + .terminal-55418416-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1454950069-title { + .terminal-55418416-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1454950069-r1 { fill: #b93c5b } - .terminal-1454950069-r2 { fill: #e1e1e1 } - .terminal-1454950069-r3 { fill: #c5c8c6 } - .terminal-1454950069-r4 { fill: #4ebf71 } + .terminal-55418416-r1 { fill: #b93c5b } + .terminal-55418416-r2 { fill: #e1e1e1 } + .terminal-55418416-r3 { fill: #c5c8c6 } + .terminal-55418416-r4 { fill: #4ebf71 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OutlineBorderApp + OutlineBorderApp - - - - ─────────────────────────────────────────────────────────────────── - ear is the mind-killer. - ear is the little-death that brings total obliteration. -  will face my fear. -  will permit it to pass over me and through me. - nd when it has gone past, I will turn the inner eye to see its path - here the fear has gone there will be nothing. Only I will remain. - ─────────────────────────────────────────────────────────────────── - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ───────────────────────────────────────────────────────────────────── - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - ───────────────────────────────────────────────────────────────────── + + + + ╭───────────────────────────────────────────────────────────────────╮ + ear is the mind-killer. + ear is the little-death that brings total obliteration. +  will face my fear. +  will permit it to pass over me and through me. + nd when it has gone past, I will turn the inner eye to see its path + here the fear has gone there will be nothing. Only I will remain. + ╰───────────────────────────────────────────────────────────────────╯ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path. + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ╭─────────────────────────────────────────────────────────────────────╮ + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path. + ╰─────────────────────────────────────────────────────────────────────╯ @@ -12470,136 +13117,136 @@ font-weight: 700; } - .terminal-1076088988-matrix { + .terminal-2364259351-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1076088988-title { + .terminal-2364259351-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1076088988-r1 { fill: #000000 } - .terminal-1076088988-r2 { fill: #c5c8c6 } - .terminal-1076088988-r3 { fill: #008000 } - .terminal-1076088988-r4 { fill: #e5f0e5 } - .terminal-1076088988-r5 { fill: #036a03 } - .terminal-1076088988-r6 { fill: #14191f } + .terminal-2364259351-r1 { fill: #000000 } + .terminal-2364259351-r2 { fill: #c5c8c6 } + .terminal-2364259351-r3 { fill: #008000 } + .terminal-2364259351-r4 { fill: #e5f0e5 } + .terminal-2364259351-r5 { fill: #036a03 } + .terminal-2364259351-r6 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OverflowApp + OverflowApp - - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - I must not fear.I must not fear. - Fear is the mind-killer.Fear is the mind-killer. - Fear is the little-death that Fear is the little-death that  - brings total obliteration.brings total obliteration. - I will face my fear.I will face my fear. - I will permit it to pass over meI will permit it to pass over me  - and through me.and through me. - And when it has gone past, I And when it has gone past, I will  - will turn the inner eye to see turn the inner eye to see its  - its path.▁▁path. - Where the fear has gone there Where the fear has gone there will - will be nothing. Only I will be nothing. Only I will remain. - remain.▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁I must not fear. - I must not fear.Fear is the mind-killer. - Fear is the mind-killer.Fear is the little-death that  - Fear is the little-death that brings total obliteration. - brings total obliteration.I will face my fear. - I will face my fear.I will permit it to pass over me  - I will permit it to pass over meand through me. + + + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + I must not fear.I must not fear. + Fear is the mind-killer.Fear is the mind-killer. + Fear is the little-death that Fear is the little-death that  + brings total obliteration.brings total obliteration. + I will face my fear.I will face my fear. + I will permit it to pass over meI will permit it to pass over me  + and through me.and through me. + And when it has gone past, I And when it has gone past, I will  + will turn the inner eye to see turn the inner eye to see its  + its path.▁▁path. + Where the fear has gone there Where the fear has gone there will + will be nothing. Only I will be nothing. Only I will remain. + remain.▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁I must not fear. + I must not fear.Fear is the mind-killer. + Fear is the mind-killer.Fear is the little-death that  + Fear is the little-death that brings total obliteration. + brings total obliteration.I will face my fear. + I will face my fear.I will permit it to pass over me  + I will permit it to pass over meand through me. @@ -12629,131 +13276,131 @@ font-weight: 700; } - .terminal-3291669704-matrix { + .terminal-1142797465-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3291669704-title { + .terminal-1142797465-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3291669704-r1 { fill: #c5c8c6 } - .terminal-3291669704-r2 { fill: #0000ff } + .terminal-1142797465-r1 { fill: #c5c8c6 } + .terminal-1142797465-r2 { fill: #0000ff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - PaddingApp + PaddingApp - - - - - - - - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its  - path. - Where the fear has gone there will be nothing. Only I will  - remain. - - - - - - - - - - + + + + + + + + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its  + path. + Where the fear has gone there will be nothing. Only I will  + remain. + + + + + + + + + + @@ -12784,139 +13431,139 @@ font-weight: 700; } - .terminal-1905931680-matrix { + .terminal-2663771085-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1905931680-title { + .terminal-2663771085-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1905931680-r1 { fill: #e7e0e6 } - .terminal-1905931680-r2 { fill: #e0e0e0 } - .terminal-1905931680-r3 { fill: #c5c8c6 } - .terminal-1905931680-r4 { fill: #eae2e4 } - .terminal-1905931680-r5 { fill: #ece5e5 } - .terminal-1905931680-r6 { fill: #eee8e3 } - .terminal-1905931680-r7 { fill: #e8ede4 } - .terminal-1905931680-r8 { fill: #e3ede7 } - .terminal-1905931680-r9 { fill: #e1eceb } - .terminal-1905931680-r10 { fill: #eeeddf } + .terminal-2663771085-r1 { fill: #e7e0e6 } + .terminal-2663771085-r2 { fill: #e0e0e0 } + .terminal-2663771085-r3 { fill: #c5c8c6 } + .terminal-2663771085-r4 { fill: #eae2e4 } + .terminal-2663771085-r5 { fill: #ece5e5 } + .terminal-2663771085-r6 { fill: #eee8e3 } + .terminal-2663771085-r7 { fill: #e8ede4 } + .terminal-2663771085-r8 { fill: #e3ede7 } + .terminal-2663771085-r9 { fill: #e1eceb } + .terminal-2663771085-r10 { fill: #eeeddf } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - PaddingAllApp + PaddingAllApp - - - - no padding - padding: 1padding:padding: 1 1 - 1 52 6 - - - - - - - - - - padding-right: 3padding-bottom: 4padding-left: 3 - - - - padding-top: 4 - - - - - - + + + + no padding + padding: 1padding:padding: 1 1 + 1 52 6 + + + + + + + + + + padding-right: 3padding-bottom: 4padding-left: 3 + + + + padding-top: 4 + + + + + + @@ -13109,132 +13756,132 @@ font-weight: 700; } - .terminal-3484348706-matrix { + .terminal-3108861724-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3484348706-title { + .terminal-3108861724-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3484348706-r1 { fill: #e1e1e1 } - .terminal-3484348706-r2 { fill: #c5c8c6 } - .terminal-3484348706-r3 { fill: #14191f } + .terminal-3108861724-r1 { fill: #e1e1e1 } + .terminal-3108861724-r2 { fill: #c5c8c6 } + .terminal-3108861724-r3 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollbarCornerColorApp + ScrollbarCornerColorApp - - - - I must not fear. Fear is the mind-killer. Fear is the little-death that brings - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - Where the fear has gone there will be nothing. Only I will remain.▅▅ - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - Where the fear has gone there will be nothing. Only I will remain. - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - Where the fear has gone there will be nothing. Only I will remain. - I must not fear. + + + + I must not fear. Fear is the mind-killer. Fear is the little-death that brings + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path. + Where the fear has gone there will be nothing. Only I will remain.▅▅ + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path. + Where the fear has gone there will be nothing. Only I will remain. + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path. + Where the fear has gone there will be nothing. Only I will remain. + I must not fear. @@ -13421,132 +14068,132 @@ font-weight: 700; } - .terminal-1624210960-matrix { + .terminal-3590949634-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1624210960-title { + .terminal-3590949634-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1624210960-r1 { fill: #c5c8c6 } - .terminal-1624210960-r2 { fill: #3333ff } - .terminal-1624210960-r3 { fill: #14191f } + .terminal-3590949634-r1 { fill: #c5c8c6 } + .terminal-3590949634-r2 { fill: #3333ff } + .terminal-3590949634-r3 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollbarApp + ScrollbarApp - - - - - - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration.▁▁▁▁ - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - Where the fear has gone there will be nothing. Only I will remain. - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - - - - - - - - - - + + + + + + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration.▁▁▁▁ + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path. + Where the fear has gone there will be nothing. Only I will remain. + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + + + + + + + + + + @@ -13577,136 +14224,136 @@ font-weight: 700; } - .terminal-709381566-matrix { + .terminal-4177949326-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-709381566-title { + .terminal-4177949326-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-709381566-r1 { fill: #e7e0e0 } - .terminal-709381566-r2 { fill: #c5c8c6 } - .terminal-709381566-r3 { fill: #e0e4e0 } - .terminal-709381566-r4 { fill: #e0e0e7 } - .terminal-709381566-r5 { fill: #14191f } - .terminal-709381566-r6 { fill: #23568b } + .terminal-4177949326-r1 { fill: #e7e0e0 } + .terminal-4177949326-r2 { fill: #c5c8c6 } + .terminal-4177949326-r3 { fill: #e0e4e0 } + .terminal-4177949326-r4 { fill: #e0e0e7 } + .terminal-4177949326-r5 { fill: #14191f } + .terminal-4177949326-r6 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollbarApp + ScrollbarApp - - - - I must not fear.I must not fear.I must not fear. - Fear is the mind-killer.Fear is the mind-killer.Fear is the mind-killer. - Fear is the little-death Fear is the little-death tFear is the little-death  - I will face my fear.I will face my fear.I will face my fear. - I will permit it to pass I will permit it to pass oI will permit it to pass  - And when it has gone pastAnd when it has gone past,And when it has gone past - Where the fear has gone tWhere the fear has gone thWhere the fear has gone t - I must not fear.I must not fear.I must not fear. - Fear is the mind-killer.Fear is the mind-killer.Fear is the mind-killer. - Fear is the little-death Fear is the little-death tFear is the little-death  - I will face my fear.I will face my fear.I will face my fear.▇▇ - I will permit it to pass I will permit it to pass oI will permit it to pass  - And when it has gone pastAnd when it has gone past,And when it has gone past - Where the fear has gone tWhere the fear has gone thWhere the fear has gone t - I must not fear.I must not fear.I must not fear. - Fear is the mind-killer.Fear is the mind-killer.Fear is the mind-killer. - Fear is the little-death Fear is the little-death tFear is the little-death  - I will face my fear.I will face my fear.I will face my fear. - I will permit it to pass I will permit it to pass oI will permit it to pass  - And when it has gone past, - Where the fear has gone th - I must not fear. - Fear is the mind-killer. - + + + + I must not fear.I must not fear.I must not fear. + Fear is the mind-killer.Fear is the mind-killer.Fear is the mind-killer. + Fear is the little-death Fear is the little-death tFear is the little-death  + I will face my fear.I will face my fear.I will face my fear. + I will permit it to pass I will permit it to pass oI will permit it to pass  + And when it has gone pastAnd when it has gone past,And when it has gone past + Where the fear has gone tWhere the fear has gone thWhere the fear has gone t + I must not fear.I must not fear.I must not fear. + Fear is the mind-killer.Fear is the mind-killer.Fear is the mind-killer. + Fear is the little-death Fear is the little-death tFear is the little-death  + I will face my fear.I will face my fear.I will face my fear.▇▇ + I will permit it to pass I will permit it to pass oI will permit it to pass  + And when it has gone pastAnd when it has gone past,And when it has gone past + Where the fear has gone tWhere the fear has gone thWhere the fear has gone t + I must not fear.I must not fear.I must not fear. + Fear is the mind-killer.Fear is the mind-killer.Fear is the mind-killer. + Fear is the little-death Fear is the little-death tFear is the little-death  + I will face my fear.I will face my fear.I will face my fear. + I will permit it to pass I will permit it to pass oI will permit it to pass  + And when it has gone past, + Where the fear has gone th + I must not fear. + Fear is the mind-killer. + @@ -13736,136 +14383,136 @@ font-weight: 700; } - .terminal-3248687298-matrix { + .terminal-2700719567-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3248687298-title { + .terminal-2700719567-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3248687298-r1 { fill: #e1e1e1 } - .terminal-3248687298-r2 { fill: #c5c8c6 } - .terminal-3248687298-r3 { fill: #14191f } - .terminal-3248687298-r4 { fill: #ff0000 } - .terminal-3248687298-r5 { fill: #23568b } - .terminal-3248687298-r6 { fill: #008000 } + .terminal-2700719567-r1 { fill: #e1e1e1 } + .terminal-2700719567-r2 { fill: #c5c8c6 } + .terminal-2700719567-r3 { fill: #14191f } + .terminal-2700719567-r4 { fill: #ff0000 } + .terminal-2700719567-r5 { fill: #23568b } + .terminal-2700719567-r6 { fill: #008000 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollbarApp + ScrollbarApp - - - - I must not fear.I must not fear. - Fear is the mind-killer.Fear is the mind-killer. - Fear is the little-death that brings tFear is the little-death that brings t - I will face my fear.I will face my fear. - I will permit it to pass over me and tI will permit it to pass over me and t - And when it has gone past, I will turnAnd when it has gone past, I will turn - see its path.see its path. - Where the fear has gone there will be Where the fear has gone there will be  - will remain.will remain. - I must not fear.I must not fear. - Fear is the mind-killer.Fear is the mind-killer. - Fear is the little-death that brings tFear is the little-death that brings t - I will face my fear.I will face my fear. - I will permit it to pass over me and tI will permit it to pass over me and t - And when it has gone past, I will turnAnd when it has gone past, I will turn - see its path.▃▃see its path.▃▃ - Where the fear has gone there will be Where the fear has gone there will be  - will remain.will remain. - I must not fear.I must not fear. - Fear is the mind-killer.Fear is the mind-killer. - Fear is the little-death that brings tFear is the little-death that brings t - I will face my fear.I will face my fear. - I will permit it to pass over me and tI will permit it to pass over me and t - + + + + I must not fear.I must not fear. + Fear is the mind-killer.Fear is the mind-killer. + Fear is the little-death that brings tFear is the little-death that brings t + I will face my fear.I will face my fear. + I will permit it to pass over me and tI will permit it to pass over me and t + And when it has gone past, I will turnAnd when it has gone past, I will turn + see its path.see its path. + Where the fear has gone there will be Where the fear has gone there will be  + will remain.will remain. + I must not fear.I must not fear. + Fear is the mind-killer.Fear is the mind-killer. + Fear is the little-death that brings tFear is the little-death that brings t + I will face my fear.I will face my fear. + I will permit it to pass over me and tI will permit it to pass over me and t + And when it has gone past, I will turnAnd when it has gone past, I will turn + see its path.▃▃see its path.▃▃ + Where the fear has gone there will be Where the fear has gone there will be  + will remain.will remain. + I must not fear.I must not fear. + Fear is the mind-killer.Fear is the mind-killer. + Fear is the little-death that brings tFear is the little-death that brings t + I will face my fear.I will face my fear. + I will permit it to pass over me and tI will permit it to pass over me and t + @@ -13895,133 +14542,133 @@ font-weight: 700; } - .terminal-3828898399-matrix { + .terminal-295079783-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3828898399-title { + .terminal-295079783-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3828898399-r1 { fill: #e1e1e1 } - .terminal-3828898399-r2 { fill: #c5c8c6 } - .terminal-3828898399-r3 { fill: #0000ff } + .terminal-295079783-r1 { fill: #e1e1e1 } + .terminal-295079783-r2 { fill: #c5c8c6 } + .terminal-295079783-r3 { fill: #0000ff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Scrollbar2App + Scrollbar2App - - - - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - Where the fear has gone there will be nothing. Only I will remain. - I must not fear. - Fear is the mind-killer.▇▇ - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - Where the fear has gone there will be nothing. Only I will remain. - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. - I will face my fear. - I will permit it to pass over me and through me. - And when it has gone past, I will turn the inner eye to see its path. - Where the fear has gone there will be nothing. Only I will remain. - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total obliteration. + + + + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path.          + Where the fear has gone there will be nothing. Only I will remain. + I must not fear. + Fear is the mind-killer.▇▇ + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path.          + Where the fear has gone there will be nothing. Only I will remain. + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. + I will face my fear. + I will permit it to pass over me and through me. + And when it has gone past, I will turn the inner eye to see its path.          + Where the fear has gone there will be nothing. Only I will remain. + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total obliteration. @@ -14051,138 +14698,138 @@ font-weight: 700; } - .terminal-3840959336-matrix { + .terminal-2525861690-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3840959336-title { + .terminal-2525861690-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3840959336-r1 { fill: #c5c8c6 } - .terminal-3840959336-r2 { fill: #161c1d;font-weight: bold } - .terminal-3840959336-r3 { fill: #161c1d } - .terminal-3840959336-r4 { fill: #f8e9e9 } - .terminal-3840959336-r5 { fill: #f8e9e9;font-weight: bold } - .terminal-3840959336-r6 { fill: #132013 } - .terminal-3840959336-r7 { fill: #132013;font-weight: bold } - .terminal-3840959336-r8 { fill: #1c0e13;font-weight: bold } - .terminal-3840959336-r9 { fill: #1c0e13 } + .terminal-2525861690-r1 { fill: #c5c8c6 } + .terminal-2525861690-r2 { fill: #161c1d;font-weight: bold } + .terminal-2525861690-r3 { fill: #161c1d } + .terminal-2525861690-r4 { fill: #f8e9e9 } + .terminal-2525861690-r5 { fill: #f8e9e9;font-weight: bold } + .terminal-2525861690-r6 { fill: #132013 } + .terminal-2525861690-r7 { fill: #132013;font-weight: bold } + .terminal-2525861690-r8 { fill: #1c0e13;font-weight: bold } + .terminal-2525861690-r9 { fill: #1c0e13 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAlign + TextAlign - - - - - Left alignedCenter aligned - I must not fear. Fear is the            I must not fear. Fear is the     - mind-killer. Fear is the                  mind-killer. Fear is the       - little-death that brings total         little-death that brings total    - obliteration. I will face my fear. Iobliteration. I will face my fear. I - will permit it to pass over me and   will permit it to pass over me and  - through me.                                     through me.              - - - - - - Right alignedJustified -         I must not fear. Fear is theI  must  not  fear.  Fear   is   the -             mind-killer. Fear is themind-killer.     Fear     is     the -       little-death that brings totallittle-death   that   brings   total - obliteration. I will face my fear. Iobliteration. I will face my fear. I -   will permit it to pass over me andwill permit it to pass over  me  and -                          through me.through me. - - - + + + + + Left alignedCenter aligned + I must not fear. Fear is the            I must not fear. Fear is the     + mind-killer. Fear is the                  mind-killer. Fear is the       + little-death that brings total         little-death that brings total    + obliteration. I will face my fear. Iobliteration. I will face my fear. I + will permit it to pass over me and   will permit it to pass over me and  + through me.                                     through me.              + + + + + + Right alignedJustified +         I must not fear. Fear is theI  must  not  fear.  Fear   is   the +             mind-killer. Fear is themind-killer.     Fear     is     the +       little-death that brings totallittle-death   that   brings   total + obliteration. I will face my fear. Iobliteration. I will face my fear. I +   will permit it to pass over me andwill permit it to pass over  me  and +                          through me.through me. + + + @@ -14529,138 +15176,138 @@ font-weight: 700; } - .terminal-3534356536-matrix { + .terminal-3629813941-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3534356536-title { + .terminal-3629813941-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3534356536-r1 { fill: #e1e1e1 } - .terminal-3534356536-r2 { fill: #c5c8c6 } - .terminal-3534356536-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-3534356536-r4 { fill: #e1e1e1;font-style: italic; } - .terminal-3534356536-r5 { fill: #1e1e1e } - .terminal-3534356536-r6 { fill: #e1e1e1;text-decoration: line-through; } - .terminal-3534356536-r7 { fill: #e1e1e1;text-decoration: underline; } - .terminal-3534356536-r8 { fill: #e1e1e1;font-weight: bold;font-style: italic; } - .terminal-3534356536-r9 { fill: #1e1e1e;text-decoration: line-through; } + .terminal-3629813941-r1 { fill: #e1e1e1 } + .terminal-3629813941-r2 { fill: #c5c8c6 } + .terminal-3629813941-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-3629813941-r4 { fill: #e1e1e1;font-style: italic; } + .terminal-3629813941-r5 { fill: #1e1e1e } + .terminal-3629813941-r6 { fill: #e1e1e1;text-decoration: line-through; } + .terminal-3629813941-r7 { fill: #e1e1e1;text-decoration: underline; } + .terminal-3629813941-r8 { fill: #e1e1e1;font-weight: bold;font-style: italic; } + .terminal-3629813941-r9 { fill: #1e1e1e;text-decoration: line-through; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AllTextStyleApp + AllTextStyleApp - - - - - nonebolditalicreverse - I must not fear.I must not fear.I must not fear.I must not fear. - Fear is the Fear is the Fear is the Fear is the  - mind-killer.mind-killer.mind-killer.mind-killer. - Fear is the Fear is the Fear is the Fear is the  - little-death thatlittle-death that little-death thatlittle-death that  - brings total brings total brings total brings total  - obliteration.obliteration.obliteration.obliteration. - I will face my I will face my I will face my I will face my  - fear.fear.fear.fear. - - strikeunderlinebold italicreverse strike - I must not fear.I must not fear.I must not fear.I must not fear. - Fear is the Fear is the Fear is the Fear is the  - mind-killer.mind-killer.mind-killer.mind-killer. - Fear is the Fear is the Fear is the Fear is the  - little-death thatlittle-death that little-death thatlittle-death that  - brings total brings total brings total brings total  - obliteration.obliteration.obliteration.obliteration. - I will face my I will face my I will face my I will face my  - fear.fear.fear.fear. - I will permit it I will permit it I will permit it I will permit it  + + + + +   nonebolditalicreverse +   I must not fear.I must not fear.I must not fear.I must not fear. +   Fear is the Fear is the Fear is the Fear is the  +   mind-killer.mind-killer.mind-killer.mind-killer. +   Fear is the Fear is the Fear is the Fear is the  +   little-death that  little-death that little-death thatlittle-death that  +   brings total brings total brings total brings total  +   obliteration.obliteration.obliteration.obliteration. +   I will face my I will face my I will face my I will face my  +   fear.fear.fear.fear. + + strikeunderlinebold italicreverse strike + I must not fear.I must not fear.I must not fear.I must not fear. + Fear is the Fear is the Fear is the Fear is the  + mind-killer.mind-killer.mind-killer.mind-killer. + Fear is the Fear is the Fear is the Fear is the  + little-death thatlittle-death that little-death thatlittle-death that  + brings total brings total brings total brings total  + obliteration.obliteration.obliteration.obliteration. + I will face my I will face my I will face my I will face my  + fear.fear.fear.fear. + I will permit it I will permit it I will permit it I will permit it  @@ -14855,132 +15502,132 @@ font-weight: 700; } - .terminal-398211359-matrix { + .terminal-3114242500-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-398211359-title { + .terminal-3114242500-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-398211359-r1 { fill: #0000ff } - .terminal-398211359-r2 { fill: #c5c8c6 } - .terminal-398211359-r3 { fill: #ddeedd } + .terminal-3114242500-r1 { fill: #0000ff } + .terminal-3114242500-r2 { fill: #c5c8c6 } + .terminal-3114242500-r3 { fill: #ddeedd } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - VisibilityApp + VisibilityApp - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Widget 1 - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Widget 3 - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - - - - + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + ┃Widget 1 + + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + ┃Widget 3 + + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + + + + + + + @@ -15328,148 +15975,148 @@ font-weight: 700; } - .terminal-3780599318-matrix { + .terminal-2486976152-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3780599318-title { + .terminal-2486976152-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3780599318-r1 { fill: #c5c8c6 } - .terminal-3780599318-r2 { fill: #e8e0e7 } - .terminal-3780599318-r3 { fill: #eae3e5 } - .terminal-3780599318-r4 { fill: #ede6e6 } - .terminal-3780599318-r5 { fill: #efe9e4 } - .terminal-3780599318-r6 { fill: #efeedf } - .terminal-3780599318-r7 { fill: #e9eee5 } - .terminal-3780599318-r8 { fill: #e4eee8 } - .terminal-3780599318-r9 { fill: #e2edeb } - .terminal-3780599318-r10 { fill: #dfebed } - .terminal-3780599318-r11 { fill: #ddedf9 } + .terminal-2486976152-r1 { fill: #c5c8c6 } + .terminal-2486976152-r2 { fill: #e8e0e7 } + .terminal-2486976152-r3 { fill: #eae3e5 } + .terminal-2486976152-r4 { fill: #ede6e6 } + .terminal-2486976152-r5 { fill: #efe9e4 } + .terminal-2486976152-r6 { fill: #efeedf } + .terminal-2486976152-r7 { fill: #e9eee5 } + .terminal-2486976152-r8 { fill: #e4eee8 } + .terminal-2486976152-r9 { fill: #e2edeb } + .terminal-2486976152-r10 { fill: #dfebed } + .terminal-2486976152-r11 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - WidthComparisonApp + WidthComparisonApp - - - - - - - - - - - - - - - #cells#percent#w#h#vw#vh#auto#fr1#fr3 - - - - - - - - - - - - ····•····•····•····•····•····•····•····•····•····•····•····•····•····•····•····• + + + + + + + + + + + + + + + #cells#percent#w#h#vw#vh#auto#fr1#fr3 + + + + + + + + + + + + ····•····•····•····•····•····•····•····•····•····•····•····•····•····•····•····• ''' # --- -# name: test_datatable_add_column +# name: test_data_table_in_tabs ''' @@ -15492,125 +16139,286 @@ font-weight: 700; } - .terminal-2146794738-matrix { + .terminal-971656130-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2146794738-title { + .terminal-971656130-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2146794738-r1 { fill: #dde6ed;font-weight: bold } - .terminal-2146794738-r2 { fill: #dde6ed } - .terminal-2146794738-r3 { fill: #c5c8c6 } - .terminal-2146794738-r4 { fill: #211505 } - .terminal-2146794738-r5 { fill: #e1e1e1 } + .terminal-971656130-r1 { fill: #c5c8c6 } + .terminal-971656130-r2 { fill: #e1e1e1 } + .terminal-971656130-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-971656130-r4 { fill: #474747 } + .terminal-971656130-r5 { fill: #0178d4 } + .terminal-971656130-r6 { fill: #dde6ed;font-weight: bold } + .terminal-971656130-r7 { fill: #dde6ed } + .terminal-971656130-r8 { fill: #211505 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AddColumn + Dashboard - - - -  Movies          No Default  With Default  Long Default          -  Severance       ABC           01234567890123456789  -  Foundation      ABC           01234567890123456789  -  Dark            Hello!      ABC           01234567890123456789  -  The Boys        ABC           01234567890123456789  -  The Last of Us  ABC           01234567890123456789  -  Lost in Space   ABC           01234567890123456789  -  Altered Carbon  ABC           01234567890123456789  - - - - - - + + + + + Workflows + ━╸━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +  Id   Description  Status  Result Id  +  1    2            3       4          +  a    b            c       d          +  fee  fy           fo      fum        + + + + + + + + + + + + + + + + + + + + + ''' +# --- +# name: test_datatable_add_column + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AddColumn + + + + + + + + + +  Movies          No Default  With Default  Long Default          +  Severance       ABC           01234567890123456789  +  Foundation      ABC           01234567890123456789  +  Dark            Hello!      ABC           01234567890123456789  +  The Boys        ABC           01234567890123456789  +  The Last of Us  ABC           01234567890123456789  +  Lost in Space   ABC           01234567890123456789  +  Altered Carbon  ABC           01234567890123456789  + + + + + + @@ -15966,134 +16774,134 @@ font-weight: 700; } - .terminal-1699433504-matrix { + .terminal-3480025555-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1699433504-title { + .terminal-3480025555-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1699433504-r1 { fill: #e1e1e1 } - .terminal-1699433504-r2 { fill: #c5c8c6 } - .terminal-1699433504-r3 { fill: #dde6ed;font-weight: bold } - .terminal-1699433504-r4 { fill: #dde6ed } - .terminal-1699433504-r5 { fill: #211505 } + .terminal-3480025555-r1 { fill: #e1e1e1 } + .terminal-3480025555-r2 { fill: #c5c8c6 } + .terminal-3480025555-r3 { fill: #dde6ed;font-weight: bold } + .terminal-3480025555-r4 { fill: #dde6ed } + .terminal-3480025555-r5 { fill: #211505 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TableApp + TableApp - - - - - one  two  three - valuevalueval   - -  one    two    three  -  value  value  val    - -   one      two      three   -   value    value    val     - -    one        two        three    -    value      value      val      - -     one          two          three     -     value        value        val       - - - - - - - - + + + + + one  two  three + valuevalueval   + +  one    two    three  +  value  value  val    + +   one      two      three   +   value    value    val     + +    one        two        three    +    value      value      val      + +     one          two          three     +     value        value        val       + + + + + + + + @@ -16124,134 +16932,134 @@ font-weight: 700; } - .terminal-236473376-matrix { + .terminal-2017065427-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-236473376-title { + .terminal-2017065427-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-236473376-r1 { fill: #e1e1e1 } - .terminal-236473376-r2 { fill: #c5c8c6 } - .terminal-236473376-r3 { fill: #dde6ed;font-weight: bold } - .terminal-236473376-r4 { fill: #dde6ed } - .terminal-236473376-r5 { fill: #211505 } + .terminal-2017065427-r1 { fill: #e1e1e1 } + .terminal-2017065427-r2 { fill: #c5c8c6 } + .terminal-2017065427-r3 { fill: #dde6ed;font-weight: bold } + .terminal-2017065427-r4 { fill: #dde6ed } + .terminal-2017065427-r5 { fill: #211505 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TableApp + TableApp - - - - - one  two  three - valuevalueval   - -  one    two    three  -  value  value  val    - -   one      two      three   -   value    value    val     - -    one        two        three    -    value      value      val      - -           one                      two                      three           -           value                    value                    val             - - - - - - - - + + + + + one  two  three + valuevalueval   + +  one    two    three  +  value  value  val    + +   one      two      three   +   value    value    val     + +    one        two        three    +    value      value      val      + +           one                      two                      three           +           value                    value                    val             + + + + + + + + @@ -17397,137 +18205,137 @@ font-weight: 700; } - .terminal-3159150741-matrix { + .terminal-535151098-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3159150741-title { + .terminal-535151098-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3159150741-r1 { fill: #e1e1e1 } - .terminal-3159150741-r2 { fill: #c5c8c6 } - .terminal-3159150741-r3 { fill: #dde6ed;font-weight: bold } - .terminal-3159150741-r4 { fill: #dde6ed } - .terminal-3159150741-r5 { fill: #fea62b;font-weight: bold;font-style: italic; } - .terminal-3159150741-r6 { fill: #e1e2e3 } - .terminal-3159150741-r7 { fill: #f4005f } - .terminal-3159150741-r8 { fill: #f4005f;font-weight: bold;font-style: italic; } + .terminal-535151098-r1 { fill: #e1e1e1 } + .terminal-535151098-r2 { fill: #c5c8c6 } + .terminal-535151098-r3 { fill: #dde6ed;font-weight: bold } + .terminal-535151098-r4 { fill: #dde6ed } + .terminal-535151098-r5 { fill: #fea62b;font-weight: bold;font-style: italic; } + .terminal-535151098-r6 { fill: #e1e2e3 } + .terminal-535151098-r7 { fill: #f4005f } + .terminal-535151098-r8 { fill: #f4005f;font-weight: bold;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DataTableCursorStyles + DataTableCursorStyles - - - - Foreground is 'css', background is 'css': -  Movies      -  Severance   - Foundation - Dark - - Foreground is 'css', background is 'renderable': -  Movies      - Severance - Foundation - Dark - - Foreground is 'renderable', background is 'renderable': -  Movies      - Severance - Foundation - Dark - - Foreground is 'renderable', background is 'css': -  Movies      - Severance - Foundation - Dark + + + + Foreground is 'css', background is 'css':                                        +  Movies      +  Severance   + Foundation + Dark + + Foreground is 'css', background is 'renderable':                                 +  Movies      + Severance + Foundation + Dark + + Foreground is 'renderable', background is 'renderable':                          +  Movies      + Severance + Foundation + Dark + + Foreground is 'renderable', background is 'css':                                 +  Movies      + Severance + Foundation + Dark @@ -17558,168 +18366,169 @@ font-weight: 700; } - .terminal-123171118-matrix { + .terminal-353476698-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-123171118-title { + .terminal-353476698-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-123171118-r1 { fill: #c5c8c6 } - .terminal-123171118-r2 { fill: #e3e3e3 } - .terminal-123171118-r3 { fill: #e1e1e1 } - .terminal-123171118-r4 { fill: #e2e2e2 } - .terminal-123171118-r5 { fill: #14191f } - .terminal-123171118-r6 { fill: #004578 } - .terminal-123171118-r7 { fill: #262626 } - .terminal-123171118-r8 { fill: #e2e2e2;font-weight: bold;text-decoration: underline; } - .terminal-123171118-r9 { fill: #e2e2e2;font-weight: bold } - .terminal-123171118-r10 { fill: #7ae998 } - .terminal-123171118-r11 { fill: #4ebf71;font-weight: bold } - .terminal-123171118-r12 { fill: #008139 } - .terminal-123171118-r13 { fill: #dde8f3;font-weight: bold } - .terminal-123171118-r14 { fill: #ddedf9 } + .terminal-353476698-r1 { fill: #c5c8c6 } + .terminal-353476698-r2 { fill: #e3e3e3 } + .terminal-353476698-r3 { fill: #e1e1e1 } + .terminal-353476698-r4 { fill: #e2e2e2 } + .terminal-353476698-r5 { fill: #14191f } + .terminal-353476698-r6 { fill: #004578 } + .terminal-353476698-r7 { fill: #262626 } + .terminal-353476698-r8 { fill: #e2e2e2;font-weight: bold;text-decoration: underline; } + .terminal-353476698-r9 { fill: #e2e2e2;font-weight: bold } + .terminal-353476698-r10 { fill: #7ae998 } + .terminal-353476698-r11 { fill: #4ebf71;font-weight: bold } + .terminal-353476698-r12 { fill: #008139 } + .terminal-353476698-r13 { fill: #fea62b;font-weight: bold } + .terminal-353476698-r14 { fill: #a7a9ab } + .terminal-353476698-r15 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Textual Demo + Textual Demo - - - - Textual Demo - - - TOP - - ▆▆ - - Widgets - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - Rich contentTextual Demo - - Welcome! Textual is a framework for creating sophisticated - applications with the terminal.                            - CSS - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Start - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - - - - - - -  CTRL+B  Sidebar  CTRL+T  Toggle Dark mode  CTRL+S  Screenshot  F1  Notes  CTRL+Q  Quit  + + + + Textual Demo + + + TOP + + ▆▆ + + Widgets + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + Rich contentTextual Demo + + Welcome! Textual is a framework for creating sophisticated + applications with the terminal.                            + CSS + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Start  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + + + + + + +  ^b Sidebar  ^t Toggle Dark mode  ^s Screenshot  f1 Notes  ^q Quit  @@ -17749,132 +18558,132 @@ font-weight: 700; } - .terminal-993607346-matrix { + .terminal-1348330842-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-993607346-title { + .terminal-1348330842-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-993607346-r1 { fill: #e1e1e1;font-weight: bold } - .terminal-993607346-r2 { fill: #c5c8c6 } - .terminal-993607346-r3 { fill: #e1e1e1 } + .terminal-1348330842-r1 { fill: #e1e1e1;font-weight: bold } + .terminal-1348330842-r2 { fill: #c5c8c6 } + .terminal-1348330842-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DigitApp + DigitApp - - - - ╺━┓  ┓ ╻ ╻╺━┓╺━┓ -  ━┫  ┃ ┗━┫┏━┛  ┃ - ╺━┛.╺┻╸  ╹┗━╸  ╹ -    ┏━┓ ┓ ╺━┓╺━┓╻ ╻┏━╸┏━╸╺━┓┏━┓┏━┓         -    ┃ ┃ ┃ ┏━┛ ━┫┗━┫┗━┓┣━┓  ┃┣━┫┗━┫╺╋╸╺━╸   -    ┗━┛╺┻╸┗━╸╺━┛  ╹╺━┛┗━┛  ╹┗━┛╺━┛      ., - ╺━┓    ┓ ┏━┓ ^ ╻ ╻ -  ━┫ ×  ┃ ┃ ┃   ┗━┫ - ╺━┛   ╺┻╸┗━┛     ╹ - - - - - - - - - - - - - - + + + + ╺━┓  ┓ ╻ ╻╺━┓╺━┓                                                                 +  ━┫  ┃ ┗━┫┏━┛  ┃                                                                 + ╺━┛.╺┻╸  ╹┗━╸  ╹                                                                 +                       ┏━┓ ┓ ╺━┓╺━┓╻ ╻┏━╸┏━╸╺━┓┏━┓┏━┓                             +                       ┃ ┃ ┃ ┏━┛ ━┫┗━┫┗━┓┣━┓  ┃┣━┫┗━┫╺╋╸╺━╸                       +                       ┗━┛╺┻╸┗━╸╺━┛  ╹╺━┛┗━┛  ╹┗━┛╺━┛      .,                     +                                                               ╺━┓    ┓ ┏━┓ ^ ╻ ╻ +                                                                ━┫ ×  ┃ ┃ ┃   ┗━┫ +                                                               ╺━┛   ╺┻╸┗━┛     ╹ + + + + + + + + + + + + + + @@ -17905,136 +18714,136 @@ font-weight: 700; } - .terminal-1907613470-matrix { + .terminal-2123922789-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1907613470-title { + .terminal-2123922789-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1907613470-r1 { fill: #e2e3e3 } - .terminal-1907613470-r2 { fill: #e2e3e3;font-weight: bold } - .terminal-1907613470-r3 { fill: #c5c8c6 } - .terminal-1907613470-r4 { fill: #008139 } - .terminal-1907613470-r5 { fill: #211505;font-weight: bold } - .terminal-1907613470-r6 { fill: #fea62b;font-weight: bold } - .terminal-1907613470-r7 { fill: #e2e3e3;font-style: italic; } + .terminal-2123922789-r1 { fill: #e2e3e3 } + .terminal-2123922789-r2 { fill: #e2e3e3;font-weight: bold } + .terminal-2123922789-r3 { fill: #c5c8c6 } + .terminal-2123922789-r4 { fill: #008139 } + .terminal-2123922789-r5 { fill: #211505;font-weight: bold } + .terminal-2123922789-r6 { fill: #fea62b;font-weight: bold } + .terminal-2123922789-r7 { fill: #e2e3e3;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DirectoryTreeReloadApp + DirectoryTreeReloadApp - - - - 📂 test_directory_tree_reloading0 - ├── 📂 b1 - │   ├── 📂 c1 - │   │   ┣━━ 📂 d1 - │   │   ┃   ┣━━ 📄 f1.txt - │   │   ┃   ┗━━ 📄 f2.txt - │   │   ┣━━ 📄 f1.txt - │   │   ┗━━ 📄 f2.txt - │   ├── 📄 f1.txt - │   └── 📄 f2.txt - ├── 📄 f1.txt - └── 📄 f2.txt - - - - - - - - - - - + + + + 📂 test_directory_tree_reloading0 + ├── 📂 b1 + │   ├── 📂 c1 + │   │   ┣━━ 📂 d1 + │   │   ┃   ┣━━ 📄 f1.txt + │   │   ┃   ┗━━ 📄 f2.txt + │   │   ┣━━ 📄 f1.txt + │   │   ┗━━ 📄 f2.txt + │   ├── 📄 f1.txt + │   └── 📄 f2.txt + ├── 📄 f1.txt + └── 📄 f2.txt + + + + + + + + + + + @@ -18065,162 +18874,162 @@ font-weight: 700; } - .terminal-3209943725-matrix { + .terminal-3864303289-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3209943725-title { + .terminal-3864303289-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3209943725-r1 { fill: #454a50 } - .terminal-3209943725-r2 { fill: #507bb3 } - .terminal-3209943725-r3 { fill: #7ae998 } - .terminal-3209943725-r4 { fill: #ffcf56 } - .terminal-3209943725-r5 { fill: #e76580 } - .terminal-3209943725-r6 { fill: #c5c8c6 } - .terminal-3209943725-r7 { fill: #24292f;font-weight: bold } - .terminal-3209943725-r8 { fill: #dde6ed;font-weight: bold } - .terminal-3209943725-r9 { fill: #0a180e;font-weight: bold } - .terminal-3209943725-r10 { fill: #211505;font-weight: bold } - .terminal-3209943725-r11 { fill: #f5e5e9;font-weight: bold } - .terminal-3209943725-r12 { fill: #000000 } - .terminal-3209943725-r13 { fill: #001541 } - .terminal-3209943725-r14 { fill: #008139 } - .terminal-3209943725-r15 { fill: #b86b00 } - .terminal-3209943725-r16 { fill: #780028 } - .terminal-3209943725-r17 { fill: #303336 } - .terminal-3209943725-r18 { fill: #364b66 } - .terminal-3209943725-r19 { fill: #4a8159 } - .terminal-3209943725-r20 { fill: #8b7439 } - .terminal-3209943725-r21 { fill: #80404d } - .terminal-3209943725-r22 { fill: #a7a7a7;font-weight: bold } - .terminal-3209943725-r23 { fill: #a5a9ac;font-weight: bold } - .terminal-3209943725-r24 { fill: #0e1510;font-weight: bold } - .terminal-3209943725-r25 { fill: #19140c;font-weight: bold } - .terminal-3209943725-r26 { fill: #b0a8aa;font-weight: bold } - .terminal-3209943725-r27 { fill: #0f0f0f } - .terminal-3209943725-r28 { fill: #0f192e } - .terminal-3209943725-r29 { fill: #0f4e2a } - .terminal-3209943725-r30 { fill: #68430f } - .terminal-3209943725-r31 { fill: #4a0f22 } - .terminal-3209943725-r32 { fill: #e2e3e3;font-weight: bold } + .terminal-3864303289-r1 { fill: #454a50 } + .terminal-3864303289-r2 { fill: #507bb3 } + .terminal-3864303289-r3 { fill: #7ae998 } + .terminal-3864303289-r4 { fill: #ffcf56 } + .terminal-3864303289-r5 { fill: #e76580 } + .terminal-3864303289-r6 { fill: #c5c8c6 } + .terminal-3864303289-r7 { fill: #24292f;font-weight: bold } + .terminal-3864303289-r8 { fill: #dde6ed;font-weight: bold } + .terminal-3864303289-r9 { fill: #0a180e;font-weight: bold } + .terminal-3864303289-r10 { fill: #211505;font-weight: bold } + .terminal-3864303289-r11 { fill: #f5e5e9;font-weight: bold } + .terminal-3864303289-r12 { fill: #000000 } + .terminal-3864303289-r13 { fill: #001541 } + .terminal-3864303289-r14 { fill: #008139 } + .terminal-3864303289-r15 { fill: #b86b00 } + .terminal-3864303289-r16 { fill: #780028 } + .terminal-3864303289-r17 { fill: #303336 } + .terminal-3864303289-r18 { fill: #364b66 } + .terminal-3864303289-r19 { fill: #4a8159 } + .terminal-3864303289-r20 { fill: #8b7439 } + .terminal-3864303289-r21 { fill: #80404d } + .terminal-3864303289-r22 { fill: #a7a7a7;font-weight: bold } + .terminal-3864303289-r23 { fill: #a5a9ac;font-weight: bold } + .terminal-3864303289-r24 { fill: #0e1510;font-weight: bold } + .terminal-3864303289-r25 { fill: #19140c;font-weight: bold } + .terminal-3864303289-r26 { fill: #b0a8aa;font-weight: bold } + .terminal-3864303289-r27 { fill: #0f0f0f } + .terminal-3864303289-r28 { fill: #0f192e } + .terminal-3864303289-r29 { fill: #0f4e2a } + .terminal-3864303289-r30 { fill: #68430f } + .terminal-3864303289-r31 { fill: #4a0f22 } + .terminal-3864303289-r32 { fill: #e2e3e3;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - WidgetDisableTestApp + WidgetDisableTestApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ButtonButtonButtonButtonButton - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ButtonButtonButtonButtonButton - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ButtonButtonButtonButtonButton - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ButtonButtonButtonButtonButton - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ButtonButtonButtonButtonButton - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ButtonButtonButtonButtonButton - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ButtonButtonButtonButtonButton - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ButtonButtonButtonButtonButton - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  Button  Button  Button  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  Button  Button  Button  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  Button  Button  Button  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  Button  Button  Button  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  Button  Button  Button  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  Button  Button  Button  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  Button  Button  Button  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  Button  Button  Button  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -18407,141 +19216,142 @@ font-weight: 700; } - .terminal-3671710802-matrix { + .terminal-3926104244-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3671710802-title { + .terminal-3926104244-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3671710802-r1 { fill: #c5c8c6 } - .terminal-3671710802-r2 { fill: #1e1e1e } - .terminal-3671710802-r3 { fill: #1f1f1f } - .terminal-3671710802-r4 { fill: #ff0000 } - .terminal-3671710802-r5 { fill: #dde8f3;font-weight: bold } - .terminal-3671710802-r6 { fill: #ddedf9 } - .terminal-3671710802-r7 { fill: #c7cdd2 } + .terminal-3926104244-r1 { fill: #c5c8c6 } + .terminal-3926104244-r2 { fill: #1e1e1e } + .terminal-3926104244-r3 { fill: #1f1f1f } + .terminal-3926104244-r4 { fill: #ff0000 } + .terminal-3926104244-r5 { fill: #004578;font-weight: bold } + .terminal-3926104244-r6 { fill: #585a5c } + .terminal-3926104244-r7 { fill: #1c1d1e } + .terminal-3926104244-r8 { fill: #c7cdd2 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TestApp + TestApp - - - - TestApp - ───────── - this - is - a - sample - sentence - and - here - are - some - wordsthis - is - a - sample - sentence - and - here - are - some - words -  CTRL+Q  Quit  - - - ▇▇ + + + + TestApp + ┌─────────┐ + this + is + a + sample + sentence + and + here + are + some + wordsthis + is + a + sample + sentence + and + here + are + some + words +  ^q Quit  + + + ▇▇ @@ -18571,140 +19381,141 @@ font-weight: 700; } - .terminal-2802609302-matrix { + .terminal-1826994938-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2802609302-title { + .terminal-1826994938-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2802609302-r1 { fill: #c5c8c6 } - .terminal-2802609302-r2 { fill: #1e1e1e } - .terminal-2802609302-r3 { fill: #1f1f1f } - .terminal-2802609302-r4 { fill: #ff0000 } - .terminal-2802609302-r5 { fill: #c7cdd2 } - .terminal-2802609302-r6 { fill: #dde8f3;font-weight: bold } - .terminal-2802609302-r7 { fill: #ddedf9 } + .terminal-1826994938-r1 { fill: #c5c8c6 } + .terminal-1826994938-r2 { fill: #1e1e1e } + .terminal-1826994938-r3 { fill: #1f1f1f } + .terminal-1826994938-r4 { fill: #ff0000 } + .terminal-1826994938-r5 { fill: #c7cdd2 } + .terminal-1826994938-r6 { fill: #004578;font-weight: bold } + .terminal-1826994938-r7 { fill: #585a5c } + .terminal-1826994938-r8 { fill: #1c1d1e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TestApp + TestApp - - - - TestApp - ───────── - this - is - a - sample - sentence - and - here - are - some - wordsthis - is - a▅▅ - sample - sentence - and - here - are - some - words -  CTRL+Q  Quit  - - + + + + TestApp + ┌─────────┐ + this + is + a + sample + sentence + and + here + are + some + wordsthis + is + a▅▅ + sample + sentence + and + here + are + some + words +  ^q Quit  + + @@ -18735,141 +19546,141 @@ font-weight: 700; } - .terminal-1431734718-matrix { + .terminal-1300400438-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1431734718-title { + .terminal-1300400438-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1431734718-r1 { fill: #1e1e1e } - .terminal-1431734718-r2 { fill: #e1e1e1 } - .terminal-1431734718-r3 { fill: #c5c8c6 } - .terminal-1431734718-r4 { fill: #434343 } - .terminal-1431734718-r5 { fill: #262626;font-weight: bold } - .terminal-1431734718-r6 { fill: #e2e2e2 } - .terminal-1431734718-r7 { fill: #23568b } - .terminal-1431734718-r8 { fill: #ddedf9 } + .terminal-1300400438-r1 { fill: #1e1e1e } + .terminal-1300400438-r2 { fill: #e1e1e1 } + .terminal-1300400438-r3 { fill: #c5c8c6 } + .terminal-1300400438-r4 { fill: #434343 } + .terminal-1300400438-r5 { fill: #262626;font-weight: bold } + .terminal-1300400438-r6 { fill: #e2e2e2 } + .terminal-1300400438-r7 { fill: #23568b } + .terminal-1300400438-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollOffByOne + ScrollOffByOne - - - - ▔▔▔▔▔▔▔▔ - X 92 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 93 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 94 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 95 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 96 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 97 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 98 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 99▁▁ - ▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔ + X 92 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 93 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 94 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 95 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 96 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 97 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 98 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 99▁▁ + ▁▁▁▁▁▁▁▁ @@ -18877,6 +19688,166 @@ ''' # --- +# name: test_dynamic_bindings + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BindingsApp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  a  c  + + + + + ''' +# --- # name: test_example_calculator ''' @@ -18900,144 +19871,144 @@ font-weight: 700; } - .terminal-710708527-matrix { + .terminal-64523855-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-710708527-title { + .terminal-64523855-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-710708527-r1 { fill: #e1e1e1 } - .terminal-710708527-r2 { fill: #c5c8c6 } - .terminal-710708527-r3 { fill: #e5ebf2;font-weight: bold } - .terminal-710708527-r4 { fill: #507bb3 } - .terminal-710708527-r5 { fill: #ffcf56 } - .terminal-710708527-r6 { fill: #004578;font-weight: bold } - .terminal-710708527-r7 { fill: #dde6ed;font-weight: bold } - .terminal-710708527-r8 { fill: #211505;font-weight: bold } - .terminal-710708527-r9 { fill: #001541 } - .terminal-710708527-r10 { fill: #b86b00 } - .terminal-710708527-r11 { fill: #454a50 } - .terminal-710708527-r12 { fill: #e2e3e3;font-weight: bold } - .terminal-710708527-r13 { fill: #000000 } - .terminal-710708527-r14 { fill: #14191f } + .terminal-64523855-r1 { fill: #e1e1e1 } + .terminal-64523855-r2 { fill: #c5c8c6 } + .terminal-64523855-r3 { fill: #e5ebf2;font-weight: bold } + .terminal-64523855-r4 { fill: #507bb3 } + .terminal-64523855-r5 { fill: #ffcf56 } + .terminal-64523855-r6 { fill: #004578;font-weight: bold } + .terminal-64523855-r7 { fill: #dde6ed;font-weight: bold } + .terminal-64523855-r8 { fill: #211505;font-weight: bold } + .terminal-64523855-r9 { fill: #001541 } + .terminal-64523855-r10 { fill: #b86b00 } + .terminal-64523855-r11 { fill: #454a50 } + .terminal-64523855-r12 { fill: #e2e3e3;font-weight: bold } + .terminal-64523855-r13 { fill: #000000 } + .terminal-64523855-r14 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CalculatorApp + CalculatorApp - - - - - - ┏━┓ - ┃ ┃ - ┗━┛ - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - AC+/-%÷ - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 789× - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 456- - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 123+ - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▅ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + + + +                                                                      ┏━┓ +                                                                      ┃ ┃ +                                                                      ┗━┛ + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  AC  +/-  %  ÷  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  7  8  9  ×  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  4  5  6  -  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  2  3  +  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▅ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ @@ -19067,133 +20038,133 @@ font-weight: 700; } - .terminal-3687631720-matrix { + .terminal-112858016-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3687631720-title { + .terminal-112858016-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3687631720-r1 { fill: #c5c8c6 } - .terminal-3687631720-r2 { fill: #e3e3e3 } - .terminal-3687631720-r3 { fill: #e1e1e1 } - .terminal-3687631720-r4 { fill: #ffdddd } + .terminal-112858016-r1 { fill: #c5c8c6 } + .terminal-112858016-r2 { fill: #e3e3e3 } + .terminal-112858016-r3 { fill: #e1e1e1 } + .terminal-112858016-r4 { fill: #eedddd } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Press ctrl + \ and type a color + Press ctrl + \ and type a color - - - - Press ctrl + \ and type a color - - - - - red - - - - - - - - - - - - - - - - - + + + + Press ctrl + \ and type a color + + + + + ansi_red + + + + + + + + + + + + + + + + + @@ -19363,7 +20334,1615 @@ ''' # --- -# name: test_example_five_by_five +# name: test_example_five_by_five + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5x5 -- A little annoying puzzle + + + + + + + + + + 5x5 -- A little annoying puzzleMoves: 0Filled: 5 + ╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮ + ││││││││ + ││││││││ + ╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯ + ╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮ + ││││ + ││││ + ╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯ + ╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮ + ││││ + ││││ + ││││ + ╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯ + ╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮ + ││││ + ││││ + ╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯ + ╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮╭──────────────╮ + ││││││││ + ││││││││ + ││││││││ + ╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯╰──────────────╯ +  n New Game  ? Help  q Quit  ^d Toggle Dark Mode  + + + + + ''' +# --- +# name: test_example_json_tree + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeApp + + + + + + + + + + TreeApp + ▼ Root + └── ▼ {} JSON▁▁ +     ├── code='5060292302201' +     ├── ▼ {} product +     │   ┣━━ _id='5060292302201' +     │   ┣━━ ▶ [] _keywords +     │   ┣━━ ▶ [] added_countries_tags +     │   ┣━━ ▶ [] additives_debug_tags +     │   ┣━━ additives_n=2 +     │   ┣━━ additives_old_n=2 +     │   ┣━━ ▶ [] additives_old_tags +     │   ┣━━ ▶ [] additives_original_tags +     │   ┣━━ ▶ [] additives_prev_original_tags +     │   ┣━━ ▶ [] additives_tags +     │   ┣━━ additives_tags_n=None +     │   ┣━━ allergens='en:milk' +     │   ┣━━ ▶ [] allergens_debug_tags +     │   ┣━━ allergens_from_ingredients='en:milk, milk' +     │   ┣━━ allergens_from_user='(en) en:milk' +     │   ┣━━ ▶ [] allergens_hierarchy +     │   ┣━━ ▶ [] allergens_tags + +  a Add node  c Clear  t Toggle root  + + + + + ''' +# --- +# name: test_example_markdown + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MarkdownApp + + + + + + + + + + + ▼ Ⅰ Textual Markdown Browser + └── Ⅱ Do You Want to Know More?Textual Markdown Browser + +   Welcome fellow adventurer! If you ran  + markdown.py from the terminal you are  +   viewing demo.md with Textual's built in      +   Markdown widget. + +   The widget supports much of the Markdown     +   spec. There is also an optional Table of     +   Contents sidebar which you will see to  +   your left. + + + Do You Want to Know More? + +   See example.md for more examples of what     +   this can do. + + + + +  t TOC  b Back  f Forward  + + + + + ''' +# --- +# name: test_example_merlin + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MerlinApp + + + + + + + + + + + + ┏━┓   ┏━┓┏━┓   ┏━┓┏━┓ + ┃ ┃ : ┃ ┃┃ ┃ : ┃ ┃┃ ┃ + ┗━┛   ┗━┛┗━┛   ┗━┛┗━┛ + + + █▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█ + +     7         8         9      + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +     4         5         6      + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +     1         2         3      + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▇▇ + + + + + ''' +# --- +# name: test_example_pride + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PrideApp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ''' +# --- +# name: test_focus_component_class + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StyleBugApp + + + + + + + + + + StyleBugApp + test widget 0 + test widget 1 + test widget 2 + test widget 3 + test widget 4 + test widget 5 + test widget 6 + test widget 7 + test widget 8 + test widget 9 + test widget 10 + test widget 11 + test widget 12▇▇ + test widget 13 + test widget 14 + test widget 15 + test widget 16 + test widget 17 + test widget 18 + test widget 19 + test widget 20 + test widget 21 + + + + + + ''' +# --- +# name: test_footer_classic_styling + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ClassicFooterStylingApp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  CTRL+T  Toggle Dark mode  CTRL+Q  Quit                                          + + + + + ''' +# --- +# name: test_footer_compact + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ToggleCompactFooterApp + + + + + + + + + + + + + + + + + + + + +                                  Compact Footer                                  + + + + + + + + + + + + ^t Toggle Compact Footer^q Quit + + + + + ''' +# --- +# name: test_footer_compact_with_hover + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ToggleCompactFooterApp + + + + + + + + + + + + + + + + + + + + +                                  Compact Footer                                  + + + + + + + + + + + + ^t Toggle Compact Footer^q Quit + + + + + ''' +# --- +# name: test_footer_render + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FooterApp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  q Quit the app  ? Show help screen  delete Delete the thing  + + + + + ''' +# --- +# name: test_footer_standard_after_reactive_change ''' @@ -19386,146 +21965,142 @@ font-weight: 700; } - .terminal-1546987173-matrix { + .terminal-3950236293-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1546987173-title { + .terminal-3950236293-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1546987173-r1 { fill: #e4e5e6 } - .terminal-1546987173-r2 { fill: #c5c8c6 } - .terminal-1546987173-r3 { fill: #0d0d0d } - .terminal-1546987173-r4 { fill: #e1e1e1;font-weight: bold } - .terminal-1546987173-r5 { fill: #e7920d } - .terminal-1546987173-r6 { fill: #211505;font-weight: bold } - .terminal-1546987173-r7 { fill: #fea62b;font-weight: bold } - .terminal-1546987173-r8 { fill: #dde8f3;font-weight: bold } - .terminal-1546987173-r9 { fill: #ddedf9 } + .terminal-3950236293-r1 { fill: #e1e1e1 } + .terminal-3950236293-r2 { fill: #c5c8c6 } + .terminal-3950236293-r3 { fill: #fea62b;font-weight: bold } + .terminal-3950236293-r4 { fill: #a7a9ab } + .terminal-3950236293-r5 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - 5x5 -- A little annoying puzzle + ToggleCompactFooterApp - - - - 5x5 -- A little annoying puzzleMoves: 0Filled: 5 - ────────────────────────────────────────────────────────────────────── - - - ────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────── - - - ────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────── - - - - ────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────── - - - ────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────── - - - - ────────────────────────────────────────────────────────────────────── -  N  New Game  ?  Help  Q  Quit  CTRL+D  Toggle Dark Mode  + + + + + + + + + + + + + + +                                 Standard Footer                                  + + + + + + + + + + + +  ^t Toggle Compact Footer  ^q Quit  ''' # --- -# name: test_example_json_tree +# name: test_footer_standard_with_hover ''' @@ -19548,153 +22123,145 @@ font-weight: 700; } - .terminal-3545091782-matrix { + .terminal-2873348574-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3545091782-title { + .terminal-2873348574-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3545091782-r1 { fill: #c5c8c6 } - .terminal-3545091782-r2 { fill: #e3e3e3 } - .terminal-3545091782-r3 { fill: #e2e3e3 } - .terminal-3545091782-r4 { fill: #008139 } - .terminal-3545091782-r5 { fill: #14191f } - .terminal-3545091782-r6 { fill: #e2e3e3;font-weight: bold } - .terminal-3545091782-r7 { fill: #98e024 } - .terminal-3545091782-r8 { fill: #211505;font-weight: bold } - .terminal-3545091782-r9 { fill: #fea62b;font-weight: bold } - .terminal-3545091782-r10 { fill: #58d1eb;font-weight: bold } - .terminal-3545091782-r11 { fill: #f4005f;font-style: italic; } - .terminal-3545091782-r12 { fill: #23568b } - .terminal-3545091782-r13 { fill: #dde8f3;font-weight: bold } - .terminal-3545091782-r14 { fill: #ddedf9 } + .terminal-2873348574-r1 { fill: #e1e1e1 } + .terminal-2873348574-r2 { fill: #c5c8c6 } + .terminal-2873348574-r3 { fill: #fea62b;font-weight: bold } + .terminal-2873348574-r4 { fill: #dddedf } + .terminal-2873348574-r5 { fill: #a7a9ab } + .terminal-2873348574-r6 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TreeApp + ToggleCompactFooterApp - - - - TreeApp - ▼ Root - └── ▼ {} JSON▁▁ - ├── code='5060292302201' - ├── ▼ {} product - │   ┣━━ _id='5060292302201' - │   ┣━━ ▶ [] _keywords - │   ┣━━ ▶ [] added_countries_tags - │   ┣━━ ▶ [] additives_debug_tags - │   ┣━━ additives_n=2 - │   ┣━━ additives_old_n=2 - │   ┣━━ ▶ [] additives_old_tags - │   ┣━━ ▶ [] additives_original_tags - │   ┣━━ ▶ [] additives_prev_original_tags - │   ┣━━ ▶ [] additives_tags - │   ┣━━ additives_tags_n=None - │   ┣━━ allergens='en:milk' - │   ┣━━ ▶ [] allergens_debug_tags - │   ┣━━ allergens_from_ingredients='en:milk, milk' - │   ┣━━ allergens_from_user='(en) en:milk' - │   ┣━━ ▶ [] allergens_hierarchy - │   ┣━━ ▶ [] allergens_tags - -  A  Add node  C  Clear  T  Toggle root  + + + + + + + + + + + + + + +                                 Standard Footer                                  + + + + + + + + + + + +  ^t Toggle Compact Footer  ^q Quit  ''' # --- -# name: test_example_markdown +# name: test_fr_margins ''' - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + - MarkdownApp + TestApp - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▼ Ⅰ Textual Markdown Browser - └── Ⅱ Do You Want to Know More?Textual Markdown Browser - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Welcome fellow adventurer! If you  - ran markdown.py from the terminal  - you are viewing demo.md with  - Textual's built in Markdown  - widget. - - The widget supports much of the  - Markdown spec. There is also an  - optional Table of Contents sidebar - which you will see to your left. - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -   Do You Want to Know More?    - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - See example.md for more examples ▆▆ - of what this can do. -  T  TOC  B  Back  F  Forward  + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + + + Hello + + + + + + + World + + + + + + + !! + + + + + + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ''' # --- -# name: test_example_merlin +# name: test_fr_unit_with_min ''' @@ -19883,147 +22443,143 @@ font-weight: 700; } - .terminal-1091601628-matrix { + .terminal-302077504-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1091601628-title { + .terminal-302077504-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1091601628-r1 { fill: #e1e1e1 } - .terminal-1091601628-r2 { fill: #c5c8c6 } - .terminal-1091601628-r3 { fill: #fea62b;font-weight: bold } - .terminal-1091601628-r4 { fill: #004578 } - .terminal-1091601628-r5 { fill: #e1e1e1;font-weight: bold } - .terminal-1091601628-r6 { fill: #1e1e1e } - .terminal-1091601628-r7 { fill: #0178d4 } - .terminal-1091601628-r8 { fill: #e2e2e2 } - .terminal-1091601628-r9 { fill: #737373;font-weight: bold } - .terminal-1091601628-r10 { fill: #14191f } + .terminal-302077504-r1 { fill: #c5c8c6 } + .terminal-302077504-r2 { fill: #e3e3e3 } + .terminal-302077504-r3 { fill: #ddddff } + .terminal-302077504-r4 { fill: #e3e4e5 } + .terminal-302077504-r5 { fill: #e2e3e3 } + .terminal-302077504-r6 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MerlinApp + ScreenSplitApp - - - - - - ┏━┓   ┏━┓┏━┓   ┏━┓┏━┓ - ┃ ┃ : ┃ ┃┃ ┃ : ┃ ┃┃ ┃ - ┗━┛   ┗━┛┗━┛   ┗━┛┗━┛ - - - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - -     7         8         9      - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -     4         5         6      - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -     1         2         3      - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▇▇ + + + + ScreenSplitApp + This is content This is content number 0 + number 0This is content number 1 + This is content ▄▄This is content number 2 + number 1This is content number 3 + This is content This is content number 4▁▁ + number 2This is content number 5 + This is content This is content number 6 + number 3This is content number 7 + This is content This is content number 8 + number 4This is content number 9 + This is content This is content number 10 + number 5This is content number 11 + This is content This is content number 12 + number 6This is content number 13 + This is content This is content number 14 + number 7This is content number 15 + This is content This is content number 16 + number 8This is content number 17 + This is content This is content number 18 + number 9This is content number 19 + This is content This is content number 20 + number 10This is content number 21 + ''' # --- -# name: test_example_pride +# name: test_fr_units ''' @@ -20046,144 +22602,140 @@ font-weight: 700; } - .terminal-3094371209-matrix { + .terminal-1610428255-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3094371209-title { + .terminal-1610428255-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3094371209-r1 { fill: #ffdddd } - .terminal-3094371209-r2 { fill: #c5c8c6 } - .terminal-3094371209-r3 { fill: #fff3dd } - .terminal-3094371209-r4 { fill: #ffffdd } - .terminal-3094371209-r5 { fill: #ddeedd } - .terminal-3094371209-r6 { fill: #ddddff } - .terminal-3094371209-r7 { fill: #eeddee } + .terminal-1610428255-r1 { fill: #ffffff } + .terminal-1610428255-r2 { fill: #c5c8c6 } + .terminal-1610428255-r3 { fill: #e2e2e2 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - PrideApp + FRApp - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + HEADER + + + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ┏━━━━━━━━┓┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┏━━━━━━┓ + foo┃┃bar┃┃baz + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┃┃┃┃ + ┗━━━━━━━━┛┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┗━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + FOOTER + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ''' # --- -# name: test_focus_component_class +# name: test_grid_auto ''' @@ -20206,135 +22758,141 @@ font-weight: 700; } - .terminal-3936062011-matrix { + .terminal-1332404670-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3936062011-title { + .terminal-1332404670-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3936062011-r1 { fill: #c5c8c6 } - .terminal-3936062011-r2 { fill: #e3e3e3 } - .terminal-3936062011-r3 { fill: #ffdddd } - .terminal-3936062011-r4 { fill: #e1e1e1 } - .terminal-3936062011-r5 { fill: #14191f } - .terminal-3936062011-r6 { fill: #ddedf9 } + .terminal-1332404670-r1 { fill: #008000 } + .terminal-1332404670-r2 { fill: #e1e1e1 } + .terminal-1332404670-r3 { fill: #c5c8c6 } + .terminal-1332404670-r4 { fill: #e6def6 } + .terminal-1332404670-r5 { fill: #e8e1f3 } + .terminal-1332404670-r6 { fill: #ebe4f4 } + .terminal-1332404670-r7 { fill: #ede7f2 } + .terminal-1332404670-r8 { fill: #edecee } + .terminal-1332404670-r9 { fill: #e7ecf3 } + .terminal-1332404670-r10 { fill: #e2ecf7 } + .terminal-1332404670-r11 { fill: #e0ebfa } + .terminal-1332404670-r12 { fill: #dde9fb } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - StyleBugApp + KeylineApp - - - - StyleBugApp - test widget 0 - test widget 1 - test widget 2 - test widget 3 - test widget 4 - test widget 5 - test widget 6 - test widget 7 - test widget 8 - test widget 9 - test widget 10 - test widget 11 - test widget 12▇▇ - test widget 13 - test widget 14 - test widget 15 - test widget 16 - test widget 17 - test widget 18 - test widget 19 - test widget 20 - test widget 21 + + + + ┌──┬──┬──┐ + abc + ├──┼──┼──┤ + def + ├──┼──┼──┤ + ghi + └──┴──┴──┘ + + + + + + + + + + + + + + + + @@ -20342,7 +22900,7 @@ ''' # --- -# name: test_footer_render +# name: test_grid_gutter ''' @@ -20365,143 +22923,147 @@ font-weight: 700; } - .terminal-1971839132-matrix { + .terminal-3470684325-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1971839132-title { + .terminal-3470684325-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1971839132-r1 { fill: #e1e1e1 } - .terminal-1971839132-r2 { fill: #c5c8c6 } - .terminal-1971839132-r3 { fill: #dde8f3;font-weight: bold } - .terminal-1971839132-r4 { fill: #ddedf9 } + .terminal-3470684325-r1 { fill: #e1e1e1 } + .terminal-3470684325-r2 { fill: #c5c8c6 } + .terminal-3470684325-r3 { fill: #0178d4 } + .terminal-3470684325-r4 { fill: #e1e1e1;font-weight: bold } + .terminal-3470684325-r5 { fill: #474747 } + .terminal-3470684325-r6 { fill: #1e1e1e } + .terminal-3470684325-r7 { fill: #121212 } + .terminal-3470684325-r8 { fill: #e1e1e1;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - FooterApp + Demonstrator - - - - - - - - - - - - - - - - - - - - - - - - - - -  Q  Quit the app  ?  Show help screen  DELETE  Delete the thing  + + + + + + ┌──────────────────────────────────────────────────────────┐ + + Information + ━╸━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎ + aaa naa aaaaa aaa aaaan, aaa aaa, aaaa?", aa aaa + aaaaanaaa anaaaaaaana aaaaaaaa aaaaaana aaa      + aaaaa aa aaa, aa aaaaaaaaa aaa aaaa, "aaaa, an   + aaaa aaa aaaa, a aa". "aaaa, naa aaaaaaaaaaa,    + aaa a aaaa aaaaaanaa aaaa aa a aaa!", aaa        + anaaaa, aaaaa aaaaaaaa aanaaaaa. "Na! aaa naa.   + aaaaa. aa aaaaa naa. aaaaa aa na aaa.", aaa      + aaaaaaaa aaaanaaaaa DONE.                        + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎ + + + + + └──────────────────────────────────────────────────────────┘ + + ''' # --- -# name: test_fr_margins +# name: test_grid_layout_basic ''' - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - TestApp + GridLayoutExample - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - Hello - - - - - - - World - - - - - - - !! - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + ┌────────────────────────┐┌─────────────────────────┐┌─────────────────────────┐ + One││Two││Three + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + └────────────────────────┘└─────────────────────────┘└─────────────────────────┘ + ┌────────────────────────┐┌─────────────────────────┐┌─────────────────────────┐ + Four││Five││Six + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + └────────────────────────┘└─────────────────────────┘└─────────────────────────┘ ''' # --- -# name: test_fr_unit_with_min +# name: test_grid_layout_basic_overflow ''' @@ -20683,144 +23240,140 @@ font-weight: 700; } - .terminal-2211506176-matrix { + .terminal-3369516122-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2211506176-title { + .terminal-3369516122-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2211506176-r1 { fill: #c5c8c6 } - .terminal-2211506176-r2 { fill: #e3e3e3 } - .terminal-2211506176-r3 { fill: #ddddff } - .terminal-2211506176-r4 { fill: #e3e4e5 } - .terminal-2211506176-r5 { fill: #e2e3e3 } - .terminal-2211506176-r6 { fill: #14191f } - .terminal-2211506176-r7 { fill: #ddedf9 } + .terminal-3369516122-r1 { fill: #008000 } + .terminal-3369516122-r2 { fill: #c5c8c6 } + .terminal-3369516122-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScreenSplitApp + GridLayoutExample - - - - ScreenSplitApp - This is content This is content number 0 - number 0This is content number 1 - This is content ▄▄This is content number 2 - number 1This is content number 3 - This is content This is content number 4▁▁ - number 2This is content number 5 - This is content This is content number 6 - number 3This is content number 7 - This is content This is content number 8 - number 4This is content number 9 - This is content This is content number 10 - number 5This is content number 11 - This is content This is content number 12 - number 6This is content number 13 - This is content This is content number 14 - number 7This is content number 15 - This is content This is content number 16 - number 8This is content number 17 - This is content This is content number 18 - number 9This is content number 19 - This is content This is content number 20 - number 10This is content number 21 - + + + + ┌────────────────────────┐┌─────────────────────────┐┌─────────────────────────┐ + One││Two││Three + ││││ + ││││ + ││││ + ││││ + ││││ + └────────────────────────┘└─────────────────────────┘└─────────────────────────┘ + ┌────────────────────────┐┌─────────────────────────┐┌─────────────────────────┐ + Four││Five││Six + ││││ + ││││ + ││││ + ││││ + ││││ + └────────────────────────┘└─────────────────────────┘└─────────────────────────┘ + ┌────────────────────────┐ + Seven + + + + + + └────────────────────────┘ ''' # --- -# name: test_fr_units +# name: test_grid_layout_gutter ''' @@ -20843,140 +23396,140 @@ font-weight: 700; } - .terminal-230484307-matrix { + .terminal-721777988-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-230484307-title { + .terminal-721777988-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-230484307-r1 { fill: #ffffff } - .terminal-230484307-r2 { fill: #c5c8c6 } - .terminal-230484307-r3 { fill: #e2e2e2 } + .terminal-721777988-r1 { fill: #efddef } + .terminal-721777988-r2 { fill: #c5c8c6 } + .terminal-721777988-r3 { fill: #f0fcf0 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - FRApp + GridLayoutExample - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - HEADER - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - foobarbaz - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - FOOTER - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + OneTwoThree + + + + + + + + + + + + FourFiveSix + + + + + + + + + + + ''' # --- -# name: test_grid_layout_basic +# name: test_hatch ''' @@ -20999,140 +23552,146 @@ font-weight: 700; } - .terminal-3077119198-matrix { + .terminal-531352656-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3077119198-title { + .terminal-531352656-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3077119198-r1 { fill: #008000 } - .terminal-3077119198-r2 { fill: #c5c8c6 } - .terminal-3077119198-r3 { fill: #e1e1e1 } + .terminal-531352656-r1 { fill: #6a5acd } + .terminal-531352656-r2 { fill: #c5c8c6 } + .terminal-531352656-r3 { fill: #4ebf71 } + .terminal-531352656-r4 { fill: #fea62b } + .terminal-531352656-r5 { fill: #b93c5b } + .terminal-531352656-r6 { fill: #ff0000 } + .terminal-531352656-r7 { fill: #004578 } + .terminal-531352656-r8 { fill: #366e47 } + .terminal-531352656-r9 { fill: #ff00ff;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - GridLayoutExample + HatchApp - - - - ────────────────────────────────────────────────────────────────────────── - OneTwoThree - - - - - - - - - - ────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────── - FourFiveSix - - - - - - - - - - ────────────────────────────────────────────────────────────────────────── + + + + ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳────────────────────────────────────────────────────────╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──┌─ Hello World ────────────────────────────────────┐──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼││││──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼Hatched┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼││││──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼││││──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼││││──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳──└──────────────────────────────────────────────────┘──╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳────────────────────────────────────────────────────────╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱ + ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱ + ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ + ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ''' # --- -# name: test_grid_layout_basic_overflow +# name: test_header_render ''' @@ -21155,140 +23714,140 @@ font-weight: 700; } - .terminal-1958232742-matrix { + .terminal-2289354751-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1958232742-title { + .terminal-2289354751-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1958232742-r1 { fill: #008000 } - .terminal-1958232742-r2 { fill: #c5c8c6 } - .terminal-1958232742-r3 { fill: #e1e1e1 } + .terminal-2289354751-r1 { fill: #c5c8c6 } + .terminal-2289354751-r2 { fill: #e3e3e3 } + .terminal-2289354751-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - GridLayoutExample + HeaderApp - - - - ────────────────────────────────────────────────────────────────────────── - OneTwoThree - - - - - - ────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────── - FourFiveSix - - - - - - ────────────────────────────────────────────────────────────────────────── - ──────────────────────── - Seven - - - - - - ──────────────────────── + + + + HeaderApp + + + + + + + + + + + + + + + + + + + + + + + ''' # --- -# name: test_grid_layout_gutter +# name: test_horizontal_layout ''' @@ -21311,140 +23870,140 @@ font-weight: 700; } - .terminal-721777988-matrix { + .terminal-2888370753-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-721777988-title { + .terminal-2888370753-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-721777988-r1 { fill: #efddef } - .terminal-721777988-r2 { fill: #c5c8c6 } - .terminal-721777988-r3 { fill: #f0fcf0 } + .terminal-2888370753-r1 { fill: #008000 } + .terminal-2888370753-r2 { fill: #c5c8c6 } + .terminal-2888370753-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - GridLayoutExample + HorizontalLayoutExample - - - - OneTwoThree - - - - - - - - - - - - FourFiveSix - - - - - - - - - - - + + + + ┌────────────────────────┐┌─────────────────────────┐┌─────────────────────────┐ + One││Two││Three + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + ││││ + └────────────────────────┘└─────────────────────────┘└─────────────────────────┘ ''' # --- -# name: test_header_render +# name: test_horizontal_layout_width_auto_dock ''' @@ -21467,132 +24026,135 @@ font-weight: 700; } - .terminal-4077214022-matrix { + .terminal-2824857946-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4077214022-title { + .terminal-2824857946-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4077214022-r1 { fill: #c5c8c6 } - .terminal-4077214022-r2 { fill: #e3e3e3 } - .terminal-4077214022-r3 { fill: #e1e1e1 } + .terminal-2824857946-r1 { fill: #e1f0ff } + .terminal-2824857946-r2 { fill: #e7e5ef } + .terminal-2824857946-r3 { fill: #e1e1e1 } + .terminal-2824857946-r4 { fill: #c5c8c6 } + .terminal-2824857946-r5 { fill: #ebf0e2 } + .terminal-2824857946-r6 { fill: #f7e0ef } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HeaderApp + HorizontalAutoWidth - - - - HeaderApp - - - - - - - - - - - - - - - - - - - - - - + + + + Docke + Widget 1Widget 2 + left  + 1Docked left 2 + + + + + + + + + + + + + + + + + + + @@ -21600,7 +24162,7 @@ ''' # --- -# name: test_horizontal_layout +# name: test_input_and_focus ''' @@ -21623,140 +24185,143 @@ font-weight: 700; } - .terminal-1769115774-matrix { + .terminal-4091889985-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1769115774-title { + .terminal-4091889985-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1769115774-r1 { fill: #008000 } - .terminal-1769115774-r2 { fill: #c5c8c6 } - .terminal-1769115774-r3 { fill: #e1e1e1 } + .terminal-4091889985-r1 { fill: #1e1e1e } + .terminal-4091889985-r2 { fill: #121212 } + .terminal-4091889985-r3 { fill: #c5c8c6 } + .terminal-4091889985-r4 { fill: #e2e2e2 } + .terminal-4091889985-r5 { fill: #0178d4 } + .terminal-4091889985-r6 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HorizontalLayoutExample + InputApp - - - - ────────────────────────────────────────────────────────────────────────── - OneTwoThree - - - - - - - - - - - - - - - - - - - - - - ────────────────────────────────────────────────────────────────────────── + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Darren                                                                     + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Burns + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + + ''' # --- -# name: test_horizontal_layout_width_auto_dock +# name: test_input_percentage_width ''' @@ -21779,143 +24344,143 @@ font-weight: 700; } - .terminal-1672359278-matrix { + .terminal-3624006926-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1672359278-title { + .terminal-3624006926-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1672359278-r1 { fill: #e1f0ff } - .terminal-1672359278-r2 { fill: #e7e5ef } - .terminal-1672359278-r3 { fill: #e1e1e1 } - .terminal-1672359278-r4 { fill: #c5c8c6 } - .terminal-1672359278-r5 { fill: #ebf0e2 } - .terminal-1672359278-r6 { fill: #f7e0ef } + .terminal-3624006926-r1 { fill: #1e1e1e } + .terminal-3624006926-r2 { fill: #e1e1e1 } + .terminal-3624006926-r3 { fill: #c5c8c6 } + .terminal-3624006926-r4 { fill: #ff0000 } + .terminal-3624006926-r5 { fill: #e2e2e2 } + .terminal-3624006926-r6 { fill: #e2e3e3;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HorizontalAutoWidth + InputVsTextArea - - - - Docke - Widget 1Widget 2 - left  - 1Docked left 2 - - - - - - - - - - - - - - - - - - - - + + + + 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ┌──────────────────────────────────────┐ + + + + └──────────────────────────────────────┘ + ┌──────────────────────────────────────┐ + + + + + └──────────────────────────────────────┘ + ┌──────────────────────────────────────┐ + + + + + └──────────────────────────────────────┘ + ┌──────────────────────────────────────┐ + +  Button  + + + └──────────────────────────────────────┘ ''' # --- -# name: test_input_and_focus +# name: test_input_suggestions ''' @@ -21938,135 +24503,137 @@ font-weight: 700; } - .terminal-596216952-matrix { + .terminal-2025105416-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-596216952-title { + .terminal-2025105416-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-596216952-r1 { fill: #1e1e1e } - .terminal-596216952-r2 { fill: #121212 } - .terminal-596216952-r3 { fill: #c5c8c6 } - .terminal-596216952-r4 { fill: #e2e2e2 } - .terminal-596216952-r5 { fill: #0178d4 } - .terminal-596216952-r6 { fill: #e1e1e1 } + .terminal-2025105416-r1 { fill: #1e1e1e } + .terminal-2025105416-r2 { fill: #0178d4 } + .terminal-2025105416-r3 { fill: #c5c8c6 } + .terminal-2025105416-r4 { fill: #e2e2e2 } + .terminal-2025105416-r5 { fill: #1e1e1e;font-style: italic; } + .terminal-2025105416-r6 { fill: #ff0000;font-style: italic; } + .terminal-2025105416-r7 { fill: #121212 } + .terminal-2025105416-r8 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - InputApp + FruitsApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Darren - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Burns - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + strawberry + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + straw                                                                      + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + p                                                                          + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + b                                                                          + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + a                                                                          + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + @@ -22074,7 +24641,7 @@ ''' # --- -# name: test_input_percentage_width +# name: test_input_validation ''' @@ -22097,143 +24664,146 @@ font-weight: 700; } - .terminal-2024488306-matrix { + .terminal-1896989657-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2024488306-title { + .terminal-1896989657-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2024488306-r1 { fill: #1e1e1e } - .terminal-2024488306-r2 { fill: #e1e1e1 } - .terminal-2024488306-r3 { fill: #c5c8c6 } - .terminal-2024488306-r4 { fill: #ff0000 } - .terminal-2024488306-r5 { fill: #e2e2e2 } - .terminal-2024488306-r6 { fill: #e2e3e3;font-weight: bold } + .terminal-1896989657-r1 { fill: #e1e1e1 } + .terminal-1896989657-r2 { fill: #c5c8c6 } + .terminal-1896989657-r3 { fill: #1e1e1e } + .terminal-1896989657-r4 { fill: #7b3042 } + .terminal-1896989657-r5 { fill: #e2e2e2 } + .terminal-1896989657-r6 { fill: #3a7e4f } + .terminal-1896989657-r7 { fill: #b93c5b } + .terminal-1896989657-r8 { fill: #121212 } + .terminal-1896989657-r9 { fill: #787878 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - InputVsTextArea + InputApp - - - - 01234567890123456789012345678901234567890123456789012345678901234567890123456789 - ────────────────────────────────────── - - - - ────────────────────────────────────── - ────────────────────────────────────── - - - - - ────────────────────────────────────── - ────────────────────────────────────── - - - - - ────────────────────────────────────── - ────────────────────────────────────── - - Button - - - ────────────────────────────────────── + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + -2                                                                     + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 3                                                                      + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + -2 + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Enter a number between 1 and 5 + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + ''' # --- -# name: test_input_suggestions +# name: test_key_display ''' @@ -22256,145 +24826,142 @@ font-weight: 700; } - .terminal-2577839347-matrix { + .terminal-1780802408-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2577839347-title { + .terminal-1780802408-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2577839347-r1 { fill: #1e1e1e } - .terminal-2577839347-r2 { fill: #0178d4 } - .terminal-2577839347-r3 { fill: #c5c8c6 } - .terminal-2577839347-r4 { fill: #e2e2e2 } - .terminal-2577839347-r5 { fill: #1e1e1e;font-style: italic; } - .terminal-2577839347-r6 { fill: #ff0000;font-style: italic; } - .terminal-2577839347-r7 { fill: #121212 } - .terminal-2577839347-r8 { fill: #e1e1e1 } + .terminal-1780802408-r1 { fill: #e1e1e1 } + .terminal-1780802408-r2 { fill: #c5c8c6 } + .terminal-1780802408-r3 { fill: #fea62b;font-weight: bold } + .terminal-1780802408-r4 { fill: #a7a9ab } + .terminal-1780802408-r5 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - FruitsApp + KeyDisplayApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - strawberry - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - straw - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - p - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - b - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - a - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +  ? Question  ^q Quit app  Escape! Escape  a Letter A  ''' # --- -# name: test_input_validation +# name: test_keyline ''' @@ -22417,146 +24984,143 @@ font-weight: 700; } - .terminal-922438230-matrix { + .terminal-2719940520-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-922438230-title { + .terminal-2719940520-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-922438230-r1 { fill: #e1e1e1 } - .terminal-922438230-r2 { fill: #c5c8c6 } - .terminal-922438230-r3 { fill: #1e1e1e } - .terminal-922438230-r4 { fill: #7b3042 } - .terminal-922438230-r5 { fill: #e2e2e2 } - .terminal-922438230-r6 { fill: #3a7e4f } - .terminal-922438230-r7 { fill: #b93c5b } - .terminal-922438230-r8 { fill: #121212 } - .terminal-922438230-r9 { fill: #787878 } + .terminal-2719940520-r1 { fill: #ff0000 } + .terminal-2719940520-r2 { fill: #c5c8c6 } + .terminal-2719940520-r3 { fill: #e1e1e1 } + .terminal-2719940520-r4 { fill: #008000 } + .terminal-2719940520-r5 { fill: #ff00ff } + .terminal-2719940520-r6 { fill: #1e1e1e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - InputApp + KeylineApp - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -2 - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 3 - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -2 - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Enter a number between 1 and 5 - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + 1 + ├──────────────────────────────────────────────────────────────────────────────┤ + 2 + ├──────────────────────────────────────────────────────────────────────────────┤ + 3 + + └──────────────────────────────────────────────────────────────────────────────┘ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + 456 + + + + + + ┗━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ╔══════════════════════════════════════╦═══════════════════════════════════════╗ + 78 + + ╠══════════════════════════════════════╬═══════════════════════════════════════╝ + 9 + + + ╚══════════════════════════════════════╝ ''' # --- -# name: test_key_display +# name: test_label_widths ''' @@ -22579,141 +25143,142 @@ font-weight: 700; } - .terminal-1765381587-matrix { + .terminal-1889976810-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1765381587-title { + .terminal-1889976810-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1765381587-r1 { fill: #e1e1e1 } - .terminal-1765381587-r2 { fill: #c5c8c6 } - .terminal-1765381587-r3 { fill: #dde8f3;font-weight: bold } - .terminal-1765381587-r4 { fill: #ddedf9 } + .terminal-1889976810-r1 { fill: #1f1f1f } + .terminal-1889976810-r2 { fill: #c5c8c6 } + .terminal-1889976810-r3 { fill: #00ff00 } + .terminal-1889976810-r4 { fill: #1b1b1b } + .terminal-1889976810-r5 { fill: #121e12 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - KeyDisplayApp + LabelWrap - - - - - - - - - - - - - - - - - - - - - - - - - - -  ?  Question  ^q  Quit app  Escape!  Escape  A  Letter A  + + + + + + + + + + Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur Phoenix Ch + + + Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur Phoenix  + Chimera Castle + + + ╭────────────────────────────────────────────────────────────────────────────╮ + │ Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur       │ + │ Phoenix Chimera Castle                                                     │ + ╰────────────────────────────────────────────────────────────────────────────╯ + + + + + + + ''' # --- -# name: test_keyline +# name: test_layer_fix ''' @@ -22736,143 +25301,144 @@ font-weight: 700; } - .terminal-1393283672-matrix { + .terminal-3315312229-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1393283672-title { + .terminal-3315312229-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1393283672-r1 { fill: #ff0000 } - .terminal-1393283672-r2 { fill: #c5c8c6 } - .terminal-1393283672-r3 { fill: #e1e1e1 } - .terminal-1393283672-r4 { fill: #008000 } - .terminal-1393283672-r5 { fill: #ff00ff } - .terminal-1393283672-r6 { fill: #1e1e1e } + .terminal-3315312229-r1 { fill: #c5c8c6 } + .terminal-3315312229-r2 { fill: #e3e3e3 } + .terminal-3315312229-r3 { fill: #e1e1e1 } + .terminal-3315312229-r4 { fill: #ff0000 } + .terminal-3315312229-r5 { fill: #fea62b;font-weight: bold } + .terminal-3315312229-r6 { fill: #a7a9ab } + .terminal-3315312229-r7 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - KeylineApp + DialogIssueApp - - - - ────────────────────────────────────────────────────────────────────────────── - 1 - ────────────────────────────────────────────────────────────────────────────── - 2 - ────────────────────────────────────────────────────────────────────────────── - 3 - - ────────────────────────────────────────────────────────────────────────────── - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - 456 - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ═════════════════════════════════════════════════════════════════════════════ - 78 - - ═════════════════════════════════════════════════════════════════════════════ - 9 - - - ══════════════════════════════════════ + + + + DialogIssueApp + + + + + + ╭──────────────────────────────────────╮ + + + + + This should not cause a scrollbar to a + + + + + + ╰──────────────────────────────────────╯ + + + + + +  d Toggle the dialog  ''' # --- -# name: test_label_widths +# name: test_layers ''' @@ -22895,134 +25461,133 @@ font-weight: 700; } - .terminal-248448564-matrix { + .terminal-3008465429-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-248448564-title { + .terminal-3008465429-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-248448564-r1 { fill: #1f1f1f } - .terminal-248448564-r2 { fill: #c5c8c6 } - .terminal-248448564-r3 { fill: #00ff00 } - .terminal-248448564-r4 { fill: #1b1b1b } - .terminal-248448564-r5 { fill: #121e12 } + .terminal-3008465429-r1 { fill: #e1e1e1 } + .terminal-3008465429-r2 { fill: #c5c8c6 } + .terminal-3008465429-r3 { fill: #ddefef } + .terminal-3008465429-r4 { fill: #211500 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LabelWrap + LayersExample - - - - - - - - - - Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur Phoenix Ch - - - Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur Phoenix  - Chimera Castle - - - ╭────────────────────────────────────────────────────────────────────────────╮ - Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur  - Phoenix Chimera Castle - ╰────────────────────────────────────────────────────────────────────────────╯ - - - - - - + + + + + + + + + + + + + + + box1 (layer = above) + + + + + + box2 (layer = below) + + + + + @@ -23030,7 +25595,7 @@ ''' # --- -# name: test_layer_fix +# name: test_layout_containers ''' @@ -23053,143 +25618,151 @@ font-weight: 700; } - .terminal-3085198851-matrix { + .terminal-3138754671-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3085198851-title { + .terminal-3138754671-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3085198851-r1 { fill: #c5c8c6 } - .terminal-3085198851-r2 { fill: #e3e3e3 } - .terminal-3085198851-r3 { fill: #e1e1e1 } - .terminal-3085198851-r4 { fill: #ff0000 } - .terminal-3085198851-r5 { fill: #dde8f3;font-weight: bold } - .terminal-3085198851-r6 { fill: #ddedf9 } + .terminal-3138754671-r1 { fill: #7ae998 } + .terminal-3138754671-r2 { fill: #e76580 } + .terminal-3138754671-r3 { fill: #1e1e1e } + .terminal-3138754671-r4 { fill: #121212 } + .terminal-3138754671-r5 { fill: #c5c8c6 } + .terminal-3138754671-r6 { fill: #4ebf71;font-weight: bold } + .terminal-3138754671-r7 { fill: #f5e5e9;font-weight: bold } + .terminal-3138754671-r8 { fill: #e2e2e2 } + .terminal-3138754671-r9 { fill: #0a180e;font-weight: bold } + .terminal-3138754671-r10 { fill: #008139 } + .terminal-3138754671-r11 { fill: #780028 } + .terminal-3138754671-r12 { fill: #e1e1e1 } + .terminal-3138754671-r13 { fill: #23568b } + .terminal-3138754671-r14 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DialogIssueApp + MyApp - - - - DialogIssueApp - - - - - - ────────────────────────────────────── - - - - - This should not cause a scrollbar to a - - - - - - ────────────────────────────────────── - - - - - -  D  Toggle the dialog  + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Accept  Decline  Accept  Decline  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Accept  Accept  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Decline  Decline  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆▆ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + 0                                 0 + + 1000000                                 1000000                                ''' # --- -# name: test_layers +# name: test_line_api_scrollbars ''' @@ -23212,133 +25785,132 @@ font-weight: 700; } - .terminal-3301495769-matrix { + .terminal-1120593594-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3301495769-title { + .terminal-1120593594-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3301495769-r1 { fill: #e1e1e1 } - .terminal-3301495769-r2 { fill: #c5c8c6 } - .terminal-3301495769-r3 { fill: #ddefef } - .terminal-3301495769-r4 { fill: #211500 } + .terminal-1120593594-r1 { fill: #e1e1e1 } + .terminal-1120593594-r2 { fill: #c5c8c6 } + .terminal-1120593594-r3 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LayersExample + ScrollViewApp - - - - - - - - - - - - - - - box1 (layer = above) - - - - - - box2 (layer = below) - - - - - + + + + + +                                  11 01234567 +                                  12 01234567 +                                  13 01234567 +                                  14 01234567 +                                  15 01234567▁▁ +                                  16 01234567 +                                  17 01234567 +                                  18 01234567 +                                  19 01234567 + +                                  11 01234567 +                                  12 01234567 +                                  13 01234567 +                                  14 01234567 +                                  15 01234567▁▁ +                                  16 01234567 +                                  17 01234567 +                                  18 01234567 +                                  19 01234567 + + @@ -23346,7 +25918,7 @@ ''' # --- -# name: test_layout_containers +# name: test_list_view ''' @@ -23369,151 +25941,142 @@ font-weight: 700; } - .terminal-3525357221-matrix { + .terminal-3775001870-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3525357221-title { + .terminal-3775001870-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3525357221-r1 { fill: #7ae998 } - .terminal-3525357221-r2 { fill: #e76580 } - .terminal-3525357221-r3 { fill: #1e1e1e } - .terminal-3525357221-r4 { fill: #121212 } - .terminal-3525357221-r5 { fill: #c5c8c6 } - .terminal-3525357221-r6 { fill: #4ebf71;font-weight: bold } - .terminal-3525357221-r7 { fill: #f5e5e9;font-weight: bold } - .terminal-3525357221-r8 { fill: #e2e2e2 } - .terminal-3525357221-r9 { fill: #0a180e;font-weight: bold } - .terminal-3525357221-r10 { fill: #008139 } - .terminal-3525357221-r11 { fill: #780028 } - .terminal-3525357221-r12 { fill: #e1e1e1 } - .terminal-3525357221-r13 { fill: #23568b } - .terminal-3525357221-r14 { fill: #14191f } + .terminal-3775001870-r1 { fill: #e1e1e1 } + .terminal-3775001870-r2 { fill: #c5c8c6 } + .terminal-3775001870-r3 { fill: #e4e5e6 } + .terminal-3775001870-r4 { fill: #ddedf9 } + .terminal-3775001870-r5 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + ListViewExample - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - AcceptDeclineAcceptDecline - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - AcceptAccept - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - DeclineDecline - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆▆ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - 00 - - 10000001000000 + + + + + + + + + + + + One + + + Two + + + Three + + + + + + + + + ''' # --- -# name: test_line_api_scrollbars +# name: test_listview_index ''' @@ -23536,132 +26099,134 @@ font-weight: 700; } - .terminal-3512435366-matrix { + .terminal-1356381627-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3512435366-title { + .terminal-1356381627-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3512435366-r1 { fill: #e1e1e1 } - .terminal-3512435366-r2 { fill: #c5c8c6 } - .terminal-3512435366-r3 { fill: #23568b } + .terminal-1356381627-r1 { fill: #e4e5e6 } + .terminal-1356381627-r2 { fill: #e1e1e1 } + .terminal-1356381627-r3 { fill: #c5c8c6 } + .terminal-1356381627-r4 { fill: #23568b } + .terminal-1356381627-r5 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollViewApp + ListViewIndexApp - - - - - - 11 01234567 - 12 01234567 - 13 01234567 - 14 01234567 - 15 01234567▁▁ - 16 01234567 - 17 01234567 - 18 01234567 - 19 01234567 - - 11 01234567 - 12 01234567 - 13 01234567 - 14 01234567 - 15 01234567▁▁ - 16 01234567 - 17 01234567 - 18 01234567 - 19 01234567 - - + + + + 10                                                                             + 12                                                                             + 14                                                                             + 16                                                                            ▆▆ + 18                                                                             + 20                                                                             + 22                                                                             + 24                                                                             + 26                                                                             + 28                                                                             + + + + + + + + + + + + + @@ -23669,7 +26234,7 @@ ''' # --- -# name: test_list_view +# name: test_loading_indicator ''' @@ -23692,141 +26257,143 @@ font-weight: 700; } - .terminal-552007062-matrix { + .terminal-1966602932-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-552007062-title { + .terminal-1966602932-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-552007062-r1 { fill: #e1e1e1 } - .terminal-552007062-r2 { fill: #c5c8c6 } - .terminal-552007062-r3 { fill: #e4e5e6 } - .terminal-552007062-r4 { fill: #ddedf9 } + .terminal-1966602932-r1 { fill: #1e1e1e } + .terminal-1966602932-r2 { fill: #0178d4 } + .terminal-1966602932-r3 { fill: #c5c8c6 } + .terminal-1966602932-r4 { fill: #e2e2e2;font-weight: bold } + .terminal-1966602932-r5 { fill: #e2e2e2 } + .terminal-1966602932-r6 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ListViewExample + LoadingOverlayRedux - - - - - - - - - - - - One - - - Two - - - Three - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + foo barfoo barfoo barfoo barfoo    + bar                                + foo barfoo barfoo barfoo barfoo   ▄▄ + bar                                + foo barfoo barfoo barfoo barfoo    + bar                                + foo barfoo barfoo barfoo barfoo    + bar                                + foo barfoo barfoo barfoo barfoo    + bar                                + Loading!foo barfoo barfoo barfoo barfoo    + bar                                + foo barfoo barfoo barfoo barfoo    + bar                                + foo barfoo barfoo barfoo barfoo    + bar                                + foo barfoo barfoo barfoo barfoo    + bar                                + foo barfoo barfoo barfoo barfoo    + bar                                + foo barfoo barfoo barfoo barfoo    + bar                                + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ''' # --- -# name: test_listview_index +# name: test_loading_indicator_disables_widget ''' @@ -23849,142 +26416,144 @@ font-weight: 700; } - .terminal-1219584187-matrix { + .terminal-1951874799-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1219584187-title { + .terminal-1951874799-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1219584187-r1 { fill: #e4e5e6 } - .terminal-1219584187-r2 { fill: #e1e1e1 } - .terminal-1219584187-r3 { fill: #c5c8c6 } - .terminal-1219584187-r4 { fill: #23568b } - .terminal-1219584187-r5 { fill: #ddedf9 } + .terminal-1951874799-r1 { fill: #1e1e1e } + .terminal-1951874799-r2 { fill: #0178d4 } + .terminal-1951874799-r3 { fill: #c5c8c6 } + .terminal-1951874799-r4 { fill: #ddedf9;font-weight: bold } + .terminal-1951874799-r5 { fill: #e2e2e2 } + .terminal-1951874799-r6 { fill: #e2e2e2;font-weight: bold } + .terminal-1951874799-r7 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ListViewIndexApp + LoadingOverlayRedux - - - - 10 - 12 - 14 - 16▆▆ - 18 - 20 - 22 - 24 - 26 - 28 - - - - - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     ▄▄foo barfoo barfoo barfoo barfoo   ▄▄ + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + hello world hello world hello     foo barfoo barfoo barfoo barfoo    + world hello world hello world     bar                                + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ''' # --- -# name: test_loading_indicator +# name: test_log_write ''' @@ -24007,143 +26576,139 @@ font-weight: 700; } - .terminal-1550026580-matrix { + .terminal-3821190285-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1550026580-title { + .terminal-3821190285-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1550026580-r1 { fill: #1e1e1e } - .terminal-1550026580-r2 { fill: #0178d4 } - .terminal-1550026580-r3 { fill: #c5c8c6 } - .terminal-1550026580-r4 { fill: #e2e2e2;font-weight: bold } - .terminal-1550026580-r5 { fill: #e2e2e2 } - .terminal-1550026580-r6 { fill: #14191f } + .terminal-3821190285-r1 { fill: #e1e1e1 } + .terminal-3821190285-r2 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LoadingOverlayRedux + LogApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - foo barfoo barfoo barfoo barfoo  - bar - foo barfoo barfoo barfoo barfoo ▄▄ - bar - foo barfoo barfoo barfoo barfoo  - bar - foo barfoo barfoo barfoo barfoo  - bar - foo barfoo barfoo barfoo barfoo  - bar - Loading!foo barfoo barfoo barfoo barfoo  - bar - foo barfoo barfoo barfoo barfoo  - bar - foo barfoo barfoo barfoo barfoo  - bar - foo barfoo barfoo barfoo barfoo  - bar - foo barfoo barfoo barfoo barfoo  - bar - foo barfoo barfoo barfoo barfoo  - bar - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + Hello, World!                                                                  + What's up?                                                                     + FOO                                                                            + + + + + + + + + + + + + + + + + + + + + ''' # --- -# name: test_loading_indicator_disables_widget +# name: test_log_write_lines ''' @@ -24166,144 +26731,141 @@ font-weight: 700; } - .terminal-3594715516-matrix { + .terminal-903827020-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3594715516-title { + .terminal-903827020-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3594715516-r1 { fill: #1e1e1e } - .terminal-3594715516-r2 { fill: #0178d4 } - .terminal-3594715516-r3 { fill: #c5c8c6 } - .terminal-3594715516-r4 { fill: #ddedf9;font-weight: bold } - .terminal-3594715516-r5 { fill: #e2e2e2 } - .terminal-3594715516-r6 { fill: #e2e2e2;font-weight: bold } - .terminal-3594715516-r7 { fill: #14191f } + .terminal-903827020-r1 { fill: #e1e1e1 } + .terminal-903827020-r2 { fill: #c5c8c6 } + .terminal-903827020-r3 { fill: #14191f } + .terminal-903827020-r4 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LoadingOverlayRedux + LogApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello ▄▄foo barfoo barfoo barfoo barfoo ▄▄ - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - hello world hello world hello foo barfoo barfoo barfoo barfoo  - world hello world hello world bar - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + I must not fear.  And when it has goHello, World      Fear is the mind-k + Fear is the mind-kWhere the fear hasFear is the little + Fear is the littleI must not fear.  I will face my fea + I will face my fea▁▁Fear is the mind-kI will permit it t + I will permit it tFear is the littleAnd when it has go + And when it has goI will face my feaWhere the fear has + Where the fear hasI will permit it t + I must not fear.  And when it has go + Fear is the mind-kWhere the fear has + Fear is the littleI must not fear.   + I will face my feaFear is the mind-k + I will permit it tFear is the little + And when it has goI will face my fea + Where the fear hasI will permit it t + I must not fear.  And when it has go + Fear is the mind-kWhere the fear has + Fear is the littleI must not fear.   + I will face my feaFear is the mind-k + I will permit it tFear is the little + And when it has goI will face my fea▇▇ + Where the fear hasI will permit it t + I must not fear.  And when it has go + Fear is the mind-kWhere the fear has + ''' # --- -# name: test_log_write +# name: test_margin_multiple ''' @@ -24326,131 +26888,134 @@ font-weight: 700; } - .terminal-2521368986-matrix { + .terminal-3867713494-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2521368986-title { + .terminal-3867713494-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2521368986-r1 { fill: #e1e1e1 } - .terminal-2521368986-r2 { fill: #c5c8c6 } + .terminal-3867713494-r1 { fill: #ffff00 } + .terminal-3867713494-r2 { fill: #e1e1e1 } + .terminal-3867713494-r3 { fill: #c5c8c6 } + .terminal-3867713494-r4 { fill: #008000 } + .terminal-3867713494-r5 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LogApp + MyApp - - - - Hello, World! - What's up? - FOO - - - - - - - - - - - - - - - - - - - - + + + + ╔═══╗ + foo + ╚═══╝ + + + ┌────────────────────────────┐ + + + ┌────────────────────────────┐ + + ╔═══╗ + bar + ╔═══╗╚═══╝ + bar + ╚═══╝ + + + + └────────────────────────────┘└────────────────────────────┘ + + + + @@ -24458,7 +27023,7 @@ ''' # --- -# name: test_log_write_lines +# name: test_markdown_component_classes_reloading ''' @@ -24481,141 +27046,150 @@ font-weight: 700; } - .terminal-100291914-matrix { + .terminal-635127795-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-100291914-title { + .terminal-635127795-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-100291914-r1 { fill: #e1e1e1 } - .terminal-100291914-r2 { fill: #c5c8c6 } - .terminal-100291914-r3 { fill: #14191f } - .terminal-100291914-r4 { fill: #23568b } + .terminal-635127795-r1 { fill: #e1e1e1 } + .terminal-635127795-r2 { fill: #c5c8c6 } + .terminal-635127795-r3 { fill: #4ebf71;font-weight: bold } + .terminal-635127795-r4 { fill: #e2e3e3 } + .terminal-635127795-r5 { fill: #e2e3e3;font-weight: bold } + .terminal-635127795-r6 { fill: #939393;font-weight: bold } + .terminal-635127795-r7 { fill: #e1e1e1;font-weight: bold } + .terminal-635127795-r8 { fill: #e1e1e1;font-style: italic; } + .terminal-635127795-r9 { fill: #e1e1e1;text-decoration: line-through; } + .terminal-635127795-r10 { fill: #d2d2d2 } + .terminal-635127795-r11 { fill: #82aaff } + .terminal-635127795-r12 { fill: #89ddff } + .terminal-635127795-r13 { fill: #c3e88d } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LogApp + MyApp - - - - I must not fear.And when it has goHello, WorldFear is the mind-k - Fear is the mind-kWhere the fear hasFear is the little - Fear is the littleI must not fear.I will face my fea - I will face my fea▁▁Fear is the mind-kI will permit it t - I will permit it tFear is the littleAnd when it has go - And when it has goI will face my feaWhere the fear has - Where the fear hasI will permit it t - I must not fear.And when it has go - Fear is the mind-kWhere the fear has - Fear is the littleI must not fear. - I will face my feaFear is the mind-k - I will permit it tFear is the little - And when it has goI will face my fea - Where the fear hasI will permit it t - I must not fear.And when it has go - Fear is the mind-kWhere the fear has - Fear is the littleI must not fear. - I will face my feaFear is the mind-k - I will permit it tFear is the little - And when it has goI will face my fea▇▇ - Where the fear hasI will permit it t - I must not fear.And when it has go - Fear is the mind-kWhere the fear has - + + + + + + This is a header + + + col1                                 col2                                 +  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  +  value 1                               value 2                               + +   Here's some code: from itertools import productBold textEmphasized text + strikethrough + + + print("Hello, world!") + + + That was some code. + + + + + + + ''' # --- -# name: test_markdown_component_classes_reloading +# name: test_markdown_dark_theme_override ''' @@ -24638,145 +27212,138 @@ font-weight: 700; } - .terminal-762794446-matrix { + .terminal-1842878139-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-762794446-title { + .terminal-1842878139-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-762794446-r1 { fill: #e1e1e1 } - .terminal-762794446-r2 { fill: #121212 } - .terminal-762794446-r3 { fill: #c5c8c6 } - .terminal-762794446-r4 { fill: #0053aa } - .terminal-762794446-r5 { fill: #dde8f3;font-weight: bold } - .terminal-762794446-r6 { fill: #e2e3e3 } - .terminal-762794446-r7 { fill: #24292f } - .terminal-762794446-r8 { fill: #e2e3e3;font-weight: bold } - .terminal-762794446-r9 { fill: #939393;font-weight: bold } - .terminal-762794446-r10 { fill: #e1e1e1;font-weight: bold } - .terminal-762794446-r11 { fill: #e1e1e1;font-style: italic; } - .terminal-762794446-r12 { fill: #e1e1e1;text-decoration: line-through; } - .terminal-762794446-r13 { fill: #d2d2d2 } - .terminal-762794446-r14 { fill: #82aaff } - .terminal-762794446-r15 { fill: #89ddff } - .terminal-762794446-r16 { fill: #c3e88d } + .terminal-1842878139-r1 { fill: #e1e1e1 } + .terminal-1842878139-r2 { fill: #c5c8c6 } + .terminal-1842878139-r3 { fill: #4ebf71;font-weight: bold } + .terminal-1842878139-r4 { fill: #d2d2d2 } + .terminal-1842878139-r5 { fill: #859900 } + .terminal-1842878139-r6 { fill: #839496 } + .terminal-1842878139-r7 { fill: #268bd2 } + .terminal-1842878139-r8 { fill: #34535b;font-style: italic; } + .terminal-1842878139-r9 { fill: #2aa198 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MarkdownThemeSwitchertApp - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - This is a header - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - col1                              col2                              -  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  - value 1                           value 2                           - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - Here's some code: from itertools import productBold textEmphasized  - textstrikethrough - - - print("Hello, world!") - - - That was some code. - + + + + + + This is a H1 + + + defmain(): + │   print("Hello world!") + + + + + + + + + + + + + + + + @@ -24784,7 +27351,7 @@ ''' # --- -# name: test_markdown_dark_theme_override +# name: test_markdown_example ''' @@ -24807,140 +27374,136 @@ font-weight: 700; } - .terminal-2700004228-matrix { + .terminal-3628679305-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2700004228-title { + .terminal-3628679305-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2700004228-r1 { fill: #e1e1e1 } - .terminal-2700004228-r2 { fill: #121212 } - .terminal-2700004228-r3 { fill: #c5c8c6 } - .terminal-2700004228-r4 { fill: #0053aa } - .terminal-2700004228-r5 { fill: #dde8f3;font-weight: bold } - .terminal-2700004228-r6 { fill: #d2d2d2 } - .terminal-2700004228-r7 { fill: #859900 } - .terminal-2700004228-r8 { fill: #839496 } - .terminal-2700004228-r9 { fill: #268bd2 } - .terminal-2700004228-r10 { fill: #34535b;font-style: italic; } - .terminal-2700004228-r11 { fill: #2aa198 } + .terminal-3628679305-r1 { fill: #e1e1e1 } + .terminal-3628679305-r2 { fill: #c5c8c6 } + .terminal-3628679305-r3 { fill: #4ebf71;font-weight: bold } + .terminal-3628679305-r4 { fill: #939393;font-weight: bold } + .terminal-3628679305-r5 { fill: #4ebf71;text-decoration: underline; } + .terminal-3628679305-r6 { fill: #e1e1e1;font-style: italic; } + .terminal-3628679305-r7 { fill: #e1e1e1;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarkdownThemeSwitchertApp + MarkdownExampleApp - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - This is a H1 - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - defmain(): - │   print("Hello world!") - - - - - - - - - - - - - - + + + + + + Markdown Document + +   This is an example of Textual's Markdown widget. + + + Features + +   Markdown syntax and extensions are supported. + + ● Typography emphasisstronginline code etc. + ● Headers + ● Lists (bullet and ordered) + ● Syntax highlighted code blocks + ● Tables! + + + + + + + @@ -24948,7 +27511,7 @@ ''' # --- -# name: test_markdown_example +# name: test_markdown_light_theme_override ''' @@ -24971,140 +27534,138 @@ font-weight: 700; } - .terminal-454878977-matrix { + .terminal-2710843744-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-454878977-title { + .terminal-2710843744-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-454878977-r1 { fill: #e1e1e1 } - .terminal-454878977-r2 { fill: #121212 } - .terminal-454878977-r3 { fill: #c5c8c6 } - .terminal-454878977-r4 { fill: #0053aa } - .terminal-454878977-r5 { fill: #dde8f3;font-weight: bold } - .terminal-454878977-r6 { fill: #939393;font-weight: bold } - .terminal-454878977-r7 { fill: #24292f } - .terminal-454878977-r8 { fill: #e2e3e3;font-weight: bold } - .terminal-454878977-r9 { fill: #4ebf71;font-weight: bold } - .terminal-454878977-r10 { fill: #e1e1e1;font-style: italic; } - .terminal-454878977-r11 { fill: #e1e1e1;font-weight: bold } + .terminal-2710843744-r1 { fill: #1f1f1f } + .terminal-2710843744-r2 { fill: #c5c8c6 } + .terminal-2710843744-r3 { fill: #004578;font-weight: bold } + .terminal-2710843744-r4 { fill: #d2d2d2 } + .terminal-2710843744-r5 { fill: #859900 } + .terminal-2710843744-r6 { fill: #657b83 } + .terminal-2710843744-r7 { fill: #268bd2 } + .terminal-2710843744-r8 { fill: #bdc3bb;font-style: italic; } + .terminal-2710843744-r9 { fill: #2aa198 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarkdownExampleApp + MarkdownThemeSwitchertApp - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - Markdown Document - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - This is an example of Textual's Markdown widget. - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -                               Features                               - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Markdown syntax and extensions are supported. - - ● Typography emphasisstronginline code etc. - ● Headers - ● Lists (bullet and ordered) - ● Syntax highlighted code blocks - ● Tables! - - - - + + + + + + This is a H1 + + + defmain(): + │   print("Hello world!") + + + + + + + + + + + + + + + + @@ -25112,7 +27673,7 @@ ''' # --- -# name: test_markdown_light_theme_override +# name: test_markdown_space_squashing ''' @@ -25135,148 +27696,152 @@ font-weight: 700; } - .terminal-3405842733-matrix { + .terminal-1634757789-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3405842733-title { + .terminal-1634757789-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3405842733-r1 { fill: #1f1f1f } - .terminal-3405842733-r2 { fill: #efefef } - .terminal-3405842733-r3 { fill: #c5c8c6 } - .terminal-3405842733-r4 { fill: #0053aa } - .terminal-3405842733-r5 { fill: #dde8f3;font-weight: bold } - .terminal-3405842733-r6 { fill: #d2d2d2 } - .terminal-3405842733-r7 { fill: #859900 } - .terminal-3405842733-r8 { fill: #657b83 } - .terminal-3405842733-r9 { fill: #268bd2 } - .terminal-3405842733-r10 { fill: #bdc3bb;font-style: italic; } - .terminal-3405842733-r11 { fill: #2aa198 } + .terminal-1634757789-r1 { fill: #ff0000 } + .terminal-1634757789-r2 { fill: #e1e1e1 } + .terminal-1634757789-r3 { fill: #c5c8c6 } + .terminal-1634757789-r4 { fill: #e1e1e1;text-decoration: underline; } + .terminal-1634757789-r5 { fill: #e1e1e1;font-style: italic; } + .terminal-1634757789-r6 { fill: #e1e1e1;font-weight: bold } + .terminal-1634757789-r7 { fill: #e1e1e1;text-decoration: line-through; } + .terminal-1634757789-r8 { fill: #d2d2d2 } + .terminal-1634757789-r9 { fill: #546e7a;font-style: italic; } + .terminal-1634757789-r10 { fill: #bb80b3 } + .terminal-1634757789-r11 { fill: #eeffff } + .terminal-1634757789-r12 { fill: #ffcb6b } + .terminal-1634757789-r13 { fill: #89ddff } + .terminal-1634757789-r14 { fill: #41565f;font-style: italic; } + .terminal-1634757789-r15 { fill: #f78c6c } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarkdownThemeSwitchertApp + MarkdownSpaceApp - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - This is a H1 - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - defmain(): - │   print("Hello world!") - - - - - - - - - - - - - - - + + + + X XX XX X X X X X + + X XX XX X X X X X + + X XX X X X X X + + X XX X X X X X + + ┌─────────────────────────────────────────────────────────────────────────────── + + + # Two spaces:  see? + classFoo: + │   '''This is    a doc    string.''' + │   some_code(1,2,3,4) + + + + + + + + + ''' # --- -# name: test_markdown_space_squashing +# name: test_markdown_theme_switching ''' @@ -25299,152 +27864,147 @@ font-weight: 700; } - .terminal-2455550894-matrix { + .terminal-1160849185-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2455550894-title { + .terminal-1160849185-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2455550894-r1 { fill: #ff0000 } - .terminal-2455550894-r2 { fill: #e1e1e1 } - .terminal-2455550894-r3 { fill: #c5c8c6 } - .terminal-2455550894-r4 { fill: #e1e1e1;text-decoration: underline; } - .terminal-2455550894-r5 { fill: #e1e1e1;font-style: italic; } - .terminal-2455550894-r6 { fill: #e1e1e1;font-weight: bold } - .terminal-2455550894-r7 { fill: #e1e1e1;text-decoration: line-through; } - .terminal-2455550894-r8 { fill: #d2d2d2 } - .terminal-2455550894-r9 { fill: #546e7a;font-style: italic; } - .terminal-2455550894-r10 { fill: #bb80b3 } - .terminal-2455550894-r11 { fill: #eeffff } - .terminal-2455550894-r12 { fill: #ffcb6b } - .terminal-2455550894-r13 { fill: #89ddff } - .terminal-2455550894-r14 { fill: #41565f;font-style: italic; } - .terminal-2455550894-r15 { fill: #f78c6c } + .terminal-1160849185-r1 { fill: #1f1f1f } + .terminal-1160849185-r2 { fill: #c5c8c6 } + .terminal-1160849185-r3 { fill: #004578;font-weight: bold } + .terminal-1160849185-r4 { fill: #d2d2d2 } + .terminal-1160849185-r5 { fill: #008000;font-weight: bold } + .terminal-1160849185-r6 { fill: #000000 } + .terminal-1160849185-r7 { fill: #0000ff } + .terminal-1160849185-r8 { fill: #87adad;font-style: italic; } + .terminal-1160849185-r9 { fill: #008000 } + .terminal-1160849185-r10 { fill: #ba2121 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarkdownSpaceApp + MarkdownThemeSwitchertApp - - - - X XX XX X X X X X - - X XX XX X X X X X - - X XX X X X X X - - X XX X X X X X - - ─────────────────────────────────────────────────────────────────────────────── - - - # Two spaces:  see? - classFoo: - │   '''This is    a doc    string.''' - │   some_code(1,2,3,4) - - - - - - - - - + + + + + + This is a H1 + + + defmain(): + │   print("Hello world!") + + + + + + + + + + + + + + + + + ''' # --- -# name: test_markdown_theme_switching +# name: test_markdown_viewer_example ''' @@ -25467,149 +28027,149 @@ font-weight: 700; } - .terminal-2184772309-matrix { + .terminal-1516567938-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2184772309-title { + .terminal-1516567938-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2184772309-r1 { fill: #1f1f1f } - .terminal-2184772309-r2 { fill: #efefef } - .terminal-2184772309-r3 { fill: #c5c8c6 } - .terminal-2184772309-r4 { fill: #0053aa } - .terminal-2184772309-r5 { fill: #dde8f3;font-weight: bold } - .terminal-2184772309-r6 { fill: #d2d2d2 } - .terminal-2184772309-r7 { fill: #008000;font-weight: bold } - .terminal-2184772309-r8 { fill: #000000 } - .terminal-2184772309-r9 { fill: #0000ff } - .terminal-2184772309-r10 { fill: #87adad;font-style: italic; } - .terminal-2184772309-r11 { fill: #008000 } - .terminal-2184772309-r12 { fill: #ba2121 } + .terminal-1516567938-r1 { fill: #c5c8c6 } + .terminal-1516567938-r2 { fill: #24292f } + .terminal-1516567938-r3 { fill: #e1e1e1 } + .terminal-1516567938-r4 { fill: #e2e3e3 } + .terminal-1516567938-r5 { fill: #96989b } + .terminal-1516567938-r6 { fill: #008139 } + .terminal-1516567938-r7 { fill: #4ebf71;font-weight: bold } + .terminal-1516567938-r8 { fill: #939393;font-weight: bold } + .terminal-1516567938-r9 { fill: #4ebf71;text-decoration: underline; } + .terminal-1516567938-r10 { fill: #14191f } + .terminal-1516567938-r11 { fill: #e1e1e1;font-style: italic; } + .terminal-1516567938-r12 { fill: #e1e1e1;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarkdownThemeSwitchertApp + MarkdownExampleApp - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - This is a H1 - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - defmain(): - │   print("Hello world!") - - - - - - - - - - - - - - - + + + + + ▼ Ⅰ Markdown Viewer + ├── Ⅱ FeaturesMarkdown Viewer + ├── Ⅱ Tables + └── Ⅱ Code Blocks  This is an example of Textual's MarkdownViewer +   widget. + + + Features + +   Markdown syntax and extensions are supported. + ▇▇ + ● Typography emphasisstronginline code etc. + ● Headers + ● Lists (bullet and ordered) + ● Syntax highlighted code blocks + ● Tables! + + + Tables + +   Tables are displayed in a DataTable widget. + + ''' # --- -# name: test_markdown_viewer_example +# name: test_max_height_100 ''' @@ -25632,152 +28192,142 @@ font-weight: 700; } - .terminal-1185109701-matrix { + .terminal-2780811796-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1185109701-title { + .terminal-2780811796-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1185109701-r1 { fill: #c5c8c6 } - .terminal-1185109701-r2 { fill: #24292f } - .terminal-1185109701-r3 { fill: #e1e1e1 } - .terminal-1185109701-r4 { fill: #121212 } - .terminal-1185109701-r5 { fill: #e2e3e3 } - .terminal-1185109701-r6 { fill: #96989b } - .terminal-1185109701-r7 { fill: #0053aa } - .terminal-1185109701-r8 { fill: #008139 } - .terminal-1185109701-r9 { fill: #dde8f3;font-weight: bold } - .terminal-1185109701-r10 { fill: #939393;font-weight: bold } - .terminal-1185109701-r11 { fill: #14191f } - .terminal-1185109701-r12 { fill: #e2e3e3;font-weight: bold } - .terminal-1185109701-r13 { fill: #4ebf71;font-weight: bold } - .terminal-1185109701-r14 { fill: #e1e1e1;font-style: italic; } - .terminal-1185109701-r15 { fill: #e1e1e1;font-weight: bold } + .terminal-2780811796-r1 { fill: #dde6ed;font-weight: bold } + .terminal-2780811796-r2 { fill: #e1e1e1 } + .terminal-2780811796-r3 { fill: #c5c8c6 } + .terminal-2780811796-r4 { fill: #211505 } + .terminal-2780811796-r5 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarkdownExampleApp + HappyDataTableFunApp - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▼ Ⅰ Markdown Viewer - ├── Ⅱ FeaturesMarkdown Viewer - ├── Ⅱ Tables - └── Ⅱ Code Blocks▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - This is an example of Textual's MarkdownViewer - widget. - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▅ - -                  Features                  - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Markdown syntax and extensions are supported. - - ● Typography emphasisstronginline code - etc. - ● Headers - ● Lists (bullet and ordered) - ● Syntax highlighted code blocks - ● Tables! - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - + + + +  Column 0  Column 1  Column 2  Column 3  Column 4  Column 5  Column 6  Column  +  0         0         0         0         0         0         0         0       +  0         1         2         3         4         5         6         7       +  0         2         4         6         8         10        12        14      +  0         3         6         9         12        15        18        21      +  0         4         8         12        16        20        24        28     ▆▆ +  0         5         10        15        20        25        30        35      +  0         6         12        18        24        30        36        42      +  0         7         14        21        28        35        42        49      +  0         8         16        24        32        40        48        56      +  0         9         18        27        36        45        54        63      +  0         10        20        30        40        50        60        70      +  0         11        22        33        44        55        66        77      +  0         12        24        36        48        60        72        84      +  0         13        26        39        52        65        78        91      +  0         14        28        42        56        70        84        98      +  0         15        30        45        60        75        90        105     +  0         16        32        48        64        80        96        112     +  0         17        34        51        68        85        102       119     +  0         18        36        54        72        90        108       126     +  0         19        38        57        76        95        114       133     +  0         20        40        60        80        100       120       140     +  0         21        42        63        84        105       126       147     + ''' # --- -# name: test_max_height_100 +# name: test_missing_vertical_scroll ''' @@ -25800,142 +28350,144 @@ font-weight: 700; } - .terminal-3027843796-matrix { + .terminal-3836203720-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3027843796-title { + .terminal-3836203720-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3027843796-r1 { fill: #dde6ed;font-weight: bold } - .terminal-3027843796-r2 { fill: #e1e1e1 } - .terminal-3027843796-r3 { fill: #c5c8c6 } - .terminal-3027843796-r4 { fill: #211505 } - .terminal-3027843796-r5 { fill: #14191f } + .terminal-3836203720-r1 { fill: #1e1e1e } + .terminal-3836203720-r2 { fill: #0178d4 } + .terminal-3836203720-r3 { fill: #c5c8c6 } + .terminal-3836203720-r4 { fill: #ddedf9;font-weight: bold } + .terminal-3836203720-r5 { fill: #e2e2e2 } + .terminal-3836203720-r6 { fill: #e2e2e2;font-weight: bold } + .terminal-3836203720-r7 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HappyDataTableFunApp + MissingScrollbarApp - - - -  Column 0  Column 1  Column 2  Column 3  Column 4  Column 5  Column 6  Column  -  0         0         0         0         0         0         0         0       -  0         1         2         3         4         5         6         7       -  0         2         4         6         8         10        12        14      -  0         3         6         9         12        15        18        21      -  0         4         8         12        16        20        24        28     ▆▆ -  0         5         10        15        20        25        30        35      -  0         6         12        18        24        30        36        42      -  0         7         14        21        28        35        42        49      -  0         8         16        24        32        40        48        56      -  0         9         18        27        36        45        54        63      -  0         10        20        30        40        50        60        70      -  0         11        22        33        44        55        66        77      -  0         12        24        36        48        60        72        84      -  0         13        26        39        52        65        78        91      -  0         14        28        42        56        70        84        98      -  0         15        30        45        60        75        90        105     -  0         16        32        48        64        80        96        112     -  0         17        34        51        68        85        102       119     -  0         18        36        54        72        90        108       126     -  0         19        38        57        76        95        114       133     -  0         20        40        60        80        100       120       140     -  0         21        42        63        84        105       126       147     - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 0                  0                  ▎▊0                        + 1                  1                  ▎▊1                        + 2                  ▄▄2                  ▄▄▎▊2                       ▄▄ + 3                  3                  ▎▊3                        + 4                  4                  ▎▊4                        + 5                  5                  ▎▊5                        + 6                  6                  ▎▊6                        + 7                  7                  ▎▊7                        + 8                  8                  ▎▊8                        + 9                  9                  ▎▊9                        + 10                 10                 ▎▊10                       + 11                 11                 ▎▊11                       + 12                 12                 ▎▊12                       + 13                 13                 ▎▊13                       + 14                 14                 ▎▊14                       + 15                 15                 ▎▊15                       + 16                 16                 ▎▊16                       + 17                 17                 ▎▊17                       + 18                 18                 ▎▊18                       + 19                 19                 ▎▊19                       + 20                 20                 ▎▊20                       + 21                 21                 ▎▊21                       + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ''' # --- -# name: test_missing_vertical_scroll +# name: test_modal_dialog_bindings ''' @@ -25958,144 +28510,143 @@ font-weight: 700; } - .terminal-3017029652-matrix { + .terminal-878467025-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3017029652-title { + .terminal-878467025-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3017029652-r1 { fill: #1e1e1e } - .terminal-3017029652-r2 { fill: #0178d4 } - .terminal-3017029652-r3 { fill: #c5c8c6 } - .terminal-3017029652-r4 { fill: #ddedf9;font-weight: bold } - .terminal-3017029652-r5 { fill: #e2e2e2 } - .terminal-3017029652-r6 { fill: #e2e2e2;font-weight: bold } - .terminal-3017029652-r7 { fill: #14191f } + .terminal-878467025-r1 { fill: #c5c8c6 } + .terminal-878467025-r2 { fill: #e3e3e3 } + .terminal-878467025-r3 { fill: #e1e1e1 } + .terminal-878467025-r4 { fill: #fea62b;font-weight: bold } + .terminal-878467025-r5 { fill: #a7a9ab } + .terminal-878467025-r6 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MissingScrollbarApp + ModalApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 000 - 111 - 2▄▄2▄▄2▄▄ - 333 - 444 - 555 - 666 - 777 - 888 - 999 - 101010 - 111111 - 121212 - 131313 - 141414 - 151515 - 161616 - 171717 - 181818 - 191919 - 202020 - 212121 - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ModalApp + Hello                                                                            + + + + + + + + + + + + + + + + + + + + + +  ⏎ Open Dialog  ''' # --- -# name: test_modal_dialog_bindings +# name: test_modal_dialog_bindings_input ''' @@ -26118,142 +28669,148 @@ font-weight: 700; } - .terminal-543315859-matrix { + .terminal-2859158718-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-543315859-title { + .terminal-2859158718-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-543315859-r1 { fill: #c5c8c6 } - .terminal-543315859-r2 { fill: #e3e3e3 } - .terminal-543315859-r3 { fill: #e1e1e1 } - .terminal-543315859-r4 { fill: #dde8f3;font-weight: bold } - .terminal-543315859-r5 { fill: #ddedf9 } + .terminal-2859158718-r1 { fill: #e0e0e0 } + .terminal-2859158718-r2 { fill: #656565 } + .terminal-2859158718-r3 { fill: #c5c8c6 } + .terminal-2859158718-r4 { fill: #121212 } + .terminal-2859158718-r5 { fill: #e1e1e1 } + .terminal-2859158718-r6 { fill: #454a50 } + .terminal-2859158718-r7 { fill: #646464 } + .terminal-2859158718-r8 { fill: #24292f;font-weight: bold } + .terminal-2859158718-r9 { fill: #000000 } + .terminal-2859158718-r10 { fill: #704d1c;font-weight: bold } + .terminal-2859158718-r11 { fill: #4d4e4f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ModalApp + ModalApp - - - - ModalApp - Hello - - - - - - - - - - - - - - - - - - - - - -  ⏎  Open Dialog  + + + + DialogModalApp + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + hi!                                                                        + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  OK  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + +  ⏎ Open Dialog  ''' # --- -# name: test_modal_dialog_bindings_input +# name: test_mount_style_fix ''' @@ -26276,148 +28833,141 @@ font-weight: 700; } - .terminal-2766044148-matrix { + .terminal-2240675037-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2766044148-title { + .terminal-2240675037-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2766044148-r1 { fill: #e0e0e0 } - .terminal-2766044148-r2 { fill: #656565 } - .terminal-2766044148-r3 { fill: #c5c8c6 } - .terminal-2766044148-r4 { fill: #121212 } - .terminal-2766044148-r5 { fill: #e1e1e1 } - .terminal-2766044148-r6 { fill: #454a50 } - .terminal-2766044148-r7 { fill: #646464 } - .terminal-2766044148-r8 { fill: #24292f;font-weight: bold } - .terminal-2766044148-r9 { fill: #000000 } - .terminal-2766044148-r10 { fill: #63676c;font-weight: bold } - .terminal-2766044148-r11 { fill: #63696e } + .terminal-2240675037-r1 { fill: #e1e1e1 } + .terminal-2240675037-r2 { fill: #c5c8c6 } + .terminal-2240675037-r3 { fill: #00ff00 } + .terminal-2240675037-r4 { fill: #ffdddd } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ModalApp + BrokenClassesApp - - - - DialogModalApp - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - hi! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - OK - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - -  ⏎  Open Dialog  + + + + + + + + + + ┌──────────────────────────────────────┐ + This should have a red background + + + + + + + + + + └──────────────────────────────────────┘ + + + + + + ''' # --- -# name: test_mount_style_fix +# name: test_multi_keys ''' @@ -26440,134 +28990,135 @@ font-weight: 700; } - .terminal-1136068071-matrix { + .terminal-3226482548-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1136068071-title { + .terminal-3226482548-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1136068071-r1 { fill: #e1e1e1 } - .terminal-1136068071-r2 { fill: #c5c8c6 } - .terminal-1136068071-r3 { fill: #00ff00 } - .terminal-1136068071-r4 { fill: #ffdddd } + .terminal-3226482548-r1 { fill: #e1e1e1 } + .terminal-3226482548-r2 { fill: #c5c8c6 } + .terminal-3226482548-r3 { fill: #fea62b;font-weight: bold } + .terminal-3226482548-r4 { fill: #a7a9ab } + .terminal-3226482548-r5 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BrokenClassesApp + MApp - - - - - - - - - - ────────────────────────────────────── - This should have a red background - - - - - - - - - - ────────────────────────────────────── - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +  o Options  @@ -26754,136 +29305,136 @@ font-weight: 700; } - .terminal-3211563364-matrix { + .terminal-700215523-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3211563364-title { + .terminal-700215523-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3211563364-r1 { fill: #90ee90 } - .terminal-3211563364-r2 { fill: #c5c8c6 } - .terminal-3211563364-r3 { fill: #add8e6 } - .terminal-3211563364-r4 { fill: #ddeedd } - .terminal-3211563364-r5 { fill: #808080 } - .terminal-3211563364-r6 { fill: #dddddd } - .terminal-3211563364-r7 { fill: #ffdddd } + .terminal-700215523-r1 { fill: #90ee90 } + .terminal-700215523-r2 { fill: #c5c8c6 } + .terminal-700215523-r3 { fill: #add8e6 } + .terminal-700215523-r4 { fill: #ddeedd } + .terminal-700215523-r5 { fill: #808080 } + .terminal-700215523-r6 { fill: #dddddd } + .terminal-700215523-r7 { fill: #ffdddd } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - NestedAutoApp + NestedAutoApp - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━ - JUST ONE LINE - ━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - - - - - - - - - - - - + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + ┏━━━━━━━━━━━━━━━┓ + ┏━━━━━━━━━━━━━┓ + JUST ONE LINE + ┗━━━━━━━━━━━━━┛ + ┗━━━━━━━━━━━━━━━┛ + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + + + + + + + + + + + + + + + @@ -26914,134 +29465,134 @@ font-weight: 700; } - .terminal-3973651935-matrix { + .terminal-2204042077-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3973651935-title { + .terminal-2204042077-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3973651935-r1 { fill: #ffffff } - .terminal-3973651935-r2 { fill: #c5c8c6 } - .terminal-3973651935-r3 { fill: #ffff00 } - .terminal-3973651935-r4 { fill: #002121 } + .terminal-2204042077-r1 { fill: #ffffff } + .terminal-2204042077-r2 { fill: #c5c8c6 } + .terminal-2204042077-r3 { fill: #ffff00 } + .terminal-2204042077-r4 { fill: #002121 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AutoApp + AutoApp - - - - ────────────────────────────────────────────────────────────────────────────── - ──────────────────────────────────────────────────────────────────────────── - Hello - World! - foo - - - - - - - - - - - - - - - - - - ──────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + ┌────────────────────────────────────────────────────────────────────────────┐ + Hello + World! + foo + + + + + + + + + + + + + + + + + + └────────────────────────────────────────────────────────────────────────────┘ + └──────────────────────────────────────────────────────────────────────────────┘ @@ -27071,135 +29622,135 @@ font-weight: 700; } - .terminal-2955864965-matrix { + .terminal-788257423-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2955864965-title { + .terminal-788257423-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2955864965-r1 { fill: #00ff00 } - .terminal-2955864965-r2 { fill: #008000 } - .terminal-2955864965-r3 { fill: #c5c8c6 } - .terminal-2955864965-r4 { fill: #e4e1e1 } - .terminal-2955864965-r5 { fill: #e0e4e0 } + .terminal-788257423-r1 { fill: #00ff00 } + .terminal-788257423-r2 { fill: #008000 } + .terminal-788257423-r3 { fill: #c5c8c6 } + .terminal-788257423-r4 { fill: #e4e1e1 } + .terminal-788257423-r5 { fill: #e0e4e0 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - NestedPseudoClassesApp + NestedPseudoClassesApp - - - - ────────────────────────────────────── - This isn't using nested CSSThis is using nested CSS - - - - - - - - - - - - - - - - - - - - - - ────────────────────────────────────── + + + + ╭──────────────────────────────────────╮ + This isn't using nested CSSThis is using nested CSS + + + + + + + + + + + + + + + + + + + + + + ╰──────────────────────────────────────╯ @@ -27229,135 +29780,135 @@ font-weight: 700; } - .terminal-2734338184-matrix { + .terminal-475433983-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2734338184-title { + .terminal-475433983-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2734338184-r1 { fill: #e1e1e1 } - .terminal-2734338184-r2 { fill: #c5c8c6 } - .terminal-2734338184-r3 { fill: #56c278 } - .terminal-2734338184-r4 { fill: #1d1d1d } - .terminal-2734338184-r5 { fill: #e3e4e4 } - .terminal-2734338184-r6 { fill: #e3e4e4;text-decoration: underline; } + .terminal-475433983-r1 { fill: #e1e1e1 } + .terminal-475433983-r2 { fill: #c5c8c6 } + .terminal-475433983-r3 { fill: #56c278 } + .terminal-475433983-r4 { fill: #1d1d1d } + .terminal-475433983-r5 { fill: #e3e4e4 } + .terminal-475433983-r6 { fill: #e3e4e4;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - NotifyWithInlineLinkApp + NotifyWithInlineLinkApp - - - - - - - - - - - - - - - - - - - - - - - - - Click here for the bell sound. - + + + + + + + + + + + + + + + + + + + + + + + + + Click here for the bell sound. + @@ -27388,135 +29939,135 @@ font-weight: 700; } - .terminal-500610332-matrix { + .terminal-2367525011-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-500610332-title { + .terminal-2367525011-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-500610332-r1 { fill: #e1e1e1 } - .terminal-500610332-r2 { fill: #c5c8c6 } - .terminal-500610332-r3 { fill: #56c278 } - .terminal-500610332-r4 { fill: #1d1d1d } - .terminal-500610332-r5 { fill: #e3e4e4 } - .terminal-500610332-r6 { fill: #ddedf9;font-weight: bold } + .terminal-2367525011-r1 { fill: #e1e1e1 } + .terminal-2367525011-r2 { fill: #c5c8c6 } + .terminal-2367525011-r3 { fill: #56c278 } + .terminal-2367525011-r4 { fill: #1d1d1d } + .terminal-2367525011-r5 { fill: #e3e4e4 } + .terminal-2367525011-r6 { fill: #ddedf9;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - NotifyWithInlineLinkApp + NotifyWithInlineLinkApp - - - - - - - - - - - - - - - - - - - - - - - - - Click here for the bell sound. - + + + + + + + + + + + + + + + + + + + + + + + + + Click here for the bell sound. + @@ -27547,139 +30098,139 @@ font-weight: 700; } - .terminal-3535066299-matrix { + .terminal-3920474212-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3535066299-title { + .terminal-3920474212-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3535066299-r1 { fill: #e1e1e1 } - .terminal-3535066299-r2 { fill: #c5c8c6 } - .terminal-3535066299-r3 { fill: #56c278 } - .terminal-3535066299-r4 { fill: #1d1d1d } - .terminal-3535066299-r5 { fill: #e3e4e4 } - .terminal-3535066299-r6 { fill: #feaa35 } - .terminal-3535066299-r7 { fill: #e89719;font-weight: bold } - .terminal-3535066299-r8 { fill: #e3e4e4;font-weight: bold } - .terminal-3535066299-r9 { fill: #e3e4e4;font-weight: bold;font-style: italic; } - .terminal-3535066299-r10 { fill: #bc4563 } + .terminal-3920474212-r1 { fill: #e1e1e1 } + .terminal-3920474212-r2 { fill: #c5c8c6 } + .terminal-3920474212-r3 { fill: #56c278 } + .terminal-3920474212-r4 { fill: #1d1d1d } + .terminal-3920474212-r5 { fill: #e3e4e4 } + .terminal-3920474212-r6 { fill: #feaa35 } + .terminal-3920474212-r7 { fill: #e89719;font-weight: bold } + .terminal-3920474212-r8 { fill: #e3e4e4;font-weight: bold } + .terminal-3920474212-r9 { fill: #e3e4e4;font-weight: bold;font-style: italic; } + .terminal-3920474212-r10 { fill: #bc4563 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ToastApp + ToastApp - - - - - - - - It's an older code, sir, but it  - checks out. - - - - Possible trap detected - Now witness the firepower of this  - fully ARMED and OPERATIONAL battle  - station! - - - - It's a trap! - - - - It's against my programming to  - impersonate a deity. - + + + + + + + + It's an older code, sir, but it  + checks out. + + + + Possible trap detected + Now witness the firepower of this  + fully ARMED and OPERATIONAL battle  + station! + + + + It's a trap! + + + + It's against my programming to  + impersonate a deity. + @@ -27710,117 +30261,117 @@ font-weight: 700; } - .terminal-4012933681-matrix { + .terminal-2138961308-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4012933681-title { + .terminal-2138961308-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4012933681-r1 { fill: #c5c8c6 } - .terminal-4012933681-r2 { fill: #56c278 } - .terminal-4012933681-r3 { fill: #1d1d1d } - .terminal-4012933681-r4 { fill: #e3e4e4 } + .terminal-2138961308-r1 { fill: #c5c8c6 } + .terminal-2138961308-r2 { fill: #56c278 } + .terminal-2138961308-r3 { fill: #1d1d1d } + .terminal-2138961308-r4 { fill: #e3e4e4 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LoadingOverlayApp + LoadingOverlayApp - - - - - - - - - - - This is a big notification. - This is a big notification. - This is a big notification. - This is a big notification. - This is a big notification. - This is a big notification. - This is a big notification. - This is a big notification. - This is a big notification. - This is a big notification. - - + + + + + + + + + + + This is a big notification. + This is a big notification. + This is a big notification. + This is a big notification. + This is a big notification. + This is a big notification. + This is a big notification. + This is a big notification. + This is a big notification. + This is a big notification. + + @@ -27851,134 +30402,134 @@ font-weight: 700; } - .terminal-1373062254-matrix { + .terminal-3370984838-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1373062254-title { + .terminal-3370984838-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1373062254-r1 { fill: #e1e1e1 } - .terminal-1373062254-r2 { fill: #56c278 } - .terminal-1373062254-r3 { fill: #c5c8c6 } - .terminal-1373062254-r4 { fill: #1d1d1d } - .terminal-1373062254-r5 { fill: #e3e4e4 } + .terminal-3370984838-r1 { fill: #e1e1e1 } + .terminal-3370984838-r2 { fill: #56c278 } + .terminal-3370984838-r3 { fill: #c5c8c6 } + .terminal-3370984838-r4 { fill: #1d1d1d } + .terminal-3370984838-r5 { fill: #e3e4e4 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - NotifyThroughModesApp + NotifyThroughModesApp - - - - This is a mode screen - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - + + + + This is a mode screen                   + 4 + + + + 5 + + + + 6 + + + + 7 + + + + 8 + + + + 9 + @@ -28009,134 +30560,134 @@ font-weight: 700; } - .terminal-3060613351-matrix { + .terminal-843325951-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3060613351-title { + .terminal-843325951-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3060613351-r1 { fill: #e1e1e1 } - .terminal-3060613351-r2 { fill: #56c278 } - .terminal-3060613351-r3 { fill: #c5c8c6 } - .terminal-3060613351-r4 { fill: #1d1d1d } - .terminal-3060613351-r5 { fill: #e3e4e4 } + .terminal-843325951-r1 { fill: #e1e1e1 } + .terminal-843325951-r2 { fill: #56c278 } + .terminal-843325951-r3 { fill: #c5c8c6 } + .terminal-843325951-r4 { fill: #1d1d1d } + .terminal-843325951-r5 { fill: #e3e4e4 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - NotifyDownScreensApp + NotifyDownScreensApp - - - - Screen 10 - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - + + + + Screen 10                               + 4 + + + + 5 + + + + 6 + + + + 7 + + + + 8 + + + + 9 + @@ -28167,133 +30718,133 @@ font-weight: 700; } - .terminal-4150241775-matrix { + .terminal-1996000257-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4150241775-title { + .terminal-1996000257-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4150241775-r1 { fill: #e1e1e1 } - .terminal-4150241775-r2 { fill: #c5c8c6 } - .terminal-4150241775-r3 { fill: #ffffff } - .terminal-4150241775-r4 { fill: #ddddef } + .terminal-1996000257-r1 { fill: #e1e1e1 } + .terminal-1996000257-r2 { fill: #c5c8c6 } + .terminal-1996000257-r3 { fill: #ffffff } + .terminal-1996000257-r4 { fill: #ddddef } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OffsetsApp + OffsetsApp - - - - - - - - - ────────────── - FOO - BAR - BAZ - ────────────── - - - - - - ────────────── - FOO - BAR - BAZ - ────────────── - - - + + + + + + + + + ┌──────────────┐ + FOO + BAR + BAZ + └──────────────┘ + + + + + + ┌──────────────┐ + FOO + BAR + BAZ + └──────────────┘ + + + @@ -28324,138 +30875,138 @@ font-weight: 700; } - .terminal-43804987-matrix { + .terminal-3880461415-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-43804987-title { + .terminal-3880461415-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-43804987-r1 { fill: #1e1e1e } - .terminal-43804987-r2 { fill: #0178d4 } - .terminal-43804987-r3 { fill: #c5c8c6 } - .terminal-43804987-r4 { fill: #ddedf9;font-weight: bold } - .terminal-43804987-r5 { fill: #e2e2e2;font-weight: bold } - .terminal-43804987-r6 { fill: #e2e2e2 } - .terminal-43804987-r7 { fill: #434343 } - .terminal-43804987-r8 { fill: #f4005f } - .terminal-43804987-r9 { fill: #e1e1e1 } + .terminal-3880461415-r1 { fill: #1e1e1e } + .terminal-3880461415-r2 { fill: #0178d4 } + .terminal-3880461415-r3 { fill: #c5c8c6 } + .terminal-3880461415-r4 { fill: #ddedf9;font-weight: bold } + .terminal-3880461415-r5 { fill: #e2e2e2;font-weight: bold } + .terminal-3880461415-r6 { fill: #e2e2e2 } + .terminal-3880461415-r7 { fill: #434343 } + .terminal-3880461415-r8 { fill: #f4005f } + .terminal-3880461415-r9 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - OneOneOne - TwoTwoTwo - ──────────────────────────────────────────────────────────────────── - ThreeThreeThree - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + One                   One                    ▎▊One                     + Two                   Two                    ▎▊Two                     + ─────────────────────────────────────────────▎▊─────────────────────── + ThreeThree▎▊Three + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + @@ -28486,140 +31037,140 @@ font-weight: 700; } - .terminal-2682133191-matrix { + .terminal-3287598191-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2682133191-title { + .terminal-3287598191-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2682133191-r1 { fill: #c5c8c6 } - .terminal-2682133191-r2 { fill: #e3e3e3 } - .terminal-2682133191-r3 { fill: #e1e1e1 } - .terminal-2682133191-r4 { fill: #1e1e1e } - .terminal-2682133191-r5 { fill: #0178d4 } - .terminal-2682133191-r6 { fill: #ddedf9;font-weight: bold } - .terminal-2682133191-r7 { fill: #e2e2e2 } - .terminal-2682133191-r8 { fill: #434343 } - .terminal-2682133191-r9 { fill: #787878 } - .terminal-2682133191-r10 { fill: #14191f } - .terminal-2682133191-r11 { fill: #ddedf9 } + .terminal-3287598191-r1 { fill: #c5c8c6 } + .terminal-3287598191-r2 { fill: #e3e3e3 } + .terminal-3287598191-r3 { fill: #e1e1e1 } + .terminal-3287598191-r4 { fill: #1e1e1e } + .terminal-3287598191-r5 { fill: #0178d4 } + .terminal-3287598191-r6 { fill: #ddedf9;font-weight: bold } + .terminal-3287598191-r7 { fill: #e2e2e2 } + .terminal-3287598191-r8 { fill: #434343 } + .terminal-3287598191-r9 { fill: #787878 } + .terminal-3287598191-r10 { fill: #14191f } + .terminal-3287598191-r11 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Aerilon - Aquaria - ────────────────────────────────────────────────── - Canceron - Caprica - ────────────────────────────────────────────────── - Gemenon - ────────────────────────────────────────────────── - Leonis - Libran - ────────────────────────────────────────────────── - Picon▁▁ - ────────────────────────────────────────────────── - Sagittaron - Scorpia - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - + + + + OptionListApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Aerilon                                            + Aquaria                                            + ────────────────────────────────────────────────── + Canceron                                           + Caprica                                            + ────────────────────────────────────────────────── + Gemenon                                            + ────────────────────────────────────────────────── + Leonis                                             + Libran                                             + ────────────────────────────────────────────────── + Picon                                             ▁▁ + ────────────────────────────────────────────────── + Sagittaron                                         + Scorpia                                            + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + @@ -28650,137 +31201,137 @@ font-weight: 700; } - .terminal-1891202557-matrix { + .terminal-4064027832-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1891202557-title { + .terminal-4064027832-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1891202557-r1 { fill: #c5c8c6 } - .terminal-1891202557-r2 { fill: #e3e3e3 } - .terminal-1891202557-r3 { fill: #1e1e1e } - .terminal-1891202557-r4 { fill: #0178d4 } - .terminal-1891202557-r5 { fill: #ddedf9;font-weight: bold } - .terminal-1891202557-r6 { fill: #e2e2e2 } - .terminal-1891202557-r7 { fill: #e1e1e1 } - .terminal-1891202557-r8 { fill: #ddedf9 } + .terminal-4064027832-r1 { fill: #c5c8c6 } + .terminal-4064027832-r2 { fill: #e3e3e3 } + .terminal-4064027832-r3 { fill: #1e1e1e } + .terminal-4064027832-r4 { fill: #0178d4 } + .terminal-4064027832-r5 { fill: #ddedf9;font-weight: bold } + .terminal-4064027832-r6 { fill: #e2e2e2 } + .terminal-4064027832-r7 { fill: #e1e1e1 } + .terminal-4064027832-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1. Another single line - 2. Two - lines - 3. Three - lines - of text - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - + + + + OptionListApp + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1. Another single line                                                       + 2. Two                                                                       + lines                                                                        + 3. Three                                                                     + lines                                                                        + of text                                                                      + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + @@ -28811,137 +31362,137 @@ font-weight: 700; } - .terminal-2188746417-matrix { + .terminal-3844865806-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2188746417-title { + .terminal-3844865806-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2188746417-r1 { fill: #c5c8c6 } - .terminal-2188746417-r2 { fill: #e3e3e3 } - .terminal-2188746417-r3 { fill: #1e1e1e } - .terminal-2188746417-r4 { fill: #0178d4 } - .terminal-2188746417-r5 { fill: #ddedf9;font-weight: bold } - .terminal-2188746417-r6 { fill: #e2e2e2 } - .terminal-2188746417-r7 { fill: #e1e1e1 } - .terminal-2188746417-r8 { fill: #ddedf9 } + .terminal-3844865806-r1 { fill: #c5c8c6 } + .terminal-3844865806-r2 { fill: #e3e3e3 } + .terminal-3844865806-r3 { fill: #1e1e1e } + .terminal-3844865806-r4 { fill: #0178d4 } + .terminal-3844865806-r5 { fill: #ddedf9;font-weight: bold } + .terminal-3844865806-r6 { fill: #e2e2e2 } + .terminal-3844865806-r7 { fill: #e1e1e1 } + .terminal-3844865806-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1. Two - lines - 2. Two - lines - 3. Three - lines - of text - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - + + + + OptionListApp + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1. Two                                                                       + lines                                                                        + 2. Two                                                                       + lines                                                                        + 3. Three                                                                     + lines                                                                        + of text                                                                      + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + @@ -28972,137 +31523,137 @@ font-weight: 700; } - .terminal-2667681921-matrix { + .terminal-570883420-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2667681921-title { + .terminal-570883420-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2667681921-r1 { fill: #c5c8c6 } - .terminal-2667681921-r2 { fill: #e3e3e3 } - .terminal-2667681921-r3 { fill: #1e1e1e } - .terminal-2667681921-r4 { fill: #0178d4 } - .terminal-2667681921-r5 { fill: #ddedf9;font-weight: bold } - .terminal-2667681921-r6 { fill: #e2e2e2 } - .terminal-2667681921-r7 { fill: #e1e1e1 } - .terminal-2667681921-r8 { fill: #ddedf9 } + .terminal-570883420-r1 { fill: #c5c8c6 } + .terminal-570883420-r2 { fill: #e3e3e3 } + .terminal-570883420-r3 { fill: #1e1e1e } + .terminal-570883420-r4 { fill: #0178d4 } + .terminal-570883420-r5 { fill: #ddedf9;font-weight: bold } + .terminal-570883420-r6 { fill: #e2e2e2 } + .terminal-570883420-r7 { fill: #e1e1e1 } + .terminal-570883420-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1. Single line - 1. Three - lines - of text - 3. Three - lines - of text - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - + + + + OptionListApp + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1. Single line                                                               + 1. Three                                                                     + lines                                                                        + of text                                                                      + 3. Three                                                                     + lines                                                                        + of text                                                                      + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + @@ -29133,136 +31684,301 @@ font-weight: 700; } - .terminal-1990786949-matrix { + .terminal-1141874057-matrix { + font-family: Fira Code, monospace; + font-size: 20px; + line-height: 24.4px; + font-variant-east-asian: full-width; + } + + .terminal-1141874057-title { + font-size: 18px; + font-weight: bold; + font-family: arial; + } + + .terminal-1141874057-r1 { fill: #1e1e1e } + .terminal-1141874057-r2 { fill: #0178d4 } + .terminal-1141874057-r3 { fill: #c5c8c6 } + .terminal-1141874057-r4 { fill: #e2e2e2 } + .terminal-1141874057-r5 { fill: #23568b } + .terminal-1141874057-r6 { fill: #ddedf9;font-weight: bold } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LongOptionListApp + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + This is option #78                                                         + This is option #79                                                         + This is option #80                                                         + This is option #81                                                         + This is option #82                                                         + This is option #83                                                         + This is option #84                                                         + This is option #85                                                         + This is option #86                                                         + This is option #87                                                         + This is option #88                                                         + This is option #89                                                         + This is option #90                                                         + This is option #91                                                         + This is option #92                                                         + This is option #93                                                         + This is option #94                                                         + This is option #95                                                        ▇▇ + This is option #96                                                         + This is option #97                                                         + This is option #98                                                         + This is option #99                                                         + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + ''' +# --- +# name: test_option_list_scrolling_with_multiline_options + ''' + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LongOptionListApp + OptionListApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - This is option #78 - This is option #79 - This is option #80 - This is option #81 - This is option #82 - This is option #83 - This is option #84 - This is option #85 - This is option #86 - This is option #87 - This is option #88 - This is option #89 - This is option #90 - This is option #91 - This is option #92 - This is option #93 - This is option #94 - This is option #95▇▇ - This is option #96 - This is option #97 - This is option #98 - This is option #99 - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + OptionListApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ + │ Dionysus      │ 450 Million   │ Celeste        │ + └───────────────┴───────────────┴────────────────┘ +                  Data for Tauron                   + ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ +  Patron God     Population     Capital City    + ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ + │ Ares          │ 2.5 Billion   │ Hypatia        │ + └───────────────┴───────────────┴────────────────┘ +                  Data for Virgon                   + ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ + ┃ Patron God    ┃ Population    ┃ Capital City   ┃▁▁ + ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ + │ Hestia        │ 4.3 Billion   │ Boskirk        │ + └───────────────┴───────────────┴────────────────┘ + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + @@ -29292,137 +32008,137 @@ font-weight: 700; } - .terminal-534841697-matrix { + .terminal-3590136409-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-534841697-title { + .terminal-3590136409-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-534841697-r1 { fill: #c5c8c6 } - .terminal-534841697-r2 { fill: #e3e3e3 } - .terminal-534841697-r3 { fill: #e1e1e1 } - .terminal-534841697-r4 { fill: #1e1e1e } - .terminal-534841697-r5 { fill: #0178d4 } - .terminal-534841697-r6 { fill: #ddedf9;font-weight: bold } - .terminal-534841697-r7 { fill: #e2e2e2 } - .terminal-534841697-r8 { fill: #ddedf9 } + .terminal-3590136409-r1 { fill: #c5c8c6 } + .terminal-3590136409-r2 { fill: #e3e3e3 } + .terminal-3590136409-r3 { fill: #e1e1e1 } + .terminal-3590136409-r4 { fill: #1e1e1e } + .terminal-3590136409-r5 { fill: #0178d4 } + .terminal-3590136409-r6 { fill: #ddedf9;font-weight: bold } + .terminal-3590136409-r7 { fill: #e2e2e2 } + .terminal-3590136409-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Aerilon - Aquaria - Canceron - Caprica - Gemenon - Leonis - Libran - Picon - Sagittaron - Scorpia - Tauron - Virgon - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - + + + + OptionListApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Aerilon                                              + Aquaria                                              + Canceron                                             + Caprica                                              + Gemenon                                              + Leonis                                               + Libran                                               + Picon                                                + Sagittaron                                           + Scorpia                                              + Tauron                                               + Virgon                                               + + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + @@ -29453,141 +32169,141 @@ font-weight: 700; } - .terminal-1755547624-matrix { + .terminal-4277961929-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1755547624-title { + .terminal-4277961929-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1755547624-r1 { fill: #c5c8c6 } - .terminal-1755547624-r2 { fill: #e3e3e3 } - .terminal-1755547624-r3 { fill: #e1e1e1 } - .terminal-1755547624-r4 { fill: #1e1e1e } - .terminal-1755547624-r5 { fill: #0178d4 } - .terminal-1755547624-r6 { fill: #ddedf9;font-weight: bold;font-style: italic; } - .terminal-1755547624-r7 { fill: #e2e2e2 } - .terminal-1755547624-r8 { fill: #ddedf9;font-weight: bold } - .terminal-1755547624-r9 { fill: #14191f } - .terminal-1755547624-r10 { fill: #e2e2e2;font-style: italic; } - .terminal-1755547624-r11 { fill: #e2e2e2;font-weight: bold } - .terminal-1755547624-r12 { fill: #ddedf9 } + .terminal-4277961929-r1 { fill: #c5c8c6 } + .terminal-4277961929-r2 { fill: #e3e3e3 } + .terminal-4277961929-r3 { fill: #e1e1e1 } + .terminal-4277961929-r4 { fill: #1e1e1e } + .terminal-4277961929-r5 { fill: #0178d4 } + .terminal-4277961929-r6 { fill: #ddedf9;font-weight: bold;font-style: italic; } + .terminal-4277961929-r7 { fill: #e2e2e2 } + .terminal-4277961929-r8 { fill: #ddedf9;font-weight: bold } + .terminal-4277961929-r9 { fill: #14191f } + .terminal-4277961929-r10 { fill: #e2e2e2;font-style: italic; } + .terminal-4277961929-r11 { fill: #e2e2e2;font-weight: bold } + .terminal-4277961929-r12 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -                  Data for Aerilon                  - ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - Patron God   Population   Capital City   - ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩▇▇ - Demeter      1.2 Billion  Gaoth          - └───────────────┴───────────────┴────────────────┘ -                  Data for Aquaria                  - ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - Patron God   Population   Capital City   - ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - Hermes       75,000       None           - └───────────────┴───────────────┴────────────────┘ -                 Data for Canceron                  - ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - Patron God   Population   Capital City   - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - + + + + OptionListApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +                  Data for Aerilon                  + ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ + ┃ Patron God    ┃ Population    ┃ Capital City   ┃ + ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩▇▇ + │ Demeter       │ 1.2 Billion   │ Gaoth          │ + └───────────────┴───────────────┴────────────────┘ +                  Data for Aquaria                  + ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ +  Patron God     Population     Capital City    + ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ + │ Hermes        │ 75,000        │ None           │ + └───────────────┴───────────────┴────────────────┘ +                 Data for Canceron                  + ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ +  Patron God     Population     Capital City    + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + @@ -29618,136 +32334,137 @@ font-weight: 700; } - .terminal-159476969-matrix { + .terminal-749580480-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-159476969-title { + .terminal-749580480-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-159476969-r1 { fill: #ffff00 } - .terminal-159476969-r2 { fill: #e3e3e3 } - .terminal-159476969-r3 { fill: #c5c8c6 } - .terminal-159476969-r4 { fill: #e1e1e1 } - .terminal-159476969-r5 { fill: #dde8f3;font-weight: bold } - .terminal-159476969-r6 { fill: #ddedf9 } + .terminal-749580480-r1 { fill: #ffff00 } + .terminal-749580480-r2 { fill: #e3e3e3 } + .terminal-749580480-r3 { fill: #c5c8c6 } + .terminal-749580480-r4 { fill: #e1e1e1 } + .terminal-749580480-r5 { fill: #fea62b;font-weight: bold } + .terminal-749580480-r6 { fill: #a7a9ab } + .terminal-749580480-r7 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Layers + Layers - - - - ──────────────────────────────────Layers - It's full of stars! My God! It's full of sta - - This should float over the top - - - ────────────────────────────────── - - - - - - - - - - - - - - - - -  T  Toggle Screen  + + + + ┌──────────────────────────────────┐Layers + It's full of stars! My God! It's full of sta + + This should float over the top + + + └──────────────────────────────────┘ + + + + + + + + + + + + + + + + +  t Toggle Screen  @@ -29777,136 +32494,137 @@ font-weight: 700; } - .terminal-4133316721-matrix { + .terminal-888187976-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4133316721-title { + .terminal-888187976-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4133316721-r1 { fill: #ffff00 } - .terminal-4133316721-r2 { fill: #e3e3e3 } - .terminal-4133316721-r3 { fill: #c5c8c6 } - .terminal-4133316721-r4 { fill: #ddeedd } - .terminal-4133316721-r5 { fill: #dde8f3;font-weight: bold } - .terminal-4133316721-r6 { fill: #ddedf9 } + .terminal-888187976-r1 { fill: #ffff00 } + .terminal-888187976-r2 { fill: #e3e3e3 } + .terminal-888187976-r3 { fill: #c5c8c6 } + .terminal-888187976-r4 { fill: #ddeedd } + .terminal-888187976-r5 { fill: #fea62b;font-weight: bold } + .terminal-888187976-r6 { fill: #a7a9ab } + .terminal-888187976-r7 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Layers + Layers - - - - ──────────────────────────────────Layers - It's full of stars! My God! It's full of sta - - This should float over the top - - - ────────────────────────────────── - - - - - - - - - - - - - - - - -  T  Toggle Screen  + + + + ┌──────────────────────────────────┐Layers + It's full of stars! My God! It's full of sta + + This should float over the top + + + └──────────────────────────────────┘ + + + + + + + + + + + + + + + + +  t Toggle Screen  @@ -30191,142 +32909,142 @@ font-weight: 700; } - .terminal-3476127971-matrix { + .terminal-4273831210-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3476127971-title { + .terminal-4273831210-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3476127971-r1 { fill: #c5c8c6 } - .terminal-3476127971-r2 { fill: #eae3e5 } - .terminal-3476127971-r3 { fill: #e8e0e7 } - .terminal-3476127971-r4 { fill: #efe9e4 } - .terminal-3476127971-r5 { fill: #ede6e6 } - .terminal-3476127971-r6 { fill: #efeedf } - .terminal-3476127971-r7 { fill: #e9eee5 } - .terminal-3476127971-r8 { fill: #e3e6eb } - .terminal-3476127971-r9 { fill: #dfe9ed;font-weight: bold } - .terminal-3476127971-r10 { fill: #e6e3e9;font-weight: bold } - .terminal-3476127971-r11 { fill: #e4eee8 } - .terminal-3476127971-r12 { fill: #e2edeb;font-weight: bold } - .terminal-3476127971-r13 { fill: #dfebed } + .terminal-4273831210-r1 { fill: #c5c8c6 } + .terminal-4273831210-r2 { fill: #eae3e5 } + .terminal-4273831210-r3 { fill: #e8e0e7 } + .terminal-4273831210-r4 { fill: #efe9e4 } + .terminal-4273831210-r5 { fill: #ede6e6 } + .terminal-4273831210-r6 { fill: #efeedf } + .terminal-4273831210-r7 { fill: #e9eee5 } + .terminal-4273831210-r8 { fill: #e3e6eb } + .terminal-4273831210-r9 { fill: #dfe9ed;font-weight: bold } + .terminal-4273831210-r10 { fill: #e6e3e9;font-weight: bold } + .terminal-4273831210-r11 { fill: #e4eee8 } + .terminal-4273831210-r12 { fill: #e2edeb;font-weight: bold } + .terminal-4273831210-r13 { fill: #dfebed } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - PlaceholderApp + PlaceholderApp - - - - - Placeholder p2 here! - This is a custom label for p1. - #p4 - #p3#p5Placeholde - r - - Lorem ipsum dolor sit  - 26 x 6amet, consectetur 27 x 6 - adipiscing elit. Etiam  - feugiat ac elit sit amet  - - - Lorem ipsum dolor sit amet,  - consectetur adipiscing elit. Etiam 40 x 6 - feugiat ac elit sit amet accumsan.  - Suspendisse bibendum nec libero quis  - gravida. Phasellus id eleifend ligula. - Nullam imperdiet sem tellus, sed  - vehicula nisl faucibus sit amet. Lorem ipsum dolor sit amet,  - Praesent iaculis tempor ultricies. Sedconsectetur adipiscing elit. Etiam  - lacinia, tellus id rutrum lacinia, feugiat ac elit sit amet accumsan.  - sapien sapien congue mauris, sit amet Suspendisse bibendum nec libero quis  + + + + + Placeholder p2 here! + This is a custom label for p1. + #p4 + #p3#p5Placeholde + r + + Lorem ipsum dolor sit  + 26 x 6amet, consectetur 27 x 6 + adipiscing elit. Etiam  + feugiat ac elit sit amet  + + + Lorem ipsum dolor sit amet,  + consectetur adipiscing elit. Etiam 40 x 6 + feugiat ac elit sit amet accumsan.  + Suspendisse bibendum nec libero quis  + gravida. Phasellus id eleifend ligula. + Nullam imperdiet sem tellus, sed  + vehicula nisl faucibus sit amet. Lorem ipsum dolor sit amet,  + Praesent iaculis tempor ultricies. Sedconsectetur adipiscing elit. Etiam  + lacinia, tellus id rutrum lacinia, feugiat ac elit sit amet accumsan.  + sapien sapien congue mauris, sit amet Suspendisse bibendum nec libero quis  @@ -30385,39 +33103,194 @@ - - + + + + + + + + + + + + + + + + + + MyApp + + + + + + + + + + ['This is a string that has some chars'] + + This should be 1 cell away from ^ + + + + + + + + + ''' +# --- +# name: test_print_capture + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + + + + + + + + + + - MyApp + CaptureApp - - - - ['This is a string that has some chars'] - - This should be 1 cell away from ^ - - - + + + + RichLog                                                                        + This will be captured!                                                         + + + + + + + + + + + + + + + + + + + + + @@ -30425,7 +33298,7 @@ ''' # --- -# name: test_print_capture +# name: test_programmatic_disable_button ''' @@ -30448,132 +33321,138 @@ font-weight: 700; } - .terminal-3935013562-matrix { + .terminal-2109104343-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3935013562-title { + .terminal-2109104343-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3935013562-r1 { fill: #e1e1e1 } - .terminal-3935013562-r2 { fill: #c5c8c6 } + .terminal-2109104343-r1 { fill: #e1e1e1 } + .terminal-2109104343-r2 { fill: #c5c8c6 } + .terminal-2109104343-r3 { fill: #303336 } + .terminal-2109104343-r4 { fill: #a7a7a7;font-weight: bold } + .terminal-2109104343-r5 { fill: #0f0f0f } + .terminal-2109104343-r6 { fill: #fea62b;font-weight: bold } + .terminal-2109104343-r7 { fill: #a7a9ab } + .terminal-2109104343-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CaptureApp + ExampleApp - - - - RichLog - This will be captured! - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + +                         Hover the button then hit space                          + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Disabled  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + +  SPACE Toggle Button  @@ -30603,132 +33482,132 @@ font-weight: 700; } - .terminal-4222066429-matrix { + .terminal-2268888456-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4222066429-title { + .terminal-2268888456-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4222066429-r1 { fill: #ffdddd } - .terminal-4222066429-r2 { fill: #c5c8c6 } - .terminal-4222066429-r3 { fill: #e1e1e1 } + .terminal-2268888456-r1 { fill: #ffdddd } + .terminal-2268888456-r2 { fill: #c5c8c6 } + .terminal-2268888456-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ProgrammaticScrollbarGutterChange + ProgrammaticScrollbarGutterChange - - - - onetwo - - - - - - - - - - - - threefour - - - - - - - - - - + + + + onetwo + + + + + + + + + + + + threefour + + + + + + + + + + @@ -30759,135 +33638,136 @@ font-weight: 700; } - .terminal-2592118671-matrix { + .terminal-3159068456-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2592118671-title { + .terminal-3159068456-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2592118671-r1 { fill: #e1e1e1 } - .terminal-2592118671-r2 { fill: #c5c8c6 } - .terminal-2592118671-r3 { fill: #4ebf71 } - .terminal-2592118671-r4 { fill: #dde8f3;font-weight: bold } - .terminal-2592118671-r5 { fill: #ddedf9 } + .terminal-3159068456-r1 { fill: #e1e1e1 } + .terminal-3159068456-r2 { fill: #c5c8c6 } + .terminal-3159068456-r3 { fill: #4ebf71 } + .terminal-3159068456-r4 { fill: #fea62b;font-weight: bold } + .terminal-3159068456-r5 { fill: #a7a9ab } + .terminal-3159068456-r6 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - IndeterminateProgressBar + IndeterminateProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:-- - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:--                  + + + + + + + + + + + +  s Start  @@ -30917,137 +33797,138 @@ font-weight: 700; } - .terminal-3036192677-matrix { + .terminal-3030506209-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3036192677-title { + .terminal-3030506209-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3036192677-r1 { fill: #e1e1e1 } - .terminal-3036192677-r2 { fill: #c5c8c6 } - .terminal-3036192677-r3 { fill: #b93c5b } - .terminal-3036192677-r4 { fill: #1e1e1e } - .terminal-3036192677-r5 { fill: #e1e1e1;text-decoration: underline; } - .terminal-3036192677-r6 { fill: #dde8f3;font-weight: bold } - .terminal-3036192677-r7 { fill: #ddedf9 } + .terminal-3030506209-r1 { fill: #e1e1e1 } + .terminal-3030506209-r2 { fill: #c5c8c6 } + .terminal-3030506209-r3 { fill: #b93c5b } + .terminal-3030506209-r4 { fill: #1e1e1e } + .terminal-3030506209-r5 { fill: #e1e1e1;text-decoration: underline; } + .terminal-3030506209-r6 { fill: #fea62b;font-weight: bold } + .terminal-3030506209-r7 { fill: #a7a9ab } + .terminal-3030506209-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - StyledProgressBar + StyledProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:-- - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:-- + + + + + + + + + + + +  s Start  @@ -31077,136 +33958,137 @@ font-weight: 700; } - .terminal-2114723073-matrix { + .terminal-3658011159-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2114723073-title { + .terminal-3658011159-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2114723073-r1 { fill: #e1e1e1 } - .terminal-2114723073-r2 { fill: #c5c8c6 } - .terminal-2114723073-r3 { fill: #fea62b } - .terminal-2114723073-r4 { fill: #323232 } - .terminal-2114723073-r5 { fill: #dde8f3;font-weight: bold } - .terminal-2114723073-r6 { fill: #ddedf9 } + .terminal-3658011159-r1 { fill: #e1e1e1 } + .terminal-3658011159-r2 { fill: #c5c8c6 } + .terminal-3658011159-r3 { fill: #fea62b } + .terminal-3658011159-r4 { fill: #323232 } + .terminal-3658011159-r5 { fill: #fea62b;font-weight: bold } + .terminal-3658011159-r6 { fill: #a7a9ab } + .terminal-3658011159-r7 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - IndeterminateProgressBar + IndeterminateProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━39%00:00:07 - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━39%00:00:07                  + + + + + + + + + + + +  s Start  @@ -31236,138 +34118,139 @@ font-weight: 700; } - .terminal-1351164996-matrix { + .terminal-2213813497-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1351164996-title { + .terminal-2213813497-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1351164996-r1 { fill: #e1e1e1 } - .terminal-1351164996-r2 { fill: #c5c8c6 } - .terminal-1351164996-r3 { fill: #004578 } - .terminal-1351164996-r4 { fill: #152939 } - .terminal-1351164996-r5 { fill: #1e1e1e } - .terminal-1351164996-r6 { fill: #e1e1e1;text-decoration: underline; } - .terminal-1351164996-r7 { fill: #dde8f3;font-weight: bold } - .terminal-1351164996-r8 { fill: #ddedf9 } + .terminal-2213813497-r1 { fill: #e1e1e1 } + .terminal-2213813497-r2 { fill: #c5c8c6 } + .terminal-2213813497-r3 { fill: #004578 } + .terminal-2213813497-r4 { fill: #152939 } + .terminal-2213813497-r5 { fill: #1e1e1e } + .terminal-2213813497-r6 { fill: #e1e1e1;text-decoration: underline; } + .terminal-2213813497-r7 { fill: #fea62b;font-weight: bold } + .terminal-2213813497-r8 { fill: #a7a9ab } + .terminal-2213813497-r9 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - StyledProgressBar + StyledProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━39%00:00:07 - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━39%00:00:07 + + + + + + + + + + + +  s Start  @@ -31397,136 +34280,137 @@ font-weight: 700; } - .terminal-852664727-matrix { + .terminal-2628588616-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-852664727-title { + .terminal-2628588616-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-852664727-r1 { fill: #e1e1e1 } - .terminal-852664727-r2 { fill: #c5c8c6 } - .terminal-852664727-r3 { fill: #323232 } - .terminal-852664727-r4 { fill: #b93c5b } - .terminal-852664727-r5 { fill: #dde8f3;font-weight: bold } - .terminal-852664727-r6 { fill: #ddedf9 } + .terminal-2628588616-r1 { fill: #e1e1e1 } + .terminal-2628588616-r2 { fill: #c5c8c6 } + .terminal-2628588616-r3 { fill: #323232 } + .terminal-2628588616-r4 { fill: #b93c5b } + .terminal-2628588616-r5 { fill: #fea62b;font-weight: bold } + .terminal-2628588616-r6 { fill: #a7a9ab } + .terminal-2628588616-r7 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - IndeterminateProgressBar + IndeterminateProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━--%--:--:-- - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━╸━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━--%--:--:--                  + + + + + + + + + + + +  s Start  @@ -31556,138 +34440,139 @@ font-weight: 700; } - .terminal-1175498223-matrix { + .terminal-1415143893-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1175498223-title { + .terminal-1415143893-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1175498223-r1 { fill: #e1e1e1 } - .terminal-1175498223-r2 { fill: #c5c8c6 } - .terminal-1175498223-r3 { fill: #fea62b } - .terminal-1175498223-r4 { fill: #004578 } - .terminal-1175498223-r5 { fill: #1e1e1e } - .terminal-1175498223-r6 { fill: #e1e1e1;text-decoration: underline; } - .terminal-1175498223-r7 { fill: #dde8f3;font-weight: bold } - .terminal-1175498223-r8 { fill: #ddedf9 } + .terminal-1415143893-r1 { fill: #e1e1e1 } + .terminal-1415143893-r2 { fill: #c5c8c6 } + .terminal-1415143893-r3 { fill: #fea62b } + .terminal-1415143893-r4 { fill: #004578 } + .terminal-1415143893-r5 { fill: #1e1e1e } + .terminal-1415143893-r6 { fill: #e1e1e1;text-decoration: underline; } + .terminal-1415143893-r7 { fill: #fea62b;font-weight: bold } + .terminal-1415143893-r8 { fill: #a7a9ab } + .terminal-1415143893-r9 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - StyledProgressBar + StyledProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━--%--:--:-- - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━╸━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━--%--:--:-- + + + + + + + + + + + +  s Start  @@ -31717,135 +34602,135 @@ font-weight: 700; } - .terminal-1103805314-matrix { + .terminal-1240162166-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1103805314-title { + .terminal-1240162166-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1103805314-r1 { fill: #c5c8c6 } - .terminal-1103805314-r2 { fill: #e1e1e1 } - .terminal-1103805314-r3 { fill: #737373 } - .terminal-1103805314-r4 { fill: #e1e1e1;font-weight: bold } - .terminal-1103805314-r5 { fill: #474747 } - .terminal-1103805314-r6 { fill: #0178d4 } + .terminal-1240162166-r1 { fill: #c5c8c6 } + .terminal-1240162166-r2 { fill: #e1e1e1 } + .terminal-1240162166-r3 { fill: #737373 } + .terminal-1240162166-r4 { fill: #e1e1e1;font-weight: bold } + .terminal-1240162166-r5 { fill: #474747 } + .terminal-1240162166-r6 { fill: #0178d4 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - QuicklyChangeTabsApp + QuicklyChangeTabsApp - - - - - onetwothree - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - three - - - - - - - - - - - - - - - - - - + + + + + onetwothree + ━━━━━━━━━━━━━╸━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + three                                                                        + + + + + + + + + + + + + + + + + + @@ -32203,135 +35088,135 @@ font-weight: 700; } - .terminal-1786282230-matrix { + .terminal-1943930343-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1786282230-title { + .terminal-1943930343-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1786282230-r1 { fill: #ff0000 } - .terminal-1786282230-r2 { fill: #c5c8c6 } - .terminal-1786282230-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-1786282230-r4 { fill: #e1e1e1 } - .terminal-1786282230-r5 { fill: #fea62b } - .terminal-1786282230-r6 { fill: #323232 } + .terminal-1943930343-r1 { fill: #ff0000 } + .terminal-1943930343-r2 { fill: #c5c8c6 } + .terminal-1943930343-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-1943930343-r4 { fill: #e1e1e1 } + .terminal-1943930343-r5 { fill: #fea62b } + .terminal-1943930343-r6 { fill: #323232 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - RecomposeApp + RecomposeApp - - - - ────────────────────────────────────────────────────────────────── -  ┓ ┏━┓ ┓  ┓  ┓ ╺━┓ ┓ ╺━┓ ┓ ╻ ╻ ┓ ┏━╸ ┓ ┏━╸ -  ┃ ┃ ┃ ┃  ┃  ┃ ┏━┛ ┃  ━┫ ┃ ┗━┫ ┃ ┗━┓ ┃ ┣━┓ - ╺┻╸┗━┛╺┻╸╺┻╸╺┻╸┗━╸╺┻╸╺━┛╺┻╸  ╹╺┻╸╺━┛╺┻╸┗━┛ - ────────────────────────────────────────────────────────────────── - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━50% - - - - - - - - - - + + + + ┌─────────┐┌─────────┐┌──────────┐┌─────────┐┌──────────┐┌─────────┐┌──────────┐ +  ┓ ┏━┓   ││ ┓  ┓    ││ ┓ ╺━┓    ││ ┓ ╺━┓   ││ ┓ ╻ ╻    ││ ┓ ┏━╸   ││ ┓ ┏━╸     +  ┃ ┃ ┃   ││ ┃  ┃    ││ ┃ ┏━┛    ││ ┃  ━┫   ││ ┃ ┗━┫    ││ ┃ ┗━┓   ││ ┃ ┣━┓     + ╺┻╸┗━┛   ││╺┻╸╺┻╸   ││╺┻╸┗━╸    ││╺┻╸╺━┛   ││╺┻╸  ╹    ││╺┻╸╺━┛   ││╺┻╸┗━┛     + └─────────┘└─────────┘└──────────┘└─────────┘└──────────┘└─────────┘└──────────┘ + + + + + + + + ━━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━50%                                            + + + + + + + + + + @@ -32362,137 +35247,138 @@ font-weight: 700; } - .terminal-1855273214-matrix { + .terminal-2357839144-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1855273214-title { + .terminal-2357839144-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1855273214-r1 { fill: #c5c8c6 } - .terminal-1855273214-r2 { fill: #e3e3e3 } - .terminal-1855273214-r3 { fill: #008000 } - .terminal-1855273214-r4 { fill: #ffff00 } - .terminal-1855273214-r5 { fill: #e1e1e1 } - .terminal-1855273214-r6 { fill: #dde8f3;font-weight: bold } - .terminal-1855273214-r7 { fill: #ddedf9 } + .terminal-2357839144-r1 { fill: #c5c8c6 } + .terminal-2357839144-r2 { fill: #e3e3e3 } + .terminal-2357839144-r3 { fill: #008000 } + .terminal-2357839144-r4 { fill: #ffff00 } + .terminal-2357839144-r5 { fill: #e1e1e1 } + .terminal-2357839144-r6 { fill: #fea62b;font-weight: bold } + .terminal-2357839144-r7 { fill: #a7a9ab } + .terminal-2357839144-r8 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - VerticalRemoveApp + VerticalRemoveApp - - - - VerticalRemoveApp - ────────────────────────────────────────────────────────────────────────────── - ──────────────────── - This is a test label - ──────────────────── - ────────────────────────────────────────────────────────────────────────────── - - - - - - - - - - - - - - - - - -  A  Add  D  Delete  + + + + VerticalRemoveApp + ╭──────────────────────────────────────────────────────────────────────────────╮ + ╭────────────────────╮ + │This is a test label│ + ╰────────────────────╯ + ╰──────────────────────────────────────────────────────────────────────────────╯ + + + + + + + + + + + + + + + + + +  a Add  d Delete  @@ -32522,131 +35408,131 @@ font-weight: 700; } - .terminal-707514202-matrix { + .terminal-2800944241-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-707514202-title { + .terminal-2800944241-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-707514202-r1 { fill: #e1e1e1 } - .terminal-707514202-r2 { fill: #c5c8c6 } + .terminal-2800944241-r1 { fill: #e1e1e1 } + .terminal-2800944241-r2 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - RichLogLines + RichLogLines - - - - Key press #3 - Key press #4 - Key press #5 - - - - - - - - - - - - - - - - - - - - + + + + Key press #3                                                                   + Key press #4                                                                   + Key press #5                                                                   + + + + + + + + + + + + + + + + + + + + @@ -32677,131 +35563,131 @@ font-weight: 700; } - .terminal-1498692038-matrix { + .terminal-1910524980-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1498692038-title { + .terminal-1910524980-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1498692038-r1 { fill: #e1e1e1 } - .terminal-1498692038-r2 { fill: #c5c8c6 } + .terminal-1910524980-r1 { fill: #e1e1e1 } + .terminal-1910524980-r2 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - RichLogScrollApp + RichLogScrollApp - - - - Line 0Line 10Line 0 - Line 1Line 11Line 1 - Line 2Line 12Line 2 - Line 3Line 13Line 3 - Line 4Line 14Line 4 - Line 5Line 15Line 5 - Line 6Line 16Line 6 - Line 7Line 17Line 7 - Line 8Line 18Line 8 - Line 9Line 19Line 9 - - - - - - - - - - - - - + + + + Line 0                  Line 10                  Line 0                    + Line 1                  Line 11                  Line 1                    + Line 2                  Line 12                  Line 2                    + Line 3                  Line 13                  Line 3                    + Line 4                  Line 14                  Line 4                    + Line 5                  Line 15                  Line 5                    + Line 6                  Line 16                  Line 6                    + Line 7                  Line 17                  Line 7                    + Line 8                  Line 18                  Line 8                    + Line 9                  Line 19                  Line 9                    + + + + + + + + + + + + + @@ -32987,132 +35873,132 @@ font-weight: 700; } - .terminal-2818358681-matrix { + .terminal-2750102054-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2818358681-title { + .terminal-2750102054-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2818358681-r1 { fill: #e1e1e1 } - .terminal-2818358681-r2 { fill: #c5c8c6 } - .terminal-2818358681-r3 { fill: #004578 } + .terminal-2750102054-r1 { fill: #e1e1e1 } + .terminal-2750102054-r2 { fill: #c5c8c6 } + .terminal-2750102054-r3 { fill: #004578 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HorizontalRulesApp + HorizontalRulesApp - - - -                         solid (default)                          - - ──────────────────────────────────────────────────────────────── - -                              heavy                               - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -                              thick                               - - ████████████████████████████████████████████████████████████████ - -                              dashed                              - - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ - -                              double                              - - ════════════════════════════════════════════════════════════════ - -                              ascii                               - - ---------------------------------------------------------------- + + + +                                 solid (default)                                  + + ──────────────────────────────────────────────────────────────── + +                                      heavy                                       + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +                                      thick                                       + + ████████████████████████████████████████████████████████████████ + +                                      dashed                                      + + ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ + +                                      double                                      + + ════════════════════════════════════════════════════════════════ + +                                      ascii                                       + + ---------------------------------------------------------------- @@ -33143,132 +36029,132 @@ font-weight: 700; } - .terminal-30569631-matrix { + .terminal-870531946-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-30569631-title { + .terminal-870531946-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-30569631-r1 { fill: #e1e1e1 } - .terminal-30569631-r2 { fill: #c5c8c6 } - .terminal-30569631-r3 { fill: #004578 } + .terminal-870531946-r1 { fill: #e1e1e1 } + .terminal-870531946-r2 { fill: #c5c8c6 } + .terminal-870531946-r3 { fill: #004578 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - VerticalRulesApp + VerticalRulesApp - - - - - - solid heavy thick dasheddoubleascii | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - - + + + + + +        solid     heavy     thick     dashed    double    ascii   | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + + @@ -33276,6 +36162,162 @@ ''' # --- +# name: test_rules + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RuleApp + + + + + + + + + + + -------------------------------------------------------------------------------- + + + + ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ + + ════════════════════════════════════════════════════════════════════════════════ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + | + | + | + | + | + | + | + | + | + | + | + | + + + + + ''' +# --- # name: test_scoped_css ''' @@ -33299,133 +36341,133 @@ font-weight: 700; } - .terminal-1271303658-matrix { + .terminal-1207765677-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1271303658-title { + .terminal-1207765677-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1271303658-r1 { fill: #ff00ff } - .terminal-1271303658-r2 { fill: #c5c8c6 } - .terminal-1271303658-r3 { fill: #008000 } - .terminal-1271303658-r4 { fill: #e1e1e1 } + .terminal-1207765677-r1 { fill: #ff00ff } + .terminal-1207765677-r2 { fill: #c5c8c6 } + .terminal-1207765677-r3 { fill: #008000 } + .terminal-1207765677-r4 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - ────────────────────────────────────────────────────────────────────────────── - ─── - foo - ─── - ─── - bar - ─── - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - ─── - foo - ─── - ─── - bar - ─── - ────────────────────────────────────────────────────────────────────────────── - I should not be styled - - - - - - + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + ┌───┐ + foo + └───┘ + ┌───┐ + bar + └───┘ + └──────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────┐ + ┌───┐ + foo + └───┘ + ┌───┐ + bar + └───┘ + └──────────────────────────────────────────────────────────────────────────────┘ + I should not be styled                                                           + + + + + + @@ -33456,135 +36498,136 @@ font-weight: 700; } - .terminal-1316892474-matrix { + .terminal-300775983-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1316892474-title { + .terminal-300775983-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1316892474-r1 { fill: #c5c8c6 } - .terminal-1316892474-r2 { fill: #e3e3e3 } - .terminal-1316892474-r3 { fill: #e1e1e1 } - .terminal-1316892474-r4 { fill: #dde8f3;font-weight: bold } - .terminal-1316892474-r5 { fill: #ddedf9 } + .terminal-300775983-r1 { fill: #c5c8c6 } + .terminal-300775983-r2 { fill: #e3e3e3 } + .terminal-300775983-r3 { fill: #e1e1e1 } + .terminal-300775983-r4 { fill: #fea62b;font-weight: bold } + .terminal-300775983-r5 { fill: #a7a9ab } + .terminal-300775983-r6 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ModalApp + ModalApp - - - - ModalApp - B - - - - - - - - - - - - - - - - - - - - - -  A  Push screen A  + + + + ModalApp + B + + + + + + + + + + + + + + + + + + + + + +  a Push screen A  @@ -33614,142 +36657,142 @@ font-weight: 700; } - .terminal-2006637091-matrix { + .terminal-2408879539-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2006637091-title { + .terminal-2408879539-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2006637091-r1 { fill: #1e1e1e } - .terminal-2006637091-r2 { fill: #c5c8c6 } - .terminal-2006637091-r3 { fill: #434343 } - .terminal-2006637091-r4 { fill: #262626;font-weight: bold } - .terminal-2006637091-r5 { fill: #e2e2e2 } - .terminal-2006637091-r6 { fill: #e1e1e1 } - .terminal-2006637091-r7 { fill: #23568b } - .terminal-2006637091-r8 { fill: #14191f } - .terminal-2006637091-r9 { fill: #ddedf9 } + .terminal-2408879539-r1 { fill: #1e1e1e } + .terminal-2408879539-r2 { fill: #e1e1e1 } + .terminal-2408879539-r3 { fill: #c5c8c6 } + .terminal-2408879539-r4 { fill: #434343 } + .terminal-2408879539-r5 { fill: #262626;font-weight: bold } + .terminal-2408879539-r6 { fill: #e2e2e2 } + .terminal-2408879539-r7 { fill: #23568b } + .terminal-2408879539-r8 { fill: #14191f } + .terminal-2408879539-r9 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollOffByOne + ScrollOffByOne - - - - X 43 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 44 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 45 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 46 - ▁▁▁▁▁▁▁▁▃▃ - ▔▔▔▔▔▔▔▔ - X 47▂▂ - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 48 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 49 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - X 50 - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ + + + + ▔▔▔▔▔▔▔▔ + X 43 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 44 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 45 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 46▄▄ + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▃▃ + X 47 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 48 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 49 + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + X 50 + ▁▁▁▁▁▁▁▁ @@ -33780,144 +36823,300 @@ font-weight: 700; } - .terminal-1340160965-matrix { + .terminal-3177497111-matrix { + font-family: Fira Code, monospace; + font-size: 20px; + line-height: 24.4px; + font-variant-east-asian: full-width; + } + + .terminal-3177497111-title { + font-size: 18px; + font-weight: bold; + font-family: arial; + } + + .terminal-3177497111-r1 { fill: #e1e1e1 } + .terminal-3177497111-r2 { fill: #c5c8c6 } + .terminal-3177497111-r3 { fill: #004578 } + .terminal-3177497111-r4 { fill: #23568b } + .terminal-3177497111-r5 { fill: #fea62b } + .terminal-3177497111-r6 { fill: #f4005f } + .terminal-3177497111-r7 { fill: #14191f } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MyApp + + + + + + + + + + SPAM                                                                           + ╭────────────────────────────────────────────────────────────────────────────╮ + SPAM                                                                       + SPAM                                                                       + SPAM                                                                       + SPAM                                                                       + SPAM                                                                       + SPAM                                                                       + SPAM                                                                       + SPAM                                                                      ▁▁ + ╭────────────────────────────────────────────────────────────────────────╮ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@>>bullseye<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + + ▄▄ + ▄▄ + + + + + + + ╰────────────────────────────────────────────────────────────────────────────╯ + SPAM                                                                           + SPAM                                                                           + + + + + ''' +# --- +# name: test_scroll_visible + ''' + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - SPAM - SPAM - SPAM - ──────────────────────────────────────────────────────────────────────────── - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM▄▄ - SPAM - SPAM - ──────────────────────────────────────────────────────────────────────── - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@>>bullseye<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - ▇▇ - ▄▄ - - - - - - - - ──────────────────────────────────────────────────────────────────────────── + + + + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + |▆▆ + | + | + | + | + SHOULD BE VISIBLE ''' # --- -# name: test_scroll_visible +# name: test_scroll_visible_with_margin ''' @@ -33940,133 +37139,137 @@ font-weight: 700; } - .terminal-150767416-matrix { + .terminal-1023786546-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-150767416-title { + .terminal-1023786546-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-150767416-r1 { fill: #e1e1e1 } - .terminal-150767416-r2 { fill: #c5c8c6 } - .terminal-150767416-r3 { fill: #23568b } + .terminal-1023786546-r1 { fill: #ff0000 } + .terminal-1023786546-r2 { fill: #454a50 } + .terminal-1023786546-r3 { fill: #e1e1e1 } + .terminal-1023786546-r4 { fill: #c5c8c6 } + .terminal-1023786546-r5 { fill: #e2e3e3;font-weight: bold } + .terminal-1023786546-r6 { fill: #000000 } + .terminal-1023786546-r7 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + ScrollVisibleMargin - - - - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |▆▆ - | - | - | - | - SHOULD BE VISIBLE + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Hello, world! (19)  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Hello, world! (20)  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Hello, world! (21)  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▅▅ +  Hello, world! (22)  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Hello, world! (23)  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Hello, world! (24)  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Hello, world! (25)  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Hello, world! (26)  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -34096,134 +37299,134 @@ font-weight: 700; } - .terminal-4204360114-matrix { + .terminal-323125957-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4204360114-title { + .terminal-323125957-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4204360114-r1 { fill: #c5c8c6 } - .terminal-4204360114-r2 { fill: #e3e3e3 } - .terminal-4204360114-r3 { fill: #ff0000 } - .terminal-4204360114-r4 { fill: #dde2e8 } - .terminal-4204360114-r5 { fill: #ddedf9 } + .terminal-323125957-r1 { fill: #c5c8c6 } + .terminal-323125957-r2 { fill: #e3e3e3 } + .terminal-323125957-r3 { fill: #ff0000 } + .terminal-323125957-r4 { fill: #dde2e8 } + .terminal-323125957-r5 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollViewTester + ScrollViewTester - - - - ScrollViewTester -  1 ────────────────────────────────────────────────────────────────────────── - Welcome to line 980 - Welcome to line 981 - Welcome to line 982 - Welcome to line 983 - Welcome to line 984 - Welcome to line 985 - Welcome to line 986 - Welcome to line 987 - Welcome to line 988 - Welcome to line 989 - Welcome to line 990 - Welcome to line 991 - Welcome to line 992 - Welcome to line 993 - Welcome to line 994 - Welcome to line 995 - Welcome to line 996 - Welcome to line 997 - Welcome to line 998 - Welcome to line 999 - ────────────────────────────────────────────────────────────────────────────── + + + + ScrollViewTester + ╭─ 1 ──────────────────────────────────────────────────────────────────────────╮ + Welcome to line 980                                                          + Welcome to line 981                                                          + Welcome to line 982                                                          + Welcome to line 983                                                          + Welcome to line 984                                                          + Welcome to line 985                                                          + Welcome to line 986                                                          + Welcome to line 987                                                          + Welcome to line 988                                                          + Welcome to line 989                                                          + Welcome to line 990                                                          + Welcome to line 991                                                          + Welcome to line 992                                                          + Welcome to line 993                                                          + Welcome to line 994                                                          + Welcome to line 995                                                          + Welcome to line 996                                                          + Welcome to line 997                                                          + Welcome to line 998                                                          + Welcome to line 999                                                          + ╰──────────────────────────────────────────────────────────────────────────────╯ @@ -34254,136 +37457,136 @@ font-weight: 700; } - .terminal-1161182100-matrix { + .terminal-1692763632-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1161182100-title { + .terminal-1692763632-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1161182100-r1 { fill: #c5c8c6 } - .terminal-1161182100-r2 { fill: #e3e3e3 } - .terminal-1161182100-r3 { fill: #e1e1e1 } - .terminal-1161182100-r4 { fill: #1e1e1e } - .terminal-1161182100-r5 { fill: #0178d4 } - .terminal-1161182100-r6 { fill: #787878 } - .terminal-1161182100-r7 { fill: #a8a8a8 } + .terminal-1692763632-r1 { fill: #c5c8c6 } + .terminal-1692763632-r2 { fill: #e3e3e3 } + .terminal-1692763632-r3 { fill: #e1e1e1 } + .terminal-1692763632-r4 { fill: #1e1e1e } + .terminal-1692763632-r5 { fill: #0178d4 } + .terminal-1692763632-r6 { fill: #787878 } + .terminal-1692763632-r7 { fill: #a8a8a8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - SelectApp - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + SelectApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + @@ -34414,140 +37617,140 @@ font-weight: 700; } - .terminal-2035490498-matrix { + .terminal-1789191797-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2035490498-title { + .terminal-1789191797-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2035490498-r1 { fill: #c5c8c6 } - .terminal-2035490498-r2 { fill: #e3e3e3 } - .terminal-2035490498-r3 { fill: #e1e1e1 } - .terminal-2035490498-r4 { fill: #1e1e1e } - .terminal-2035490498-r5 { fill: #0178d4 } - .terminal-2035490498-r6 { fill: #787878 } - .terminal-2035490498-r7 { fill: #a8a8a8 } - .terminal-2035490498-r8 { fill: #121212 } - .terminal-2035490498-r9 { fill: #ddedf9;font-weight: bold } - .terminal-2035490498-r10 { fill: #85beea;font-weight: bold } - .terminal-2035490498-r11 { fill: #e2e3e3 } + .terminal-1789191797-r1 { fill: #c5c8c6 } + .terminal-1789191797-r2 { fill: #e3e3e3 } + .terminal-1789191797-r3 { fill: #e1e1e1 } + .terminal-1789191797-r4 { fill: #1e1e1e } + .terminal-1789191797-r5 { fill: #0178d4 } + .terminal-1789191797-r6 { fill: #787878 } + .terminal-1789191797-r7 { fill: #a8a8a8 } + .terminal-1789191797-r8 { fill: #121212 } + .terminal-1789191797-r9 { fill: #ddedf9;font-weight: bold } + .terminal-1789191797-r10 { fill: #85beea;font-weight: bold } + .terminal-1789191797-r11 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - SelectApp - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total  - obliteration. - I will face my fear. - I will permit it to pass over me and through me. - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - + + + + SelectApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select +  I must not fear.                                        +  Fear is the mind-killer.                                +  Fear is the little-death that brings total              +  obliteration.                                           +  I will face my fear.                                    +  I will permit it to pass over me and through me.        + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + @@ -34578,136 +37781,136 @@ font-weight: 700; } - .terminal-4010426174-matrix { + .terminal-1609533850-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4010426174-title { + .terminal-1609533850-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4010426174-r1 { fill: #c5c8c6 } - .terminal-4010426174-r2 { fill: #e3e3e3 } - .terminal-4010426174-r3 { fill: #e1e1e1 } - .terminal-4010426174-r4 { fill: #1e1e1e } - .terminal-4010426174-r5 { fill: #0178d4 } - .terminal-4010426174-r6 { fill: #e2e2e2 } - .terminal-4010426174-r7 { fill: #a8a8a8 } + .terminal-1609533850-r1 { fill: #c5c8c6 } + .terminal-1609533850-r2 { fill: #e3e3e3 } + .terminal-1609533850-r3 { fill: #e1e1e1 } + .terminal-1609533850-r4 { fill: #1e1e1e } + .terminal-1609533850-r5 { fill: #0178d4 } + .terminal-1609533850-r6 { fill: #e2e2e2 } + .terminal-1609533850-r7 { fill: #a8a8a8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - I must not fear. - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I must not fear. - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + I must not fear. + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I must not fear. + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + @@ -34738,140 +37941,140 @@ font-weight: 700; } - .terminal-2035490498-matrix { + .terminal-1789191797-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2035490498-title { + .terminal-1789191797-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2035490498-r1 { fill: #c5c8c6 } - .terminal-2035490498-r2 { fill: #e3e3e3 } - .terminal-2035490498-r3 { fill: #e1e1e1 } - .terminal-2035490498-r4 { fill: #1e1e1e } - .terminal-2035490498-r5 { fill: #0178d4 } - .terminal-2035490498-r6 { fill: #787878 } - .terminal-2035490498-r7 { fill: #a8a8a8 } - .terminal-2035490498-r8 { fill: #121212 } - .terminal-2035490498-r9 { fill: #ddedf9;font-weight: bold } - .terminal-2035490498-r10 { fill: #85beea;font-weight: bold } - .terminal-2035490498-r11 { fill: #e2e3e3 } + .terminal-1789191797-r1 { fill: #c5c8c6 } + .terminal-1789191797-r2 { fill: #e3e3e3 } + .terminal-1789191797-r3 { fill: #e1e1e1 } + .terminal-1789191797-r4 { fill: #1e1e1e } + .terminal-1789191797-r5 { fill: #0178d4 } + .terminal-1789191797-r6 { fill: #787878 } + .terminal-1789191797-r7 { fill: #a8a8a8 } + .terminal-1789191797-r8 { fill: #121212 } + .terminal-1789191797-r9 { fill: #ddedf9;font-weight: bold } + .terminal-1789191797-r10 { fill: #85beea;font-weight: bold } + .terminal-1789191797-r11 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - SelectApp - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total  - obliteration. - I will face my fear. - I will permit it to pass over me and through me. - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - + + + + SelectApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select +  I must not fear.                                        +  Fear is the mind-killer.                                +  Fear is the little-death that brings total              +  obliteration.                                           +  I will face my fear.                                    +  I will permit it to pass over me and through me.        + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + @@ -34902,136 +38105,136 @@ font-weight: 700; } - .terminal-4010426174-matrix { + .terminal-1609533850-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4010426174-title { + .terminal-1609533850-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4010426174-r1 { fill: #c5c8c6 } - .terminal-4010426174-r2 { fill: #e3e3e3 } - .terminal-4010426174-r3 { fill: #e1e1e1 } - .terminal-4010426174-r4 { fill: #1e1e1e } - .terminal-4010426174-r5 { fill: #0178d4 } - .terminal-4010426174-r6 { fill: #e2e2e2 } - .terminal-4010426174-r7 { fill: #a8a8a8 } + .terminal-1609533850-r1 { fill: #c5c8c6 } + .terminal-1609533850-r2 { fill: #e3e3e3 } + .terminal-1609533850-r3 { fill: #e1e1e1 } + .terminal-1609533850-r4 { fill: #1e1e1e } + .terminal-1609533850-r5 { fill: #0178d4 } + .terminal-1609533850-r6 { fill: #e2e2e2 } + .terminal-1609533850-r7 { fill: #a8a8a8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - I must not fear. - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I must not fear. - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + I must not fear. + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I must not fear. + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + @@ -35062,139 +38265,139 @@ font-weight: 700; } - .terminal-330554958-matrix { + .terminal-1173659784-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-330554958-title { + .terminal-1173659784-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-330554958-r1 { fill: #1e1e1e } - .terminal-330554958-r2 { fill: #0178d4 } - .terminal-330554958-r3 { fill: #c5c8c6 } - .terminal-330554958-r4 { fill: #787878 } - .terminal-330554958-r5 { fill: #a8a8a8 } - .terminal-330554958-r6 { fill: #121212 } - .terminal-330554958-r7 { fill: #ddedf9;font-weight: bold } - .terminal-330554958-r8 { fill: #85beea;font-weight: bold } - .terminal-330554958-r9 { fill: #e2e3e3 } - .terminal-330554958-r10 { fill: #e1e1e1 } + .terminal-1173659784-r1 { fill: #1e1e1e } + .terminal-1173659784-r2 { fill: #0178d4 } + .terminal-1173659784-r3 { fill: #c5c8c6 } + .terminal-1173659784-r4 { fill: #787878 } + .terminal-1173659784-r5 { fill: #a8a8a8 } + .terminal-1173659784-r6 { fill: #121212 } + .terminal-1173659784-r7 { fill: #ddedf9;font-weight: bold } + .terminal-1173659784-r8 { fill: #85beea;font-weight: bold } + .terminal-1173659784-r9 { fill: #e2e3e3 } + .terminal-1173659784-r10 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectRebuildApp + SelectRebuildApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - This - Should - Be - What - Goes - Into - The - Snapshit - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select +  This                                                                        +  Should                                                                      +  Be                                                                          +  What                                                                        +  Goes                                                                        +  Into                                                                        +  The                                                                         +  Snapshit                                                                    + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + @@ -35225,136 +38428,136 @@ font-weight: 700; } - .terminal-202000048-matrix { + .terminal-3673133324-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-202000048-title { + .terminal-3673133324-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-202000048-r1 { fill: #c5c8c6 } - .terminal-202000048-r2 { fill: #e3e3e3 } - .terminal-202000048-r3 { fill: #e1e1e1 } - .terminal-202000048-r4 { fill: #1e1e1e } - .terminal-202000048-r5 { fill: #0178d4 } - .terminal-202000048-r6 { fill: #e2e2e2 } - .terminal-202000048-r7 { fill: #a8a8a8 } + .terminal-3673133324-r1 { fill: #c5c8c6 } + .terminal-3673133324-r2 { fill: #e3e3e3 } + .terminal-3673133324-r3 { fill: #e1e1e1 } + .terminal-3673133324-r4 { fill: #1e1e1e } + .terminal-3673133324-r5 { fill: #0178d4 } + .terminal-3673133324-r6 { fill: #e2e2e2 } + .terminal-3673133324-r7 { fill: #a8a8a8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - Twinkle, twinkle, little star, - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Twinkle, twinkle, little star, - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + Twinkle, twinkle, little star, + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Twinkle, twinkle, little star, + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + @@ -35385,141 +38588,141 @@ font-weight: 700; } - .terminal-1590895671-matrix { + .terminal-4188177788-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1590895671-title { + .terminal-4188177788-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1590895671-r1 { fill: #c5c8c6 } - .terminal-1590895671-r2 { fill: #e3e3e3 } - .terminal-1590895671-r3 { fill: #e1e1e1 } - .terminal-1590895671-r4 { fill: #0178d4 } - .terminal-1590895671-r5 { fill: #e1e1e1;font-weight: bold } - .terminal-1590895671-r6 { fill: #575757 } - .terminal-1590895671-r7 { fill: #4ebf71;font-weight: bold } - .terminal-1590895671-r8 { fill: #ddedf9;font-weight: bold } - .terminal-1590895671-r9 { fill: #98e024 } - .terminal-1590895671-r10 { fill: #262626;font-weight: bold } - .terminal-1590895671-r11 { fill: #e2e2e2 } - .terminal-1590895671-r12 { fill: #ddedf9 } + .terminal-4188177788-r1 { fill: #c5c8c6 } + .terminal-4188177788-r2 { fill: #e3e3e3 } + .terminal-4188177788-r3 { fill: #e1e1e1 } + .terminal-4188177788-r4 { fill: #0178d4 } + .terminal-4188177788-r5 { fill: #e1e1e1;font-weight: bold } + .terminal-4188177788-r6 { fill: #575757 } + .terminal-4188177788-r7 { fill: #4ebf71;font-weight: bold } + .terminal-4188177788-r8 { fill: #ddedf9;font-weight: bold } + .terminal-4188177788-r9 { fill: #98e024 } + .terminal-4188177788-r10 { fill: #262626;font-weight: bold } + .terminal-4188177788-r11 { fill: #e2e2e2 } + .terminal-4188177788-r12 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectionListApp + SelectionListApp - - - - SelectionListApp - - -  Shall we play some games? ── Selected games ───────────── - [ - XFalken's Maze'secret_back_door', - XBlack Jack'a_nice_game_of_chess', - XGin Rummy'fighter_combat' - XHearts] - XBridge────────────────────────────── - XCheckers - XChess - XPoker - XFighter Combat - - ────────────────────────────── - - - - - - - + + + + SelectionListApp + + + ┌─ Shall we play some games? ──┐┌─ Selected games ─────────────┐ + [ + X Falken's Maze           'secret_back_door', + X Black Jack              'a_nice_game_of_chess', + X Gin Rummy               'fighter_combat' + X Hearts                  ] + X Bridge                  └──────────────────────────────┘ + X Checkers                 + X Chess                    + X Poker                    + X Fighter Combat           + + └──────────────────────────────┘ + + + + + + + @@ -35550,139 +38753,139 @@ font-weight: 700; } - .terminal-2928221602-matrix { + .terminal-2007146543-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2928221602-title { + .terminal-2007146543-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2928221602-r1 { fill: #c5c8c6 } - .terminal-2928221602-r2 { fill: #e3e3e3 } - .terminal-2928221602-r3 { fill: #e1e1e1 } - .terminal-2928221602-r4 { fill: #0178d4 } - .terminal-2928221602-r5 { fill: #575757 } - .terminal-2928221602-r6 { fill: #4ebf71;font-weight: bold } - .terminal-2928221602-r7 { fill: #ddedf9;font-weight: bold } - .terminal-2928221602-r8 { fill: #262626;font-weight: bold } - .terminal-2928221602-r9 { fill: #e2e2e2 } - .terminal-2928221602-r10 { fill: #ddedf9 } + .terminal-2007146543-r1 { fill: #c5c8c6 } + .terminal-2007146543-r2 { fill: #e3e3e3 } + .terminal-2007146543-r3 { fill: #e1e1e1 } + .terminal-2007146543-r4 { fill: #0178d4 } + .terminal-2007146543-r5 { fill: #575757 } + .terminal-2007146543-r6 { fill: #4ebf71;font-weight: bold } + .terminal-2007146543-r7 { fill: #ddedf9;font-weight: bold } + .terminal-2007146543-r8 { fill: #262626;font-weight: bold } + .terminal-2007146543-r9 { fill: #e2e2e2 } + .terminal-2007146543-r10 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectionListApp + SelectionListApp - - - - SelectionListApp - - -  Shall we play some games? ────────────────────────────────── - - XFalken's Maze - XBlack Jack - XGin Rummy - XHearts - XBridge - XCheckers - XChess - XPoker - XFighter Combat - - - - - - ────────────────────────────────────────────────────────────── - - - + + + + SelectionListApp + + + ┌─ Shall we play some games? ──────────────────────────────────┐ + + X Falken's Maze                                            + X Black Jack                                               + X Gin Rummy                                                + X Hearts                                                   + X Bridge                                                   + X Checkers                                                 + X Chess                                                    + X Poker                                                    + X Fighter Combat                                           + + + + + + └──────────────────────────────────────────────────────────────┘ + + + @@ -35713,139 +38916,139 @@ font-weight: 700; } - .terminal-2928221602-matrix { + .terminal-2007146543-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2928221602-title { + .terminal-2007146543-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2928221602-r1 { fill: #c5c8c6 } - .terminal-2928221602-r2 { fill: #e3e3e3 } - .terminal-2928221602-r3 { fill: #e1e1e1 } - .terminal-2928221602-r4 { fill: #0178d4 } - .terminal-2928221602-r5 { fill: #575757 } - .terminal-2928221602-r6 { fill: #4ebf71;font-weight: bold } - .terminal-2928221602-r7 { fill: #ddedf9;font-weight: bold } - .terminal-2928221602-r8 { fill: #262626;font-weight: bold } - .terminal-2928221602-r9 { fill: #e2e2e2 } - .terminal-2928221602-r10 { fill: #ddedf9 } + .terminal-2007146543-r1 { fill: #c5c8c6 } + .terminal-2007146543-r2 { fill: #e3e3e3 } + .terminal-2007146543-r3 { fill: #e1e1e1 } + .terminal-2007146543-r4 { fill: #0178d4 } + .terminal-2007146543-r5 { fill: #575757 } + .terminal-2007146543-r6 { fill: #4ebf71;font-weight: bold } + .terminal-2007146543-r7 { fill: #ddedf9;font-weight: bold } + .terminal-2007146543-r8 { fill: #262626;font-weight: bold } + .terminal-2007146543-r9 { fill: #e2e2e2 } + .terminal-2007146543-r10 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectionListApp + SelectionListApp - - - - SelectionListApp - - -  Shall we play some games? ────────────────────────────────── - - XFalken's Maze - XBlack Jack - XGin Rummy - XHearts - XBridge - XCheckers - XChess - XPoker - XFighter Combat - - - - - - ────────────────────────────────────────────────────────────── - - - + + + + SelectionListApp + + + ┌─ Shall we play some games? ──────────────────────────────────┐ + + X Falken's Maze                                            + X Black Jack                                               + X Gin Rummy                                                + X Hearts                                                   + X Bridge                                                   + X Checkers                                                 + X Chess                                                    + X Poker                                                    + X Fighter Combat                                           + + + + + + └──────────────────────────────────────────────────────────────┘ + + + @@ -35876,137 +39079,137 @@ font-weight: 700; } - .terminal-4088188211-matrix { + .terminal-1104434961-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4088188211-title { + .terminal-1104434961-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4088188211-r1 { fill: #008000 } - .terminal-4088188211-r2 { fill: #c5c8c6 } - .terminal-4088188211-r3 { fill: #e1e1e1 } + .terminal-1104434961-r1 { fill: #008000 } + .terminal-1104434961-r2 { fill: #c5c8c6 } + .terminal-1104434961-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SortApp + SortApp - - - - ────────────────────────────────────────────────────────────────────────── - 515 - ───────────────────────── - ───────────────────────── - 2 - - ────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────── - 134 - ──────────────────────── - ──────────────────────── - 3───────────────────────── - ────────────────────────────────────────────────── - 4───────────────────────── - ────────────────────────3 - ──────────────────────── - 2 - ────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────── - ────────────────────────52 - 4 - ───────────────────────── - ───────────────────────── - 1 - ────────────────────────────────────────────────────────────────────────── + + + + ┌────────────────────────┐┌─────────────────────────┐┌─────────────────────────┐ + 5││1││5 + │└─────────────────────────┘│ + │┌─────────────────────────┐│ + ││2││ + ││││ + └────────────────────────┘└─────────────────────────┘└─────────────────────────┘ + ┌────────────────────────┐┌─────────────────────────┐┌─────────────────────────┐ + 1││3││4 + └────────────────────────┘│││ + ┌────────────────────────┐│││ + 3│└─────────────────────────┘│ + │┌─────────────────────────┐└─────────────────────────┘ + ││4│┌─────────────────────────┐ + └────────────────────────┘│││3 + ┌────────────────────────┐│││ + 2││││ + │└─────────────────────────┘└─────────────────────────┘ + └────────────────────────┘┌─────────────────────────┐┌─────────────────────────┐ + ┌────────────────────────┐│5││2 + 4││││ + │││└─────────────────────────┘ + │││┌─────────────────────────┐ + ││││1 + └────────────────────────┘└─────────────────────────┘└─────────────────────────┘ @@ -36036,682 +39239,682 @@ font-weight: 700; } - .terminal-2491064415-matrix { + .terminal-1281792983-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2491064415-title { - font-size: 18px; - font-weight: bold; - font-family: arial; - } - - .terminal-2491064415-r1 { fill: #e1e1e1 } - .terminal-2491064415-r2 { fill: #c5c8c6 } - .terminal-2491064415-r3 { fill: #fea62b } - .terminal-2491064415-r4 { fill: #eea831 } - .terminal-2491064415-r5 { fill: #d0ac3c } - .terminal-2491064415-r6 { fill: #c2ae42 } - .terminal-2491064415-r7 { fill: #b4b048 } - .terminal-2491064415-r8 { fill: #9ab452 } - .terminal-2491064415-r9 { fill: #8db557 } - .terminal-2491064415-r10 { fill: #78b860 } - .terminal-2491064415-r11 { fill: #6eba63 } - .terminal-2491064415-r12 { fill: #66bb67 } - .terminal-2491064415-r13 { fill: #59bd6c } - .terminal-2491064415-r14 { fill: #54be6e } - .terminal-2491064415-r15 { fill: #4ebe70 } - .terminal-2491064415-r16 { fill: #50be70 } - .terminal-2491064415-r17 { fill: #57bd6d } - .terminal-2491064415-r18 { fill: #5cbc6b } - .terminal-2491064415-r19 { fill: #63bb68 } - .terminal-2491064415-r20 { fill: #74b961 } - .terminal-2491064415-r21 { fill: #7eb85d } - .terminal-2491064415-r22 { fill: #94b454 } - .terminal-2491064415-r23 { fill: #a1b34f } - .terminal-2491064415-r24 { fill: #aeb14a } - .terminal-2491064415-r25 { fill: #caad3f } - .terminal-2491064415-r26 { fill: #d9ab39 } - .terminal-2491064415-r27 { fill: #f7a62d } - .terminal-2491064415-r28 { fill: #f5a72e } - .terminal-2491064415-r29 { fill: #d7ab3a } - .terminal-2491064415-r30 { fill: #c8ad40 } - .terminal-2491064415-r31 { fill: #baaf45 } - .terminal-2491064415-r32 { fill: #9fb350 } - .terminal-2491064415-r33 { fill: #93b555 } - .terminal-2491064415-r34 { fill: #7cb85e } - .terminal-2491064415-r35 { fill: #72b962 } - .terminal-2491064415-r36 { fill: #6abb65 } - .terminal-2491064415-r37 { fill: #5bbd6b } - .terminal-2491064415-r38 { fill: #56bd6d } - .terminal-2491064415-r39 { fill: #4fbe70 } - .terminal-2491064415-r40 { fill: #55bd6e } - .terminal-2491064415-r41 { fill: #5abd6c } - .terminal-2491064415-r42 { fill: #60bc69 } - .terminal-2491064415-r43 { fill: #70ba63 } - .terminal-2491064415-r44 { fill: #79b85f } - .terminal-2491064415-r45 { fill: #8fb556 } - .terminal-2491064415-r46 { fill: #9bb352 } - .terminal-2491064415-r47 { fill: #a8b24c } - .terminal-2491064415-r48 { fill: #c4ae41 } - .terminal-2491064415-r49 { fill: #d3ac3c } - .terminal-2491064415-r50 { fill: #f1a730 } - .terminal-2491064415-r51 { fill: #fba62b } - .terminal-2491064415-r52 { fill: #ddaa37 } - .terminal-2491064415-r53 { fill: #ceac3d } - .terminal-2491064415-r54 { fill: #c0ae43 } - .terminal-2491064415-r55 { fill: #a5b24e } - .terminal-2491064415-r56 { fill: #98b453 } - .terminal-2491064415-r57 { fill: #81b75c } - .terminal-2491064415-r58 { fill: #76b960 } - .terminal-2491064415-r59 { fill: #6dba64 } - .terminal-2491064415-r60 { fill: #5ebc6a } - .terminal-2491064415-r61 { fill: #58bd6c } - .terminal-2491064415-r62 { fill: #50be6f } - .terminal-2491064415-r63 { fill: #4ebf71 } - .terminal-2491064415-r64 { fill: #53be6e } - .terminal-2491064415-r65 { fill: #58bd6d } - .terminal-2491064415-r66 { fill: #5dbc6a } - .terminal-2491064415-r67 { fill: #6cba64 } - .terminal-2491064415-r68 { fill: #75b961 } - .terminal-2491064415-r69 { fill: #8ab658 } - .terminal-2491064415-r70 { fill: #96b454 } - .terminal-2491064415-r71 { fill: #a3b24f } - .terminal-2491064415-r72 { fill: #beaf44 } - .terminal-2491064415-r73 { fill: #ccac3e } - .terminal-2491064415-r74 { fill: #7bb85f } - .terminal-2491064415-r75 { fill: #89b659 } - .terminal-2491064415-r76 { fill: #97b453 } - .terminal-2491064415-r77 { fill: #b1b049 } - .terminal-2491064415-r78 { fill: #d3ac3b } - .terminal-2491064415-r79 { fill: #ddaa38 } - .terminal-2491064415-r80 { fill: #e5a934 } - .terminal-2491064415-r81 { fill: #f2a72f } - .terminal-2491064415-r82 { fill: #fda62b } - .terminal-2491064415-r83 { fill: #f4a72e } - .terminal-2491064415-r84 { fill: #efa830 } - .terminal-2491064415-r85 { fill: #e8a933 } - .terminal-2491064415-r86 { fill: #cdac3e } - .terminal-2491064415-r87 { fill: #b7b047 } - .terminal-2491064415-r88 { fill: #aab14c } - .terminal-2491064415-r89 { fill: #9db351 } - .terminal-2491064415-r90 { fill: #83b75b } - .terminal-2491064415-r91 { fill: #91b556 } - .terminal-2491064415-r92 { fill: #acb14b } - .terminal-2491064415-r93 { fill: #b8af46 } - .terminal-2491064415-r94 { fill: #cfac3d } - .terminal-2491064415-r95 { fill: #e1a936 } - .terminal-2491064415-r96 { fill: #f0a730 } - .terminal-2491064415-r97 { fill: #fca62b } - .terminal-2491064415-r98 { fill: #f6a72d } - .terminal-2491064415-r99 { fill: #f1a72f } - .terminal-2491064415-r100 { fill: #eba832 } - .terminal-2491064415-r101 { fill: #dbaa38 } - .terminal-2491064415-r102 { fill: #d2ac3c } - .terminal-2491064415-r103 { fill: #bcaf45 } - .terminal-2491064415-r104 { fill: #b0b149 } - .terminal-2491064415-r105 { fill: #87b65a } - .terminal-2491064415-r106 { fill: #78b85f } - .terminal-2491064415-r107 { fill: #5abd6b } - .terminal-2491064415-r108 { fill: #6eba64 } - .terminal-2491064415-r109 { fill: #7db85e } - .terminal-2491064415-r110 { fill: #8bb658 } - .terminal-2491064415-r111 { fill: #a6b24d } - .terminal-2491064415-r112 { fill: #b3b048 } - .terminal-2491064415-r113 { fill: #d5ab3b } - .terminal-2491064415-r114 { fill: #deaa37 } - .terminal-2491064415-r115 { fill: #eda831 } - .terminal-2491064415-r116 { fill: #f3a72f } - .terminal-2491064415-r117 { fill: #fba62c } - .terminal-2491064415-r118 { fill: #f8a62d } - .terminal-2491064415-r119 { fill: #f3a72e } - .terminal-2491064415-r120 { fill: #dfaa37 } - .terminal-2491064415-r121 { fill: #d6ab3a } - .terminal-2491064415-r122 { fill: #c1ae43 } - .terminal-2491064415-r123 { fill: #b5b047 } - .terminal-2491064415-r124 { fill: #7fb85d } - .terminal-2491064415-r125 { fill: #f89c2f } - .terminal-2491064415-r126 { fill: #ec8a37 } - .terminal-2491064415-r127 { fill: #e6823b } - .terminal-2491064415-r128 { fill: #e1793f } - .terminal-2491064415-r129 { fill: #d66946 } - .terminal-2491064415-r130 { fill: #d26249 } - .terminal-2491064415-r131 { fill: #c9554f } - .terminal-2491064415-r132 { fill: #c54f52 } - .terminal-2491064415-r133 { fill: #c24a54 } - .terminal-2491064415-r134 { fill: #bd4257 } - .terminal-2491064415-r135 { fill: #bb4059 } - .terminal-2491064415-r136 { fill: #b93c5a } - .terminal-2491064415-r137 { fill: #b93d5a } - .terminal-2491064415-r138 { fill: #bc4158 } - .terminal-2491064415-r139 { fill: #be4456 } - .terminal-2491064415-r140 { fill: #c14855 } - .terminal-2491064415-r141 { fill: #c75350 } - .terminal-2491064415-r142 { fill: #cb584d } - .terminal-2491064415-r143 { fill: #d46647 } - .terminal-2491064415-r144 { fill: #d96e44 } - .terminal-2491064415-r145 { fill: #de7640 } - .terminal-2491064415-r146 { fill: #e98738 } - .terminal-2491064415-r147 { fill: #ef8f34 } - .terminal-2491064415-r148 { fill: #fba22c } - .terminal-2491064415-r149 { fill: #faa02d } - .terminal-2491064415-r150 { fill: #ee8e35 } - .terminal-2491064415-r151 { fill: #e98539 } - .terminal-2491064415-r152 { fill: #e37d3d } - .terminal-2491064415-r153 { fill: #d86d44 } - .terminal-2491064415-r154 { fill: #d46548 } - .terminal-2491064415-r155 { fill: #cb584e } - .terminal-2491064415-r156 { fill: #c75250 } - .terminal-2491064415-r157 { fill: #c44c53 } - .terminal-2491064415-r158 { fill: #be4457 } - .terminal-2491064415-r159 { fill: #bd4357 } - .terminal-2491064415-r160 { fill: #c04755 } - .terminal-2491064415-r161 { fill: #c65051 } - .terminal-2491064415-r162 { fill: #ca564f } - .terminal-2491064415-r163 { fill: #d26349 } - .terminal-2491064415-r164 { fill: #d76a45 } - .terminal-2491064415-r165 { fill: #dc7242 } - .terminal-2491064415-r166 { fill: #e7833a } - .terminal-2491064415-r167 { fill: #ed8c36 } - .terminal-2491064415-r168 { fill: #f89e2e } - .terminal-2491064415-r169 { fill: #fda42b } - .terminal-2491064415-r170 { fill: #f19233 } - .terminal-2491064415-r171 { fill: #eb8937 } - .terminal-2491064415-r172 { fill: #e5803b } - .terminal-2491064415-r173 { fill: #db7043 } - .terminal-2491064415-r174 { fill: #d66846 } - .terminal-2491064415-r175 { fill: #cd5a4d } - .terminal-2491064415-r176 { fill: #c9544f } - .terminal-2491064415-r177 { fill: #bf4556 } - .terminal-2491064415-r178 { fill: #bd4258 } - .terminal-2491064415-r179 { fill: #ba3d5a } - .terminal-2491064415-r180 { fill: #b93c5b } - .terminal-2491064415-r181 { fill: #bb3f59 } - .terminal-2491064415-r182 { fill: #bc4258 } - .terminal-2491064415-r183 { fill: #c44e52 } - .terminal-2491064415-r184 { fill: #c85350 } - .terminal-2491064415-r185 { fill: #d0604a } - .terminal-2491064415-r186 { fill: #d56747 } - .terminal-2491064415-r187 { fill: #da6f43 } - .terminal-2491064415-r188 { fill: #e57f3c } - .terminal-2491064415-r189 { fill: #ea8838 } - .terminal-2491064415-r190 { fill: #be4556 } - .terminal-2491064415-r191 { fill: #ca574e } - .terminal-2491064415-r192 { fill: #d05f4a } - .terminal-2491064415-r193 { fill: #d56846 } - .terminal-2491064415-r194 { fill: #e0783f } - .terminal-2491064415-r195 { fill: #e47f3c } - .terminal-2491064415-r196 { fill: #f49731 } - .terminal-2491064415-r197 { fill: #f99f2e } - .terminal-2491064415-r198 { fill: #fba12c } - .terminal-2491064415-r199 { fill: #fda52b } - .terminal-2491064415-r200 { fill: #f89d2f } - .terminal-2491064415-r201 { fill: #f59930 } - .terminal-2491064415-r202 { fill: #ef8e35 } - .terminal-2491064415-r203 { fill: #eb8938 } - .terminal-2491064415-r204 { fill: #e27b3e } - .terminal-2491064415-r205 { fill: #dd7341 } - .terminal-2491064415-r206 { fill: #d86b45 } - .terminal-2491064415-r207 { fill: #c75251 } - .terminal-2491064415-r208 { fill: #cd5c4c } - .terminal-2491064415-r209 { fill: #d36448 } - .terminal-2491064415-r210 { fill: #de7441 } - .terminal-2491064415-r211 { fill: #e27c3d } - .terminal-2491064415-r212 { fill: #ef8f35 } - .terminal-2491064415-r213 { fill: #f29532 } - .terminal-2491064415-r214 { fill: #f89d2e } - .terminal-2491064415-r215 { fill: #f99e2e } - .terminal-2491064415-r216 { fill: #f69a30 } - .terminal-2491064415-r217 { fill: #f09134 } - .terminal-2491064415-r218 { fill: #ec8b36 } - .terminal-2491064415-r219 { fill: #e47e3c } - .terminal-2491064415-r220 { fill: #df7740 } - .terminal-2491064415-r221 { fill: #cf5e4b } - .terminal-2491064415-r222 { fill: #be4357 } - .terminal-2491064415-r223 { fill: #d1614a } - .terminal-2491064415-r224 { fill: #db7142 } - .terminal-2491064415-r225 { fill: #e0793f } - .terminal-2491064415-r226 { fill: #ed8d36 } - .terminal-2491064415-r227 { fill: #f79c2f } - .terminal-2491064415-r228 { fill: #f99f2d } - .terminal-2491064415-r229 { fill: #fca42b } - .terminal-2491064415-r230 { fill: #fa9f2d } - .terminal-2491064415-r231 { fill: #f29333 } - .terminal-2491064415-r232 { fill: #e6813b } - .terminal-2491064415-r233 { fill: #e17a3e } - .terminal-2491064415-r234 { fill: #d16249 } - .terminal-2491064415-r235 { fill: #cc594d } - .terminal-2491064415-r236 { fill: #153954 } - .terminal-2491064415-r237 { fill: #133e5f } - .terminal-2491064415-r238 { fill: #0f4974 } - .terminal-2491064415-r239 { fill: #0e4e7f } - .terminal-2491064415-r240 { fill: #0c5389 } - .terminal-2491064415-r241 { fill: #095c9c } - .terminal-2491064415-r242 { fill: #0861a5 } - .terminal-2491064415-r243 { fill: #0568b5 } - .terminal-2491064415-r244 { fill: #046cbc } - .terminal-2491064415-r245 { fill: #036fc2 } - .terminal-2491064415-r246 { fill: #0273cb } - .terminal-2491064415-r247 { fill: #0175cf } - .terminal-2491064415-r248 { fill: #0177d3 } - .terminal-2491064415-r249 { fill: #0177d2 } - .terminal-2491064415-r250 { fill: #0274cd } - .terminal-2491064415-r251 { fill: #0272c9 } - .terminal-2491064415-r252 { fill: #0370c4 } - .terminal-2491064415-r253 { fill: #056ab8 } - .terminal-2491064415-r254 { fill: #0666b0 } - .terminal-2491064415-r255 { fill: #095ea0 } - .terminal-2491064415-r256 { fill: #0a5a97 } - .terminal-2491064415-r257 { fill: #0b558d } - .terminal-2491064415-r258 { fill: #0f4b79 } - .terminal-2491064415-r259 { fill: #10466e } - .terminal-2491064415-r260 { fill: #143b58 } - .terminal-2491064415-r261 { fill: #143c5a } - .terminal-2491064415-r262 { fill: #104670 } - .terminal-2491064415-r263 { fill: #0e4c7a } - .terminal-2491064415-r264 { fill: #0d5185 } - .terminal-2491064415-r265 { fill: #0a5a98 } - .terminal-2491064415-r266 { fill: #085fa1 } - .terminal-2491064415-r267 { fill: #0667b2 } - .terminal-2491064415-r268 { fill: #056ab9 } - .terminal-2491064415-r269 { fill: #046dbf } - .terminal-2491064415-r270 { fill: #0273c9 } - .terminal-2491064415-r271 { fill: #0174cd } - .terminal-2491064415-r272 { fill: #0175ce } - .terminal-2491064415-r273 { fill: #0371c6 } - .terminal-2491064415-r274 { fill: #046bbb } - .terminal-2491064415-r275 { fill: #0568b4 } - .terminal-2491064415-r276 { fill: #0860a4 } - .terminal-2491064415-r277 { fill: #095c9b } - .terminal-2491064415-r278 { fill: #0b5791 } - .terminal-2491064415-r279 { fill: #0e4d7d } - .terminal-2491064415-r280 { fill: #104873 } - .terminal-2491064415-r281 { fill: #133d5d } - .terminal-2491064415-r282 { fill: #143955 } - .terminal-2491064415-r283 { fill: #11446b } - .terminal-2491064415-r284 { fill: #0f4976 } - .terminal-2491064415-r285 { fill: #0e4f80 } - .terminal-2491064415-r286 { fill: #0a5894 } - .terminal-2491064415-r287 { fill: #095d9d } - .terminal-2491064415-r288 { fill: #0665ae } - .terminal-2491064415-r289 { fill: #0569b6 } - .terminal-2491064415-r290 { fill: #0272c7 } - .terminal-2491064415-r291 { fill: #0274cc } - .terminal-2491064415-r292 { fill: #0177d1 } - .terminal-2491064415-r293 { fill: #0178d4 } - .terminal-2491064415-r294 { fill: #0176cf } - .terminal-2491064415-r295 { fill: #0272c8 } - .terminal-2491064415-r296 { fill: #046dbd } - .terminal-2491064415-r297 { fill: #0569b7 } - .terminal-2491064415-r298 { fill: #0762a7 } - .terminal-2491064415-r299 { fill: #095e9f } - .terminal-2491064415-r300 { fill: #0a5996 } - .terminal-2491064415-r301 { fill: #0d4f82 } - .terminal-2491064415-r302 { fill: #0f4a77 } - .terminal-2491064415-r303 { fill: #0667b3 } - .terminal-2491064415-r304 { fill: #0762a8 } - .terminal-2491064415-r305 { fill: #095d9e } - .terminal-2491064415-r306 { fill: #0c548b } - .terminal-2491064415-r307 { fill: #104872 } - .terminal-2491064415-r308 { fill: #124165 } - .terminal-2491064415-r309 { fill: #133d5c } - .terminal-2491064415-r310 { fill: #143954 } - .terminal-2491064415-r311 { fill: #133c5a } - .terminal-2491064415-r312 { fill: #133e5e } - .terminal-2491064415-r313 { fill: #124063 } - .terminal-2491064415-r314 { fill: #10466f } - .terminal-2491064415-r315 { fill: #0c5287 } - .terminal-2491064415-r316 { fill: #0b5690 } - .terminal-2491064415-r317 { fill: #0a5b9a } - .terminal-2491064415-r318 { fill: #056ab7 } - .terminal-2491064415-r319 { fill: #0764ad } - .terminal-2491064415-r320 { fill: #085fa2 } - .terminal-2491064415-r321 { fill: #0b568f } - .terminal-2491064415-r322 { fill: #0d5186 } - .terminal-2491064415-r323 { fill: #0f4975 } - .terminal-2491064415-r324 { fill: #114368 } - .terminal-2491064415-r325 { fill: #133d5e } - .terminal-2491064415-r326 { fill: #143b59 } - .terminal-2491064415-r327 { fill: #123f61 } - .terminal-2491064415-r328 { fill: #11456c } - .terminal-2491064415-r329 { fill: #0d5083 } - .terminal-2491064415-r330 { fill: #0c548c } - .terminal-2491064415-r331 { fill: #0763aa } - .terminal-2491064415-r332 { fill: #0273ca } - .terminal-2491064415-r333 { fill: #0667b1 } - .terminal-2491064415-r334 { fill: #0761a7 } - .terminal-2491064415-r335 { fill: #0b5893 } - .terminal-2491064415-r336 { fill: #0c538a } - .terminal-2491064415-r337 { fill: #104771 } - .terminal-2491064415-r338 { fill: #133e60 } - .terminal-2491064415-r339 { fill: #133c5b } - .terminal-2491064415-r340 { fill: #143956 } - .terminal-2491064415-r341 { fill: #143a58 } - .terminal-2491064415-r342 { fill: #11436a } - .terminal-2491064415-r343 { fill: #104770 } - .terminal-2491064415-r344 { fill: #0e4e80 } - .terminal-2491064415-r345 { fill: #0c5288 } - .terminal-2491064415-r346 { fill: #4c2730 } - .terminal-2491064415-r347 { fill: #552833 } - .terminal-2491064415-r348 { fill: #672c3b } - .terminal-2491064415-r349 { fill: #702e3e } - .terminal-2491064415-r350 { fill: #792f41 } - .terminal-2491064415-r351 { fill: #893248 } - .terminal-2491064415-r352 { fill: #91344b } - .terminal-2491064415-r353 { fill: #9e3650 } - .terminal-2491064415-r354 { fill: #a43852 } - .terminal-2491064415-r355 { fill: #a93954 } - .terminal-2491064415-r356 { fill: #b13a58 } - .terminal-2491064415-r357 { fill: #b43b59 } - .terminal-2491064415-r358 { fill: #b83b5a } - .terminal-2491064415-r359 { fill: #b73b5a } - .terminal-2491064415-r360 { fill: #b33a58 } - .terminal-2491064415-r361 { fill: #af3a57 } - .terminal-2491064415-r362 { fill: #ab3955 } - .terminal-2491064415-r363 { fill: #a13751 } - .terminal-2491064415-r364 { fill: #9b364f } - .terminal-2491064415-r365 { fill: #8d3349 } - .terminal-2491064415-r366 { fill: #853246 } - .terminal-2491064415-r367 { fill: #7d3043 } - .terminal-2491064415-r368 { fill: #6b2d3c } - .terminal-2491064415-r369 { fill: #622b38 } - .terminal-2491064415-r370 { fill: #502731 } - .terminal-2491064415-r371 { fill: #512832 } - .terminal-2491064415-r372 { fill: #632b39 } - .terminal-2491064415-r373 { fill: #6d2d3d } - .terminal-2491064415-r374 { fill: #752f40 } - .terminal-2491064415-r375 { fill: #863247 } - .terminal-2491064415-r376 { fill: #8e334a } - .terminal-2491064415-r377 { fill: #9c364f } - .terminal-2491064415-r378 { fill: #a23751 } - .terminal-2491064415-r379 { fill: #a73854 } - .terminal-2491064415-r380 { fill: #b03a57 } - .terminal-2491064415-r381 { fill: #b13a57 } - .terminal-2491064415-r382 { fill: #ad3956 } - .terminal-2491064415-r383 { fill: #a33752 } - .terminal-2491064415-r384 { fill: #9d3650 } - .terminal-2491064415-r385 { fill: #90344a } - .terminal-2491064415-r386 { fill: #883247 } - .terminal-2491064415-r387 { fill: #803144 } - .terminal-2491064415-r388 { fill: #6f2d3e } - .terminal-2491064415-r389 { fill: #662c3a } - .terminal-2491064415-r390 { fill: #542833 } - .terminal-2491064415-r391 { fill: #4d2730 } - .terminal-2491064415-r392 { fill: #602a37 } - .terminal-2491064415-r393 { fill: #692c3b } - .terminal-2491064415-r394 { fill: #722e3f } - .terminal-2491064415-r395 { fill: #833145 } - .terminal-2491064415-r396 { fill: #8a3348 } - .terminal-2491064415-r397 { fill: #99354e } - .terminal-2491064415-r398 { fill: #9f3751 } - .terminal-2491064415-r399 { fill: #a53853 } - .terminal-2491064415-r400 { fill: #ae3a56 } - .terminal-2491064415-r401 { fill: #b23a58 } - .terminal-2491064415-r402 { fill: #b53b59 } - .terminal-2491064415-r403 { fill: #a63853 } - .terminal-2491064415-r404 { fill: #a03751 } - .terminal-2491064415-r405 { fill: #93344c } - .terminal-2491064415-r406 { fill: #8c3349 } - .terminal-2491064415-r407 { fill: #843146 } - .terminal-2491064415-r408 { fill: #732e3f } - .terminal-2491064415-r409 { fill: #6a2c3c } - .terminal-2491064415-r410 { fill: #9d364f } - .terminal-2491064415-r411 { fill: #94344c } - .terminal-2491064415-r412 { fill: #8b3349 } - .terminal-2491064415-r413 { fill: #7b3042 } - .terminal-2491064415-r414 { fill: #602a38 } - .terminal-2491064415-r415 { fill: #5b2936 } - .terminal-2491064415-r416 { fill: #532832 } - .terminal-2491064415-r417 { fill: #592935 } - .terminal-2491064415-r418 { fill: #772f41 } - .terminal-2491064415-r419 { fill: #7f3044 } - .terminal-2491064415-r420 { fill: #873247 } - .terminal-2491064415-r421 { fill: #a23752 } - .terminal-2491064415-r422 { fill: #97354d } - .terminal-2491064415-r423 { fill: #8f334a } - .terminal-2491064415-r424 { fill: #7e3043 } - .terminal-2491064415-r425 { fill: #762f40 } - .terminal-2491064415-r426 { fill: #682c3b } - .terminal-2491064415-r427 { fill: #622b39 } - .terminal-2491064415-r428 { fill: #5d2a36 } - .terminal-2491064415-r429 { fill: #532833 } - .terminal-2491064415-r430 { fill: #572934 } - .terminal-2491064415-r431 { fill: #612b38 } - .terminal-2491064415-r432 { fill: #672c3a } - .terminal-2491064415-r433 { fill: #742e40 } - .terminal-2491064415-r434 { fill: #7c3043 } - .terminal-2491064415-r435 { fill: #95354c } - .terminal-2491064415-r436 { fill: #a43853 } - .terminal-2491064415-r437 { fill: #92344b } - .terminal-2491064415-r438 { fill: #813145 } - .terminal-2491064415-r439 { fill: #7a2f42 } - .terminal-2491064415-r440 { fill: #652b39 } - .terminal-2491064415-r441 { fill: #5f2a37 } - .terminal-2491064415-r442 { fill: #562834 } - .terminal-2491064415-r443 { fill: #522832 } - .terminal-2491064415-r444 { fill: #4f2731 } - .terminal-2491064415-r445 { fill: #5e2a37 } - .terminal-2491064415-r446 { fill: #642b39 } - .terminal-2491064415-r447 { fill: #712e3e } - .terminal-2491064415-r448 { fill: #782f41 } - .terminal-2491064415-r449 { fill: #9a364e } - .terminal-2491064415-r450 { fill: #2c4e36 } - .terminal-2491064415-r451 { fill: #2e573b } - .terminal-2491064415-r452 { fill: #346a45 } - .terminal-2491064415-r453 { fill: #377449 } - .terminal-2491064415-r454 { fill: #3a7d4e } - .terminal-2491064415-r455 { fill: #3f8e57 } - .terminal-2491064415-r456 { fill: #41955b } - .terminal-2491064415-r457 { fill: #45a362 } - .terminal-2491064415-r458 { fill: #47a965 } - .terminal-2491064415-r459 { fill: #49af68 } - .terminal-2491064415-r460 { fill: #4bb76d } - .terminal-2491064415-r461 { fill: #4cba6e } - .terminal-2491064415-r462 { fill: #4dbe70 } - .terminal-2491064415-r463 { fill: #4dbd70 } - .terminal-2491064415-r464 { fill: #4cb96d } - .terminal-2491064415-r465 { fill: #4bb56c } - .terminal-2491064415-r466 { fill: #49b169 } - .terminal-2491064415-r467 { fill: #46a664 } - .terminal-2491064415-r468 { fill: #44a060 } - .terminal-2491064415-r469 { fill: #409159 } - .terminal-2491064415-r470 { fill: #3d8955 } - .terminal-2491064415-r471 { fill: #3b8050 } - .terminal-2491064415-r472 { fill: #356e47 } - .terminal-2491064415-r473 { fill: #336542 } - .terminal-2491064415-r474 { fill: #2d5238 } - .terminal-2491064415-r475 { fill: #2d5338 } - .terminal-2491064415-r476 { fill: #336642 } - .terminal-2491064415-r477 { fill: #367047 } - .terminal-2491064415-r478 { fill: #39794c } - .terminal-2491064415-r479 { fill: #3e8a55 } - .terminal-2491064415-r480 { fill: #409259 } - .terminal-2491064415-r481 { fill: #44a161 } - .terminal-2491064415-r482 { fill: #46a764 } - .terminal-2491064415-r483 { fill: #48ac67 } - .terminal-2491064415-r484 { fill: #4bb66c } - .terminal-2491064415-r485 { fill: #4cb96e } - .terminal-2491064415-r486 { fill: #4bb76c } - .terminal-2491064415-r487 { fill: #4ab36a } - .terminal-2491064415-r488 { fill: #45a262 } - .terminal-2491064415-r489 { fill: #41945a } - .terminal-2491064415-r490 { fill: #3e8c56 } - .terminal-2491064415-r491 { fill: #3c8452 } - .terminal-2491064415-r492 { fill: #377249 } - .terminal-2491064415-r493 { fill: #346944 } - .terminal-2491064415-r494 { fill: #2e563a } - .terminal-2491064415-r495 { fill: #2c4f36 } - .terminal-2491064415-r496 { fill: #326240 } - .terminal-2491064415-r497 { fill: #356c45 } - .terminal-2491064415-r498 { fill: #37754a } - .terminal-2491064415-r499 { fill: #3d8753 } - .terminal-2491064415-r500 { fill: #3f8f58 } - .terminal-2491064415-r501 { fill: #449e5f } - .terminal-2491064415-r502 { fill: #46a463 } - .terminal-2491064415-r503 { fill: #47aa66 } - .terminal-2491064415-r504 { fill: #4ab46b } - .terminal-2491064415-r505 { fill: #4bb86d } - .terminal-2491064415-r506 { fill: #4cbb6f } - .terminal-2491064415-r507 { fill: #4cb86d } - .terminal-2491064415-r508 { fill: #48ab66 } - .terminal-2491064415-r509 { fill: #46a563 } - .terminal-2491064415-r510 { fill: #42985c } - .terminal-2491064415-r511 { fill: #3f9058 } - .terminal-2491064415-r512 { fill: #3d8854 } - .terminal-2491064415-r513 { fill: #38764b } - .terminal-2491064415-r514 { fill: #356d46 } - .terminal-2491064415-r515 { fill: #4bb56b } - .terminal-2491064415-r516 { fill: #45a261 } - .terminal-2491064415-r517 { fill: #42985d } - .terminal-2491064415-r518 { fill: #3a7e4f } - .terminal-2491064415-r519 { fill: #38774b } - .terminal-2491064415-r520 { fill: #326341 } - .terminal-2491064415-r521 { fill: #305d3e } - .terminal-2491064415-r522 { fill: #2e5539 } - .terminal-2491064415-r523 { fill: #2d5339 } - .terminal-2491064415-r524 { fill: #2e573a } - .terminal-2491064415-r525 { fill: #305b3d } - .terminal-2491064415-r526 { fill: #356c46 } - .terminal-2491064415-r527 { fill: #397b4d } - .terminal-2491064415-r528 { fill: #3c8351 } - .terminal-2491064415-r529 { fill: #439c5f } - .terminal-2491064415-r530 { fill: #40935a } - .terminal-2491064415-r531 { fill: #3b8251 } - .terminal-2491064415-r532 { fill: #397a4d } - .terminal-2491064415-r533 { fill: #356b45 } - .terminal-2491064415-r534 { fill: #31603f } - .terminal-2491064415-r535 { fill: #2e553a } - .terminal-2491064415-r536 { fill: #2f593c } - .terminal-2491064415-r537 { fill: #346a44 } - .terminal-2491064415-r538 { fill: #38784c } - .terminal-2491064415-r539 { fill: #429a5d } - .terminal-2491064415-r540 { fill: #44a061 } - .terminal-2491064415-r541 { fill: #42975c } - .terminal-2491064415-r542 { fill: #3c8553 } - .terminal-2491064415-r543 { fill: #336843 } - .terminal-2491064415-r544 { fill: #2f583b } - .terminal-2491064415-r545 { fill: #2e5439 } - .terminal-2491064415-r546 { fill: #2d5137 } - .terminal-2491064415-r547 { fill: #2d5439 } - .terminal-2491064415-r548 { fill: #316140 } - .terminal-2491064415-r549 { fill: #336743 } - .terminal-2491064415-r550 { fill: #37744a } - .terminal-2491064415-r551 { fill: #3a7c4e } - .terminal-2491064415-r552 { fill: #41965b } - .terminal-2491064415-r553 { fill: #449f60 } - - - - + .terminal-1281792983-title { + font-size: 18px; + font-weight: bold; + font-family: arial; + } + + .terminal-1281792983-r1 { fill: #e1e1e1 } + .terminal-1281792983-r2 { fill: #c5c8c6 } + .terminal-1281792983-r3 { fill: #fea62b } + .terminal-1281792983-r4 { fill: #eea831 } + .terminal-1281792983-r5 { fill: #d0ac3c } + .terminal-1281792983-r6 { fill: #c2ae42 } + .terminal-1281792983-r7 { fill: #b4b048 } + .terminal-1281792983-r8 { fill: #9ab452 } + .terminal-1281792983-r9 { fill: #8db557 } + .terminal-1281792983-r10 { fill: #78b860 } + .terminal-1281792983-r11 { fill: #6eba63 } + .terminal-1281792983-r12 { fill: #66bb67 } + .terminal-1281792983-r13 { fill: #59bd6c } + .terminal-1281792983-r14 { fill: #54be6e } + .terminal-1281792983-r15 { fill: #4ebe70 } + .terminal-1281792983-r16 { fill: #50be70 } + .terminal-1281792983-r17 { fill: #57bd6d } + .terminal-1281792983-r18 { fill: #5cbc6b } + .terminal-1281792983-r19 { fill: #63bb68 } + .terminal-1281792983-r20 { fill: #74b961 } + .terminal-1281792983-r21 { fill: #7eb85d } + .terminal-1281792983-r22 { fill: #94b454 } + .terminal-1281792983-r23 { fill: #a1b34f } + .terminal-1281792983-r24 { fill: #aeb14a } + .terminal-1281792983-r25 { fill: #caad3f } + .terminal-1281792983-r26 { fill: #d9ab39 } + .terminal-1281792983-r27 { fill: #f7a62d } + .terminal-1281792983-r28 { fill: #f5a72e } + .terminal-1281792983-r29 { fill: #d7ab3a } + .terminal-1281792983-r30 { fill: #c8ad40 } + .terminal-1281792983-r31 { fill: #baaf45 } + .terminal-1281792983-r32 { fill: #9fb350 } + .terminal-1281792983-r33 { fill: #93b555 } + .terminal-1281792983-r34 { fill: #7cb85e } + .terminal-1281792983-r35 { fill: #72b962 } + .terminal-1281792983-r36 { fill: #6abb65 } + .terminal-1281792983-r37 { fill: #5bbd6b } + .terminal-1281792983-r38 { fill: #56bd6d } + .terminal-1281792983-r39 { fill: #4fbe70 } + .terminal-1281792983-r40 { fill: #55bd6e } + .terminal-1281792983-r41 { fill: #5abd6c } + .terminal-1281792983-r42 { fill: #60bc69 } + .terminal-1281792983-r43 { fill: #70ba63 } + .terminal-1281792983-r44 { fill: #79b85f } + .terminal-1281792983-r45 { fill: #8fb556 } + .terminal-1281792983-r46 { fill: #9bb352 } + .terminal-1281792983-r47 { fill: #a8b24c } + .terminal-1281792983-r48 { fill: #c4ae41 } + .terminal-1281792983-r49 { fill: #d3ac3c } + .terminal-1281792983-r50 { fill: #f1a730 } + .terminal-1281792983-r51 { fill: #fba62b } + .terminal-1281792983-r52 { fill: #ddaa37 } + .terminal-1281792983-r53 { fill: #ceac3d } + .terminal-1281792983-r54 { fill: #c0ae43 } + .terminal-1281792983-r55 { fill: #a5b24e } + .terminal-1281792983-r56 { fill: #98b453 } + .terminal-1281792983-r57 { fill: #81b75c } + .terminal-1281792983-r58 { fill: #76b960 } + .terminal-1281792983-r59 { fill: #6dba64 } + .terminal-1281792983-r60 { fill: #5ebc6a } + .terminal-1281792983-r61 { fill: #58bd6c } + .terminal-1281792983-r62 { fill: #50be6f } + .terminal-1281792983-r63 { fill: #4ebf71 } + .terminal-1281792983-r64 { fill: #53be6e } + .terminal-1281792983-r65 { fill: #58bd6d } + .terminal-1281792983-r66 { fill: #5dbc6a } + .terminal-1281792983-r67 { fill: #6cba64 } + .terminal-1281792983-r68 { fill: #75b961 } + .terminal-1281792983-r69 { fill: #8ab658 } + .terminal-1281792983-r70 { fill: #96b454 } + .terminal-1281792983-r71 { fill: #a3b24f } + .terminal-1281792983-r72 { fill: #beaf44 } + .terminal-1281792983-r73 { fill: #ccac3e } + .terminal-1281792983-r74 { fill: #7bb85f } + .terminal-1281792983-r75 { fill: #89b659 } + .terminal-1281792983-r76 { fill: #97b453 } + .terminal-1281792983-r77 { fill: #b1b049 } + .terminal-1281792983-r78 { fill: #d3ac3b } + .terminal-1281792983-r79 { fill: #ddaa38 } + .terminal-1281792983-r80 { fill: #e5a934 } + .terminal-1281792983-r81 { fill: #f2a72f } + .terminal-1281792983-r82 { fill: #fda62b } + .terminal-1281792983-r83 { fill: #f4a72e } + .terminal-1281792983-r84 { fill: #efa830 } + .terminal-1281792983-r85 { fill: #e8a933 } + .terminal-1281792983-r86 { fill: #cdac3e } + .terminal-1281792983-r87 { fill: #b7b047 } + .terminal-1281792983-r88 { fill: #aab14c } + .terminal-1281792983-r89 { fill: #9db351 } + .terminal-1281792983-r90 { fill: #83b75b } + .terminal-1281792983-r91 { fill: #91b556 } + .terminal-1281792983-r92 { fill: #acb14b } + .terminal-1281792983-r93 { fill: #b8af46 } + .terminal-1281792983-r94 { fill: #cfac3d } + .terminal-1281792983-r95 { fill: #e1a936 } + .terminal-1281792983-r96 { fill: #f0a730 } + .terminal-1281792983-r97 { fill: #fca62b } + .terminal-1281792983-r98 { fill: #f6a72d } + .terminal-1281792983-r99 { fill: #f1a72f } + .terminal-1281792983-r100 { fill: #eba832 } + .terminal-1281792983-r101 { fill: #dbaa38 } + .terminal-1281792983-r102 { fill: #d2ac3c } + .terminal-1281792983-r103 { fill: #bcaf45 } + .terminal-1281792983-r104 { fill: #b0b149 } + .terminal-1281792983-r105 { fill: #87b65a } + .terminal-1281792983-r106 { fill: #78b85f } + .terminal-1281792983-r107 { fill: #5abd6b } + .terminal-1281792983-r108 { fill: #6eba64 } + .terminal-1281792983-r109 { fill: #7db85e } + .terminal-1281792983-r110 { fill: #8bb658 } + .terminal-1281792983-r111 { fill: #a6b24d } + .terminal-1281792983-r112 { fill: #b3b048 } + .terminal-1281792983-r113 { fill: #d5ab3b } + .terminal-1281792983-r114 { fill: #deaa37 } + .terminal-1281792983-r115 { fill: #eda831 } + .terminal-1281792983-r116 { fill: #f3a72f } + .terminal-1281792983-r117 { fill: #fba62c } + .terminal-1281792983-r118 { fill: #f8a62d } + .terminal-1281792983-r119 { fill: #f3a72e } + .terminal-1281792983-r120 { fill: #dfaa37 } + .terminal-1281792983-r121 { fill: #d6ab3a } + .terminal-1281792983-r122 { fill: #c1ae43 } + .terminal-1281792983-r123 { fill: #b5b047 } + .terminal-1281792983-r124 { fill: #7fb85d } + .terminal-1281792983-r125 { fill: #f89c2f } + .terminal-1281792983-r126 { fill: #ec8a37 } + .terminal-1281792983-r127 { fill: #e6823b } + .terminal-1281792983-r128 { fill: #e1793f } + .terminal-1281792983-r129 { fill: #d66946 } + .terminal-1281792983-r130 { fill: #d26249 } + .terminal-1281792983-r131 { fill: #c9554f } + .terminal-1281792983-r132 { fill: #c54f52 } + .terminal-1281792983-r133 { fill: #c24a54 } + .terminal-1281792983-r134 { fill: #bd4257 } + .terminal-1281792983-r135 { fill: #bb4059 } + .terminal-1281792983-r136 { fill: #b93c5a } + .terminal-1281792983-r137 { fill: #b93d5a } + .terminal-1281792983-r138 { fill: #bc4158 } + .terminal-1281792983-r139 { fill: #be4456 } + .terminal-1281792983-r140 { fill: #c14855 } + .terminal-1281792983-r141 { fill: #c75350 } + .terminal-1281792983-r142 { fill: #cb584d } + .terminal-1281792983-r143 { fill: #d46647 } + .terminal-1281792983-r144 { fill: #d96e44 } + .terminal-1281792983-r145 { fill: #de7640 } + .terminal-1281792983-r146 { fill: #e98738 } + .terminal-1281792983-r147 { fill: #ef8f34 } + .terminal-1281792983-r148 { fill: #fba22c } + .terminal-1281792983-r149 { fill: #faa02d } + .terminal-1281792983-r150 { fill: #ee8e35 } + .terminal-1281792983-r151 { fill: #e98539 } + .terminal-1281792983-r152 { fill: #e37d3d } + .terminal-1281792983-r153 { fill: #d86d44 } + .terminal-1281792983-r154 { fill: #d46548 } + .terminal-1281792983-r155 { fill: #cb584e } + .terminal-1281792983-r156 { fill: #c75250 } + .terminal-1281792983-r157 { fill: #c44c53 } + .terminal-1281792983-r158 { fill: #be4457 } + .terminal-1281792983-r159 { fill: #bd4357 } + .terminal-1281792983-r160 { fill: #c04755 } + .terminal-1281792983-r161 { fill: #c65051 } + .terminal-1281792983-r162 { fill: #ca564f } + .terminal-1281792983-r163 { fill: #d26349 } + .terminal-1281792983-r164 { fill: #d76a45 } + .terminal-1281792983-r165 { fill: #dc7242 } + .terminal-1281792983-r166 { fill: #e7833a } + .terminal-1281792983-r167 { fill: #ed8c36 } + .terminal-1281792983-r168 { fill: #f89e2e } + .terminal-1281792983-r169 { fill: #fda42b } + .terminal-1281792983-r170 { fill: #f19233 } + .terminal-1281792983-r171 { fill: #eb8937 } + .terminal-1281792983-r172 { fill: #e5803b } + .terminal-1281792983-r173 { fill: #db7043 } + .terminal-1281792983-r174 { fill: #d66846 } + .terminal-1281792983-r175 { fill: #cd5a4d } + .terminal-1281792983-r176 { fill: #c9544f } + .terminal-1281792983-r177 { fill: #bf4556 } + .terminal-1281792983-r178 { fill: #bd4258 } + .terminal-1281792983-r179 { fill: #ba3d5a } + .terminal-1281792983-r180 { fill: #b93c5b } + .terminal-1281792983-r181 { fill: #bb3f59 } + .terminal-1281792983-r182 { fill: #bc4258 } + .terminal-1281792983-r183 { fill: #c44e52 } + .terminal-1281792983-r184 { fill: #c85350 } + .terminal-1281792983-r185 { fill: #d0604a } + .terminal-1281792983-r186 { fill: #d56747 } + .terminal-1281792983-r187 { fill: #da6f43 } + .terminal-1281792983-r188 { fill: #e57f3c } + .terminal-1281792983-r189 { fill: #ea8838 } + .terminal-1281792983-r190 { fill: #be4556 } + .terminal-1281792983-r191 { fill: #ca574e } + .terminal-1281792983-r192 { fill: #d05f4a } + .terminal-1281792983-r193 { fill: #d56846 } + .terminal-1281792983-r194 { fill: #e0783f } + .terminal-1281792983-r195 { fill: #e47f3c } + .terminal-1281792983-r196 { fill: #f49731 } + .terminal-1281792983-r197 { fill: #f99f2e } + .terminal-1281792983-r198 { fill: #fba12c } + .terminal-1281792983-r199 { fill: #fda52b } + .terminal-1281792983-r200 { fill: #f89d2f } + .terminal-1281792983-r201 { fill: #f59930 } + .terminal-1281792983-r202 { fill: #ef8e35 } + .terminal-1281792983-r203 { fill: #eb8938 } + .terminal-1281792983-r204 { fill: #e27b3e } + .terminal-1281792983-r205 { fill: #dd7341 } + .terminal-1281792983-r206 { fill: #d86b45 } + .terminal-1281792983-r207 { fill: #c75251 } + .terminal-1281792983-r208 { fill: #cd5c4c } + .terminal-1281792983-r209 { fill: #d36448 } + .terminal-1281792983-r210 { fill: #de7441 } + .terminal-1281792983-r211 { fill: #e27c3d } + .terminal-1281792983-r212 { fill: #ef8f35 } + .terminal-1281792983-r213 { fill: #f29532 } + .terminal-1281792983-r214 { fill: #f89d2e } + .terminal-1281792983-r215 { fill: #f99e2e } + .terminal-1281792983-r216 { fill: #f69a30 } + .terminal-1281792983-r217 { fill: #f09134 } + .terminal-1281792983-r218 { fill: #ec8b36 } + .terminal-1281792983-r219 { fill: #e47e3c } + .terminal-1281792983-r220 { fill: #df7740 } + .terminal-1281792983-r221 { fill: #cf5e4b } + .terminal-1281792983-r222 { fill: #be4357 } + .terminal-1281792983-r223 { fill: #d1614a } + .terminal-1281792983-r224 { fill: #db7142 } + .terminal-1281792983-r225 { fill: #e0793f } + .terminal-1281792983-r226 { fill: #ed8d36 } + .terminal-1281792983-r227 { fill: #f79c2f } + .terminal-1281792983-r228 { fill: #f99f2d } + .terminal-1281792983-r229 { fill: #fca42b } + .terminal-1281792983-r230 { fill: #fa9f2d } + .terminal-1281792983-r231 { fill: #f29333 } + .terminal-1281792983-r232 { fill: #e6813b } + .terminal-1281792983-r233 { fill: #e17a3e } + .terminal-1281792983-r234 { fill: #d16249 } + .terminal-1281792983-r235 { fill: #cc594d } + .terminal-1281792983-r236 { fill: #153954 } + .terminal-1281792983-r237 { fill: #133e5f } + .terminal-1281792983-r238 { fill: #0f4974 } + .terminal-1281792983-r239 { fill: #0e4e7f } + .terminal-1281792983-r240 { fill: #0c5389 } + .terminal-1281792983-r241 { fill: #095c9c } + .terminal-1281792983-r242 { fill: #0861a5 } + .terminal-1281792983-r243 { fill: #0568b5 } + .terminal-1281792983-r244 { fill: #046cbc } + .terminal-1281792983-r245 { fill: #036fc2 } + .terminal-1281792983-r246 { fill: #0273cb } + .terminal-1281792983-r247 { fill: #0175cf } + .terminal-1281792983-r248 { fill: #0177d3 } + .terminal-1281792983-r249 { fill: #0177d2 } + .terminal-1281792983-r250 { fill: #0274cd } + .terminal-1281792983-r251 { fill: #0272c9 } + .terminal-1281792983-r252 { fill: #0370c4 } + .terminal-1281792983-r253 { fill: #056ab8 } + .terminal-1281792983-r254 { fill: #0666b0 } + .terminal-1281792983-r255 { fill: #095ea0 } + .terminal-1281792983-r256 { fill: #0a5a97 } + .terminal-1281792983-r257 { fill: #0b558d } + .terminal-1281792983-r258 { fill: #0f4b79 } + .terminal-1281792983-r259 { fill: #10466e } + .terminal-1281792983-r260 { fill: #143b58 } + .terminal-1281792983-r261 { fill: #143c5a } + .terminal-1281792983-r262 { fill: #104670 } + .terminal-1281792983-r263 { fill: #0e4c7a } + .terminal-1281792983-r264 { fill: #0d5185 } + .terminal-1281792983-r265 { fill: #0a5a98 } + .terminal-1281792983-r266 { fill: #085fa1 } + .terminal-1281792983-r267 { fill: #0667b2 } + .terminal-1281792983-r268 { fill: #056ab9 } + .terminal-1281792983-r269 { fill: #046dbf } + .terminal-1281792983-r270 { fill: #0273c9 } + .terminal-1281792983-r271 { fill: #0174cd } + .terminal-1281792983-r272 { fill: #0175ce } + .terminal-1281792983-r273 { fill: #0371c6 } + .terminal-1281792983-r274 { fill: #046bbb } + .terminal-1281792983-r275 { fill: #0568b4 } + .terminal-1281792983-r276 { fill: #0860a4 } + .terminal-1281792983-r277 { fill: #095c9b } + .terminal-1281792983-r278 { fill: #0b5791 } + .terminal-1281792983-r279 { fill: #0e4d7d } + .terminal-1281792983-r280 { fill: #104873 } + .terminal-1281792983-r281 { fill: #133d5d } + .terminal-1281792983-r282 { fill: #143955 } + .terminal-1281792983-r283 { fill: #11446b } + .terminal-1281792983-r284 { fill: #0f4976 } + .terminal-1281792983-r285 { fill: #0e4f80 } + .terminal-1281792983-r286 { fill: #0a5894 } + .terminal-1281792983-r287 { fill: #095d9d } + .terminal-1281792983-r288 { fill: #0665ae } + .terminal-1281792983-r289 { fill: #0569b6 } + .terminal-1281792983-r290 { fill: #0272c7 } + .terminal-1281792983-r291 { fill: #0274cc } + .terminal-1281792983-r292 { fill: #0177d1 } + .terminal-1281792983-r293 { fill: #0178d4 } + .terminal-1281792983-r294 { fill: #0176cf } + .terminal-1281792983-r295 { fill: #0272c8 } + .terminal-1281792983-r296 { fill: #046dbd } + .terminal-1281792983-r297 { fill: #0569b7 } + .terminal-1281792983-r298 { fill: #0762a7 } + .terminal-1281792983-r299 { fill: #095e9f } + .terminal-1281792983-r300 { fill: #0a5996 } + .terminal-1281792983-r301 { fill: #0d4f82 } + .terminal-1281792983-r302 { fill: #0f4a77 } + .terminal-1281792983-r303 { fill: #0667b3 } + .terminal-1281792983-r304 { fill: #0762a8 } + .terminal-1281792983-r305 { fill: #095d9e } + .terminal-1281792983-r306 { fill: #0c548b } + .terminal-1281792983-r307 { fill: #104872 } + .terminal-1281792983-r308 { fill: #124165 } + .terminal-1281792983-r309 { fill: #133d5c } + .terminal-1281792983-r310 { fill: #143954 } + .terminal-1281792983-r311 { fill: #133c5a } + .terminal-1281792983-r312 { fill: #133e5e } + .terminal-1281792983-r313 { fill: #124063 } + .terminal-1281792983-r314 { fill: #10466f } + .terminal-1281792983-r315 { fill: #0c5287 } + .terminal-1281792983-r316 { fill: #0b5690 } + .terminal-1281792983-r317 { fill: #0a5b9a } + .terminal-1281792983-r318 { fill: #056ab7 } + .terminal-1281792983-r319 { fill: #0764ad } + .terminal-1281792983-r320 { fill: #085fa2 } + .terminal-1281792983-r321 { fill: #0b568f } + .terminal-1281792983-r322 { fill: #0d5186 } + .terminal-1281792983-r323 { fill: #0f4975 } + .terminal-1281792983-r324 { fill: #114368 } + .terminal-1281792983-r325 { fill: #133d5e } + .terminal-1281792983-r326 { fill: #143b59 } + .terminal-1281792983-r327 { fill: #123f61 } + .terminal-1281792983-r328 { fill: #11456c } + .terminal-1281792983-r329 { fill: #0d5083 } + .terminal-1281792983-r330 { fill: #0c548c } + .terminal-1281792983-r331 { fill: #0763aa } + .terminal-1281792983-r332 { fill: #0273ca } + .terminal-1281792983-r333 { fill: #0667b1 } + .terminal-1281792983-r334 { fill: #0761a7 } + .terminal-1281792983-r335 { fill: #0b5893 } + .terminal-1281792983-r336 { fill: #0c538a } + .terminal-1281792983-r337 { fill: #104771 } + .terminal-1281792983-r338 { fill: #133e60 } + .terminal-1281792983-r339 { fill: #133c5b } + .terminal-1281792983-r340 { fill: #143956 } + .terminal-1281792983-r341 { fill: #143a58 } + .terminal-1281792983-r342 { fill: #11436a } + .terminal-1281792983-r343 { fill: #104770 } + .terminal-1281792983-r344 { fill: #0e4e80 } + .terminal-1281792983-r345 { fill: #0c5288 } + .terminal-1281792983-r346 { fill: #4c2730 } + .terminal-1281792983-r347 { fill: #552833 } + .terminal-1281792983-r348 { fill: #672c3b } + .terminal-1281792983-r349 { fill: #702e3e } + .terminal-1281792983-r350 { fill: #792f41 } + .terminal-1281792983-r351 { fill: #893248 } + .terminal-1281792983-r352 { fill: #91344b } + .terminal-1281792983-r353 { fill: #9e3650 } + .terminal-1281792983-r354 { fill: #a43852 } + .terminal-1281792983-r355 { fill: #a93954 } + .terminal-1281792983-r356 { fill: #b13a58 } + .terminal-1281792983-r357 { fill: #b43b59 } + .terminal-1281792983-r358 { fill: #b83b5a } + .terminal-1281792983-r359 { fill: #b73b5a } + .terminal-1281792983-r360 { fill: #b33a58 } + .terminal-1281792983-r361 { fill: #af3a57 } + .terminal-1281792983-r362 { fill: #ab3955 } + .terminal-1281792983-r363 { fill: #a13751 } + .terminal-1281792983-r364 { fill: #9b364f } + .terminal-1281792983-r365 { fill: #8d3349 } + .terminal-1281792983-r366 { fill: #853246 } + .terminal-1281792983-r367 { fill: #7d3043 } + .terminal-1281792983-r368 { fill: #6b2d3c } + .terminal-1281792983-r369 { fill: #622b38 } + .terminal-1281792983-r370 { fill: #502731 } + .terminal-1281792983-r371 { fill: #512832 } + .terminal-1281792983-r372 { fill: #632b39 } + .terminal-1281792983-r373 { fill: #6d2d3d } + .terminal-1281792983-r374 { fill: #752f40 } + .terminal-1281792983-r375 { fill: #863247 } + .terminal-1281792983-r376 { fill: #8e334a } + .terminal-1281792983-r377 { fill: #9c364f } + .terminal-1281792983-r378 { fill: #a23751 } + .terminal-1281792983-r379 { fill: #a73854 } + .terminal-1281792983-r380 { fill: #b03a57 } + .terminal-1281792983-r381 { fill: #b13a57 } + .terminal-1281792983-r382 { fill: #ad3956 } + .terminal-1281792983-r383 { fill: #a33752 } + .terminal-1281792983-r384 { fill: #9d3650 } + .terminal-1281792983-r385 { fill: #90344a } + .terminal-1281792983-r386 { fill: #883247 } + .terminal-1281792983-r387 { fill: #803144 } + .terminal-1281792983-r388 { fill: #6f2d3e } + .terminal-1281792983-r389 { fill: #662c3a } + .terminal-1281792983-r390 { fill: #542833 } + .terminal-1281792983-r391 { fill: #4d2730 } + .terminal-1281792983-r392 { fill: #602a37 } + .terminal-1281792983-r393 { fill: #692c3b } + .terminal-1281792983-r394 { fill: #722e3f } + .terminal-1281792983-r395 { fill: #833145 } + .terminal-1281792983-r396 { fill: #8a3348 } + .terminal-1281792983-r397 { fill: #99354e } + .terminal-1281792983-r398 { fill: #9f3751 } + .terminal-1281792983-r399 { fill: #a53853 } + .terminal-1281792983-r400 { fill: #ae3a56 } + .terminal-1281792983-r401 { fill: #b23a58 } + .terminal-1281792983-r402 { fill: #b53b59 } + .terminal-1281792983-r403 { fill: #a63853 } + .terminal-1281792983-r404 { fill: #a03751 } + .terminal-1281792983-r405 { fill: #93344c } + .terminal-1281792983-r406 { fill: #8c3349 } + .terminal-1281792983-r407 { fill: #843146 } + .terminal-1281792983-r408 { fill: #732e3f } + .terminal-1281792983-r409 { fill: #6a2c3c } + .terminal-1281792983-r410 { fill: #9d364f } + .terminal-1281792983-r411 { fill: #94344c } + .terminal-1281792983-r412 { fill: #8b3349 } + .terminal-1281792983-r413 { fill: #7b3042 } + .terminal-1281792983-r414 { fill: #602a38 } + .terminal-1281792983-r415 { fill: #5b2936 } + .terminal-1281792983-r416 { fill: #532832 } + .terminal-1281792983-r417 { fill: #592935 } + .terminal-1281792983-r418 { fill: #772f41 } + .terminal-1281792983-r419 { fill: #7f3044 } + .terminal-1281792983-r420 { fill: #873247 } + .terminal-1281792983-r421 { fill: #a23752 } + .terminal-1281792983-r422 { fill: #97354d } + .terminal-1281792983-r423 { fill: #8f334a } + .terminal-1281792983-r424 { fill: #7e3043 } + .terminal-1281792983-r425 { fill: #762f40 } + .terminal-1281792983-r426 { fill: #682c3b } + .terminal-1281792983-r427 { fill: #622b39 } + .terminal-1281792983-r428 { fill: #5d2a36 } + .terminal-1281792983-r429 { fill: #532833 } + .terminal-1281792983-r430 { fill: #572934 } + .terminal-1281792983-r431 { fill: #612b38 } + .terminal-1281792983-r432 { fill: #672c3a } + .terminal-1281792983-r433 { fill: #742e40 } + .terminal-1281792983-r434 { fill: #7c3043 } + .terminal-1281792983-r435 { fill: #95354c } + .terminal-1281792983-r436 { fill: #a43853 } + .terminal-1281792983-r437 { fill: #92344b } + .terminal-1281792983-r438 { fill: #813145 } + .terminal-1281792983-r439 { fill: #7a2f42 } + .terminal-1281792983-r440 { fill: #652b39 } + .terminal-1281792983-r441 { fill: #5f2a37 } + .terminal-1281792983-r442 { fill: #562834 } + .terminal-1281792983-r443 { fill: #522832 } + .terminal-1281792983-r444 { fill: #4f2731 } + .terminal-1281792983-r445 { fill: #5e2a37 } + .terminal-1281792983-r446 { fill: #642b39 } + .terminal-1281792983-r447 { fill: #712e3e } + .terminal-1281792983-r448 { fill: #782f41 } + .terminal-1281792983-r449 { fill: #9a364e } + .terminal-1281792983-r450 { fill: #2c4e36 } + .terminal-1281792983-r451 { fill: #2e573b } + .terminal-1281792983-r452 { fill: #346a45 } + .terminal-1281792983-r453 { fill: #377449 } + .terminal-1281792983-r454 { fill: #3a7d4e } + .terminal-1281792983-r455 { fill: #3f8e57 } + .terminal-1281792983-r456 { fill: #41955b } + .terminal-1281792983-r457 { fill: #45a362 } + .terminal-1281792983-r458 { fill: #47a965 } + .terminal-1281792983-r459 { fill: #49af68 } + .terminal-1281792983-r460 { fill: #4bb76d } + .terminal-1281792983-r461 { fill: #4cba6e } + .terminal-1281792983-r462 { fill: #4dbe70 } + .terminal-1281792983-r463 { fill: #4dbd70 } + .terminal-1281792983-r464 { fill: #4cb96d } + .terminal-1281792983-r465 { fill: #4bb56c } + .terminal-1281792983-r466 { fill: #49b169 } + .terminal-1281792983-r467 { fill: #46a664 } + .terminal-1281792983-r468 { fill: #44a060 } + .terminal-1281792983-r469 { fill: #409159 } + .terminal-1281792983-r470 { fill: #3d8955 } + .terminal-1281792983-r471 { fill: #3b8050 } + .terminal-1281792983-r472 { fill: #356e47 } + .terminal-1281792983-r473 { fill: #336542 } + .terminal-1281792983-r474 { fill: #2d5238 } + .terminal-1281792983-r475 { fill: #2d5338 } + .terminal-1281792983-r476 { fill: #336642 } + .terminal-1281792983-r477 { fill: #367047 } + .terminal-1281792983-r478 { fill: #39794c } + .terminal-1281792983-r479 { fill: #3e8a55 } + .terminal-1281792983-r480 { fill: #409259 } + .terminal-1281792983-r481 { fill: #44a161 } + .terminal-1281792983-r482 { fill: #46a764 } + .terminal-1281792983-r483 { fill: #48ac67 } + .terminal-1281792983-r484 { fill: #4bb66c } + .terminal-1281792983-r485 { fill: #4cb96e } + .terminal-1281792983-r486 { fill: #4bb76c } + .terminal-1281792983-r487 { fill: #4ab36a } + .terminal-1281792983-r488 { fill: #45a262 } + .terminal-1281792983-r489 { fill: #41945a } + .terminal-1281792983-r490 { fill: #3e8c56 } + .terminal-1281792983-r491 { fill: #3c8452 } + .terminal-1281792983-r492 { fill: #377249 } + .terminal-1281792983-r493 { fill: #346944 } + .terminal-1281792983-r494 { fill: #2e563a } + .terminal-1281792983-r495 { fill: #2c4f36 } + .terminal-1281792983-r496 { fill: #326240 } + .terminal-1281792983-r497 { fill: #356c45 } + .terminal-1281792983-r498 { fill: #37754a } + .terminal-1281792983-r499 { fill: #3d8753 } + .terminal-1281792983-r500 { fill: #3f8f58 } + .terminal-1281792983-r501 { fill: #449e5f } + .terminal-1281792983-r502 { fill: #46a463 } + .terminal-1281792983-r503 { fill: #47aa66 } + .terminal-1281792983-r504 { fill: #4ab46b } + .terminal-1281792983-r505 { fill: #4bb86d } + .terminal-1281792983-r506 { fill: #4cbb6f } + .terminal-1281792983-r507 { fill: #4cb86d } + .terminal-1281792983-r508 { fill: #48ab66 } + .terminal-1281792983-r509 { fill: #46a563 } + .terminal-1281792983-r510 { fill: #42985c } + .terminal-1281792983-r511 { fill: #3f9058 } + .terminal-1281792983-r512 { fill: #3d8854 } + .terminal-1281792983-r513 { fill: #38764b } + .terminal-1281792983-r514 { fill: #356d46 } + .terminal-1281792983-r515 { fill: #4bb56b } + .terminal-1281792983-r516 { fill: #45a261 } + .terminal-1281792983-r517 { fill: #42985d } + .terminal-1281792983-r518 { fill: #3a7e4f } + .terminal-1281792983-r519 { fill: #38774b } + .terminal-1281792983-r520 { fill: #326341 } + .terminal-1281792983-r521 { fill: #305d3e } + .terminal-1281792983-r522 { fill: #2e5539 } + .terminal-1281792983-r523 { fill: #2d5339 } + .terminal-1281792983-r524 { fill: #2e573a } + .terminal-1281792983-r525 { fill: #305b3d } + .terminal-1281792983-r526 { fill: #356c46 } + .terminal-1281792983-r527 { fill: #397b4d } + .terminal-1281792983-r528 { fill: #3c8351 } + .terminal-1281792983-r529 { fill: #439c5f } + .terminal-1281792983-r530 { fill: #40935a } + .terminal-1281792983-r531 { fill: #3b8251 } + .terminal-1281792983-r532 { fill: #397a4d } + .terminal-1281792983-r533 { fill: #356b45 } + .terminal-1281792983-r534 { fill: #31603f } + .terminal-1281792983-r535 { fill: #2e553a } + .terminal-1281792983-r536 { fill: #2f593c } + .terminal-1281792983-r537 { fill: #346a44 } + .terminal-1281792983-r538 { fill: #38784c } + .terminal-1281792983-r539 { fill: #429a5d } + .terminal-1281792983-r540 { fill: #44a061 } + .terminal-1281792983-r541 { fill: #42975c } + .terminal-1281792983-r542 { fill: #3c8553 } + .terminal-1281792983-r543 { fill: #336843 } + .terminal-1281792983-r544 { fill: #2f583b } + .terminal-1281792983-r545 { fill: #2e5439 } + .terminal-1281792983-r546 { fill: #2d5137 } + .terminal-1281792983-r547 { fill: #2d5439 } + .terminal-1281792983-r548 { fill: #316140 } + .terminal-1281792983-r549 { fill: #336743 } + .terminal-1281792983-r550 { fill: #37744a } + .terminal-1281792983-r551 { fill: #3a7c4e } + .terminal-1281792983-r552 { fill: #41965b } + .terminal-1281792983-r553 { fill: #449f60 } + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SparklineColorsApp + SparklineColorsApp - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + ▇▇▇▇▇ + + ▇▇▇▇▇ + + ▇▇▇▇▇▇ + + ▇▇▇▇▇▇ + + ▇▇▇▇▇▇ + + ▇▇▇▇▇▇ + + ▇▇▇▇▇▇ + + ▇▇▇▇▇▇▇█▇ + + ▇▇▇▇▇▇ + + ▇▇▇▇▇▇▇█▇ + + + @@ -36742,217 +39945,217 @@ font-weight: 700; } - .terminal-3805192064-matrix { + .terminal-2944033235-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3805192064-title { + .terminal-2944033235-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3805192064-r1 { fill: #e1e1e1 } - .terminal-3805192064-r2 { fill: #c5c8c6 } - .terminal-3805192064-r3 { fill: #104670 } - .terminal-3805192064-r4 { fill: #0c548b } - .terminal-3805192064-r5 { fill: #104772 } - .terminal-3805192064-r6 { fill: #0a5996 } - .terminal-3805192064-r7 { fill: #0d5083 } - .terminal-3805192064-r8 { fill: #0d5186 } - .terminal-3805192064-r9 { fill: #0569b6 } - .terminal-3805192064-r10 { fill: #0762a7 } - .terminal-3805192064-r11 { fill: #0e4d7d } - .terminal-3805192064-r12 { fill: #104872 } - .terminal-3805192064-r13 { fill: #0f4a77 } - .terminal-3805192064-r14 { fill: #0b5791 } - .terminal-3805192064-r15 { fill: #0274cc } - .terminal-3805192064-r16 { fill: #0d5084 } - .terminal-3805192064-r17 { fill: #0371c6 } - .terminal-3805192064-r18 { fill: #085fa1 } - .terminal-3805192064-r19 { fill: #0a5b99 } - .terminal-3805192064-r20 { fill: #0c538a } - .terminal-3805192064-r21 { fill: #0a5a97 } - .terminal-3805192064-r22 { fill: #0c5288 } - .terminal-3805192064-r23 { fill: #11456d } - .terminal-3805192064-r24 { fill: #0d4f81 } - .terminal-3805192064-r25 { fill: #0d5185 } - .terminal-3805192064-r26 { fill: #0b568f } - .terminal-3805192064-r27 { fill: #0178d4 } - .terminal-3805192064-r28 { fill: #0668b3 } - .terminal-3805192064-r29 { fill: #0f4a76 } - .terminal-3805192064-r30 { fill: #0f4b78 } - .terminal-3805192064-r31 { fill: #0763aa } - .terminal-3805192064-r32 { fill: #0b5690 } - .terminal-3805192064-r33 { fill: #0e4c7c } - .terminal-3805192064-r34 { fill: #0175cf } - .terminal-3805192064-r35 { fill: #0e4e80 } - .terminal-3805192064-r36 { fill: #0c5388 } - .terminal-3805192064-r37 { fill: #0c5287 } - .terminal-3805192064-r38 { fill: #0a5894 } - .terminal-3805192064-r39 { fill: #0b558d } - .terminal-3805192064-r40 { fill: #056ab8 } - .terminal-3805192064-r41 { fill: #0e4c7b } - .terminal-3805192064-r42 { fill: #0762a8 } - .terminal-3805192064-r43 { fill: #0665ad } - .terminal-3805192064-r44 { fill: #0e4d7c } - .terminal-3805192064-r45 { fill: #0c548c } - .terminal-3805192064-r46 { fill: #0e4e7f } - .terminal-3805192064-r47 { fill: #0f4b7a } - .terminal-3805192064-r48 { fill: #0667b2 } - .terminal-3805192064-r49 { fill: #11446c } - .terminal-3805192064-r50 { fill: #0f4975 } - .terminal-3805192064-r51 { fill: #0568b4 } - .terminal-3805192064-r52 { fill: #0f4976 } - .terminal-3805192064-r53 { fill: #085fa2 } - .terminal-3805192064-r54 { fill: #0a5a98 } - .terminal-3805192064-r55 { fill: #124164 } - .terminal-3805192064-r56 { fill: #0d5287 } - .terminal-3805192064-r57 { fill: #133e5e } - .terminal-3805192064-r58 { fill: #10466f } - .terminal-3805192064-r59 { fill: #124266 } - .terminal-3805192064-r60 { fill: #123f61 } - .terminal-3805192064-r61 { fill: #124063 } - .terminal-3805192064-r62 { fill: #114267 } - .terminal-3805192064-r63 { fill: #114369 } - .terminal-3805192064-r64 { fill: #124062 } - .terminal-3805192064-r65 { fill: #133e5f } - .terminal-3805192064-r66 { fill: #124165 } - .terminal-3805192064-r67 { fill: #124166 } - .terminal-3805192064-r68 { fill: #10456d } - .terminal-3805192064-r69 { fill: #123f62 } - .terminal-3805192064-r70 { fill: #114368 } - .terminal-3805192064-r71 { fill: #11446a } - .terminal-3805192064-r72 { fill: #124064 } - .terminal-3805192064-r73 { fill: #104873 } - .terminal-3805192064-r74 { fill: #133f60 } - .terminal-3805192064-r75 { fill: #133d5d } - .terminal-3805192064-r76 { fill: #11446b } - .terminal-3805192064-r77 { fill: #11456c } - .terminal-3805192064-r78 { fill: #123f60 } - .terminal-3805192064-r79 { fill: #11436a } - .terminal-3805192064-r80 { fill: #133d5c } - .terminal-3805192064-r81 { fill: #143954 } - .terminal-3805192064-r82 { fill: #143b58 } - .terminal-3805192064-r83 { fill: #143a56 } - .terminal-3805192064-r84 { fill: #143955 } - .terminal-3805192064-r85 { fill: #153954 } - .terminal-3805192064-r86 { fill: #143a57 } - .terminal-3805192064-r87 { fill: #143956 } - .terminal-3805192064-r88 { fill: #143c5a } - - - - + .terminal-2944033235-r1 { fill: #e1e1e1 } + .terminal-2944033235-r2 { fill: #c5c8c6 } + .terminal-2944033235-r3 { fill: #104670 } + .terminal-2944033235-r4 { fill: #0c548b } + .terminal-2944033235-r5 { fill: #104772 } + .terminal-2944033235-r6 { fill: #0a5996 } + .terminal-2944033235-r7 { fill: #0d5083 } + .terminal-2944033235-r8 { fill: #0d5186 } + .terminal-2944033235-r9 { fill: #0569b6 } + .terminal-2944033235-r10 { fill: #0762a7 } + .terminal-2944033235-r11 { fill: #0e4d7d } + .terminal-2944033235-r12 { fill: #104872 } + .terminal-2944033235-r13 { fill: #0f4a77 } + .terminal-2944033235-r14 { fill: #0b5791 } + .terminal-2944033235-r15 { fill: #0274cc } + .terminal-2944033235-r16 { fill: #0d5084 } + .terminal-2944033235-r17 { fill: #0371c6 } + .terminal-2944033235-r18 { fill: #085fa1 } + .terminal-2944033235-r19 { fill: #0a5b99 } + .terminal-2944033235-r20 { fill: #0c538a } + .terminal-2944033235-r21 { fill: #0a5a97 } + .terminal-2944033235-r22 { fill: #0c5288 } + .terminal-2944033235-r23 { fill: #11456d } + .terminal-2944033235-r24 { fill: #0d4f81 } + .terminal-2944033235-r25 { fill: #0d5185 } + .terminal-2944033235-r26 { fill: #0b568f } + .terminal-2944033235-r27 { fill: #0178d4 } + .terminal-2944033235-r28 { fill: #0668b3 } + .terminal-2944033235-r29 { fill: #0f4a76 } + .terminal-2944033235-r30 { fill: #0f4b78 } + .terminal-2944033235-r31 { fill: #0763aa } + .terminal-2944033235-r32 { fill: #0b5690 } + .terminal-2944033235-r33 { fill: #0e4c7c } + .terminal-2944033235-r34 { fill: #0175cf } + .terminal-2944033235-r35 { fill: #0e4e80 } + .terminal-2944033235-r36 { fill: #0c5388 } + .terminal-2944033235-r37 { fill: #0c5287 } + .terminal-2944033235-r38 { fill: #0a5894 } + .terminal-2944033235-r39 { fill: #0b558d } + .terminal-2944033235-r40 { fill: #056ab8 } + .terminal-2944033235-r41 { fill: #0e4c7b } + .terminal-2944033235-r42 { fill: #0762a8 } + .terminal-2944033235-r43 { fill: #0665ad } + .terminal-2944033235-r44 { fill: #0e4d7c } + .terminal-2944033235-r45 { fill: #0c548c } + .terminal-2944033235-r46 { fill: #0e4e7f } + .terminal-2944033235-r47 { fill: #0f4b7a } + .terminal-2944033235-r48 { fill: #0667b2 } + .terminal-2944033235-r49 { fill: #11446c } + .terminal-2944033235-r50 { fill: #0f4975 } + .terminal-2944033235-r51 { fill: #0568b4 } + .terminal-2944033235-r52 { fill: #0f4976 } + .terminal-2944033235-r53 { fill: #085fa2 } + .terminal-2944033235-r54 { fill: #0a5a98 } + .terminal-2944033235-r55 { fill: #124164 } + .terminal-2944033235-r56 { fill: #0d5287 } + .terminal-2944033235-r57 { fill: #133e5e } + .terminal-2944033235-r58 { fill: #10466f } + .terminal-2944033235-r59 { fill: #124266 } + .terminal-2944033235-r60 { fill: #123f61 } + .terminal-2944033235-r61 { fill: #124063 } + .terminal-2944033235-r62 { fill: #114267 } + .terminal-2944033235-r63 { fill: #114369 } + .terminal-2944033235-r64 { fill: #124062 } + .terminal-2944033235-r65 { fill: #133e5f } + .terminal-2944033235-r66 { fill: #124165 } + .terminal-2944033235-r67 { fill: #124166 } + .terminal-2944033235-r68 { fill: #10456d } + .terminal-2944033235-r69 { fill: #123f62 } + .terminal-2944033235-r70 { fill: #114368 } + .terminal-2944033235-r71 { fill: #11446a } + .terminal-2944033235-r72 { fill: #124064 } + .terminal-2944033235-r73 { fill: #104873 } + .terminal-2944033235-r74 { fill: #133f60 } + .terminal-2944033235-r75 { fill: #133d5d } + .terminal-2944033235-r76 { fill: #11446b } + .terminal-2944033235-r77 { fill: #11456c } + .terminal-2944033235-r78 { fill: #123f60 } + .terminal-2944033235-r79 { fill: #11436a } + .terminal-2944033235-r80 { fill: #133d5c } + .terminal-2944033235-r81 { fill: #143954 } + .terminal-2944033235-r82 { fill: #143b58 } + .terminal-2944033235-r83 { fill: #143a56 } + .terminal-2944033235-r84 { fill: #143955 } + .terminal-2944033235-r85 { fill: #153954 } + .terminal-2944033235-r86 { fill: #143a57 } + .terminal-2944033235-r87 { fill: #143956 } + .terminal-2944033235-r88 { fill: #143c5a } + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SparklineSummaryFunctionApp + SparklineSummaryFunctionApp - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + ▂▂▁▁ + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + @@ -36983,136 +40186,136 @@ font-weight: 700; } - .terminal-3011216141-matrix { + .terminal-2056257650-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3011216141-title { + .terminal-2056257650-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3011216141-r1 { fill: #e1e1e1 } - .terminal-3011216141-r2 { fill: #c5c8c6 } - .terminal-3011216141-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-3011216141-r4 { fill: #1e1e1e } - .terminal-3011216141-r5 { fill: #0178d4 } - .terminal-3011216141-r6 { fill: #e2e2e2 } - .terminal-3011216141-r7 { fill: #e3e8e8 } + .terminal-2056257650-r1 { fill: #e1e1e1 } + .terminal-2056257650-r2 { fill: #c5c8c6 } + .terminal-2056257650-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-2056257650-r4 { fill: #1e1e1e } + .terminal-2056257650-r5 { fill: #0178d4 } + .terminal-2056257650-r6 { fill: #e2e2e2 } + .terminal-2056257650-r7 { fill: #e3e8e8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SwitchApp + SwitchApp - - - - - - - - Example switches - - - ▔▔▔▔▔▔▔▔ - off:      - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - on:       - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - focused:  - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - custom:   - ▁▁▁▁▁▁▁▁ - - - - + + + + + + + + Example switches + + + ▔▔▔▔▔▔▔▔ +                               off:      + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ +                               on:       + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ +                               focused:  + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ +                               custom:   + ▁▁▁▁▁▁▁▁ + + + + @@ -37143,135 +40346,135 @@ font-weight: 700; } - .terminal-1525284282-matrix { + .terminal-4106412953-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1525284282-title { + .terminal-4106412953-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1525284282-r1 { fill: #c5c8c6 } - .terminal-1525284282-r2 { fill: #e1e1e1 } - .terminal-1525284282-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-1525284282-r4 { fill: #737373 } - .terminal-1525284282-r5 { fill: #474747 } - .terminal-1525284282-r6 { fill: #0178d4 } + .terminal-4106412953-r1 { fill: #c5c8c6 } + .terminal-4106412953-r2 { fill: #e1e1e1 } + .terminal-4106412953-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-4106412953-r4 { fill: #737373 } + .terminal-4106412953-r5 { fill: #474747 } + .terminal-4106412953-r6 { fill: #0178d4 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TabRenameApp + TabRenameApp - - - - - This is a much longer label for the tab011222333344444 - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - TabPane#test - - - - - - - - - - - - - - - - - - + + + + + This is a much longer label for the tab011222333344444 + ━╸━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + TabPane#test + + + + + + + + + + + + + + + + + + @@ -37302,141 +40505,141 @@ font-weight: 700; } - .terminal-19952654-matrix { + .terminal-1980553710-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-19952654-title { + .terminal-1980553710-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-19952654-r1 { fill: #c5c8c6 } - .terminal-19952654-r2 { fill: #e1e1e1 } - .terminal-19952654-r3 { fill: #737373 } - .terminal-19952654-r4 { fill: #e1e1e1;font-weight: bold } - .terminal-19952654-r5 { fill: #474747 } - .terminal-19952654-r6 { fill: #0178d4 } - .terminal-19952654-r7 { fill: #121212 } - .terminal-19952654-r8 { fill: #0053aa } - .terminal-19952654-r9 { fill: #dde8f3;font-weight: bold } - .terminal-19952654-r10 { fill: #323232 } - .terminal-19952654-r11 { fill: #ddedf9 } + .terminal-1980553710-r1 { fill: #c5c8c6 } + .terminal-1980553710-r2 { fill: #e1e1e1 } + .terminal-1980553710-r3 { fill: #737373 } + .terminal-1980553710-r4 { fill: #e1e1e1;font-weight: bold } + .terminal-1980553710-r5 { fill: #474747 } + .terminal-1980553710-r6 { fill: #0178d4 } + .terminal-1980553710-r7 { fill: #4ebf71;font-weight: bold } + .terminal-1980553710-r8 { fill: #323232 } + .terminal-1980553710-r9 { fill: #fea62b;font-weight: bold } + .terminal-1980553710-r10 { fill: #a7a9ab } + .terminal-1980553710-r11 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TabbedApp + TabbedApp - - - - - LetoJessicaPaul - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - Lady Jessica - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Bene Gesserit and concubine of Leto, and mother of Paul and Alia. - - - - PaulAlia - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - First child - - - - - - -  L  Leto  J  Jessica  P  Paul  + + + + + LetoJessicaPaul + ━━━━━━━━╸━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + Lady Jessica + +   Bene Gesserit and concubine of Leto, and mother of Paul and Alia. + + + + PaulAlia + ━╸━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + First child                                                              + + + + + + + +  l Leto  j Jessica  p Paul  @@ -37466,139 +40669,139 @@ font-weight: 700; } - .terminal-1634536353-matrix { + .terminal-15712074-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1634536353-title { + .terminal-15712074-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1634536353-r1 { fill: #c5c8c6 } - .terminal-1634536353-r2 { fill: #e1e1e1 } - .terminal-1634536353-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-1634536353-r4 { fill: #474747 } - .terminal-1634536353-r5 { fill: #0178d4 } - .terminal-1634536353-r6 { fill: #454a50 } - .terminal-1634536353-r7 { fill: #e2e3e3;font-weight: bold } - .terminal-1634536353-r8 { fill: #000000 } - .terminal-1634536353-r9 { fill: #737373 } - .terminal-1634536353-r10 { fill: #323232 } + .terminal-15712074-r1 { fill: #c5c8c6 } + .terminal-15712074-r2 { fill: #e1e1e1 } + .terminal-15712074-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-15712074-r4 { fill: #474747 } + .terminal-15712074-r5 { fill: #0178d4 } + .terminal-15712074-r6 { fill: #454a50 } + .terminal-15712074-r7 { fill: #e2e3e3;font-weight: bold } + .terminal-15712074-r8 { fill: #000000 } + .terminal-15712074-r9 { fill: #737373 } + .terminal-15712074-r10 { fill: #323232 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TabbedContentStyleLeakTestApp + TabbedContentStyleLeakTestApp - - - - - Leak Test - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - This label should come first - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - This button should come second - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - TheseTabsShouldComeLast - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - - - - - - - - + + + + + Leak Test + ━╸━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + This label should come first                                                 + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  This button should come second  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + TheseTabsShouldComeLast + ━╸━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + + + + + + + + + @@ -37629,140 +40832,140 @@ font-weight: 700; } - .terminal-3976501021-matrix { + .terminal-4066879127-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3976501021-title { + .terminal-4066879127-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3976501021-r1 { fill: #c5c8c6 } - .terminal-3976501021-r2 { fill: #e1e1e1 } - .terminal-3976501021-r3 { fill: #484848;font-weight: bold } - .terminal-3976501021-r4 { fill: #484848 } - .terminal-3976501021-r5 { fill: #737373 } - .terminal-3976501021-r6 { fill: #474747 } - .terminal-3976501021-r7 { fill: #0178d4 } - .terminal-3976501021-r8 { fill: #a32327 } - .terminal-3976501021-r9 { fill: #ffdddd } - .terminal-3976501021-r10 { fill: #f09d9e;font-weight: bold } - .terminal-3976501021-r11 { fill: #810000 } + .terminal-4066879127-r1 { fill: #c5c8c6 } + .terminal-4066879127-r2 { fill: #e1e1e1 } + .terminal-4066879127-r3 { fill: #484848;font-weight: bold } + .terminal-4066879127-r4 { fill: #484848 } + .terminal-4066879127-r5 { fill: #737373 } + .terminal-4066879127-r6 { fill: #474747 } + .terminal-4066879127-r7 { fill: #0178d4 } + .terminal-4066879127-r8 { fill: #a32327 } + .terminal-4066879127-r9 { fill: #ffdddd } + .terminal-4066879127-r10 { fill: #f09d9e;font-weight: bold } + .terminal-4066879127-r11 { fill: #810000 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - FiddleWithTabsApp + FiddleWithTabsApp - - - - - Tab 1Tab 2Tab 4Tab 5 - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - + + + + + Tab 1Tab 2Tab 4Tab 5 + ━╸━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Button  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + @@ -37793,136 +40996,136 @@ font-weight: 700; } - .terminal-2580112047-matrix { + .terminal-284173292-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2580112047-title { + .terminal-284173292-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2580112047-r1 { fill: #e1e1e1 } - .terminal-2580112047-r2 { fill: #c5c8c6 } - .terminal-2580112047-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-2580112047-r4 { fill: #98e024;font-weight: bold;font-style: italic; } - .terminal-2580112047-r5 { fill: #f4005f;font-weight: bold } - .terminal-2580112047-r6 { fill: #e1e1e1;font-style: italic; } - .terminal-2580112047-r7 { fill: #e1e1e1;text-decoration: underline; } + .terminal-284173292-r1 { fill: #e1e1e1 } + .terminal-284173292-r2 { fill: #c5c8c6 } + .terminal-284173292-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-284173292-r4 { fill: #98e024;font-weight: bold;font-style: italic; } + .terminal-284173292-r5 { fill: #f4005f;font-weight: bold } + .terminal-284173292-r6 { fill: #e1e1e1;font-style: italic; } + .terminal-284173292-r7 { fill: #e1e1e1;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TableStaticApp + TableStaticApp - - - - ┏━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┓ - FooBar   baz       - ┡━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━┩ - Hello World!ItalicUnderline - └──────────────┴────────┴───────────┘ - - - - - - - - - - - - - - - - - - + + + + ┏━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┓ + Foo Bar     baz        + ┡━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━┩ + │ Hello World! │ Italic │ Underline │ + └──────────────┴────────┴───────────┘ + + + + + + + + + + + + + + + + + + @@ -37953,136 +41156,136 @@ font-weight: 700; } - .terminal-3233270805-matrix { + .terminal-3291459360-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3233270805-title { + .terminal-3291459360-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3233270805-r1 { fill: #c5c8c6 } - .terminal-3233270805-r2 { fill: #e1e1e1 } - .terminal-3233270805-r3 { fill: #737373 } - .terminal-3233270805-r4 { fill: #e1e1e1;font-weight: bold } - .terminal-3233270805-r5 { fill: #474747 } - .terminal-3233270805-r6 { fill: #0178d4 } - .terminal-3233270805-r7 { fill: #0000ff } + .terminal-3291459360-r1 { fill: #c5c8c6 } + .terminal-3291459360-r2 { fill: #e1e1e1 } + .terminal-3291459360-r3 { fill: #737373 } + .terminal-3291459360-r4 { fill: #e1e1e1;font-weight: bold } + .terminal-3291459360-r5 { fill: #474747 } + .terminal-3291459360-r6 { fill: #0178d4 } + .terminal-3291459360-r7 { fill: #0000ff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TabApp + TabApp - - - - - Tab 1Tab 2 - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ────────────────────────────────────────────────────────────────────────────── - - world - - ────────────────────────────────────────────────────────────────────────────── - - - - - - - - - - - - - - - + + + + + Tab 1Tab 2 + ━━━━━━━━━╸━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ┌──────────────────────────────────────────────────────────────────────────────┐ + + world                                                                      + + └──────────────────────────────────────────────────────────────────────────────┘ + + + + + + + + + + + + + + + @@ -38113,80 +41316,80 @@ font-weight: 700; } - .terminal-266942663-matrix { + .terminal-2149892491-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-266942663-title { + .terminal-2149892491-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-266942663-r1 { fill: #1e1e1e } - .terminal-266942663-r2 { fill: #0178d4 } - .terminal-266942663-r3 { fill: #c5c8c6 } - .terminal-266942663-r4 { fill: #e1e1e1 } - .terminal-266942663-r5 { fill: #151515 } - .terminal-266942663-r6 { fill: #e2e2e2 } + .terminal-2149892491-r1 { fill: #1e1e1e } + .terminal-2149892491-r2 { fill: #0178d4 } + .terminal-2149892491-r3 { fill: #c5c8c6 } + .terminal-2149892491-r4 { fill: #e1e1e1 } + .terminal-2149892491-r5 { fill: #151515 } + .terminal-2149892491-r6 { fill: #e2e2e2 } - + - + - + - + - + - + - + - + - + - + - TABug + TABug - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - foo                                          - bar  - baz  - - - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎ + foo                                          + bar                                          + baz                                          + + + + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎ @@ -38216,457 +41419,457 @@ font-weight: 700; } - .terminal-2745061894-matrix { + .terminal-2017841020-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2745061894-title { + .terminal-2017841020-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2745061894-r1 { fill: #1e1e1e } - .terminal-2745061894-r2 { fill: #0178d4 } - .terminal-2745061894-r3 { fill: #c5c8c6 } - .terminal-2745061894-r4 { fill: #c2c2bf } - .terminal-2745061894-r5 { fill: #272822 } - .terminal-2745061894-r6 { fill: #75715e } - .terminal-2745061894-r7 { fill: #f8f8f2 } - .terminal-2745061894-r8 { fill: #90908a } - .terminal-2745061894-r9 { fill: #e6db74 } - .terminal-2745061894-r10 { fill: #a6e22e } - .terminal-2745061894-r11 { fill: #f92672 } + .terminal-2017841020-r1 { fill: #1e1e1e } + .terminal-2017841020-r2 { fill: #0178d4 } + .terminal-2017841020-r3 { fill: #c5c8c6 } + .terminal-2017841020-r4 { fill: #c2c2bf } + .terminal-2017841020-r5 { fill: #272822 } + .terminal-2017841020-r6 { fill: #75715e } + .terminal-2017841020-r7 { fill: #f8f8f2 } + .terminal-2017841020-r8 { fill: #90908a } + .terminal-2017841020-r9 { fill: #e6db74 } + .terminal-2017841020-r10 { fill: #a6e22e } + .terminal-2017841020-r11 { fill: #f92672 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -   1  #!/bin/bash -   2   -   3  # Variables -   4  name="John" -   5  age=30  -   6  is_student=true  -   7   -   8  # Printing variables -   9  echo"Hello, $name! You are $age years old." -  10   -  11  # Conditional statements -  12  if [[ $age -ge 18 &&$is_student == true ]]; then -  13  echo"You are an adult student." -  14  elif [[ $age -ge 18 ]]; then -  15  echo"You are an adult." -  16  else -  17  echo"You are a minor." -  18  fi -  19   -  20  # Arrays -  21  numbers=(1 2 3 4 5)  -  22  echo"Numbers: ${numbers[@]}" -  23   -  24  # Loops -  25  for num in"${numbers[@]}"do -  26  echo"Number: $num" -  27  done -  28   -  29  # Functions -  30  greet() {  -  31    local name=$ -  32  echo"Hello, $name!" -  33   -  34  greet"Alice" -  35   -  36  # Command substitution -  37  current_date=$(date +%Y-%m-%d)  -  38  echo"Current date: $current_date" -  39   -  40  # File operations -  41  touch file.txt  -  42  echo"Some content"> file.txt  -  43  cat file.txt  -  44   -  45  # Conditionals with file checks -  46  if [[ -f file.txt ]]; then -  47  echo"file.txt exists." -  48  else -  49  echo"file.txt does not exist." -  50  fi -  51   -  52  # Case statement -  53  case$age in -  54    18)  -  55  echo"You are 18 years old." -  56      ;;  -  57    30)  -  58  echo"You are 30 years old." -  59      ;;  -  60    *)  -  61  echo"You are neither 18 nor 30 years old." -  62      ;;  -  63  esac -  64   -  65  # While loop -  66  counter=0  -  67  while [[ $counter -lt 5 ]]; do -  68  echo"Counter: $counter" -  69    ((counter++))  -  70  done -  71   -  72  # Until loop -  73  until [[ $counter -eq 0 ]]; do -  74  echo"Counter: $counter" -  75    ((counter--))  -  76  done -  77   -  78  # Heredoc -  79  cat << EOF -  80  This is a heredoc.  -  81  It allows you to write multiple lines of text.  -  82  EOF  -  83   -  84  # Redirection -  85  ls> file_list.txt  -  86  grep"file" file_list.txt > filtered_list.txt  -  87   -  88  # Pipes -  89  cat file_list.txt |wc -l  -  90   -  91  # Arithmetic operations -  92  result=$((10 + 5))  -  93  echo"Result: $result" -  94   -  95  # Exporting variables -  96  export DB_PASSWORD="secret" -  97   -  98  # Sourcing external files -  99  source config.sh  - 100   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +   1  #!/bin/bash +   2   +   3  # Variables +   4  name="John" +   5  age=30                                                                  +   6  is_student=true                                                         +   7   +   8  # Printing variables +   9  echo"Hello, $name! You are $age years old." +  10   +  11  # Conditional statements +  12  if [[ $age -ge 18 &&$is_student == true ]]; then +  13  echo"You are an adult student." +  14  elif [[ $age -ge 18 ]]; then +  15  echo"You are an adult." +  16  else +  17  echo"You are a minor." +  18  fi +  19   +  20  # Arrays +  21  numbers=(1 2 3 4 5)                                                     +  22  echo"Numbers: ${numbers[@]}" +  23   +  24  # Loops +  25  for num in"${numbers[@]}"do +  26  echo"Number: $num" +  27  done +  28   +  29  # Functions +  30  greet() {                                                               +  31    local name=$1                                                         +  32  echo"Hello, $name!" +  33  }                                                                       +  34  greet"Alice" +  35   +  36  # Command substitution +  37  current_date=$(date +%Y-%m-%d)                                          +  38  echo"Current date: $current_date" +  39   +  40  # File operations +  41  touch file.txt                                                          +  42  echo"Some content"> file.txt                                          +  43  cat file.txt                                                            +  44   +  45  # Conditionals with file checks +  46  if [[ -f file.txt ]]; then +  47  echo"file.txt exists." +  48  else +  49  echo"file.txt does not exist." +  50  fi +  51   +  52  # Case statement +  53  case$age in +  54    18)                                                                   +  55  echo"You are 18 years old." +  56      ;;                                                                  +  57    30)                                                                   +  58  echo"You are 30 years old." +  59      ;;                                                                  +  60    *)                                                                    +  61  echo"You are neither 18 nor 30 years old." +  62      ;;                                                                  +  63  esac +  64   +  65  # While loop +  66  counter=0                                                               +  67  while [[ $counter -lt 5 ]]; do +  68  echo"Counter: $counter" +  69    ((counter++))                                                         +  70  done +  71   +  72  # Until loop +  73  until [[ $counter -eq 0 ]]; do +  74  echo"Counter: $counter" +  75    ((counter--))                                                         +  76  done +  77   +  78  # Heredoc +  79  cat << EOF +  80  This is a heredoc.  +  81  It allows you to write multiple lines of text.  +  82  EOF  +  83   +  84  # Redirection +  85  ls> file_list.txt                                                      +  86  grep"file" file_list.txt > filtered_list.txt                           +  87   +  88  # Pipes +  89  cat file_list.txt |wc -l                                               +  90   +  91  # Arithmetic operations +  92  result=$((10 + 5))                                                      +  93  echo"Result: $result" +  94   +  95  # Exporting variables +  96  export DB_PASSWORD="secret" +  97   +  98  # Sourcing external files +  99  source config.sh                                                        + 100   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -38696,330 +41899,330 @@ font-weight: 700; } - .terminal-39861072-matrix { + .terminal-4232402843-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-39861072-title { + .terminal-4232402843-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-39861072-r1 { fill: #1e1e1e } - .terminal-39861072-r2 { fill: #0178d4 } - .terminal-39861072-r3 { fill: #c5c8c6 } - .terminal-39861072-r4 { fill: #c2c2bf } - .terminal-39861072-r5 { fill: #272822 } - .terminal-39861072-r6 { fill: #75715e } - .terminal-39861072-r7 { fill: #f8f8f2 } - .terminal-39861072-r8 { fill: #90908a } - .terminal-39861072-r9 { fill: #e6db74 } - .terminal-39861072-r10 { fill: #ae81ff } - .terminal-39861072-r11 { fill: #f92672 } - .terminal-39861072-r12 { fill: #a6e22e } + .terminal-4232402843-r1 { fill: #1e1e1e } + .terminal-4232402843-r2 { fill: #0178d4 } + .terminal-4232402843-r3 { fill: #c5c8c6 } + .terminal-4232402843-r4 { fill: #c2c2bf } + .terminal-4232402843-r5 { fill: #272822 } + .terminal-4232402843-r6 { fill: #75715e } + .terminal-4232402843-r7 { fill: #f8f8f2 } + .terminal-4232402843-r8 { fill: #90908a } + .terminal-4232402843-r9 { fill: #e6db74 } + .terminal-4232402843-r10 { fill: #ae81ff } + .terminal-4232402843-r11 { fill: #f92672 } + .terminal-4232402843-r12 { fill: #a6e22e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  /* This is a comment in CSS */ -  2   -  3  /* Basic selectors and properties */ -  4  body {  -  5      font-family: Arial, sans-serif;  -  6      background-color: #f4f4f4 -  7      margin: 0 -  8      padding: 0 -  9   - 10   - 11  /* Class and ID selectors */ - 12  .header {  - 13      background-color: #333 - 14      color: #fff - 15      padding: 10px0 - 16      text-align: center;  - 17   - 18   - 19  #logo {  - 20      font-size: 24px - 21      font-weight: bold;  - 22   - 23   - 24  /* Descendant and child selectors */ - 25  .nav ul {  - 26      list-style-type: none;  - 27      padding: 0 - 28   - 29   - 30  .nav > li {  - 31      display: inline-block;  - 32      margin-right: 10px - 33   - 34   - 35  /* Pseudo-classes */ - 36  a:hover {  - 37      text-decoration: underline;  - 38   - 39   - 40  input:focus {  - 41      border-color: #007BFF - 42   - 43   - 44  /* Media query */ - 45  @media (max-width: 768px) {  - 46      body {  - 47          font-size: 16px - 48      }  - 49   - 50      .header {  - 51          padding: 5px0 - 52      }  - 53   - 54   - 55  /* Keyframes animation */ - 56  @keyframes slideIn {  - 57  from {  - 58          transform: translateX(-100%);  - 59      }  - 60  to {  - 61          transform: translateX(0);  - 62      }  - 63   - 64   - 65  .slide-in-element {  - 66      animation: slideIn 0.5s forwards;  - 67   - 68   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  /* This is a comment in CSS */ +  2   +  3  /* Basic selectors and properties */ +  4  body {                                                                   +  5      font-family: Arial, sans-serif;                                      +  6      background-color: #f4f4f4;                                           +  7      margin: 0;                                                           +  8      padding: 0;                                                          +  9  }                                                                        + 10   + 11  /* Class and ID selectors */ + 12  .header {                                                                + 13      background-color: #333;                                              + 14      color: #fff;                                                         + 15      padding: 10px0;                                                     + 16      text-align: center;                                                  + 17  }                                                                        + 18   + 19  #logo {                                                                  + 20      font-size: 24px;                                                     + 21      font-weight: bold;                                                   + 22  }                                                                        + 23   + 24  /* Descendant and child selectors */ + 25  .nav ul {                                                                + 26      list-style-type: none;                                               + 27      padding: 0;                                                          + 28  }                                                                        + 29   + 30  .nav > li {                                                              + 31      display: inline-block;                                               + 32      margin-right: 10px;                                                  + 33  }                                                                        + 34   + 35  /* Pseudo-classes */ + 36  a:hover {                                                                + 37      text-decoration: underline;                                          + 38  }                                                                        + 39   + 40  input:focus {                                                            + 41      border-color: #007BFF;                                               + 42  }                                                                        + 43   + 44  /* Media query */ + 45  @media (max-width: 768px) {                                              + 46      body {                                                               + 47          font-size: 16px;                                                 + 48      }                                                                    + 49   + 50      .header {                                                            + 51          padding: 5px0;                                                  + 52      }                                                                    + 53  }                                                                        + 54   + 55  /* Keyframes animation */ + 56  @keyframes slideIn {                                                     + 57  from {                                                               + 58          transform: translateX(-100%);                                    + 59      }                                                                    + 60  to {                                                                 + 61          transform: translateX(0);                                        + 62      }                                                                    + 63  }                                                                        + 64   + 65  .slide-in-element {                                                      + 66      animation: slideIn 0.5s forwards;                                    + 67  }                                                                        + 68   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -39049,334 +42252,334 @@ font-weight: 700; } - .terminal-338470241-matrix { + .terminal-2156887659-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-338470241-title { + .terminal-2156887659-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-338470241-r1 { fill: #1e1e1e } - .terminal-338470241-r2 { fill: #0178d4 } - .terminal-338470241-r3 { fill: #c5c8c6 } - .terminal-338470241-r4 { fill: #c2c2bf } - .terminal-338470241-r5 { fill: #272822 } - .terminal-338470241-r6 { fill: #f92672 } - .terminal-338470241-r7 { fill: #f8f8f2 } - .terminal-338470241-r8 { fill: #90908a } - .terminal-338470241-r9 { fill: #e6db74 } - .terminal-338470241-r10 { fill: #ae81ff } - .terminal-338470241-r11 { fill: #a6e22e } - .terminal-338470241-r12 { fill: #66d9ef;font-style: italic; } + .terminal-2156887659-r1 { fill: #1e1e1e } + .terminal-2156887659-r2 { fill: #0178d4 } + .terminal-2156887659-r3 { fill: #c5c8c6 } + .terminal-2156887659-r4 { fill: #c2c2bf } + .terminal-2156887659-r5 { fill: #272822 } + .terminal-2156887659-r6 { fill: #f92672 } + .terminal-2156887659-r7 { fill: #f8f8f2 } + .terminal-2156887659-r8 { fill: #90908a } + .terminal-2156887659-r9 { fill: #e6db74 } + .terminal-2156887659-r10 { fill: #ae81ff } + .terminal-2156887659-r11 { fill: #a6e22e } + .terminal-2156887659-r12 { fill: #66d9ef;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  package main                                                             -  2   -  3  import (  -  4  "fmt" -  5  "math" -  6  "strings" -  7   -  8   -  9  const PI =3.14159 - 10   - 11  type Shape interface {  - 12      Area() float64  - 13   - 14   - 15  type Circle struct {  - 16      Radius float64  - 17   - 18   - 19  func (c Circle) Area() float64 {  - 20  return PI * c.Radius * c.Radius  - 21   - 22   - 23  funcmain() {  - 24  var name string ="John" - 25      age :=30 - 26      isStudent :=true - 27   - 28      fmt.Printf("Hello, %s! You are %d years old.", name, age)  - 29   - 30  if age >=18&& isStudent {  - 31          fmt.Println("You are an adult student." - 32      } elseif age >=18 {  - 33          fmt.Println("You are an adult." - 34      } else {  - 35          fmt.Println("You are a minor." - 36      }  - 37   - 38      numbers := []int{12345 - 39      sum :=0 - 40  for _, num :=range numbers {  - 41          sum += num  - 42      }  - 43      fmt.Printf("The sum is: %d", sum)  - 44   - 45      message :="Hello, World!" - 46      uppercaseMessage := strings.ToUpper(message)  - 47      fmt.Println(uppercaseMessage)  - 48   - 49      circle := Circle{Radius: 5 - 50      fmt.Printf("Circle area: %.2f", circle.Area())  - 51   - 52      result :=factorial(5 - 53      fmt.Printf("Factorial of 5: %d", result)  - 54   - 55  defer fmt.Println("Program finished." - 56   - 57      sqrt :=func(x float64) float64 {  - 58  return math.Sqrt(x)  - 59      }  - 60      fmt.Printf("Square root of 16: %.2f"sqrt(16))  - 61   - 62   - 63  funcfactorial(n int) int {  - 64  if n ==0 {  - 65  return1 - 66      }  - 67  return n *factorial(n-1 - 68   - 69   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  package main                                                             +  2   +  3  import (                                                                 +  4  "fmt" +  5  "math" +  6  "strings" +  7  )                                                                        +  8   +  9  const PI =3.14159 + 10   + 11  type Shape interface {                                                   + 12      Area() float64                                                       + 13  }                                                                        + 14   + 15  type Circle struct {                                                     + 16      Radius float64                                                       + 17  }                                                                        + 18   + 19  func (c Circle) Area() float64 {                                         + 20  return PI * c.Radius * c.Radius                                      + 21  }                                                                        + 22   + 23  funcmain() {                                                            + 24  var name string ="John" + 25      age :=30 + 26      isStudent :=true + 27   + 28      fmt.Printf("Hello, %s! You are %d years old.", name, age)            + 29   + 30  if age >=18&& isStudent {                                          + 31          fmt.Println("You are an adult student.")                         + 32      } elseif age >=18 {                                                + 33          fmt.Println("You are an adult.")                                 + 34      } else {                                                             + 35          fmt.Println("You are a minor.")                                  + 36      }                                                                    + 37   + 38      numbers := []int{12345}                                      + 39      sum :=0 + 40  for _, num :=range numbers {                                        + 41          sum += num                                                       + 42      }                                                                    + 43      fmt.Printf("The sum is: %d", sum)                                    + 44   + 45      message :="Hello, World!" + 46      uppercaseMessage := strings.ToUpper(message)                         + 47      fmt.Println(uppercaseMessage)                                        + 48   + 49      circle := Circle{Radius: 5}                                          + 50      fmt.Printf("Circle area: %.2f", circle.Area())                       + 51   + 52      result :=factorial(5)                                               + 53      fmt.Printf("Factorial of 5: %d", result)                             + 54   + 55  defer fmt.Println("Program finished.")                               + 56   + 57      sqrt :=func(x float64) float64 {                                    + 58  return math.Sqrt(x)                                              + 59      }                                                                    + 60      fmt.Printf("Square root of 16: %.2f"sqrt(16))                      + 61  }                                                                        + 62   + 63  funcfactorial(n int) int {                                              + 64  if n ==0 {                                                          + 65  return1 + 66      }                                                                    + 67  return n *factorial(n-1)                                            + 68  }                                                                        + 69   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -39406,285 +42609,285 @@ font-weight: 700; } - .terminal-3772695061-matrix { + .terminal-3896910164-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3772695061-title { + .terminal-3896910164-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3772695061-r1 { fill: #1e1e1e } - .terminal-3772695061-r2 { fill: #0178d4 } - .terminal-3772695061-r3 { fill: #c5c8c6 } - .terminal-3772695061-r4 { fill: #c2c2bf } - .terminal-3772695061-r5 { fill: #272822 } - .terminal-3772695061-r6 { fill: #f8f8f2 } - .terminal-3772695061-r7 { fill: #90908a } - .terminal-3772695061-r8 { fill: #f92672 } - .terminal-3772695061-r9 { fill: #e6db74 } - .terminal-3772695061-r10 { fill: #75715e } - .terminal-3772695061-r11 { fill: #23568b } + .terminal-3896910164-r1 { fill: #1e1e1e } + .terminal-3896910164-r2 { fill: #0178d4 } + .terminal-3896910164-r3 { fill: #c5c8c6 } + .terminal-3896910164-r4 { fill: #c2c2bf } + .terminal-3896910164-r5 { fill: #272822 } + .terminal-3896910164-r6 { fill: #f8f8f2 } + .terminal-3896910164-r7 { fill: #90908a } + .terminal-3896910164-r8 { fill: #f92672 } + .terminal-3896910164-r9 { fill: #e6db74 } + .terminal-3896910164-r10 { fill: #75715e } + .terminal-3896910164-r11 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  <!DOCTYPE html>                                                          -  2  <html lang="en" -  3   -  4  <head -  5  <!-- Meta tags --> -  6      <meta charset="UTF-8" -  7      <meta name="viewport" content="width=device-width, initial-scale=1.0 -  8  <!-- Title --> -  9      <title>HTML Test Page</title - 10  <!-- Link to CSS --> - 11      <link rel="stylesheet" href="styles.css" - 12  </head - 13   - 14  <body - 15  <!-- Header section --> - 16      <header class="header" - 17          <h1 id="logo">HTML Test Page</h1 - 18      </header - 19   - 20  <!-- Navigation --> - 21      <nav class="nav" - 22          <ul - 23              <li><a href="#">Home</a></li - 24              <li><a href="#">About</a></li - 25              <li><a href="#">Contact</a></li - 26          </ul - 27      </nav - 28   - 29  <!-- Main content area --> - 30      <main - 31          <article - 32              <h2>Welcome to the Test Page</h2 - 33              <p>This is a paragraph to test the HTML structure.</p - 34              <img src="test-image.jpg" alt="Test Image" width="300" - 35          </article - 36      </main - 37   - 38  <!-- Form --> - 39      <section - 40          <form action="/submit" method="post" - 41              <label for="name">Name:</label - 42              <input type="text" id="name" name="name" - 43              <input type="submit" value="Submit" - 44          </form - 45      </section - 46   - 47  <!-- Footer --> - 48      <footer - 49          <p>&copy; 2023 HTML Test Page</p - 50      </footer - 51   - 52  <!-- Script tag --> - 53      <script src="scripts.js"></script - 54  </body - 55   - 56  </html - 57   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  <!DOCTYPE html>                                                          +  2  <html lang="en">                                                         +  3   +  4  <head>                                                                   +  5  <!-- Meta tags --> +  6      <meta charset="UTF-8">                                               +  7      <meta name="viewport" content="width=device-width, initial-scale=1.0 +  8  <!-- Title --> +  9      <title>HTML Test Page</title>                                        + 10  <!-- Link to CSS --> + 11      <link rel="stylesheet" href="styles.css">                            + 12  </head>                                                                  + 13   + 14  <body>                                                                   + 15  <!-- Header section --> + 16      <header class="header">                                              + 17          <h1 id="logo">HTML Test Page</h1>                                + 18      </header>                                                            + 19   + 20  <!-- Navigation --> + 21      <nav class="nav">                                                    + 22          <ul>                                                             + 23              <li><a href="#">Home</a></li>                                + 24              <li><a href="#">About</a></li>                               + 25              <li><a href="#">Contact</a></li>                             + 26          </ul>                                                            + 27      </nav>                                                               + 28   + 29  <!-- Main content area --> + 30      <main>                                                               + 31          <article>                                                        + 32              <h2>Welcome to the Test Page</h2>                            + 33              <p>This is a paragraph to test the HTML structure.</p>       + 34              <img src="test-image.jpg" alt="Test Image" width="300">      + 35          </article>                                                       + 36      </main>                                                              + 37   + 38  <!-- Form --> + 39      <section>                                                            + 40          <form action="/submit" method="post">                            + 41              <label for="name">Name:</label>                              + 42              <input type="text" id="name" name="name">                    + 43              <input type="submit" value="Submit">                         + 44          </form>                                                          + 45      </section>                                                           + 46   + 47  <!-- Footer --> + 48      <footer>                                                             + 49          <p>&copy; 2023 HTML Test Page</p>                                + 50      </footer>                                                            + 51   + 52  <!-- Script tag --> + 53      <script src="scripts.js"></script>                                   + 54  </body>                                                                  + 55   + 56  </html>                                                                  + 57   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -39714,475 +42917,475 @@ font-weight: 700; } - .terminal-4174787564-matrix { + .terminal-3881725266-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4174787564-title { + .terminal-3881725266-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4174787564-r1 { fill: #1e1e1e } - .terminal-4174787564-r2 { fill: #0178d4 } - .terminal-4174787564-r3 { fill: #c5c8c6 } - .terminal-4174787564-r4 { fill: #c2c2bf } - .terminal-4174787564-r5 { fill: #272822 } - .terminal-4174787564-r6 { fill: #f92672 } - .terminal-4174787564-r7 { fill: #f8f8f2 } - .terminal-4174787564-r8 { fill: #90908a } - .terminal-4174787564-r9 { fill: #75715e } - .terminal-4174787564-r10 { fill: #ae81ff } - .terminal-4174787564-r11 { fill: #e6db74 } - .terminal-4174787564-r12 { fill: #66d9ef;font-style: italic; } - .terminal-4174787564-r13 { fill: #23568b } + .terminal-3881725266-r1 { fill: #1e1e1e } + .terminal-3881725266-r2 { fill: #0178d4 } + .terminal-3881725266-r3 { fill: #c5c8c6 } + .terminal-3881725266-r4 { fill: #c2c2bf } + .terminal-3881725266-r5 { fill: #272822 } + .terminal-3881725266-r6 { fill: #f92672 } + .terminal-3881725266-r7 { fill: #f8f8f2 } + .terminal-3881725266-r8 { fill: #90908a } + .terminal-3881725266-r9 { fill: #75715e } + .terminal-3881725266-r10 { fill: #ae81ff } + .terminal-3881725266-r11 { fill: #e6db74 } + .terminal-3881725266-r12 { fill: #66d9ef;font-style: italic; } + .terminal-3881725266-r13 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -   1  import java.util.ArrayList;                                             -   2  import java.util.HashMap;  -   3  import java.util.List;  -   4  import java.util.Map;  -   5   -   6  // Classes and interfaces -   7  interface Shape {  -   8      double getArea();  -   9   -  10   -  11  class Rectangle implements Shape {  -  12  private double width;  -  13  private double height;  -  14   -  15  public Rectangle(double width, double height) {  -  16          this.width = width;  -  17          this.height = height;  -  18      }  -  19   -  20  @Override  -  21  public double getArea() {  -  22  return width * height;  -  23      }  -  24   -  25   -  26  // Enums -  27  enum DaysOfWeek {  -  28      MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY  -  29   -  30   -  31  publicclass Main {  -  32  // Constants -  33  privatestaticfinal double PI = 3.14159 -  34   -  35  // Methods -  36  publicstatic int sum(int a, int b) {  -  37  return a + b;  -  38      }  -  39   -  40  publicstatic void main(String[] args) {  -  41  // Variables -  42          String name = "John" -  43          int age = 30 -  44          boolean isStudent = true -  45   -  46  // Printing variables -  47          System.out.println("Hello, " + name + "! You are " + age + " ye -  48   -  49  // Conditional statements -  50  if (age >= 18 && isStudent) {  -  51              System.out.println("You are an adult student.");  -  52          } elseif (age >= 18) {  -  53              System.out.println("You are an adult.");  -  54          } else {  -  55              System.out.println("You are a minor.");  -  56          }  -  57   -  58  // Arrays -  59          int[] numbers = {12345};  -  60          System.out.println("Numbers: " + Arrays.toString(numbers));  -  61   -  62  // Lists -  63          List<String> fruits = new ArrayList<>();  -  64          fruits.add("apple");  -  65          fruits.add("banana");  -  66          fruits.add("orange");  -  67          System.out.println("Fruits: " + fruits);  -  68   -  69  // Loops -  70  for (int num : numbers) {  -  71              System.out.println("Number: " + num);  -  72          }  -  73   -  74  // Hash maps -  75          Map<String, Integer> scores = new HashMap<>();  -  76          scores.put("Alice"100);  -  77          scores.put("Bob"80);  -  78          System.out.println("Alice's score: " + scores.get("Alice"));  -  79   -  80  // Exception handling -  81  try {  -  82              int result = 10 / 0 -  83          } catch (ArithmeticException e) {  -  84              System.out.println("Error: " + e.getMessage());  -  85          }  -  86   -  87  // Instantiating objects -  88          Rectangle rect = new Rectangle(1020);  -  89          System.out.println("Rectangle area: " + rect.getArea());  -  90   -  91  // Enums -  92          DaysOfWeek today = DaysOfWeek.MONDAY;  -  93          System.out.println("Today is " + today);  -  94   -  95  // Calling methods -  96          int sum = sum(510);  -  97          System.out.println("Sum: " + sum);  -  98   -  99  // Ternary operator - 100          String message = age >= 18 ? "You are an adult." : "You are a m - 101          System.out.println(message);  - 102      }  - 103   - 104   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +   1  import java.util.ArrayList;                                             +   2  import java.util.HashMap;                                               +   3  import java.util.List;                                                  +   4  import java.util.Map;                                                   +   5   +   6  // Classes and interfaces +   7  interface Shape {                                                       +   8      double getArea();                                                   +   9  }                                                                       +  10   +  11  class Rectangle implements Shape {                                      +  12  private double width;                                               +  13  private double height;                                              +  14   +  15  public Rectangle(double width, double height) {                     +  16          this.width = width;                                             +  17          this.height = height;                                           +  18      }                                                                   +  19   +  20  @Override                                                           +  21  public double getArea() {                                           +  22  return width * height;                                          +  23      }                                                                   +  24  }                                                                       +  25   +  26  // Enums +  27  enum DaysOfWeek {                                                       +  28      MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY      +  29  }                                                                       +  30   +  31  publicclass Main {                                                     +  32  // Constants +  33  privatestaticfinal double PI = 3.14159;                           +  34   +  35  // Methods +  36  publicstatic int sum(int a, int b) {                               +  37  return a + b;                                                   +  38      }                                                                   +  39   +  40  publicstatic void main(String[] args) {                            +  41  // Variables +  42          String name = "John";                                           +  43          int age = 30;                                                   +  44          boolean isStudent = true;                                       +  45   +  46  // Printing variables +  47          System.out.println("Hello, " + name + "! You are " + age + " ye +  48   +  49  // Conditional statements +  50  if (age >= 18 && isStudent) {                                   +  51              System.out.println("You are an adult student.");            +  52          } elseif (age >= 18) {                                         +  53              System.out.println("You are an adult.");                    +  54          } else {                                                        +  55              System.out.println("You are a minor.");                     +  56          }                                                               +  57   +  58  // Arrays +  59          int[] numbers = {12345};                                +  60          System.out.println("Numbers: " + Arrays.toString(numbers));     +  61   +  62  // Lists +  63          List<String> fruits = new ArrayList<>();                        +  64          fruits.add("apple");                                            +  65          fruits.add("banana");                                           +  66          fruits.add("orange");                                           +  67          System.out.println("Fruits: " + fruits);                        +  68   +  69  // Loops +  70  for (int num : numbers) {                                       +  71              System.out.println("Number: " + num);                       +  72          }                                                               +  73   +  74  // Hash maps +  75          Map<String, Integer> scores = new HashMap<>();                  +  76          scores.put("Alice"100);                                       +  77          scores.put("Bob"80);                                          +  78          System.out.println("Alice's score: " + scores.get("Alice"));    +  79   +  80  // Exception handling +  81  try {                                                           +  82              int result = 10 / 0;                                        +  83          } catch (ArithmeticException e) {                               +  84              System.out.println("Error: " + e.getMessage());             +  85          }                                                               +  86   +  87  // Instantiating objects +  88          Rectangle rect = new Rectangle(1020);                         +  89          System.out.println("Rectangle area: " + rect.getArea());        +  90   +  91  // Enums +  92          DaysOfWeek today = DaysOfWeek.MONDAY;                           +  93          System.out.println("Today is " + today);                        +  94   +  95  // Calling methods +  96          int sum = sum(510);                                           +  97          System.out.println("Sum: " + sum);                              +  98   +  99  // Ternary operator + 100          String message = age >= 18 ? "You are an adult." : "You are a m + 101          System.out.println(message);                                    + 102      }                                                                   + 103  }                                                                       + 104   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -40212,371 +43415,371 @@ font-weight: 700; } - .terminal-2080023319-matrix { + .terminal-2517311162-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2080023319-title { + .terminal-2517311162-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2080023319-r1 { fill: #1e1e1e } - .terminal-2080023319-r2 { fill: #0178d4 } - .terminal-2080023319-r3 { fill: #c5c8c6 } - .terminal-2080023319-r4 { fill: #c2c2bf } - .terminal-2080023319-r5 { fill: #272822 } - .terminal-2080023319-r6 { fill: #75715e } - .terminal-2080023319-r7 { fill: #f8f8f2 } - .terminal-2080023319-r8 { fill: #90908a } - .terminal-2080023319-r9 { fill: #f92672 } - .terminal-2080023319-r10 { fill: #e6db74 } - .terminal-2080023319-r11 { fill: #ae81ff } - .terminal-2080023319-r12 { fill: #66d9ef;font-style: italic; } - .terminal-2080023319-r13 { fill: #a6e22e } + .terminal-2517311162-r1 { fill: #1e1e1e } + .terminal-2517311162-r2 { fill: #0178d4 } + .terminal-2517311162-r3 { fill: #c5c8c6 } + .terminal-2517311162-r4 { fill: #c2c2bf } + .terminal-2517311162-r5 { fill: #272822 } + .terminal-2517311162-r6 { fill: #75715e } + .terminal-2517311162-r7 { fill: #f8f8f2 } + .terminal-2517311162-r8 { fill: #90908a } + .terminal-2517311162-r9 { fill: #f92672 } + .terminal-2517311162-r10 { fill: #e6db74 } + .terminal-2517311162-r11 { fill: #ae81ff } + .terminal-2517311162-r12 { fill: #66d9ef;font-style: italic; } + .terminal-2517311162-r13 { fill: #a6e22e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  // Variable declarations -  2  const name ="John" -  3  let age =30 -  4  var isStudent =true -  5   -  6  // Template literals -  7  console.log(`Hello, ${name}! You are ${age} years old.`);  -  8   -  9  // Conditional statements - 10  if (age >=18&& isStudent) {  - 11    console.log("You are an adult student.");  - 12  elseif (age >=18) {  - 13    console.log("You are an adult.");  - 14  else {  - 15    console.log("You are a minor.");  - 16   - 17   - 18  // Arrays and array methods - 19  const numbers = [12345];  - 20  const doubledNumbers = numbers.map((num) => num *2);  - 21  console.log("Doubled numbers:", doubledNumbers);  - 22   - 23  // Objects - 24  const person = {  - 25    firstName: "John" - 26    lastName: "Doe" - 27    getFullName() {  - 28  return`${this.firstName} ${this.lastName}` - 29    },  - 30  };  - 31  console.log("Full name:", person.getFullName());  - 32   - 33  // Classes - 34  class Rectangle {  - 35    constructor(width, height) {  - 36      this.width = width;  - 37      this.height = height;  - 38    }  - 39   - 40    getArea() {  - 41  return this.width * this.height;  - 42    }  - 43   - 44  const rectangle =new Rectangle(53);  - 45  console.log("Rectangle area:", rectangle.getArea());  - 46   - 47  // Async/Await and Promises - 48  asyncfunctionfetchData() {  - 49  try {  - 50  const response =awaitfetch("https://api.example.com/data");  - 51  const data =await response.json();  - 52      console.log("Fetched data:", data);  - 53    } catch (error) {  - 54      console.error("Error:", error);  - 55    }  - 56   - 57  fetchData();  - 58   - 59  // Arrow functions - 60  constgreet= (name) => {  - 61    console.log(`Hello, ${name}!`);  - 62  };  - 63  greet("Alice");  - 64   - 65  // Destructuring assignment - 66  const [a, b, ...rest] = [12345];  - 67  console.log(a, b, rest);  - 68   - 69  // Spread operator - 70  const arr1 = [123];  - 71  const arr2 = [456];  - 72  const combinedArr = [...arr1, ...arr2];  - 73  console.log("Combined array:", combinedArr);  - 74   - 75  // Ternary operator - 76  const message = age >=18 ? "You are an adult." : "You are a minor." - 77  console.log(message);  - 78   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  // Variable declarations +  2  const name ="John";                                                     +  3  let age =30;                                                            +  4  var isStudent =true;                                                    +  5   +  6  // Template literals +  7  console.log(`Hello, ${name}! You are ${age} years old.`);                +  8   +  9  // Conditional statements + 10  if (age >=18&& isStudent) {                                            + 11    console.log("You are an adult student.");                              + 12  elseif (age >=18) {                                                  + 13    console.log("You are an adult.");                                      + 14  else {                                                                 + 15    console.log("You are a minor.");                                       + 16  }                                                                        + 17   + 18  // Arrays and array methods + 19  const numbers = [12345];                                         + 20  const doubledNumbers = numbers.map((num) => num *2);                    + 21  console.log("Doubled numbers:", doubledNumbers);                         + 22   + 23  // Objects + 24  const person = {                                                         + 25    firstName: "John",                                                     + 26    lastName: "Doe",                                                       + 27    getFullName() {                                                        + 28  return`${this.firstName} ${this.lastName}`;                         + 29    },                                                                     + 30  };                                                                       + 31  console.log("Full name:", person.getFullName());                         + 32   + 33  // Classes + 34  class Rectangle {                                                        + 35    constructor(width, height) {                                           + 36      this.width = width;                                                  + 37      this.height = height;                                                + 38    }                                                                      + 39   + 40    getArea() {                                                            + 41  return this.width * this.height;                                     + 42    }                                                                      + 43  }                                                                        + 44  const rectangle =new Rectangle(53);                                   + 45  console.log("Rectangle area:", rectangle.getArea());                     + 46   + 47  // Async/Await and Promises + 48  asyncfunctionfetchData() {                                             + 49  try {                                                                  + 50  const response =awaitfetch("https://api.example.com/data");        + 51  const data =await response.json();                                  + 52      console.log("Fetched data:", data);                                  + 53    } catch (error) {                                                      + 54      console.error("Error:", error);                                      + 55    }                                                                      + 56  }                                                                        + 57  fetchData();                                                             + 58   + 59  // Arrow functions + 60  constgreet= (name) => {                                                + 61    console.log(`Hello, ${name}!`);                                        + 62  };                                                                       + 63  greet("Alice");                                                          + 64   + 65  // Destructuring assignment + 66  const [a, b, ...rest] = [12345];                                 + 67  console.log(a, b, rest);                                                 + 68   + 69  // Spread operator + 70  const arr1 = [123];                                                  + 71  const arr2 = [456];                                                  + 72  const combinedArr = [...arr1, ...arr2];                                  + 73  console.log("Combined array:", combinedArr);                             + 74   + 75  // Ternary operator + 76  const message = age >=18 ? "You are an adult." : "You are a minor.";    + 77  console.log(message);                                                    + 78   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -40606,182 +43809,182 @@ font-weight: 700; } - .terminal-1618191314-matrix { + .terminal-3615717656-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1618191314-title { + .terminal-3615717656-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1618191314-r1 { fill: #1e1e1e } - .terminal-1618191314-r2 { fill: #0178d4 } - .terminal-1618191314-r3 { fill: #c5c8c6 } - .terminal-1618191314-r4 { fill: #c2c2bf } - .terminal-1618191314-r5 { fill: #272822;font-weight: bold } - .terminal-1618191314-r6 { fill: #f8f8f2 } - .terminal-1618191314-r7 { fill: #90908a } - .terminal-1618191314-r8 { fill: #f92672;font-weight: bold } - .terminal-1618191314-r9 { fill: #e6db74 } - .terminal-1618191314-r10 { fill: #ae81ff } - .terminal-1618191314-r11 { fill: #66d9ef;font-style: italic; } - .terminal-1618191314-r12 { fill: #f8f8f2;font-weight: bold } + .terminal-3615717656-r1 { fill: #1e1e1e } + .terminal-3615717656-r2 { fill: #0178d4 } + .terminal-3615717656-r3 { fill: #c5c8c6 } + .terminal-3615717656-r4 { fill: #c2c2bf } + .terminal-3615717656-r5 { fill: #272822;font-weight: bold } + .terminal-3615717656-r6 { fill: #f8f8f2 } + .terminal-3615717656-r7 { fill: #90908a } + .terminal-3615717656-r8 { fill: #f92672;font-weight: bold } + .terminal-3615717656-r9 { fill: #e6db74 } + .terminal-3615717656-r10 { fill: #ae81ff } + .terminal-3615717656-r11 { fill: #66d9ef;font-style: italic; } + .terminal-3615717656-r12 { fill: #f8f8f2;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  { -  2  "name""John Doe" -  3  "age"30 -  4  "isStudent"false -  5  "address": {  -  6  "street""123 Main St" -  7  "city""Anytown" -  8  "state""CA" -  9  "zip""12345" - 10      },  - 11  "phoneNumbers": [  - 12          {  - 13  "type""home" - 14  "number""555-555-1234" - 15          },  - 16          {  - 17  "type""work" - 18  "number""555-555-5678" - 19          }  - 20      ],  - 21  "hobbies": ["reading""hiking""swimming"],  - 22  "pets": [  - 23          {  - 24  "type""dog" - 25  "name""Fido" - 26          },  - 27      ],  - 28  "graduationYear"null - 29  } - 30   - 31   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  { +  2  "name""John Doe",                                                  +  3  "age"30,                                                           +  4  "isStudent"false,                                                  +  5  "address": {                                                         +  6  "street""123 Main St",                                         +  7  "city""Anytown",                                               +  8  "state""CA",                                                   +  9  "zip""12345" + 10      },                                                                   + 11  "phoneNumbers": [                                                    + 12          {                                                                + 13  "type""home",                                              + 14  "number""555-555-1234" + 15          },                                                               + 16          {                                                                + 17  "type""work",                                              + 18  "number""555-555-5678" + 19          }                                                                + 20      ],                                                                   + 21  "hobbies": ["reading""hiking""swimming"],                        + 22  "pets": [                                                            + 23          {                                                                + 24  "type""dog",                                               + 25  "name""Fido" + 26          },                                                               + 27      ],                                                                   + 28  "graduationYear"null + 29  } + 30   + 31   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -40811,443 +44014,443 @@ font-weight: 700; } - .terminal-1353160984-matrix { + .terminal-772561374-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1353160984-title { + .terminal-772561374-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1353160984-r1 { fill: #1e1e1e } - .terminal-1353160984-r2 { fill: #0178d4 } - .terminal-1353160984-r3 { fill: #c5c8c6 } - .terminal-1353160984-r4 { fill: #c2c2bf } - .terminal-1353160984-r5 { fill: #272822 } - .terminal-1353160984-r6 { fill: #75715e } - .terminal-1353160984-r7 { fill: #f8f8f2 } - .terminal-1353160984-r8 { fill: #90908a } - .terminal-1353160984-r9 { fill: #f92672 } - .terminal-1353160984-r10 { fill: #e6db74 } - .terminal-1353160984-r11 { fill: #ae81ff } - .terminal-1353160984-r12 { fill: #66d9ef;font-style: italic; } - .terminal-1353160984-r13 { fill: #a6e22e } + .terminal-772561374-r1 { fill: #1e1e1e } + .terminal-772561374-r2 { fill: #0178d4 } + .terminal-772561374-r3 { fill: #c5c8c6 } + .terminal-772561374-r4 { fill: #c2c2bf } + .terminal-772561374-r5 { fill: #272822 } + .terminal-772561374-r6 { fill: #75715e } + .terminal-772561374-r7 { fill: #f8f8f2 } + .terminal-772561374-r8 { fill: #90908a } + .terminal-772561374-r9 { fill: #f92672 } + .terminal-772561374-r10 { fill: #e6db74 } + .terminal-772561374-r11 { fill: #ae81ff } + .terminal-772561374-r12 { fill: #66d9ef;font-style: italic; } + .terminal-772561374-r13 { fill: #a6e22e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  // Variables -  2  val name ="John" -  3  var age =30 -  4  var isStudent =true -  5   -  6  // Printing variables -  7  println("Hello, $name! You are $age years old." -  8   -  9  // Conditional statements - 10  when {  - 11      age >=18&& isStudent ->println("You are an adult student." - 12      age >=18->println("You are an adult." - 13  else->println("You are a minor." - 14   - 15   - 16  // Arrays - 17  val numbers =arrayOf(12345 - 18  println("Numbers: ${numbers.contentToString()}" - 19   - 20  // Lists - 21  val fruits =listOf("apple""banana""orange" - 22  println("Fruits: $fruits" - 23   - 24  // Loops - 25  for (num in numbers) {  - 26  println("Number: $num" - 27   - 28   - 29  // Functions - 30  fungreet(name: String) {  - 31  println("Hello, $name!" - 32   - 33  greet("Alice" - 34   - 35  // Lambda functions - 36  val square = { num: Int -> num * num }  - 37  println("Square of 5: ${square(5)}" - 38   - 39  // Extension functions - 40  fun String.reverse(): String {  - 41  return this.reversed() - 42   - 43  val reversed ="Hello".reverse()  - 44  println("Reversed: $reversed" - 45   - 46  // Data classes - 47  dataclass Person(val name: String, val age: Int)  - 48  val person =Person("John"30 - 49  println("Person: $person" - 50   - 51  // Null safety - 52  var nullable: String? =null - 53  println("Length: ${nullable?.length}" - 54   - 55  // Elvis operator - 56  val length = nullable?.length ?:0 - 57  println("Length (Elvis): $length" - 58   - 59  // Smart casts - 60  funprintLength(obj: Any) {  - 61  if (obj is String) {  - 62  println("Length: ${obj.length}" - 63      }  - 64   - 65  printLength("Hello" - 66   - 67  // Object expressions - 68  val comparator =object : Comparator<Int> {  - 69  overridefun compare(a: Int, b: Int): Int {  - 70  return a - b - 71      }  - 72   - 73  val sortedNumbers = numbers.sortedWith(comparator)  - 74  println("Sorted numbers: ${sortedNumbers.contentToString()}" - 75   - 76  // Companion objects - 77  class MyClass {  - 78      companion object {  - 79  funcreate(): MyClass {  - 80  return MyClass() - 81          }  - 82      }  - 83   - 84  val obj = MyClass.create()  - 85   - 86  // Sealed classes - 87  sealedclass Result {  - 88  dataclass Success(val data: String) : Result()  - 89  dataclass Error(val message: String) : Result()  - 90   - 91  val result: Result = Result.Success("Data" - 92  when (result) {  - 93  is Result.Success ->println("Success: ${result.data}" - 94  is Result.Error ->println("Error: ${result.message}" - 95   - 96   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  // Variables +  2  val name ="John" +  3  var age =30 +  4  var isStudent =true +  5   +  6  // Printing variables +  7  println("Hello, $name! You are $age years old.")                         +  8   +  9  // Conditional statements + 10  when {                                                                   + 11      age >=18&& isStudent ->println("You are an adult student.")       + 12      age >=18->println("You are an adult.")                            + 13  else->println("You are a minor.")                                  + 14  }                                                                        + 15   + 16  // Arrays + 17  val numbers =arrayOf(12345)                                     + 18  println("Numbers: ${numbers.contentToString()}")                         + 19   + 20  // Lists + 21  val fruits =listOf("apple""banana""orange")                         + 22  println("Fruits: $fruits")                                               + 23   + 24  // Loops + 25  for (num in numbers) {                                                   + 26  println("Number: $num")                                              + 27  }                                                                        + 28   + 29  // Functions + 30  fungreet(name: String) {                                                + 31  println("Hello, $name!")                                             + 32  }                                                                        + 33  greet("Alice")                                                           + 34   + 35  // Lambda functions + 36  val square = { num: Int -> num * num }                                   + 37  println("Square of 5: ${square(5)}")                                     + 38   + 39  // Extension functions + 40  fun String.reverse(): String {                                           + 41  return this.reversed() + 42  }                                                                        + 43  val reversed ="Hello".reverse()                                         + 44  println("Reversed: $reversed")                                           + 45   + 46  // Data classes + 47  dataclass Person(val name: String, val age: Int)                        + 48  val person =Person("John"30)                                          + 49  println("Person: $person")                                               + 50   + 51  // Null safety + 52  var nullable: String? =null + 53  println("Length: ${nullable?.length}")                                   + 54   + 55  // Elvis operator + 56  val length = nullable?.length ?:0 + 57  println("Length (Elvis): $length")                                       + 58   + 59  // Smart casts + 60  funprintLength(obj: Any) {                                              + 61  if (obj is String) {                                                 + 62  println("Length: ${obj.length}")                                 + 63      }                                                                    + 64  }                                                                        + 65  printLength("Hello")                                                     + 66   + 67  // Object expressions + 68  val comparator =object : Comparator<Int> {                              + 69  overridefun compare(a: Int, b: Int): Int {                          + 70  return a - b + 71      }                                                                    + 72  }                                                                        + 73  val sortedNumbers = numbers.sortedWith(comparator)                       + 74  println("Sorted numbers: ${sortedNumbers.contentToString()}")            + 75   + 76  // Companion objects + 77  class MyClass {                                                          + 78      companion object {                                                   + 79  funcreate(): MyClass {                                          + 80  return MyClass() + 81          }                                                                + 82      }                                                                    + 83  }                                                                        + 84  val obj = MyClass.create()                                               + 85   + 86  // Sealed classes + 87  sealedclass Result {                                                    + 88  dataclass Success(val data: String) : Result()                      + 89  dataclass Error(val message: String) : Result()                     + 90  }                                                                        + 91  val result: Result = Result.Success("Data")                              + 92  when (result) {                                                          + 93  is Result.Success ->println("Success: ${result.data}")              + 94  is Result.Error ->println("Error: ${result.message}")               + 95  }                                                                        + 96   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -41277,332 +44480,332 @@ font-weight: 700; } - .terminal-195314275-matrix { + .terminal-4200118721-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-195314275-title { + .terminal-4200118721-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-195314275-r1 { fill: #1e1e1e } - .terminal-195314275-r2 { fill: #0178d4 } - .terminal-195314275-r3 { fill: #c5c8c6 } - .terminal-195314275-r4 { fill: #c2c2bf } - .terminal-195314275-r5 { fill: #272822;font-weight: bold } - .terminal-195314275-r6 { fill: #f92672;font-weight: bold } - .terminal-195314275-r7 { fill: #f8f8f2 } - .terminal-195314275-r8 { fill: #90908a } - .terminal-195314275-r9 { fill: #f8f8f2;font-style: italic; } - .terminal-195314275-r10 { fill: #f8f8f2;font-weight: bold } - .terminal-195314275-r11 { fill: #e6db74 } - .terminal-195314275-r12 { fill: #75715e } - .terminal-195314275-r13 { fill: #66d9ef;text-decoration: underline; } - .terminal-195314275-r14 { fill: #23568b } + .terminal-4200118721-r1 { fill: #1e1e1e } + .terminal-4200118721-r2 { fill: #0178d4 } + .terminal-4200118721-r3 { fill: #c5c8c6 } + .terminal-4200118721-r4 { fill: #c2c2bf } + .terminal-4200118721-r5 { fill: #272822;font-weight: bold } + .terminal-4200118721-r6 { fill: #f92672;font-weight: bold } + .terminal-4200118721-r7 { fill: #f8f8f2 } + .terminal-4200118721-r8 { fill: #90908a } + .terminal-4200118721-r9 { fill: #f8f8f2;font-style: italic; } + .terminal-4200118721-r10 { fill: #f8f8f2;font-weight: bold } + .terminal-4200118721-r11 { fill: #e6db74 } + .terminal-4200118721-r12 { fill: #75715e } + .terminal-4200118721-r13 { fill: #66d9ef;text-decoration: underline; } + .terminal-4200118721-r14 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  Heading -  2  =======  -  3   -  4  Sub-heading -  5  -----------  -  6   -  7  ### Heading -  8   -  9  #### H4 Heading - 10   - 11  ##### H5 Heading - 12   - 13  ###### H6 Heading - 14   - 15   - 16  Paragraphs are separated  - 17  by a blank line.  - 18   - 19  Two spaces at the end of a line    - 20  produces a line break.  - 21   - 22  Text attributes _italic_,   - 23  **bold**`monospace` - 24   - 25  Horizontal rule:  - 26   - 27  ---  - 28   - 29  Bullet list:  - 30   - 31  * apples  - 32  * oranges  - 33  * pears  - 34   - 35  Numbered list:  - 36   - 37  1. lather  - 38  2. rinse  - 39  3. repeat  - 40   - 41  An [example](http://example.com) - 42   - 43  > Markdown uses email-style > characters for blockquoting.  - 44   - 45  > Lorem ipsum  - 46   - 47  ![progress](https://github.com/textualize/rich/raw/master/imgs/progress. - 48   - 49   - 50  ```  - 51  a=1  - 52  ```  - 53   - 54  ```python  - 55  import this  - 56  ```  - 57   - 58  ```somelang  - 59  foobar  - 60  ```  - 61   - 62      import this  - 63   - 64   - 65  1. List item  - 66   - 67         Code block  - 68   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  Heading +  2  =======                                                                  +  3   +  4  Sub-heading +  5  -----------                                                              +  6   +  7  ### Heading +  8   +  9  #### H4 Heading + 10   + 11  ##### H5 Heading + 12   + 13  ###### H6 Heading + 14   + 15   + 16  Paragraphs are separated                                                 + 17  by a blank line.                                                         + 18   + 19  Two spaces at the end of a line                                          + 20  produces a line break.                                                   + 21   + 22  Text attributes _italic_,                                                + 23  **bold**`monospace`.                                                   + 24   + 25  Horizontal rule:                                                         + 26   + 27  ---                                                                      + 28   + 29  Bullet list:                                                             + 30   + 31  * apples                                                               + 32  * oranges                                                              + 33  * pears                                                                + 34   + 35  Numbered list:                                                           + 36   + 37  1. lather                                                              + 38  2. rinse                                                               + 39  3. repeat                                                              + 40   + 41  An [example](http://example.com).                                        + 42   + 43  > Markdown uses email-style > characters for blockquoting.               + 44  >                                                                        + 45  > Lorem ipsum                                                            + 46   + 47  ![progress](https://github.com/textualize/rich/raw/master/imgs/progress. + 48   + 49   + 50  ```                                                                      + 51  a=1                                                                      + 52  ```                                                                      + 53   + 54  ```python                                                                + 55  import this                                                              + 56  ```                                                                      + 57   + 58  ```somelang                                                              + 59  foobar                                                                   + 60  ```                                                                      + 61   + 62      import this                                                          + 63   + 64   + 65  1. List item                                                             + 66   + 67         Code block                                                        + 68   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -41632,375 +44835,375 @@ font-weight: 700; } - .terminal-3074211860-matrix { + .terminal-3239794038-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3074211860-title { + .terminal-3239794038-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3074211860-r1 { fill: #1e1e1e } - .terminal-3074211860-r2 { fill: #0178d4 } - .terminal-3074211860-r3 { fill: #c5c8c6 } - .terminal-3074211860-r4 { fill: #c2c2bf } - .terminal-3074211860-r5 { fill: #272822 } - .terminal-3074211860-r6 { fill: #f92672 } - .terminal-3074211860-r7 { fill: #f8f8f2 } - .terminal-3074211860-r8 { fill: #90908a } - .terminal-3074211860-r9 { fill: #75715e } - .terminal-3074211860-r10 { fill: #e6db74 } - .terminal-3074211860-r11 { fill: #ae81ff } - .terminal-3074211860-r12 { fill: #a6e22e } - .terminal-3074211860-r13 { fill: #23568b } + .terminal-3239794038-r1 { fill: #1e1e1e } + .terminal-3239794038-r2 { fill: #0178d4 } + .terminal-3239794038-r3 { fill: #c5c8c6 } + .terminal-3239794038-r4 { fill: #c2c2bf } + .terminal-3239794038-r5 { fill: #272822 } + .terminal-3239794038-r6 { fill: #f92672 } + .terminal-3239794038-r7 { fill: #f8f8f2 } + .terminal-3239794038-r8 { fill: #90908a } + .terminal-3239794038-r9 { fill: #75715e } + .terminal-3239794038-r10 { fill: #e6db74 } + .terminal-3239794038-r11 { fill: #ae81ff } + .terminal-3239794038-r12 { fill: #a6e22e } + .terminal-3239794038-r13 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  import math                                                              -  2  from os import path  -  3   -  4  # I'm a comment :) -  5   -  6  string_var ="Hello, world!" -  7  int_var =42 -  8  float_var =3.14 -  9  complex_var =1+2j - 10   - 11  list_var = [12345 - 12  tuple_var = (12345 - 13  set_var = {12345 - 14  dict_var = {"a"1"b"2"c"3 - 15   - 16  deffunction_no_args():  - 17  return"No arguments" - 18   - 19  deffunction_with_args(a, b):  - 20  return a + b  - 21   - 22  deffunction_with_default_args(a=0, b=0):  - 23  return a * b  - 24   - 25  lambda_func =lambda x: x**2 - 26   - 27  if int_var ==42 - 28  print("It's the answer!" - 29  elif int_var <42 - 30  print("Less than the answer." - 31  else - 32  print("Greater than the answer." - 33   - 34  for index, value inenumerate(list_var):  - 35  print(f"Index: {index}, Value: {value}" - 36   - 37  counter =0 - 38  while counter <5 - 39  print(f"Counter value: {counter}" - 40      counter +=1 - 41   - 42  squared_numbers = [x**2for x inrange(10if x %2==0 - 43   - 44  try - 45      result =10/0 - 46  except ZeroDivisionError:  - 47  print("Cannot divide by zero!" - 48  finally - 49  print("End of try-except block." - 50   - 51  classAnimal - 52  def__init__(self, name):  - 53          self.name = name  - 54   - 55  defspeak(self):  - 56  raiseNotImplementedError("Subclasses must implement this method - 57   - 58  classDog(Animal):  - 59  defspeak(self):  - 60  returnf"{self.name} says Woof!" - 61   - 62  deffibonacci(n):  - 63      a, b =01 - 64  for _ inrange(n):  - 65  yield a  - 66          a, b = b, a + b  - 67   - 68  for num infibonacci(5):  - 69  print(num)  - 70   - 71  withopen('test.txt''w'as f:  - 72      f.write("Testing with statement." - 73   - 74  @my_decorator  - 75  defsay_hello():  - 76  print("Hello!" - 77   - 78  say_hello()  - 79   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  import math                                                              +  2  from os import path                                                      +  3   +  4  # I'm a comment :) +  5   +  6  string_var ="Hello, world!" +  7  int_var =42 +  8  float_var =3.14 +  9  complex_var =1+2j + 10   + 11  list_var = [12345]                                               + 12  tuple_var = (12345)                                              + 13  set_var = {12345}                                                + 14  dict_var = {"a"1"b"2"c"3}                                      + 15   + 16  deffunction_no_args():                                                  + 17  return"No arguments" + 18   + 19  deffunction_with_args(a, b):                                            + 20  return a + b                                                         + 21   + 22  deffunction_with_default_args(a=0, b=0):                                + 23  return a * b                                                         + 24   + 25  lambda_func =lambda x: x**2 + 26   + 27  if int_var ==42:                                                        + 28  print("It's the answer!")                                            + 29  elif int_var <42:                                                       + 30  print("Less than the answer.")                                       + 31  else:                                                                    + 32  print("Greater than the answer.")                                    + 33   + 34  for index, value inenumerate(list_var):                                 + 35  print(f"Index: {index}, Value: {value}")                             + 36   + 37  counter =0 + 38  while counter <5:                                                       + 39  print(f"Counter value: {counter}")                                   + 40      counter +=1 + 41   + 42  squared_numbers = [x**2for x inrange(10if x %2==0]                + 43   + 44  try:                                                                     + 45      result =10/0 + 46  except ZeroDivisionError:                                                + 47  print("Cannot divide by zero!")                                      + 48  finally:                                                                 + 49  print("End of try-except block.")                                    + 50   + 51  classAnimal:                                                            + 52  def__init__(self, name):                                            + 53          self.name = name                                                 + 54   + 55  defspeak(self):                                                     + 56  raiseNotImplementedError("Subclasses must implement this method + 57   + 58  classDog(Animal):                                                       + 59  defspeak(self):                                                     + 60  returnf"{self.name} says Woof!" + 61   + 62  deffibonacci(n):                                                        + 63      a, b =01 + 64  for _ inrange(n):                                                   + 65  yield a                                                          + 66          a, b = b, a + b                                                  + 67   + 68  for num infibonacci(5):                                                 + 69  print(num)                                                           + 70   + 71  withopen('test.txt''w'as f:                                         + 72      f.write("Testing with statement.")                                   + 73   + 74  @my_decorator                                                            + 75  defsay_hello():                                                         + 76  print("Hello!")                                                      + 77   + 78  say_hello()                                                              + 79   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -42030,157 +45233,157 @@ font-weight: 700; } - .terminal-3279709-matrix { + .terminal-1112497941-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3279709-title { + .terminal-1112497941-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3279709-r1 { fill: #1e1e1e } - .terminal-3279709-r2 { fill: #0178d4 } - .terminal-3279709-r3 { fill: #c5c8c6 } - .terminal-3279709-r4 { fill: #c2c2bf } - .terminal-3279709-r5 { fill: #272822 } - .terminal-3279709-r6 { fill: #e6db74 } - .terminal-3279709-r7 { fill: #f8f8f2 } - .terminal-3279709-r8 { fill: #90908a } - .terminal-3279709-r9 { fill: #f92672 } - .terminal-3279709-r10 { fill: #ae81ff } - .terminal-3279709-r11 { fill: #23568b } + .terminal-1112497941-r1 { fill: #1e1e1e } + .terminal-1112497941-r2 { fill: #0178d4 } + .terminal-1112497941-r3 { fill: #c5c8c6 } + .terminal-1112497941-r4 { fill: #c2c2bf } + .terminal-1112497941-r5 { fill: #272822 } + .terminal-1112497941-r6 { fill: #e6db74 } + .terminal-1112497941-r7 { fill: #f8f8f2 } + .terminal-1112497941-r8 { fill: #90908a } + .terminal-1112497941-r9 { fill: #f92672 } + .terminal-1112497941-r10 { fill: #ae81ff } + .terminal-1112497941-r11 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  ^abc            # Matches any string that starts with "abc" -  2  abc$            # Matches any string that ends with "abc" -  3  ^abc$           # Matches the string "abc" and nothing else -  4  a.b             # Matches any string containing "a", any character, then -  5  a[.]b           # Matches the string "a.b" -  6  a|b             # Matches either "a" or "b" -  7  a{2}            # Matches "aa" -  8  a{2,}           # Matches two or more consecutive "a" characters -  9  a{2,5}          # Matches between 2 and 5 consecutive "a" characters - 10  a?              # Matches "a" or nothing (0 or 1 occurrence of "a" - 11  a*              # Matches zero or more consecutive "a" characters - 12  a+              # Matches one or more consecutive "a" characters - 13  \d              # Matches any digit (equivalent to [0-9])  - 14  \D              # Matches any non-digit - 15  \w              # Matches any word character (equivalent to [a-zA-Z0-9_] - 16  \W              # Matches any non-word character - 17  \s              # Matches any whitespace character (spaces, tabs, line b - 18  \S              # Matches any non-whitespace character - 19  (?i)abc         # Case-insensitive match for "abc" - 20  (?:a|b)         # Non-capturing group for either "a" or "b" - 21  (?<=a)b         # Positive lookbehind: matches "b" that is preceded by " - 22  (?<!a)b         # Negative lookbehind: matches "b" that is not preceded  - 23  a(?=b)          # Positive lookahead: matches "a" that is followed by "b - 24  a(?!b)          # Negative lookahead: matches "a" that is not followed b - 25   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  ^abc            # Matches any string that starts with "abc" +  2  abc$            # Matches any string that ends with "abc" +  3  ^abc$           # Matches the string "abc" and nothing else +  4  a.b             # Matches any string containing "a", any character, then +  5  a[.]b           # Matches the string "a.b" +  6  a|b             # Matches either "a" or "b" +  7  a{2}            # Matches "aa" +  8  a{2,}           # Matches two or more consecutive "a" characters +  9  a{2,5}          # Matches between 2 and 5 consecutive "a" characters + 10  a?              # Matches "a" or nothing (0 or 1 occurrence of "a")      + 11  a*              # Matches zero or more consecutive "a" characters + 12  a+              # Matches one or more consecutive "a" characters + 13  \d              # Matches any digit (equivalent to [0-9])                + 14  \D              # Matches any non-digit + 15  \w              # Matches any word character (equivalent to [a-zA-Z0-9_] + 16  \W              # Matches any non-word character + 17  \s              # Matches any whitespace character (spaces, tabs, line b + 18  \S              # Matches any non-whitespace character + 19  (?i)abc         # Case-insensitive match for "abc" + 20  (?:a|b)         # Non-capturing group for either "a" or "b" + 21  (?<=a)b         # Positive lookbehind: matches "b" that is preceded by " + 22  (?<!a)b         # Negative lookbehind: matches "b" that is not preceded  + 23  a(?=b)          # Positive lookahead: matches "a" that is followed by "b + 24  a(?!b)          # Negative lookahead: matches "a" that is not followed b + 25   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -42210,479 +45413,479 @@ font-weight: 700; } - .terminal-3348148056-matrix { + .terminal-4012336662-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3348148056-title { + .terminal-4012336662-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3348148056-r1 { fill: #1e1e1e } - .terminal-3348148056-r2 { fill: #0178d4 } - .terminal-3348148056-r3 { fill: #c5c8c6 } - .terminal-3348148056-r4 { fill: #c2c2bf } - .terminal-3348148056-r5 { fill: #272822 } - .terminal-3348148056-r6 { fill: #f92672 } - .terminal-3348148056-r7 { fill: #f8f8f2 } - .terminal-3348148056-r8 { fill: #90908a } - .terminal-3348148056-r9 { fill: #75715e } - .terminal-3348148056-r10 { fill: #66d9ef;font-style: italic; } - .terminal-3348148056-r11 { fill: #a6e22e } - .terminal-3348148056-r12 { fill: #e6db74 } - .terminal-3348148056-r13 { fill: #23568b } + .terminal-4012336662-r1 { fill: #1e1e1e } + .terminal-4012336662-r2 { fill: #0178d4 } + .terminal-4012336662-r3 { fill: #c5c8c6 } + .terminal-4012336662-r4 { fill: #c2c2bf } + .terminal-4012336662-r5 { fill: #272822 } + .terminal-4012336662-r6 { fill: #f92672 } + .terminal-4012336662-r7 { fill: #f8f8f2 } + .terminal-4012336662-r8 { fill: #90908a } + .terminal-4012336662-r9 { fill: #75715e } + .terminal-4012336662-r10 { fill: #66d9ef;font-style: italic; } + .terminal-4012336662-r11 { fill: #a6e22e } + .terminal-4012336662-r12 { fill: #e6db74 } + .terminal-4012336662-r13 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -   1  use std::collections::HashMap;                                          -   2   -   3  // Constants -   4  const PI: f64 = 3.14159 -   5   -   6  // Structs -   7  struct Rectangle {  -   8      width: u32,  -   9      height: u32,  -  10   -  11   -  12  impl Rectangle {  -  13  fnarea(&self) -> u32 {  -  14          self.width * self.height  -  15      }  -  16   -  17   -  18  // Enums -  19  enum Result<T, E> {  -  20      Ok(T),  -  21      Err(E),  -  22   -  23   -  24  // Functions -  25  fngreet(name: &str) {  -  26      println!("Hello, {}!", name);  -  27   -  28   -  29  fnmain() {  -  30  // Variables -  31  let name = "John" -  32  letmut age = 30 -  33  let is_student = true -  34   -  35  // Printing variables -  36      println!("Hello, {}! You are {} years old.", name, age);  -  37   -  38  // Conditional statements -  39  if age >= 18 && is_student {  -  40          println!("You are an adult student.");  -  41      } elseif age >= 18 {  -  42          println!("You are an adult.");  -  43      } else {  -  44          println!("You are a minor.");  -  45      }  -  46   -  47  // Arrays -  48  let numbers = [12345];  -  49      println!("Numbers: {:?}", numbers);  -  50   -  51  // Vectors -  52  letmut fruits = vec!["apple""banana""orange"];  -  53      fruits.push("grape");  -  54      println!("Fruits: {:?}", fruits);  -  55   -  56  // Loops -  57  for num in&numbers {  -  58          println!("Number: {}", num);  -  59      }  -  60   -  61  // Pattern matching -  62  let result = Result::Ok(42);  -  63  match result {  -  64          Result::Ok(value) => println!("Value: {}", value),  -  65          Result::Err(error) => println!("Error: {:?}", error),  -  66      }  -  67   -  68  // Ownership and borrowing -  69  let s1 = String::from("hello");  -  70  let s2 = s1.clone();  -  71      println!("s1: {}, s2: {}", s1, s2);  -  72   -  73  // References -  74  let rect = Rectangle {  -  75          width: 10 -  76          height: 20 -  77      };  -  78      println!("Rectangle area: {}", rect.area());  -  79   -  80  // Hash maps -  81  letmut scores = HashMap::new();  -  82      scores.insert("Alice"100);  -  83      scores.insert("Bob"80);  -  84      println!("Alice's score: {}", scores["Alice"]);  -  85   -  86  // Closures -  87  let square = |num: i32| num * num;  -  88      println!("Square of 5: {}", square(5));  -  89   -  90  // Traits -  91  trait Printable {  -  92  fnprint(&self);  -  93      }  -  94   -  95  impl Printable for Rectangle {  -  96  fnprint(&self) {  -  97              println!("Rectangle: width={}, height={}", self.width, self -  98          }  -  99      }  - 100      rect.print();  - 101   - 102  // Modules - 103  greet("Alice");  - 104   - 105   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +   1  use std::collections::HashMap;                                          +   2   +   3  // Constants +   4  const PI: f64 = 3.14159;                                                +   5   +   6  // Structs +   7  struct Rectangle {                                                      +   8      width: u32,                                                         +   9      height: u32,                                                        +  10  }                                                                       +  11   +  12  impl Rectangle {                                                        +  13  fnarea(&self) -> u32 {                                             +  14          self.width * self.height                                        +  15      }                                                                   +  16  }                                                                       +  17   +  18  // Enums +  19  enum Result<T, E> {                                                     +  20      Ok(T),                                                              +  21      Err(E),                                                             +  22  }                                                                       +  23   +  24  // Functions +  25  fngreet(name: &str) {                                                  +  26      println!("Hello, {}!", name);                                       +  27  }                                                                       +  28   +  29  fnmain() {                                                             +  30  // Variables +  31  let name = "John";                                                  +  32  letmut age = 30;                                                   +  33  let is_student = true;                                              +  34   +  35  // Printing variables +  36      println!("Hello, {}! You are {} years old.", name, age);            +  37   +  38  // Conditional statements +  39  if age >= 18 && is_student {                                        +  40          println!("You are an adult student.");                          +  41      } elseif age >= 18 {                                               +  42          println!("You are an adult.");                                  +  43      } else {                                                            +  44          println!("You are a minor.");                                   +  45      }                                                                   +  46   +  47  // Arrays +  48  let numbers = [12345];                                      +  49      println!("Numbers: {:?}", numbers);                                 +  50   +  51  // Vectors +  52  letmut fruits = vec!["apple""banana""orange"];                 +  53      fruits.push("grape");                                               +  54      println!("Fruits: {:?}", fruits);                                   +  55   +  56  // Loops +  57  for num in&numbers {                                               +  58          println!("Number: {}", num);                                    +  59      }                                                                   +  60   +  61  // Pattern matching +  62  let result = Result::Ok(42);                                        +  63  match result {                                                      +  64          Result::Ok(value) => println!("Value: {}", value),              +  65          Result::Err(error) => println!("Error: {:?}", error),           +  66      }                                                                   +  67   +  68  // Ownership and borrowing +  69  let s1 = String::from("hello");                                     +  70  let s2 = s1.clone();                                                +  71      println!("s1: {}, s2: {}", s1, s2);                                 +  72   +  73  // References +  74  let rect = Rectangle {                                              +  75          width: 10,                                                      +  76          height: 20,                                                     +  77      };                                                                  +  78      println!("Rectangle area: {}", rect.area());                        +  79   +  80  // Hash maps +  81  letmut scores = HashMap::new();                                    +  82      scores.insert("Alice"100);                                        +  83      scores.insert("Bob"80);                                           +  84      println!("Alice's score: {}", scores["Alice"]);                     +  85   +  86  // Closures +  87  let square = |num: i32| num * num;                                  +  88      println!("Square of 5: {}", square(5));                             +  89   +  90  // Traits +  91  trait Printable {                                                   +  92  fnprint(&self);                                                +  93      }                                                                   +  94   +  95  impl Printable for Rectangle {                                      +  96  fnprint(&self) {                                               +  97              println!("Rectangle: width={}, height={}", self.width, self +  98          }                                                               +  99      }                                                                   + 100      rect.print();                                                       + 101   + 102  // Modules + 103  greet("Alice");                                                     + 104  }                                                                       + 105   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -42712,235 +45915,235 @@ font-weight: 700; } - .terminal-276599044-matrix { + .terminal-2922741550-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-276599044-title { + .terminal-2922741550-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-276599044-r1 { fill: #1e1e1e } - .terminal-276599044-r2 { fill: #0178d4 } - .terminal-276599044-r3 { fill: #c5c8c6 } - .terminal-276599044-r4 { fill: #c2c2bf } - .terminal-276599044-r5 { fill: #272822 } - .terminal-276599044-r6 { fill: #75715e } - .terminal-276599044-r7 { fill: #f8f8f2 } - .terminal-276599044-r8 { fill: #90908a } - .terminal-276599044-r9 { fill: #f92672 } - .terminal-276599044-r10 { fill: #ae81ff } - .terminal-276599044-r11 { fill: #66d9ef;font-style: italic; } - .terminal-276599044-r12 { fill: #e6db74 } - .terminal-276599044-r13 { fill: #23568b } + .terminal-2922741550-r1 { fill: #1e1e1e } + .terminal-2922741550-r2 { fill: #0178d4 } + .terminal-2922741550-r3 { fill: #c5c8c6 } + .terminal-2922741550-r4 { fill: #c2c2bf } + .terminal-2922741550-r5 { fill: #272822 } + .terminal-2922741550-r6 { fill: #75715e } + .terminal-2922741550-r7 { fill: #f8f8f2 } + .terminal-2922741550-r8 { fill: #90908a } + .terminal-2922741550-r9 { fill: #f92672 } + .terminal-2922741550-r10 { fill: #ae81ff } + .terminal-2922741550-r11 { fill: #66d9ef;font-style: italic; } + .terminal-2922741550-r12 { fill: #e6db74 } + .terminal-2922741550-r13 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  -- This is a comment in SQL -  2   -  3  -- Create tables -  4  CREATETABLE Authors (  -  5      AuthorID INT PRIMARY KEY -  6      Name VARCHAR(255NOT NULL -  7      Country VARCHAR(50 -  8  );  -  9   - 10  CREATETABLE Books (  - 11      BookID INT PRIMARY KEY - 12      Title VARCHAR(255NOT NULL - 13      AuthorID INT,  - 14      PublishedDate DATE,  - 15      FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID)  - 16  );  - 17   - 18  -- Insert data - 19  INSERTINTO Authors (AuthorID, Name, Country) VALUES (1'George Orwell' - 20   - 21  INSERTINTO Books (BookID, Title, AuthorID, PublishedDate) VALUES (1'1 - 22   - 23  -- Update data - 24  UPDATE Authors SET Country ='United Kingdom'WHERE Country ='UK' - 25   - 26  -- Select data with JOIN - 27  SELECT Books.Title, Authors.Name   - 28  FROM Books   - 29  JOIN Authors ON Books.AuthorID = Authors.AuthorID;  - 30   - 31  -- Delete data (commented to preserve data for other examples) - 32  -- DELETE FROM Books WHERE BookID = 1; - 33   - 34  -- Alter table structure - 35  ALTER TABLE Authors ADD COLUMN BirthDate DATE;  - 36   - 37  -- Create index - 38  CREATEINDEX idx_author_name ON Authors(Name);  - 39   - 40  -- Drop index (commented to avoid actually dropping it) - 41  -- DROP INDEX idx_author_name ON Authors; - 42   - 43  -- End of script - 44   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  -- This is a comment in SQL +  2   +  3  -- Create tables +  4  CREATETABLE Authors (                                                   +  5      AuthorID INT PRIMARY KEY,                                            +  6      Name VARCHAR(255NOT NULL,                                          +  7      Country VARCHAR(50)                                                  +  8  );                                                                       +  9   + 10  CREATETABLE Books (                                                     + 11      BookID INT PRIMARY KEY,                                              + 12      Title VARCHAR(255NOT NULL,                                         + 13      AuthorID INT,                                                        + 14      PublishedDate DATE,                                                  + 15      FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID)                  + 16  );                                                                       + 17   + 18  -- Insert data + 19  INSERTINTO Authors (AuthorID, Name, Country) VALUES (1'George Orwell' + 20   + 21  INSERTINTO Books (BookID, Title, AuthorID, PublishedDate) VALUES (1'1 + 22   + 23  -- Update data + 24  UPDATE Authors SET Country ='United Kingdom'WHERE Country ='UK';      + 25   + 26  -- Select data with JOIN + 27  SELECT Books.Title, Authors.Name                                         + 28  FROM Books                                                               + 29  JOIN Authors ON Books.AuthorID = Authors.AuthorID;                       + 30   + 31  -- Delete data (commented to preserve data for other examples) + 32  -- DELETE FROM Books WHERE BookID = 1; + 33   + 34  -- Alter table structure + 35  ALTER TABLE Authors ADD COLUMN BirthDate DATE;                           + 36   + 37  -- Create index + 38  CREATEINDEX idx_author_name ON Authors(Name);                           + 39   + 40  -- Drop index (commented to avoid actually dropping it) + 41  -- DROP INDEX idx_author_name ON Authors; + 42   + 43  -- End of script + 44   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -42970,162 +46173,162 @@ font-weight: 700; } - .terminal-2370608276-matrix { + .terminal-2854928942-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2370608276-title { + .terminal-2854928942-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2370608276-r1 { fill: #1e1e1e } - .terminal-2370608276-r2 { fill: #0178d4 } - .terminal-2370608276-r3 { fill: #c5c8c6 } - .terminal-2370608276-r4 { fill: #c2c2bf } - .terminal-2370608276-r5 { fill: #272822 } - .terminal-2370608276-r6 { fill: #75715e } - .terminal-2370608276-r7 { fill: #f8f8f2 } - .terminal-2370608276-r8 { fill: #90908a } - .terminal-2370608276-r9 { fill: #f92672 } - .terminal-2370608276-r10 { fill: #e6db74 } - .terminal-2370608276-r11 { fill: #ae81ff } - .terminal-2370608276-r12 { fill: #66d9ef;font-style: italic; } + .terminal-2854928942-r1 { fill: #1e1e1e } + .terminal-2854928942-r2 { fill: #0178d4 } + .terminal-2854928942-r3 { fill: #c5c8c6 } + .terminal-2854928942-r4 { fill: #c2c2bf } + .terminal-2854928942-r5 { fill: #272822 } + .terminal-2854928942-r6 { fill: #75715e } + .terminal-2854928942-r7 { fill: #f8f8f2 } + .terminal-2854928942-r8 { fill: #90908a } + .terminal-2854928942-r9 { fill: #f92672 } + .terminal-2854928942-r10 { fill: #e6db74 } + .terminal-2854928942-r11 { fill: #ae81ff } + .terminal-2854928942-r12 { fill: #66d9ef;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  # This is a comment in TOML -  2   -  3  string = "Hello, world!" -  4  integer = 42 -  5  float = 3.14 -  6  boolean = true -  7  datetime = 1979-05-27T07:32:00Z -  8   -  9  fruits = ["apple""banana""cherry" - 10   - 11  [address - 12  street = "123 Main St" - 13  city = "Anytown" - 14  state = "CA" - 15  zip = "12345" - 16   - 17  [person.john - 18  name = "John Doe" - 19  age = 28 - 20  is_student = false - 21   - 22   - 23  [[animals]]  - 24  name = "Fido" - 25  type = "dog" - 26   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  # This is a comment in TOML +  2   +  3  string = "Hello, world!" +  4  integer = 42 +  5  float = 3.14 +  6  boolean = true +  7  datetime = 1979-05-27T07:32:00Z +  8   +  9  fruits = ["apple""banana""cherry"]                                   + 10   + 11  [address]                                                                + 12  street = "123 Main St" + 13  city = "Anytown" + 14  state = "CA" + 15  zip = "12345" + 16   + 17  [person.john]                                                            + 18  name = "John Doe" + 19  age = 28 + 20  is_student = false + 21   + 22   + 23  [[animals]]                                                              + 24  name = "Fido" + 25  type = "dog" + 26   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -43155,210 +46358,307 @@ font-weight: 700; } - .terminal-2030166811-matrix { + .terminal-245524493-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2030166811-title { + .terminal-245524493-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2030166811-r1 { fill: #1e1e1e } - .terminal-2030166811-r2 { fill: #0178d4 } - .terminal-2030166811-r3 { fill: #c5c8c6 } - .terminal-2030166811-r4 { fill: #c2c2bf } - .terminal-2030166811-r5 { fill: #272822 } - .terminal-2030166811-r6 { fill: #75715e } - .terminal-2030166811-r7 { fill: #f8f8f2 } - .terminal-2030166811-r8 { fill: #90908a } - .terminal-2030166811-r9 { fill: #f92672;font-weight: bold } - .terminal-2030166811-r10 { fill: #e6db74 } - .terminal-2030166811-r11 { fill: #ae81ff } - .terminal-2030166811-r12 { fill: #66d9ef;font-style: italic; } + .terminal-245524493-r1 { fill: #1e1e1e } + .terminal-245524493-r2 { fill: #0178d4 } + .terminal-245524493-r3 { fill: #c5c8c6 } + .terminal-245524493-r4 { fill: #c2c2bf } + .terminal-245524493-r5 { fill: #272822 } + .terminal-245524493-r6 { fill: #75715e } + .terminal-245524493-r7 { fill: #f8f8f2 } + .terminal-245524493-r8 { fill: #90908a } + .terminal-245524493-r9 { fill: #f92672;font-weight: bold } + .terminal-245524493-r10 { fill: #e6db74 } + .terminal-245524493-r11 { fill: #ae81ff } + .terminal-245524493-r12 { fill: #66d9ef;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  # This is a comment in YAML +  2   +  3  # Scalars +  4  string"Hello, world!" +  5  integer42 +  6  float3.14 +  7  booleantrue +  8   +  9  # Sequences (Arrays) + 10  fruits:                                                                  + 11    - Apple + 12    - Banana + 13    - Cherry + 14   + 15  # Nested sequences + 16  persons:                                                                 + 17    - nameJohn + 18  age28 + 19  is_studentfalse + 20    - nameJane + 21  age22 + 22  is_studenttrue + 23   + 24  # Mappings (Dictionaries) + 25  address:                                                                 + 26  street123 Main St + 27  cityAnytown + 28  stateCA + 29  zip'12345' + 30   + 31  # Multiline string + 32  description + 33    This is a multiline  + 34    string in YAML. + 35   + 36  # Inline and nested collections + 37  colors: { redFF0000green00FF00blue0000FF }                     + 38   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + ''' +# --- +# name: test_text_area_line_number_start + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LineNumbersReactive - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  # This is a comment in YAML -  2   -  3  # Scalars -  4  string"Hello, world!" -  5  integer42 -  6  float3.14 -  7  booleantrue -  8   -  9  # Sequences (Arrays) - 10  fruits - 11    - Apple - 12    - Banana - 13    - Cherry - 14   - 15  # Nested sequences - 16  persons - 17    - nameJohn - 18  age28 - 19  is_studentfalse - 20    - nameJane - 21  age22 - 22  is_studenttrue - 23   - 24  # Mappings (Dictionaries) - 25  address - 26  street123 Main St - 27  cityAnytown - 28  stateCA - 29  zip'12345' - 30   - 31  # Multiline string - 32  description - 33    This is a multiline  - 34    string in YAML. - 35   - 36  # Inline and nested collections - 37  colors: { redFF0000green00FF00blue0000FF }  - 38   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎ +  9999  Foo                   + 10000  Bar                   + 10001  Baz                   + 10002   + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎ @@ -43388,60 +46688,60 @@ font-weight: 700; } - .terminal-822131592-matrix { + .terminal-745351744-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-822131592-title { + .terminal-745351744-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-822131592-r1 { fill: #1e1e1e } - .terminal-822131592-r2 { fill: #0178d4 } - .terminal-822131592-r3 { fill: #c5c8c6 } - .terminal-822131592-r4 { fill: #abaca9;font-weight: bold } - .terminal-822131592-r5 { fill: #170e01 } - .terminal-822131592-r6 { fill: #f8f8f2 } + .terminal-745351744-r1 { fill: #1e1e1e } + .terminal-745351744-r2 { fill: #0178d4 } + .terminal-745351744-r3 { fill: #c5c8c6 } + .terminal-745351744-r4 { fill: #abaca9;font-weight: bold } + .terminal-745351744-r5 { fill: #170e01 } + .terminal-745351744-r6 { fill: #f8f8f2 } - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1  Hello, world!           - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1  Hello, world!           + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -43471,72 +46771,72 @@ font-weight: 700; } - .terminal-1664373573-matrix { + .terminal-135020751-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1664373573-title { + .terminal-135020751-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1664373573-r1 { fill: #1e1e1e } - .terminal-1664373573-r2 { fill: #0178d4 } - .terminal-1664373573-r3 { fill: #c5c8c6 } - .terminal-1664373573-r4 { fill: #f8f8f2 } - .terminal-1664373573-r5 { fill: #65686a } - .terminal-1664373573-r6 { fill: #272822 } + .terminal-135020751-r1 { fill: #1e1e1e } + .terminal-135020751-r2 { fill: #0178d4 } + .terminal-135020751-r3 { fill: #c5c8c6 } + .terminal-135020751-r4 { fill: #f8f8f2 } + .terminal-135020751-r5 { fill: #65686a } + .terminal-135020751-r6 { fill: #272822 } - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I am a line. - - I am another line.         - - I am the final line.  - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I am a line. + + I am another line.         + + I am the final line.       + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -43566,72 +46866,72 @@ font-weight: 700; } - .terminal-2072628212-matrix { + .terminal-2909452670-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2072628212-title { + .terminal-2909452670-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2072628212-r1 { fill: #1e1e1e } - .terminal-2072628212-r2 { fill: #0178d4 } - .terminal-2072628212-r3 { fill: #c5c8c6 } - .terminal-2072628212-r4 { fill: #f8f8f2 } - .terminal-2072628212-r5 { fill: #272822 } - .terminal-2072628212-r6 { fill: #65686a } + .terminal-2909452670-r1 { fill: #1e1e1e } + .terminal-2909452670-r2 { fill: #0178d4 } + .terminal-2909452670-r3 { fill: #c5c8c6 } + .terminal-2909452670-r4 { fill: #f8f8f2 } + .terminal-2909452670-r5 { fill: #272822 } + .terminal-2909452670-r6 { fill: #65686a } - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I am a line. - - I am another line.  - - I am the final line.  - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I am a line. + + I am another line.         + + I am the final line.       + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -43661,72 +46961,72 @@ font-weight: 700; } - .terminal-1647941616-matrix { + .terminal-1050134715-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1647941616-title { + .terminal-1050134715-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1647941616-r1 { fill: #1e1e1e } - .terminal-1647941616-r2 { fill: #0178d4 } - .terminal-1647941616-r3 { fill: #c5c8c6 } - .terminal-1647941616-r4 { fill: #f8f8f2 } - .terminal-1647941616-r5 { fill: #272822 } - .terminal-1647941616-r6 { fill: #65686a } + .terminal-1050134715-r1 { fill: #1e1e1e } + .terminal-1050134715-r2 { fill: #0178d4 } + .terminal-1050134715-r3 { fill: #c5c8c6 } + .terminal-1050134715-r4 { fill: #f8f8f2 } + .terminal-1050134715-r5 { fill: #272822 } + .terminal-1050134715-r6 { fill: #65686a } - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I am a line. - - I am another line. - - I am the final line.  - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I am a line. + + I am another line. + + I am the final line.       + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -43756,72 +47056,72 @@ font-weight: 700; } - .terminal-2865766209-matrix { + .terminal-2591379468-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2865766209-title { + .terminal-2591379468-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2865766209-r1 { fill: #1e1e1e } - .terminal-2865766209-r2 { fill: #0178d4 } - .terminal-2865766209-r3 { fill: #c5c8c6 } - .terminal-2865766209-r4 { fill: #f8f8f2 } - .terminal-2865766209-r5 { fill: #65686a } - .terminal-2865766209-r6 { fill: #272822 } + .terminal-2591379468-r1 { fill: #1e1e1e } + .terminal-2591379468-r2 { fill: #0178d4 } + .terminal-2591379468-r3 { fill: #c5c8c6 } + .terminal-2591379468-r4 { fill: #f8f8f2 } + .terminal-2591379468-r5 { fill: #65686a } + .terminal-2591379468-r6 { fill: #272822 } - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I am a line. - - I am another line. - - I am the final line. - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I am a line. + + I am another line. + + I am the final line. + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -43851,71 +47151,71 @@ font-weight: 700; } - .terminal-1829716808-matrix { + .terminal-622980482-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1829716808-title { + .terminal-622980482-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1829716808-r1 { fill: #1e1e1e } - .terminal-1829716808-r2 { fill: #0178d4 } - .terminal-1829716808-r3 { fill: #c5c8c6 } - .terminal-1829716808-r4 { fill: #f8f8f2 } - .terminal-1829716808-r5 { fill: #272822 } + .terminal-622980482-r1 { fill: #1e1e1e } + .terminal-622980482-r2 { fill: #0178d4 } + .terminal-622980482-r3 { fill: #c5c8c6 } + .terminal-622980482-r4 { fill: #f8f8f2 } + .terminal-622980482-r5 { fill: #272822 } - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I am a line.  - - I am another line.  - - I am the final line.  - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I am a line.               + + I am another line.         + + I am the final line.       + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -43945,71 +47245,71 @@ font-weight: 700; } - .terminal-3991176931-matrix { + .terminal-1932865836-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3991176931-title { + .terminal-1932865836-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3991176931-r1 { fill: #1e1e1e } - .terminal-3991176931-r2 { fill: #0178d4 } - .terminal-3991176931-r3 { fill: #c5c8c6 } - .terminal-3991176931-r4 { fill: #f8f8f2 } - .terminal-3991176931-r5 { fill: #272822 } + .terminal-1932865836-r1 { fill: #1e1e1e } + .terminal-1932865836-r2 { fill: #0178d4 } + .terminal-1932865836-r3 { fill: #c5c8c6 } + .terminal-1932865836-r4 { fill: #f8f8f2 } + .terminal-1932865836-r5 { fill: #272822 } - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I am a line.  - - I am another line.         - - I am the final line.  - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I am a line.               + + I am another line.         + + I am the final line.       + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -44039,82 +47339,82 @@ font-weight: 700; } - .terminal-421307802-matrix { + .terminal-4041936034-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-421307802-title { + .terminal-4041936034-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-421307802-r1 { fill: #1e1e1e } - .terminal-421307802-r2 { fill: #0178d4 } - .terminal-421307802-r3 { fill: #c5c8c6 } - .terminal-421307802-r4 { fill: #7d7e7a } - .terminal-421307802-r5 { fill: #569cd6 } - .terminal-421307802-r6 { fill: #f8f8f2 } - .terminal-421307802-r7 { fill: #4ec9b0 } - .terminal-421307802-r8 { fill: #abaca9;font-weight: bold } - .terminal-421307802-r9 { fill: #b5cea8 } - .terminal-421307802-r10 { fill: #151515 } - .terminal-421307802-r11 { fill: #7daf9c } - .terminal-421307802-r12 { fill: #ce9178 } + .terminal-4041936034-r1 { fill: #1e1e1e } + .terminal-4041936034-r2 { fill: #0178d4 } + .terminal-4041936034-r3 { fill: #c5c8c6 } + .terminal-4041936034-r4 { fill: #7d7e7a } + .terminal-4041936034-r5 { fill: #569cd6 } + .terminal-4041936034-r6 { fill: #f8f8f2 } + .terminal-4041936034-r7 { fill: #4ec9b0 } + .terminal-4041936034-r8 { fill: #abaca9;font-weight: bold } + .terminal-4041936034-r9 { fill: #b5cea8 } + .terminal-4041936034-r10 { fill: #151515 } + .terminal-4041936034-r11 { fill: #7daf9c } + .terminal-4041936034-r12 { fill: #ce9178 } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1  defhello(name): - 2      x =123 - 3  whilenotFalse - 4  print("hello "+ name)  - 5  continue - 6   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1  defhello(name): + 2      x =123 + 3  whilenotFalse:                      + 4  print("hello "+ name)            + 5  continue + 6   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -44144,81 +47444,81 @@ font-weight: 700; } - .terminal-1555804657-matrix { + .terminal-502077087-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1555804657-title { + .terminal-502077087-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1555804657-r1 { fill: #1e1e1e } - .terminal-1555804657-r2 { fill: #0178d4 } - .terminal-1555804657-r3 { fill: #c5c8c6 } - .terminal-1555804657-r4 { fill: #6272a4 } - .terminal-1555804657-r5 { fill: #ff79c6 } - .terminal-1555804657-r6 { fill: #f8f8f2 } - .terminal-1555804657-r7 { fill: #50fa7b } - .terminal-1555804657-r8 { fill: #c2c2bf;font-weight: bold } - .terminal-1555804657-r9 { fill: #bd93f9 } - .terminal-1555804657-r10 { fill: #282a36 } - .terminal-1555804657-r11 { fill: #f1fa8c } + .terminal-502077087-r1 { fill: #1e1e1e } + .terminal-502077087-r2 { fill: #0178d4 } + .terminal-502077087-r3 { fill: #c5c8c6 } + .terminal-502077087-r4 { fill: #6272a4 } + .terminal-502077087-r5 { fill: #ff79c6 } + .terminal-502077087-r6 { fill: #f8f8f2 } + .terminal-502077087-r7 { fill: #50fa7b } + .terminal-502077087-r8 { fill: #c2c2bf;font-weight: bold } + .terminal-502077087-r9 { fill: #bd93f9 } + .terminal-502077087-r10 { fill: #282a36 } + .terminal-502077087-r11 { fill: #f1fa8c } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1  defhello(name): - 2      x =123 - 3  whilenotFalse - 4  print("hello "+ name)  - 5  continue - 6   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1  defhello(name): + 2      x =123 + 3  whilenotFalse:                      + 4  print("hello "+ name)            + 5  continue + 6   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -44248,84 +47548,84 @@ font-weight: 700; } - .terminal-2471123788-matrix { + .terminal-934033613-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2471123788-title { + .terminal-934033613-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2471123788-r1 { fill: #1e1e1e } - .terminal-2471123788-r2 { fill: #0178d4 } - .terminal-2471123788-r3 { fill: #c5c8c6 } - .terminal-2471123788-r4 { fill: #bbbbbb } - .terminal-2471123788-r5 { fill: #cf222e } - .terminal-2471123788-r6 { fill: #24292e } - .terminal-2471123788-r7 { fill: #6639bb } - .terminal-2471123788-r8 { fill: #a4a4a4 } - .terminal-2471123788-r9 { fill: #e36209 } - .terminal-2471123788-r10 { fill: #0450ae } - .terminal-2471123788-r11 { fill: #d73a49 } - .terminal-2471123788-r12 { fill: #fafbfc } - .terminal-2471123788-r13 { fill: #7daf9c } - .terminal-2471123788-r14 { fill: #093069 } + .terminal-934033613-r1 { fill: #1e1e1e } + .terminal-934033613-r2 { fill: #0178d4 } + .terminal-934033613-r3 { fill: #c5c8c6 } + .terminal-934033613-r4 { fill: #bbbbbb } + .terminal-934033613-r5 { fill: #cf222e } + .terminal-934033613-r6 { fill: #24292e } + .terminal-934033613-r7 { fill: #6639bb } + .terminal-934033613-r8 { fill: #a4a4a4 } + .terminal-934033613-r9 { fill: #e36209 } + .terminal-934033613-r10 { fill: #0450ae } + .terminal-934033613-r11 { fill: #d73a49 } + .terminal-934033613-r12 { fill: #fafbfc } + .terminal-934033613-r13 { fill: #7daf9c } + .terminal-934033613-r14 { fill: #093069 } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1  defhello(name): - 2  x=123 - 3  whilenotFalse - 4  print("hello "+name - 5  continue - 6   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1  defhello(name): + 2  x=123 + 3  whilenotFalse:                      + 4  print("hello "+name)            + 5  continue + 6   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -44355,82 +47655,82 @@ font-weight: 700; } - .terminal-3434128494-matrix { + .terminal-3938913142-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3434128494-title { + .terminal-3938913142-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3434128494-r1 { fill: #1e1e1e } - .terminal-3434128494-r2 { fill: #0178d4 } - .terminal-3434128494-r3 { fill: #c5c8c6 } - .terminal-3434128494-r4 { fill: #90908a } - .terminal-3434128494-r5 { fill: #f92672 } - .terminal-3434128494-r6 { fill: #f8f8f2 } - .terminal-3434128494-r7 { fill: #a6e22e } - .terminal-3434128494-r8 { fill: #c2c2bf } - .terminal-3434128494-r9 { fill: #ae81ff } - .terminal-3434128494-r10 { fill: #272822 } - .terminal-3434128494-r11 { fill: #66d9ef;font-style: italic; } - .terminal-3434128494-r12 { fill: #e6db74 } + .terminal-3938913142-r1 { fill: #1e1e1e } + .terminal-3938913142-r2 { fill: #0178d4 } + .terminal-3938913142-r3 { fill: #c5c8c6 } + .terminal-3938913142-r4 { fill: #90908a } + .terminal-3938913142-r5 { fill: #f92672 } + .terminal-3938913142-r6 { fill: #f8f8f2 } + .terminal-3938913142-r7 { fill: #a6e22e } + .terminal-3938913142-r8 { fill: #c2c2bf } + .terminal-3938913142-r9 { fill: #ae81ff } + .terminal-3938913142-r10 { fill: #272822 } + .terminal-3938913142-r11 { fill: #66d9ef;font-style: italic; } + .terminal-3938913142-r12 { fill: #e6db74 } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1  defhello(name): - 2      x =123 - 3  whilenotFalse - 4  print("hello "+ name)  - 5  continue - 6   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1  defhello(name): + 2      x =123 + 3  whilenotFalse:                      + 4  print("hello "+ name)            + 5  continue + 6   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -44460,80 +47760,80 @@ font-weight: 700; } - .terminal-2595209508-matrix { + .terminal-364913275-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2595209508-title { + .terminal-364913275-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2595209508-r1 { fill: #1e1e1e } - .terminal-2595209508-r2 { fill: #0178d4 } - .terminal-2595209508-r3 { fill: #c5c8c6 } - .terminal-2595209508-r4 { fill: #6e7681 } - .terminal-2595209508-r5 { fill: #569cd6 } - .terminal-2595209508-r6 { fill: #cccccc } - .terminal-2595209508-r7 { fill: #4ec9b0 } - .terminal-2595209508-r8 { fill: #b5cea8 } - .terminal-2595209508-r9 { fill: #7daf9c } - .terminal-2595209508-r10 { fill: #ce9178 } + .terminal-364913275-r1 { fill: #1e1e1e } + .terminal-364913275-r2 { fill: #0178d4 } + .terminal-364913275-r3 { fill: #c5c8c6 } + .terminal-364913275-r4 { fill: #6e7681 } + .terminal-364913275-r5 { fill: #569cd6 } + .terminal-364913275-r6 { fill: #cccccc } + .terminal-364913275-r7 { fill: #4ec9b0 } + .terminal-364913275-r8 { fill: #b5cea8 } + .terminal-364913275-r9 { fill: #7daf9c } + .terminal-364913275-r10 { fill: #ce9178 } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - 1  defhello(name): - 2      x =123 - 3  whilenotFalse - 4  print("hello "+ name)  - 5  continue - 6   - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 1  defhello(name): + 2      x =123 + 3  whilenotFalse:                      + 4  print("hello "+ name)            + 5  continue + 6   + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -44563,148 +47863,148 @@ font-weight: 700; } - .terminal-1059934093-matrix { + .terminal-2785562991-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1059934093-title { + .terminal-2785562991-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1059934093-r1 { fill: #1e1e1e } - .terminal-1059934093-r2 { fill: #0178d4 } - .terminal-1059934093-r3 { fill: #c5c8c6 } - .terminal-1059934093-r4 { fill: #c2c2bf } - .terminal-1059934093-r5 { fill: #272822 } - .terminal-1059934093-r6 { fill: #f92672;font-weight: bold } - .terminal-1059934093-r7 { fill: #f8f8f2 } - .terminal-1059934093-r8 { fill: #90908a } - .terminal-1059934093-r9 { fill: #f8f8f2;font-style: italic; } - .terminal-1059934093-r10 { fill: #14191f } + .terminal-2785562991-r1 { fill: #1e1e1e } + .terminal-2785562991-r2 { fill: #0178d4 } + .terminal-2785562991-r3 { fill: #c5c8c6 } + .terminal-2785562991-r4 { fill: #c2c2bf } + .terminal-2785562991-r5 { fill: #272822 } + .terminal-2785562991-r6 { fill: #f92672;font-weight: bold } + .terminal-2785562991-r7 { fill: #f8f8f2 } + .terminal-2785562991-r8 { fill: #90908a } + .terminal-2785562991-r9 { fill: #f8f8f2;font-style: italic; } + .terminal-2785562991-r10 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaWrapping + TextAreaWrapping - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -  1  # The  - Wonders  - of Space  - Explorati - on -  2   -  3  Space  - explorati - on has  - *always* - captured  - the  - human  - imaginati - on.  -  4  ▃▃ -  5  ダレンバ - ーンズ  -  6   -  7   - Thisissom - elongtext - thatshoul - dfoldcorr - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  1  # The  + Wonders  + of Space  + Explorati + on +  2   +  3  Space      + explorati  + on has     + *always* + captured   + the        + human      + imaginati  + on.        +  4  ▃▃ +  5  ダレンバ   + ーンズ     +  6   +  7   + Thisissom  + elongtext  + thatshoul  + dfoldcorr  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -44734,131 +48034,131 @@ font-weight: 700; } - .terminal-2946008658-matrix { + .terminal-2364210941-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2946008658-title { + .terminal-2364210941-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2946008658-r1 { fill: #e1e1e1 } - .terminal-2946008658-r2 { fill: #c5c8c6 } + .terminal-2364210941-r1 { fill: #e1e1e1 } + .terminal-2364210941-r2 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - RichLogApp + RichLogApp - - - - Hello - - World - - - - - - - - - - - - - - - - - - - - + + + + Hello                                                                          + + World                                                                          + + + + + + + + + + + + + + + + + + + + @@ -44889,139 +48189,139 @@ font-weight: 700; } - .terminal-1538625093-matrix { + .terminal-3879744432-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1538625093-title { + .terminal-3879744432-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1538625093-r1 { fill: #454a50 } - .terminal-1538625093-r2 { fill: #e1e1e1 } - .terminal-1538625093-r3 { fill: #c5c8c6 } - .terminal-1538625093-r4 { fill: #24292f;font-weight: bold } - .terminal-1538625093-r5 { fill: #000000 } - .terminal-1538625093-r6 { fill: #fea62b } - .terminal-1538625093-r7 { fill: #e2e3e3;font-weight: bold } - .terminal-1538625093-r8 { fill: #e2e3e3 } - .terminal-1538625093-r9 { fill: #14191f } + .terminal-3879744432-r1 { fill: #454a50 } + .terminal-3879744432-r2 { fill: #e1e1e1 } + .terminal-3879744432-r3 { fill: #c5c8c6 } + .terminal-3879744432-r4 { fill: #24292f;font-weight: bold } + .terminal-3879744432-r5 { fill: #000000 } + .terminal-3879744432-r6 { fill: #fea62b } + .terminal-3879744432-r7 { fill: #e2e3e3;font-weight: bold } + .terminal-3879744432-r8 { fill: #e2e3e3 } + .terminal-3879744432-r9 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderApp + BorderApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ascii - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔+------------------- ascii --------------------+ - blank|| - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|| - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|I must not fear.| - dashed|Fear is the mind-killer.| - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|Fear is the little-death that brings | - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|total obliteration.| - double|I will face my fear.| - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▅|I will permit it to pass over me and | - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|through me.| - heavy|And when it has gone past, I will turn| - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|the inner eye to see its path.| - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|Where the fear has gone there will be | - hidden|nothing. Only I will remain.| - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|| - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|| - hkey+----------------------------------------------+ - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - inner - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  ascii  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔+------------------- ascii --------------------+ +  blank || + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|| + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|I must not fear.| +  dashed |Fear is the mind-killer.| + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|Fear is the little-death that brings | + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|total obliteration.| +  double |I will face my fear.| + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▅|I will permit it to pass over me and | + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|through me.| +  heavy |And when it has gone past, I will turn| + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|the inner eye to see its path.| + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|Where the fear has gone there will be | +  hidden |nothing. Only I will remain.| + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|| + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|| +  hkey +----------------------------------------------+ + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  inner  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -45051,152 +48351,153 @@ font-weight: 700; } - .terminal-3186860707-matrix { + .terminal-37821291-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3186860707-title { + .terminal-37821291-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3186860707-r1 { fill: #c5c8c6 } - .terminal-3186860707-r2 { fill: #e1e1e1 } - .terminal-3186860707-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-3186860707-r4 { fill: #737373 } - .terminal-3186860707-r5 { fill: #474747 } - .terminal-3186860707-r6 { fill: #0178d4 } - .terminal-3186860707-r7 { fill: #454a50 } - .terminal-3186860707-r8 { fill: #e0e0e0 } - .terminal-3186860707-r9 { fill: #e2e3e3;font-weight: bold } - .terminal-3186860707-r10 { fill: #000000 } - .terminal-3186860707-r11 { fill: #1e1e1e } - .terminal-3186860707-r12 { fill: #dde0e6 } - .terminal-3186860707-r13 { fill: #99a1b3 } - .terminal-3186860707-r14 { fill: #dde2e8 } - .terminal-3186860707-r15 { fill: #99a7b9 } - .terminal-3186860707-r16 { fill: #dde4ea } - .terminal-3186860707-r17 { fill: #99adc1 } - .terminal-3186860707-r18 { fill: #dde6ed } - .terminal-3186860707-r19 { fill: #99b4c9 } - .terminal-3186860707-r20 { fill: #23568b } - .terminal-3186860707-r21 { fill: #dde8f3;font-weight: bold } - .terminal-3186860707-r22 { fill: #ddedf9 } + .terminal-37821291-r1 { fill: #c5c8c6 } + .terminal-37821291-r2 { fill: #e1e1e1 } + .terminal-37821291-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-37821291-r4 { fill: #737373 } + .terminal-37821291-r5 { fill: #474747 } + .terminal-37821291-r6 { fill: #0178d4 } + .terminal-37821291-r7 { fill: #454a50 } + .terminal-37821291-r8 { fill: #e0e0e0 } + .terminal-37821291-r9 { fill: #e2e3e3;font-weight: bold } + .terminal-37821291-r10 { fill: #000000 } + .terminal-37821291-r11 { fill: #1e1e1e } + .terminal-37821291-r12 { fill: #dde0e6 } + .terminal-37821291-r13 { fill: #99a1b3 } + .terminal-37821291-r14 { fill: #dde2e8 } + .terminal-37821291-r15 { fill: #99a7b9 } + .terminal-37821291-r16 { fill: #dde4ea } + .terminal-37821291-r17 { fill: #99adc1 } + .terminal-37821291-r18 { fill: #dde6ed } + .terminal-37821291-r19 { fill: #99b4c9 } + .terminal-37821291-r20 { fill: #23568b } + .terminal-37821291-r21 { fill: #fea62b;font-weight: bold } + .terminal-37821291-r22 { fill: #a7a9ab } + .terminal-37821291-r23 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ColorsApp + ColorsApp - - - - - Theme ColorsNamed Colors - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - primary - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - secondary"primary" - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - background$primary-darken-3$t - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - primary-background$primary-darken-2$t - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - secondary-background$primary-darken-1$t - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - surface$primary$t - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -  D  Toggle dark mode  + + + + + Theme ColorsNamed Colors + ━╸━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  primary  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  secondary "primary" + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  background $primary-darken-3$t + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  primary-background $primary-darken-2$t + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  secondary-background $primary-darken-1$t + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  surface $primary$t + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +  d Toggle dark mode  @@ -45226,148 +48527,149 @@ font-weight: 700; } - .terminal-2839369084-matrix { + .terminal-3862960447-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2839369084-title { + .terminal-3862960447-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2839369084-r1 { fill: #454a50 } - .terminal-2839369084-r2 { fill: #e1e1e1 } - .terminal-2839369084-r3 { fill: #c5c8c6 } - .terminal-2839369084-r4 { fill: #24292f;font-weight: bold } - .terminal-2839369084-r5 { fill: #262626 } - .terminal-2839369084-r6 { fill: #e2e2e2 } - .terminal-2839369084-r7 { fill: #000000 } - .terminal-2839369084-r8 { fill: #e3e3e3 } - .terminal-2839369084-r9 { fill: #e2e3e3;font-weight: bold } - .terminal-2839369084-r10 { fill: #14191f } - .terminal-2839369084-r11 { fill: #b93c5b } - .terminal-2839369084-r12 { fill: #121212 } - .terminal-2839369084-r13 { fill: #1e1e1e } - .terminal-2839369084-r14 { fill: #fea62b } - .terminal-2839369084-r15 { fill: #211505;font-weight: bold } - .terminal-2839369084-r16 { fill: #211505 } - .terminal-2839369084-r17 { fill: #dde8f3;font-weight: bold } - .terminal-2839369084-r18 { fill: #ddedf9 } + .terminal-3862960447-r1 { fill: #454a50 } + .terminal-3862960447-r2 { fill: #e1e1e1 } + .terminal-3862960447-r3 { fill: #c5c8c6 } + .terminal-3862960447-r4 { fill: #24292f;font-weight: bold } + .terminal-3862960447-r5 { fill: #262626 } + .terminal-3862960447-r6 { fill: #e2e2e2 } + .terminal-3862960447-r7 { fill: #000000 } + .terminal-3862960447-r8 { fill: #e3e3e3 } + .terminal-3862960447-r9 { fill: #e2e3e3;font-weight: bold } + .terminal-3862960447-r10 { fill: #14191f } + .terminal-3862960447-r11 { fill: #b93c5b } + .terminal-3862960447-r12 { fill: #121212 } + .terminal-3862960447-r13 { fill: #1e1e1e } + .terminal-3862960447-r14 { fill: #fea62b } + .terminal-3862960447-r15 { fill: #211505;font-weight: bold } + .terminal-3862960447-r16 { fill: #211505 } + .terminal-3862960447-r17 { fill: #fea62b;font-weight: bold } + .terminal-3862960447-r18 { fill: #a7a9ab } + .terminal-3862960447-r19 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - EasingApp + EasingApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - round▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁Animation Duration:1.0 - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - out_sine - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - out_quint - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁Welcome to Textual! - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - out_quartI must not fear. - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁Fear is the  - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔mind-killer. - out_quadFear is the  - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁little-death that  - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔brings total  - out_expoobliteration. - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁I will face my fear. - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔I will permit it to  - out_elasticpass over me and  - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁through me. - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔And when it has gone  - out_cubic - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ CTRL+P  Focus: Duration Input  CTRL+B  Toggle Dark  + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  round ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁Animation Duration:1.0                        + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +  out_sine  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +  out_quint  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁Welcome to Textual! + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  out_quart I must not fear. + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁Fear is the  + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔mind-killer. +  out_quad Fear is the  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁little-death that  + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔brings total  +  out_expo obliteration. + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁I will face my fear. + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔I will permit it to  +  out_elastic pass over me and  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁through me. + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔And when it has gone  +  out_cubic  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ^p Focus: Duration Input  ^b Toggle Dark  @@ -45397,146 +48699,310 @@ font-weight: 700; } - .terminal-2882699257-matrix { + .terminal-2889259904-matrix { + font-family: Fira Code, monospace; + font-size: 20px; + line-height: 24.4px; + font-variant-east-asian: full-width; + } + + .terminal-2889259904-title { + font-size: 18px; + font-weight: bold; + font-family: arial; + } + + .terminal-2889259904-r1 { fill: #c5c8c6 } + .terminal-2889259904-r2 { fill: #e3e3e3 } + .terminal-2889259904-r3 { fill: #e1e1e1 } + .terminal-2889259904-r4 { fill: #e1e1e1;text-decoration: underline; } + .terminal-2889259904-r5 { fill: #e1e1e1;font-weight: bold } + .terminal-2889259904-r6 { fill: #e1e1e1;font-style: italic; } + .terminal-2889259904-r7 { fill: #f4005f;font-weight: bold } + .terminal-2889259904-r8 { fill: #fd971f } + .terminal-2889259904-r9 { fill: #98e024 } + .terminal-2889259904-r10 { fill: #98e024;font-style: italic; } + .terminal-2889259904-r11 { fill: #ffcf56 } + .terminal-2889259904-r12 { fill: #e76580 } + .terminal-2889259904-r13 { fill: #fea62b;font-weight: bold } + .terminal-2889259904-r14 { fill: #f5e5e9;font-weight: bold } + .terminal-2889259904-r15 { fill: #b86b00 } + .terminal-2889259904-r16 { fill: #780028 } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Textual Keys + + + + + + + + + + Textual Keys + ╭────────────────────────────────────────────────────────────────────────────╮ + │ Press some keys!                                                           │ + │                                                                            │ + │ To quit the app press ctrl+ctwice or press the Quit button below.         │ + ╰────────────────────────────────────────────────────────────────────────────╯ + Key(key='a'character='a'name='a'is_printable=True) + Key(key='b'character='b'name='b'is_printable=True) + + + + + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  Clear  Quit  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + ''' +# --- +# name: test_toggle_style_order + ''' + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Textual Keys + CheckboxApp - - - - Textual Keys - ╭────────────────────────────────────────────────────────────────────────────╮ - Press some keys! - - To quit the app press ctrl+ctwice or press the Quit button below. - ╰────────────────────────────────────────────────────────────────────────────╯ - Key(key='a'character='a'name='a'is_printable=True) - Key(key='b'character='b'name='b'is_printable=True) - - - - - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ClearQuit - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + XThis is just some text. + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + This is just some text. + + + + + + + + + + + + + + + + + + + + @@ -45566,134 +49032,134 @@ font-weight: 700; } - .terminal-3216424293-matrix { + .terminal-3361617742-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3216424293-title { + .terminal-3361617742-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3216424293-r1 { fill: #fea62b } - .terminal-3216424293-r2 { fill: #323232 } - .terminal-3216424293-r3 { fill: #c5c8c6 } - .terminal-3216424293-r4 { fill: #e1e1e1 } - .terminal-3216424293-r5 { fill: #e2e3e3 } + .terminal-3361617742-r1 { fill: #fea62b } + .terminal-3361617742-r2 { fill: #323232 } + .terminal-3361617742-r3 { fill: #c5c8c6 } + .terminal-3361617742-r4 { fill: #e1e1e1 } + .terminal-3361617742-r5 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TooltipApp + TooltipApp - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━10% - - Hello, Tooltip! - - - - - - - - - - - - - - - - - - - - + + + + ━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━10%                                            + + Hello, Tooltip! + + + + + + + + + + + + + + + + + + + + @@ -45724,133 +49190,133 @@ font-weight: 700; } - .terminal-3765519511-matrix { + .terminal-1892565393-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3765519511-title { + .terminal-1892565393-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3765519511-r1 { fill: #e2e3e3 } - .terminal-3765519511-r2 { fill: #211505;font-weight: bold } - .terminal-3765519511-r3 { fill: #1a1000;font-weight: bold } - .terminal-3765519511-r4 { fill: #c5c8c6 } + .terminal-1892565393-r1 { fill: #e2e3e3 } + .terminal-1892565393-r2 { fill: #211505;font-weight: bold } + .terminal-1892565393-r3 { fill: #1a1000;font-weight: bold } + .terminal-1892565393-r4 { fill: #c5c8c6 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TreeClearingSnapshotApp + TreeClearingSnapshotApp - - - - ▼ Left▶ Right - - - - - - - - - - - - - - - - - - - - - - + + + + ▼ Left▶ Right + + + + + + + + + + + + + + + + + + + + + + @@ -45881,133 +49347,133 @@ font-weight: 700; } - .terminal-3137592172-matrix { + .terminal-1078671588-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3137592172-title { + .terminal-1078671588-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3137592172-r1 { fill: #e2e3e3 } - .terminal-3137592172-r2 { fill: #211505;font-weight: bold } - .terminal-3137592172-r3 { fill: #c5c8c6 } - .terminal-3137592172-r4 { fill: #fea62b;font-weight: bold } + .terminal-1078671588-r1 { fill: #e2e3e3 } + .terminal-1078671588-r2 { fill: #211505;font-weight: bold } + .terminal-1078671588-r3 { fill: #c5c8c6 } + .terminal-1078671588-r4 { fill: #fea62b;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TreeApp + TreeApp - - - - ▼ Dune - ┗━━ ▼ Characters - ┣━━ Paul - ┣━━ Jessica - ┗━━ Chani - - - - - - - - - - - - - - - - - - + + + + ▼ Dune + ┗━━ ▼ Characters +     ┣━━ Paul +     ┣━━ Jessica +     ┗━━ Chani + + + + + + + + + + + + + + + + + + @@ -46038,133 +49504,133 @@ font-weight: 700; } - .terminal-699265099-matrix { + .terminal-3679627198-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-699265099-title { + .terminal-3679627198-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-699265099-r1 { fill: #ff00ff } - .terminal-699265099-r2 { fill: #c5c8c6 } - .terminal-699265099-r3 { fill: #008000 } - .terminal-699265099-r4 { fill: #e1e1e1 } + .terminal-3679627198-r1 { fill: #ff00ff } + .terminal-3679627198-r2 { fill: #c5c8c6 } + .terminal-3679627198-r3 { fill: #008000 } + .terminal-3679627198-r4 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - ────────────────────────────────────────────────────────────────────────────── - ─── - foo - ─── - ─── - bar - ─── - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - ─── - foo - ─── - ─── - bar - ─── - ────────────────────────────────────────────────────────────────────────────── - ─────────────────── - This will be styled - ─────────────────── - - - - + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + ┌───┐ + foo + └───┘ + ┌───┐ + bar + └───┘ + └──────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────┐ + ┌───┐ + foo + └───┘ + ┌───┐ + bar + └───┘ + └──────────────────────────────────────────────────────────────────────────────┘ + ┌───────────────────┐ + This will be styled + └───────────────────┘ + + + + @@ -46195,133 +49661,133 @@ font-weight: 700; } - .terminal-452684828-matrix { + .terminal-660754755-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-452684828-title { + .terminal-660754755-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-452684828-r1 { fill: #008000 } - .terminal-452684828-r2 { fill: #c5c8c6 } - .terminal-452684828-r3 { fill: #e1e1e1 } + .terminal-660754755-r1 { fill: #008000 } + .terminal-660754755-r2 { fill: #c5c8c6 } + .terminal-660754755-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - VerticalLayoutExample + VerticalLayoutExample - - - - ────────────────────────────────────────────────────────────────────────────── - One - - - - - - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - Two - - - - - - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - Three - - - - - - ────────────────────────────────────────────────────────────────────────────── + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + One + + + + + + └──────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────┐ + Two + + + + + + └──────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────┐ + Three + + + + + + └──────────────────────────────────────────────────────────────────────────────┘ @@ -46351,134 +49817,134 @@ font-weight: 700; } - .terminal-4274547359-matrix { + .terminal-2197244517-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4274547359-title { + .terminal-2197244517-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4274547359-r1 { fill: #ffffff } - .terminal-4274547359-r2 { fill: #c5c8c6 } - .terminal-4274547359-r3 { fill: #e8e0e7 } - .terminal-4274547359-r4 { fill: #eae3e5 } + .terminal-2197244517-r1 { fill: #ffffff } + .terminal-2197244517-r2 { fill: #c5c8c6 } + .terminal-2197244517-r3 { fill: #e8e0e7 } + .terminal-2197244517-r4 { fill: #eae3e5 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - VerticalApp + VerticalApp - - - - ────────────────────────────────────────────────────────────────────────────── - - - - - - #top - - - - - - - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - - - - #bottom - - - - - ────────────────────────────────────────────────────────────────────────────── + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + + + + + + #top + + + + + + + └──────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────┐ + + + + #bottom + + + + + └──────────────────────────────────────────────────────────────────────────────┘ @@ -46508,134 +49974,134 @@ font-weight: 700; } - .terminal-1897622187-matrix { + .terminal-2458077809-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1897622187-title { + .terminal-2458077809-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1897622187-r1 { fill: #ffffff } - .terminal-1897622187-r2 { fill: #c5c8c6 } - .terminal-1897622187-r3 { fill: #e8e0e7 } - .terminal-1897622187-r4 { fill: #eae3e5 } + .terminal-2458077809-r1 { fill: #ffffff } + .terminal-2458077809-r2 { fill: #c5c8c6 } + .terminal-2458077809-r3 { fill: #e8e0e7 } + .terminal-2458077809-r4 { fill: #eae3e5 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - VerticalApp + VerticalApp - - - - ────────────────────────────────────────────────────────────────────────────── - - - - #top - - - - - ────────────────────────────────────────────────────────────────────────────── - ────────────────────────────────────────────────────────────────────────────── - - - - - - #bottom - - - - - - - ────────────────────────────────────────────────────────────────────────────── + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + + + + #top + + + + + └──────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────┐ + + + + + + #bottom + + + + + + + └──────────────────────────────────────────────────────────────────────────────┘ @@ -46665,133 +50131,133 @@ font-weight: 700; } - .terminal-1952935039-matrix { + .terminal-3902037656-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1952935039-title { + .terminal-3902037656-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1952935039-r1 { fill: #00ffff } - .terminal-1952935039-r2 { fill: #c5c8c6 } - .terminal-1952935039-r3 { fill: #e1e1e1 } + .terminal-3902037656-r1 { fill: #00ffff } + .terminal-3902037656-r2 { fill: #c5c8c6 } + .terminal-3902037656-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ViewportUnits + ViewportUnits - - - - ────────────────────────────────────────────────────────────────────────────── - Hello, world! - - - - - - - - - - - - - - - - - - - - - - ────────────────────────────────────────────────────────────────────────────── + + + + ┌──────────────────────────────────────────────────────────────────────────────┐ + Hello, world! + + + + + + + + + + + + + + + + + + + + + + └──────────────────────────────────────────────────────────────────────────────┘ @@ -46821,134 +50287,134 @@ font-weight: 700; } - .terminal-4287604798-matrix { + .terminal-585402537-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4287604798-title { + .terminal-585402537-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4287604798-r1 { fill: #e1e1e1 } - .terminal-4287604798-r2 { fill: #ff0000 } - .terminal-4287604798-r3 { fill: #c5c8c6 } - .terminal-4287604798-r4 { fill: #0000ff } + .terminal-585402537-r1 { fill: #e1e1e1 } + .terminal-585402537-r2 { fill: #ff0000 } + .terminal-585402537-r3 { fill: #c5c8c6 } + .terminal-585402537-r4 { fill: #0000ff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Visibility + Visibility - - - - ────────────────────────────────────── - bar - ──────────────────────────────────────────────────────────────────────── - floatfloat - ──────────────────────────────────────────────────────────────────────── - - - - - - - - - - - - - - - - - - - ────────────────────────────────────── + + + + ┌──────────────────────────────────────┐ + bar + ┌────────────────────────────────────┐┌────────────────────────────────────┐ + floatfloat + └────────────────────────────────────┘└────────────────────────────────────┘ + + + + + + + + + + + + + + + + + + + └──────────────────────────────────────┘ @@ -46978,139 +50444,139 @@ font-weight: 700; } - .terminal-410897316-matrix { + .terminal-1548880427-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-410897316-title { + .terminal-1548880427-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-410897316-r1 { fill: #c5c8c6 } - .terminal-410897316-r2 { fill: #e2e3e3 } - .terminal-410897316-r3 { fill: #e2e3e3;font-weight: bold } - .terminal-410897316-r4 { fill: #e2e3e3;font-style: italic; } - .terminal-410897316-r5 { fill: #e2e3e3;font-weight: bold;text-decoration: underline; } - .terminal-410897316-r6 { fill: #f4005f } - .terminal-410897316-r7 { fill: #7ae998 } - .terminal-410897316-r8 { fill: #4ebf71;font-weight: bold } - .terminal-410897316-r9 { fill: #008139 } + .terminal-1548880427-r1 { fill: #c5c8c6 } + .terminal-1548880427-r2 { fill: #e2e3e3 } + .terminal-1548880427-r3 { fill: #e2e3e3;font-weight: bold } + .terminal-1548880427-r4 { fill: #e2e3e3;font-style: italic; } + .terminal-1548880427-r5 { fill: #e2e3e3;font-weight: bold;text-decoration: underline; } + .terminal-1548880427-r6 { fill: #f4005f } + .terminal-1548880427-r7 { fill: #7ae998 } + .terminal-1548880427-r8 { fill: #4ebf71;font-weight: bold } + .terminal-1548880427-r9 { fill: #008139 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - WelcomeApp + WelcomeApp - - - - - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - Welcome! - ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - Textual is a TUI, or Text User Interface, framework for Python inspired by   - modern web development. We hope you enjoy using Textual! - - - Dune quote - - ▌ "I must not fear.Fear is the mind-killer.Fear is the little-death that - ▌ brings total obliteration.I will face my fear.I will permit it to pass - ▌ over me and through me.And when it has gone past, I will turn the inner - ▌ eye to see its path.Where the fear has gone there will be nothing. Only - ▌ I will remain." - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - OK - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + +  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓  +  ┃                                 Welcome!                                 ┃  +  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛  + +  Textual is a TUI, or Text User Interface, framework for Python inspired by    +  modern web development. We hope you enjoy using Textual! + + + Dune quote + + ▌ "I must not fear. Fear is the mind-killer. Fear is the little-death that + ▌ brings total obliteration. I will face my fear. I will permit it to pass + ▌ over me and through me. And when it has gone past, I will turn the inner + ▌ eye to see its path. Where the fear has gone there will be nothing. Only + ▌ I will remain."                                                          + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +  OK  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -47140,134 +50606,134 @@ font-weight: 700; } - .terminal-285784620-matrix { + .terminal-3398278591-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-285784620-title { + .terminal-3398278591-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-285784620-r1 { fill: #ff0000 } - .terminal-285784620-r2 { fill: #e1e1e1 } - .terminal-285784620-r3 { fill: #c5c8c6 } - .terminal-285784620-r4 { fill: #008000 } + .terminal-3398278591-r1 { fill: #ff0000 } + .terminal-3398278591-r2 { fill: #e1e1e1 } + .terminal-3398278591-r3 { fill: #c5c8c6 } + .terminal-3398278591-r4 { fill: #008000 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Width100PCentApp + Width100PCentApp - - - - ─────────────────────────────────────────────────────────── - ───────────────────────────────────────────────────────── - I want to be 100% of my parent - ───────────────────────────────────────────────────────── - ───────────────────────────────────────────────────────── - I want my parent to be wide enough to wrap me and no more - ───────────────────────────────────────────────────────── - - - - - - - - - - - - - - - - - ─────────────────────────────────────────────────────────── + + + + ┌───────────────────────────────────────────────────────────┐ + ┌─────────────────────────────────────────────────────────┐ + I want to be 100% of my parent + └─────────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────┐ + I want my parent to be wide enough to wrap me and no more + └─────────────────────────────────────────────────────────┘ + + + + + + + + + + + + + + + + + └───────────────────────────────────────────────────────────┘ diff --git a/tests/snapshot_tests/snapshot_apps/auto_tab_active.py b/tests/snapshot_tests/snapshot_apps/auto_tab_active.py new file mode 100644 index 0000000000..07c9897e2c --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/auto_tab_active.py @@ -0,0 +1,36 @@ +from textual.app import App, ComposeResult +from textual.widgets import Button, Footer, TabbedContent, TabPane + + +class ExampleApp(App): + BINDINGS = [("space", "focus_button_2_2", "Focus button 2.2")] + + def compose(self) -> ComposeResult: + with TabbedContent(id="tabbed-root"): + with TabPane("[red]Parent 1[/]"): + with TabbedContent(): + with TabPane("[red]Child 1.1[/]"): + yield Button("Button 1.1", variant="error") + with TabPane("[red]Child 1.2[/]"): + yield Button("Button 1.2", variant="error") + + with TabPane("[green]Parent 2[/]", id="parent-2"): + with TabbedContent(id="tabbed-parent-2"): + with TabPane("[green]Child 2.1[/]"): + yield Button("Button 2.1", variant="success") + with TabPane("[green]Child 2.2[/]", id="child-2-2"): + yield Button( + "Button 2.2", + variant="success", + id="button-2-2", + ) + + yield Footer() + + def action_focus_button_2_2(self) -> None: + self.query_one("#button-2-2", Button).focus() + + +if __name__ == "__main__": + app = ExampleApp() + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/auto_width_input.py b/tests/snapshot_tests/snapshot_apps/auto_width_input.py index 9c6199eadc..ab6457b5ad 100644 --- a/tests/snapshot_tests/snapshot_apps/auto_width_input.py +++ b/tests/snapshot_tests/snapshot_apps/auto_width_input.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.widgets import Header, Footer, Input +from textual.widgets import Header, Footer, Input, ClassicFooter class InputWidthAutoApp(App[None]): diff --git a/tests/snapshot_tests/snapshot_apps/bindings_screen_overrides_show.py b/tests/snapshot_tests/snapshot_apps/bindings_screen_overrides_show.py new file mode 100644 index 0000000000..03ecc6db1d --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/bindings_screen_overrides_show.py @@ -0,0 +1,29 @@ +from textual.app import App, ComposeResult +from textual.screen import Screen +from textual.binding import Binding +from textual.widgets import Footer + + +class ShowBindingScreen(Screen): + BINDINGS = [ + Binding("p", "app.pop_screen", "Binding shown"), + ] + + def compose(self) -> ComposeResult: + yield Footer() + + +class HideBindingApp(App): + """Regression test for https://github.com/Textualize/textual/issues/4382""" + + BINDINGS = [ + Binding("p", "app.pop_screen", "Binding hidden", show=False), + ] + + def on_mount(self) -> None: + self.push_screen(ShowBindingScreen()) + + +if __name__ == "__main__": + app = HideBindingApp() + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/component_text_opacity.py b/tests/snapshot_tests/snapshot_apps/component_text_opacity.py new file mode 100644 index 0000000000..647bffcab5 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/component_text_opacity.py @@ -0,0 +1,33 @@ +from textual.app import App, ComposeResult +from textual.scroll_view import ScrollView +from textual.strip import Strip +from rich.segment import Segment + + +class LineWidget(ScrollView): + COMPONENT_CLASSES = {"faded"} + + DEFAULT_CSS = """ + LineWidget { + background:blue; + color:white; + &>.faded{ + text-opacity:0.5; + + } + } + """ + + def render_line(self, y: int) -> Strip: + style = self.get_component_rich_style("faded") + return Strip([Segment("W" * self.size.width, style)]) + + +class TestApp(App): + def compose(self) -> ComposeResult: + yield LineWidget() + + +if __name__ == "__main__": + app = TestApp() + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/data_table_tabs.py b/tests/snapshot_tests/snapshot_apps/data_table_tabs.py new file mode 100644 index 0000000000..2f5fc15467 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/data_table_tabs.py @@ -0,0 +1,35 @@ +from textual.app import App, ComposeResult +from textual.binding import Binding +from textual.widgets import TabbedContent, DataTable + + +class Dashboard(App): + """Dashboard""" + + BINDINGS = [ + ("d", "toggle_dark", "Toggle dark mode"), + Binding("ctrl+q", "app.quit", "Quit", show=True), + ] + TITLE = "Dashboard" + CSS = """ + + DataTable { + height: auto; + } + """ + + def compose(self) -> ComposeResult: + """Create child widgets for the app.""" + with TabbedContent("Workflows"): + yield DataTable(id="table") + + def on_mount(self) -> None: + table = self.query_one(DataTable) + table.add_columns("Id", "Description", "Status", "Result Id") + for row in [(1, 2, 3, 4), ("a", "b", "c", "d"), ("fee", "fy", "fo", "fum")]: + table.add_row(key=row[0], *row) + + +if __name__ == "__main__": + app = Dashboard() + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/dynamic_bindings.py b/tests/snapshot_tests/snapshot_apps/dynamic_bindings.py new file mode 100644 index 0000000000..373b46696c --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/dynamic_bindings.py @@ -0,0 +1,41 @@ +from __future__ import annotations +from textual.app import App, ComposeResult +from textual.widgets import Footer + + +class BindingsApp(App): + """Check dynamic actions are displayed in the footer.""" + + BINDINGS = [ + ("a", "a", "A"), + ("b", "b", "B"), + ("c", "c", "C"), + ] + + def compose(self) -> ComposeResult: + yield Footer() + + def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None: + if action == "b": + # A is disabled (not show) + return False + if action == "c": + # B is disabled (shown grayed out) + return None + # Everything else is fine + return True + + def action_a(self): + self.bell() + + def action_b(self): + # If dynamic actions is working we won't get here + 1 / 0 + + def action_c(self): + # If dynamic actions is working we won't get here + 1 / 0 + + +if __name__ == "__main__": + BindingsApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/footer_classic_styling.py b/tests/snapshot_tests/snapshot_apps/footer_classic_styling.py new file mode 100644 index 0000000000..7f43027d49 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/footer_classic_styling.py @@ -0,0 +1,50 @@ +from textual.app import App, ComposeResult +from textual.widgets import Footer + + +class ClassicFooterStylingApp(App): + """ + This app attempts to replicate the styling of the classic footer in + Textual before v0.63.0, in particular the distinct background color + for the binding keys and spacing between the key and binding description. + + Regression test for https://github.com/Textualize/textual/issues/4557 + """ + + CSS = """ + Footer { + background: $accent; + + FooterKey { + background: $accent; + color: $text; + + .footer-key--key { + background: $accent-darken-2; + color: $text; + } + + .footer-key--description { + padding: 0 1; + } + } + } + """ + + BINDINGS = [ + ("ctrl+t", "app.toggle_dark", "Toggle Dark mode"), + ("ctrl+q", "quit", "Quit"), + ] + + def compose(self) -> ComposeResult: + yield Footer() + + def on_mount(self) -> None: + footer = self.query_one(Footer) + footer.upper_case_keys = True + footer.ctrl_to_caret = False + + +if __name__ == "__main__": + app = ClassicFooterStylingApp() + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/footer_toggle_compact.py b/tests/snapshot_tests/snapshot_apps/footer_toggle_compact.py new file mode 100644 index 0000000000..de8a01d9f3 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/footer_toggle_compact.py @@ -0,0 +1,34 @@ +from textual.app import App, ComposeResult +from textual.widgets import Footer, Label + + +class ToggleCompactFooterApp(App): + CSS = """ + Screen { + align: center middle; + } + """ + + BINDINGS = [ + ("ctrl+t", "toggle_compact_footer", "Toggle Compact Footer"), + ("ctrl+q", "quit", "Quit"), + ] + + def compose(self) -> ComposeResult: + yield Label("Compact Footer") + yield Footer() + + def on_mount(self) -> None: + footer = self.query_one(Footer) + footer.compact = True + + def action_toggle_compact_footer(self) -> None: + footer = self.query_one(Footer) + footer.compact = not footer.compact + footer_type = "Compact" if footer.compact else "Standard" + self.query_one(Label).update(f"{footer_type} Footer") + + +if __name__ == "__main__": + app = ToggleCompactFooterApp() + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/grid_auto.py b/tests/snapshot_tests/snapshot_apps/grid_auto.py new file mode 100644 index 0000000000..93772b9278 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/grid_auto.py @@ -0,0 +1,34 @@ +from textual.app import App, ComposeResult +from textual.containers import Grid +from textual.widgets import Placeholder + + +class KeylineApp(App): + CSS = """ + Grid { + grid-size: 3 3; + grid-gutter: 1; + grid-columns: 2; + grid-rows: 1; + keyline: thin; + width: auto; + height: auto; + background: blue; + } + """ + + def compose(self) -> ComposeResult: + with Grid(): + yield Placeholder("a") + yield Placeholder("b") + yield Placeholder("c") + yield Placeholder("d") + yield Placeholder("e") + yield Placeholder("f") + yield Placeholder("g") + yield Placeholder("h") + yield Placeholder("i") + + +if __name__ == "__main__": + KeylineApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/grid_gutter.py b/tests/snapshot_tests/snapshot_apps/grid_gutter.py new file mode 100644 index 0000000000..73ac562632 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/grid_gutter.py @@ -0,0 +1,42 @@ +from rich.markdown import Markdown +from textual.app import App, ComposeResult +from textual.containers import Grid, Vertical +from textual.widgets import Label, TabbedContent, TabPane + + +class FooApp(App): + TITLE = "Demonstrator" + + CSS = """ + Screen { + align: center middle; + } + + #root { + width: 60; + height: 20; + border: solid $accent; + } + .info-container { + grid-rows: auto; + } + .value { + padding: 0 2; + border: tall $background; + } + """ + + def compose(self) -> ComposeResult: + with Vertical(id="root"): + with TabbedContent(): + with TabPane("Information"): + with Grid(classes="info-container"): + yield Label(Markdown(long_text()), classes="value") + + +def long_text() -> str: + return 'aaa naa aaaaa aaa aaaan, aaa\naaa, aaaa?", aa aaa aaaaanaaa *anaaaaaaana* aaaaaaaa aaaaaana aaa aaaaa aa\naaa, aa *aaaaaaaaa* aaa aaaa, "aaaa, an *aaaa* aaa aaaa, a aa". "aaaa, naa\naaaaaaaaaaa, aaa a aaaa aaaaaanaa aaaa aa a aaa!", aaa anaaaa, aaaaa\naaaaaaaa aanaaaaa. "Na! aaa naa. aaaaa. aa aaaaa naa. aaaaa aa na aaa.",\naaa aaaaaaaa aaaanaaaaa DONE.\n' + + +if __name__ == "__main__": + FooApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/hatch.py b/tests/snapshot_tests/snapshot_apps/hatch.py new file mode 100644 index 0000000000..19859fd35f --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/hatch.py @@ -0,0 +1,56 @@ +from textual.app import App, ComposeResult +from textual.widgets import Label +from textual.containers import Container + + +class HatchApp(App): + CSS = """ + Screen { + hatch: right slateblue; + } + #one { + hatch: left $success; + margin: 2 4; + } + + #two { + hatch: cross $warning; + margin: 2 4; + } + + #three { + hatch: horizontal $error; + margin: 2 4; + } + + #four { + hatch: vertical $primary; + margin: 1 2; + padding: 1 2; + align: center middle; + border: solid red; + } + + #five { + hatch: "┼" $success 50%; + margin: 1 2; + align: center middle; + text-style: bold; + color: magenta; + } + """ + + def compose(self) -> ComposeResult: + with Container(id="one"): + with Container(id="two"): + with Container(id="three"): + with Container(id="four"): + with Container(id="five"): + yield Label("Hatched") + + def on_mount(self) -> None: + self.query_one("#four").border_title = "Hello World" + + +if __name__ == "__main__": + HatchApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/margin_multiple.py b/tests/snapshot_tests/snapshot_apps/margin_multiple.py new file mode 100644 index 0000000000..81eb017bff --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/margin_multiple.py @@ -0,0 +1,43 @@ +from textual.app import App, ComposeResult +from textual.containers import Container, ScrollableContainer, Horizontal +from textual.widgets import Label + + +class CompoundWidget(ScrollableContainer): + DEFAULT_CSS = """ + #inner { + width: 1fr; + background: $panel; + align: center middle; + margin: 5; + border: green; + + } + + Label { + border: double yellow; + } + """ + + def compose(self) -> ComposeResult: + yield Label("foo") + with Container(id="inner"): + yield Label("bar") + + +class MyApp(App): + CSS = """ + #widget2 > Label { + display: none; + } + + """ + + def compose(self) -> ComposeResult: + with Horizontal(): + yield CompoundWidget(id="widget1") + yield CompoundWidget(id="widget2") + + +if __name__ == "__main__": + MyApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/multi_keys.py b/tests/snapshot_tests/snapshot_apps/multi_keys.py new file mode 100644 index 0000000000..eb48dc03cb --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/multi_keys.py @@ -0,0 +1,11 @@ +from textual.app import App, ComposeResult +from textual.binding import Binding +from textual.widgets import Footer + +class MApp(App): + BINDINGS = [Binding("o,ctrl+o", "options", "Options")] + def compose(self) -> ComposeResult: + yield Footer() + +if __name__ == "__main__": + MApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/programmatic_disable_button.py b/tests/snapshot_tests/snapshot_apps/programmatic_disable_button.py new file mode 100644 index 0000000000..1d9318c8d6 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/programmatic_disable_button.py @@ -0,0 +1,35 @@ +from textual.app import App, ComposeResult +from textual.containers import Center +from textual.widgets import Button, Footer, Label + + +class ExampleApp(App): + CSS = """ + Screen { + align: center middle; + } + """ + + BINDINGS = [("space", "toggle_button", "Toggle Button")] + + def compose(self) -> ComposeResult: + with Center(): + yield Label("Hover the button then hit space") + with Center(): + yield Button("Enabled", id="disable-btn") + yield Footer() + + def action_toggle_button(self) -> None: + self.app.bell() + button = self.query_one("#disable-btn", Button) + if button.disabled is False: + button.disabled = True + button.label = "Disabled" + else: + button.disabled = False + button.label = "Enabled" + + +if __name__ == "__main__": + app = ExampleApp() + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/recompose.py b/tests/snapshot_tests/snapshot_apps/recompose.py index f0e5909f95..676b1746c8 100644 --- a/tests/snapshot_tests/snapshot_apps/recompose.py +++ b/tests/snapshot_tests/snapshot_apps/recompose.py @@ -7,7 +7,6 @@ class Numbers(Vertical): - DEFAULT_CSS = """ Digits { border: red; @@ -25,7 +24,6 @@ def compose(self) -> ComposeResult: class Progress(Horizontal): - progress = reactive(0, recompose=True) def compose(self) -> ComposeResult: @@ -35,7 +33,6 @@ def compose(self) -> ComposeResult: class RecomposeApp(App): - start = reactive(0, recompose=True) end = reactive(5, recompose=True) progress: reactive[int] = reactive(0, recompose=True) diff --git a/tests/snapshot_tests/snapshot_apps/rules.py b/tests/snapshot_tests/snapshot_apps/rules.py new file mode 100644 index 0000000000..9c61157ccd --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/rules.py @@ -0,0 +1,30 @@ +from textual.app import App, ComposeResult +from textual.containers import Horizontal, Vertical +from textual.widgets import Rule + +RULE_STYLES = [ + "ascii", + "blank", + "dashed", + "double", + "heavy", + "hidden", + "none", + "solid", + "thick", +] + + +class RuleApp(App): + def compose(self) -> ComposeResult: + with Vertical(): + for rule_style in RULE_STYLES: + yield Rule(line_style=rule_style) + + with Horizontal(): + for rule_style in RULE_STYLES: + yield Rule("vertical", line_style=rule_style) + + +if __name__ == "__main__": + RuleApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/scroll_to_center.py b/tests/snapshot_tests/snapshot_apps/scroll_to_center.py index 7a81a9b7f1..b4ec68881a 100644 --- a/tests/snapshot_tests/snapshot_apps/scroll_to_center.py +++ b/tests/snapshot_tests/snapshot_apps/scroll_to_center.py @@ -35,7 +35,7 @@ def compose(self) -> ComposeResult: yield Label(("SPAM\n" * 51)[:-1]) def key_s(self) -> None: - self.screen.scroll_to_center(self.query_one("#bullseye")) + self.screen.scroll_to_center(self.query_one("#bullseye"), origin_visible=False) if __name__ == "__main__": diff --git a/tests/snapshot_tests/snapshot_apps/scroll_visible_margin.py b/tests/snapshot_tests/snapshot_apps/scroll_visible_margin.py new file mode 100644 index 0000000000..7765947761 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/scroll_visible_margin.py @@ -0,0 +1,28 @@ +from textual.app import App, ComposeResult +from textual.containers import VerticalScroll, Container +from textual.widgets import Button + + +class ScrollVisibleMargin(App): + CSS = """ + Container { + height: auto; + margin-top: 8; + border: solid red; + } + """ + + def compose(self) -> ComposeResult: + with VerticalScroll(): + with Container(): + for index in range(1, 51): + yield Button(f"Hello, world! ({index})", id=f"b{index}") + + def key_x(self): + button_twenty = self.query_one("#b26") + button_twenty.scroll_visible() + + +app = ScrollVisibleMargin() +if __name__ == "__main__": + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/text_area_line_number_start.py b/tests/snapshot_tests/snapshot_apps/text_area_line_number_start.py new file mode 100644 index 0000000000..d8db335960 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/text_area_line_number_start.py @@ -0,0 +1,25 @@ +from textual.app import App, ComposeResult +from textual.widgets import TextArea + +TEXT = """\ +Foo +Bar +Baz +""" + + +class LineNumbersReactive(App[None]): + START_LINE_NUMBER = 9999 + + def compose(self) -> ComposeResult: + yield TextArea( + TEXT, + soft_wrap=True, + show_line_numbers=True, + line_number_start=self.START_LINE_NUMBER, + ) + + +app = LineNumbersReactive() +if __name__ == "__main__": + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/toggle_style_order.py b/tests/snapshot_tests/snapshot_apps/toggle_style_order.py new file mode 100644 index 0000000000..a92e41a421 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/toggle_style_order.py @@ -0,0 +1,19 @@ +from textual.app import App +from textual.widgets import Checkbox, Label + + +class CheckboxApp(App): + CSS = """ + Checkbox > .toggle--label, Label { + color: white; + text-opacity: 50%; + } + """ + + def compose(self): + yield Checkbox("[red bold]This is just[/] some text.") + yield Label("[red bold]This is just[/] some text.") + + +if __name__ == "__main__": + CheckboxApp().run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 77e9855578..35e8e12317 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -3,6 +3,7 @@ import pytest from tests.snapshot_tests.language_snippets import SNIPPETS +from textual.pilot import Pilot from textual.widgets.text_area import Selection, BUILTIN_LANGUAGES from textual.widgets import RichLog, TextArea, Input, Button from textual.widgets.text_area import TextAreaTheme @@ -1000,6 +1001,12 @@ def test_text_area_wrapping_and_folding(snap_compare): ) +def test_text_area_line_number_start(snap_compare): + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area_line_number_start.py", terminal_size=(32, 8) + ) + + def test_digits(snap_compare) -> None: assert snap_compare(SNAPSHOT_APPS_DIR / "digits.py") @@ -1188,7 +1195,7 @@ def test_example_color_command(snap_compare): """Test the color_command example.""" assert snap_compare( EXAMPLES_DIR / "color_command.py", - press=["ctrl+backslash", "r", "e", "d", "down", "down", "enter"], + press=["ctrl+backslash", "r", "e", "d", "down", "enter"], ) @@ -1236,7 +1243,7 @@ def test_example_pride(snap_compare): """Test the pride example.""" assert snap_compare(EXAMPLES_DIR / "pride.py") - + def test_button_with_console_markup(snap_compare): """Regression test for https://github.com/Textualize/textual/issues/4328""" assert snap_compare(SNAPSHOT_APPS_DIR / "button_markup.py") @@ -1249,3 +1256,124 @@ def test_width_100(snap_compare): def test_button_with_multiline_label(snap_compare): assert snap_compare(SNAPSHOT_APPS_DIR / "button_multiline_label.py") + + +def test_margin_multiple(snap_compare): + assert snap_compare(SNAPSHOT_APPS_DIR / "margin_multiple.py") + + +def test_dynamic_bindings(snap_compare): + assert snap_compare( + SNAPSHOT_APPS_DIR / "dynamic_bindings.py", press=["a", "b", "c"] + ) + + +def test_grid_gutter(snap_compare): + # https://github.com/Textualize/textual/issues/4522 + assert snap_compare(SNAPSHOT_APPS_DIR / "grid_gutter.py") + + +def test_multi_keys(snap_compare): + # https://github.com/Textualize/textual/issues/4542 + assert snap_compare(SNAPSHOT_APPS_DIR / "multi_keys.py") + + +def test_data_table_in_tabs(snap_compare): + assert snap_compare(SNAPSHOT_APPS_DIR / "data_table_tabs.py") + + +def test_auto_tab_active(snap_compare): + # https://github.com/Textualize/textual/issues/4593 + assert snap_compare(SNAPSHOT_APPS_DIR / "auto_tab_active.py", press=["space"]) + + +def test_hatch(snap_compare): + """Test hatch styles.""" + assert snap_compare(SNAPSHOT_APPS_DIR / "hatch.py") + + +def test_rules(snap_compare): + """Test rules.""" + assert snap_compare(SNAPSHOT_APPS_DIR / "rules.py") + + +def test_grid_auto(snap_compare): + """Test grid with keyline and auto-dimension.""" + # https://github.com/Textualize/textual/issues/4678 + assert snap_compare(SNAPSHOT_APPS_DIR / "grid_auto.py") + + +def test_footer_compact(snap_compare): + """Test Footer in the compact style""" + assert snap_compare(SNAPSHOT_APPS_DIR / "footer_toggle_compact.py") + + +def test_footer_compact_with_hover(snap_compare): + """Test Footer in the compact style when the mouse is hovering over a keybinding""" + + async def run_before(pilot) -> None: + await pilot.hover("Footer", offset=(0, 0)) + + assert snap_compare( + SNAPSHOT_APPS_DIR / "footer_toggle_compact.py", run_before=run_before + ) + + +def test_footer_standard_after_reactive_change(snap_compare): + """Test Footer in the standard style after `compact` reactive change""" + assert snap_compare( + SNAPSHOT_APPS_DIR / "footer_toggle_compact.py", press=["ctrl+t"] + ) + + +def test_footer_standard_with_hover(snap_compare): + """Test Footer in the standard style when the mouse is hovering over a keybinding""" + + async def run_before(pilot) -> None: + await pilot.press("ctrl+t") + await pilot.hover("Footer", offset=(0, 0)) + + assert snap_compare( + SNAPSHOT_APPS_DIR / "footer_toggle_compact.py", run_before=run_before + ) + + +def test_footer_classic_styling(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/4557""" + assert snap_compare(SNAPSHOT_APPS_DIR / "footer_classic_styling.py") + + +def test_option_list_scrolling_with_multiline_options(snap_compare): + # https://github.com/Textualize/textual/issues/4705 + assert snap_compare(WIDGET_EXAMPLES_DIR / "option_list_tables.py", press=["up"]) + + +def test_bindings_screen_overrides_show(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/4382""" + assert snap_compare(SNAPSHOT_APPS_DIR / "bindings_screen_overrides_show.py") + + +def test_scroll_visible_with_margin(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/2181""" + assert snap_compare(SNAPSHOT_APPS_DIR / "scroll_visible_margin.py", press=["x"]) + + +def test_programmatic_disable_button(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/3130""" + + async def run_before(pilot: Pilot) -> None: + await pilot.hover("#disable-btn") + await pilot.press("space") + + assert snap_compare( + SNAPSHOT_APPS_DIR / "programmatic_disable_button.py", run_before=run_before + ) + +def test_toggle_style_order(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/3421""" + assert snap_compare(SNAPSHOT_APPS_DIR / "toggle_style_order.py") + +def test_component_text_opacity(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/3413""" + assert snap_compare(SNAPSHOT_APPS_DIR / "component_text_opacity.py") + diff --git a/tests/test_actions.py b/tests/test_actions.py index 1392e73719..e8812ce227 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -8,20 +8,24 @@ @pytest.mark.parametrize( - ("action_string", "expected_name", "expected_arguments"), + ("action_string", "expected_namespace", "expected_name", "expected_arguments"), [ - ("spam", "spam", ()), - ("hypothetical_action()", "hypothetical_action", ()), - ("another_action(1)", "another_action", (1,)), - ("foo(True, False)", "foo", (True, False)), - ("foo.bar.baz(3, 3.15, 'Python')", "foo.bar.baz", (3, 3.15, "Python")), - ("m1234.n5678(None, [1, 2])", "m1234.n5678", (None, [1, 2])), + ("spam", "", "spam", ()), + ("hypothetical_action()", "", "hypothetical_action", ()), + ("another_action(1)", "", "another_action", (1,)), + ("foo(True, False)", "", "foo", (True, False)), + ("foo.bar.baz(3, 3.15, 'Python')", "foo.bar", "baz", (3, 3.15, "Python")), + ("m1234.n5678(None, [1, 2])", "m1234", "n5678", (None, [1, 2])), ], ) def test_parse_action( - action_string: str, expected_name: str, expected_arguments: tuple[Any] + action_string: str, + expected_namespace: str, + expected_name: str, + expected_arguments: tuple[Any], ) -> None: - action_name, action_arguments = parse(action_string) + namespace, action_name, action_arguments = parse(action_string) + assert namespace == expected_namespace assert action_name == expected_name assert action_arguments == expected_arguments @@ -44,7 +48,7 @@ def test_nested_and_convoluted_tuple_arguments( action_string: str, expected_arguments: tuple[Any] ) -> None: """Test that tuple arguments are parsed correctly.""" - _, args = parse(action_string) + _, _, args = parse(action_string) assert args == expected_arguments @@ -67,7 +71,7 @@ def test_parse_action_nested_special_character_arguments( See also: https://github.com/Textualize/textual/issues/2088 """ - _, args = parse(action_string) + _, _, args = parse(action_string) assert args == expected_arguments diff --git a/tests/test_color.py b/tests/test_color.py index bdae7a7cbb..127d24d716 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -245,8 +245,9 @@ def test_gradient_errors(): def test_gradient(): gradient = Gradient( (0, Color(255, 0, 0)), - (0.5, Color(0, 0, 255)), + (0.5, "blue"), (1, Color(0, 255, 0)), + quality=11, ) assert gradient.get_color(-1) == Color(255, 0, 0) @@ -255,7 +256,3 @@ def test_gradient(): assert gradient.get_color(1.2) == Color(0, 255, 0) assert gradient.get_color(0.5) == Color(0, 0, 255) assert gradient.get_color(0.7) == Color(0, 101, 153) - - gradient._stops.pop() - with pytest.raises(AssertionError): - gradient.get_color(1.0) diff --git a/tests/test_content_switcher.py b/tests/test_content_switcher.py index a5e329e497..cc1e37a3e9 100644 --- a/tests/test_content_switcher.py +++ b/tests/test_content_switcher.py @@ -107,3 +107,16 @@ async def test_set_current_to_unknown_id() -> None: ) with pytest.raises(NoMatches): pilot.app.query_one(ContentSwitcher).current = "does-not-exist" + + +async def test_add_content() -> None: + async with SwitcherApp().run_test() as pilot: + switcher = pilot.app.query_one(ContentSwitcher) + await switcher.add_content(Widget(id="foo")) + assert not switcher.query_one("#foo").display + await switcher.add_content(Widget(), id="bar", set_current=True) + assert not switcher.query_one("#foo").display + assert switcher.query_one("#bar").display + assert switcher.current == "bar" + with pytest.raises(ValueError): + switcher.add_content(Widget()) diff --git a/tests/test_data_table.py b/tests/test_data_table.py index cb921ae638..018a3939a3 100644 --- a/tests/test_data_table.py +++ b/tests/test_data_table.py @@ -192,11 +192,11 @@ async def test_cursor_movement_with_home_pagedown_etc(show_header): await pilot.pause() assert table.cursor_coordinate == Coordinate(0, 1) - await pilot.press("end") + await pilot.press("home") await pilot.pause() - assert table.cursor_coordinate == Coordinate(2, 1) + assert table.cursor_coordinate == Coordinate(0, 0) - await pilot.press("home") + await pilot.press("end") await pilot.pause() assert table.cursor_coordinate == Coordinate(0, 1) @@ -255,6 +255,16 @@ async def test_add_row_duplicate_key(): table.add_row("2", key="1") # Duplicate row key +async def test_add_row_too_many_values(): + app = DataTableApp() + async with app.run_test(): + table = app.query_one(DataTable) + table.add_column("A") + table.add_row("1", key="1") + with pytest.raises(ValueError): + table.add_row("1", "2") + + async def test_add_column_duplicate_key(): app = DataTableApp() async with app.run_test(): @@ -1438,7 +1448,7 @@ def compose(self) -> ComposeResult: def on_mount(self) -> None: table = self.query_one(DataTable) - table.border_title = "[@click=test_link]Border Link[/]" + table.border_title = "[@click=app.test_link]Border Link[/]" def action_test_link(self) -> None: self.link_clicked = True diff --git a/tests/test_dynamic_bindings.py b/tests/test_dynamic_bindings.py new file mode 100644 index 0000000000..05b9baecd3 --- /dev/null +++ b/tests/test_dynamic_bindings.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from textual.app import App + + +async def test_dynamic_disabled(): + """Check we can dynamically disable bindings.""" + actions = [] + + class DynamicApp(App): + BINDINGS = [ + ("a", "register('a')", "A"), + ("b", "register('b')", "B"), + ("c", "register('c')", "B"), + ] + + def action_register(self, key: str) -> None: + actions.append(key) + + def check_action( + self, action: str, parameters: tuple[object, ...] + ) -> bool | None: + if action == "register": + if parameters == ("b",): + return False + if parameters == ("c",): + return None + return True + + app = DynamicApp() + async with app.run_test() as pilot: + await pilot.press("a", "b", "c") + assert actions == ["a"] diff --git a/tests/test_footer.py b/tests/test_footer.py deleted file mode 100644 index 26e46cf29f..0000000000 --- a/tests/test_footer.py +++ /dev/null @@ -1,28 +0,0 @@ -from textual.app import App, ComposeResult -from textual.geometry import Offset -from textual.screen import ModalScreen -from textual.widgets import Footer, Label - - -async def test_footer_highlight_when_pushing_modal(): - """Regression test for https://github.com/Textualize/textual/issues/2606""" - - class MyModalScreen(ModalScreen): - def compose(self) -> ComposeResult: - yield Label("apple") - - class MyApp(App[None]): - BINDINGS = [("a", "p", "push")] - - def compose(self) -> ComposeResult: - yield Footer() - - def action_p(self): - self.push_screen(MyModalScreen()) - - app = MyApp() - async with app.run_test(size=(80, 2)) as pilot: - await pilot.hover(None, Offset(0, 1)) - await pilot.click(None, Offset(0, 1)) - assert isinstance(app.screen, MyModalScreen) - assert app.screen_stack[0].query_one(Footer).highlight_key is None diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 021b216369..acb06f57b8 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -486,3 +486,17 @@ def test_size_with_height(): def test_size_with_width(): """Test Size.with_width""" assert Size(1, 2).with_width(10) == Size(10, 2) + + +def test_offset_clamp(): + assert Offset(1, 2).clamp(3, 3) == Offset(1, 2) + assert Offset(3, 2).clamp(3, 3) == Offset(2, 2) + assert Offset(-3, 2).clamp(3, 3) == Offset(0, 2) + assert Offset(5, 4).clamp(3, 3) == Offset(2, 2) + + +def test_size_clamp_offset(): + assert Size(3, 3).clamp_offset(Offset(1, 2)) == Offset(1, 2) + assert Size(3, 3).clamp_offset(Offset(3, 2)) == Offset(2, 2) + assert Size(3, 3).clamp_offset(Offset(-3, 2)) == Offset(0, 2) + assert Size(3, 3).clamp_offset(Offset(5, 4)) == Offset(2, 2) diff --git a/tests/test_issue_4248.py b/tests/test_issue_4248.py index de3eee0928..012b11edae 100644 --- a/tests/test_issue_4248.py +++ b/tests/test_issue_4248.py @@ -10,16 +10,15 @@ async def test_issue_4248() -> None: bumps = 0 class ActionApp(App[None]): - def compose(self) -> ComposeResult: yield Label("[@click]click me and crash[/]", id="nothing") yield Label("[@click=]click me and crash[/]", id="no-params") yield Label("[@click=()]click me and crash[/]", id="empty-params") yield Label("[@click=foobar]click me[/]", id="unknown-sans-parens") yield Label("[@click=foobar()]click me[/]", id="unknown-with-parens") - yield Label("[@click=bump]click me[/]", id="known-sans-parens") - yield Label("[@click=bump()]click me[/]", id="known-empty-parens") - yield Label("[@click=bump(100)]click me[/]", id="known-with-param") + yield Label("[@click=app.bump]click me[/]", id="known-sans-parens") + yield Label("[@click=app.bump()]click me[/]", id="known-empty-parens") + yield Label("[@click=app.bump(100)]click me[/]", id="known-with-param") def action_bump(self, by_value: int = 1) -> None: nonlocal bumps diff --git a/tests/test_keys.py b/tests/test_keys.py index 3aac179fc6..54068b1901 100644 --- a/tests/test_keys.py +++ b/tests/test_keys.py @@ -64,8 +64,8 @@ def test_get_key_display_when_used_in_conjunction(): right_square_bracket = _get_key_display("right_square_bracket") ctrl_right_square_bracket = _get_key_display("ctrl+right_square_bracket") - assert ctrl_right_square_bracket == f"CTRL+{right_square_bracket}" + assert ctrl_right_square_bracket == f"ctrl+{right_square_bracket}" left = _get_key_display("left") ctrl_left = _get_key_display("ctrl+left") - assert ctrl_left == f"CTRL+{left}" + assert ctrl_left == f"ctrl+{left}" diff --git a/tests/test_links.py b/tests/test_links.py new file mode 100644 index 0000000000..37cb286a4d --- /dev/null +++ b/tests/test_links.py @@ -0,0 +1,37 @@ +from textual.app import App, ComposeResult +from textual.screen import Screen +from textual.widgets import Label + + +async def test_links() -> None: + """Regression test for https://github.com/Textualize/textual/issues/4536""" + messages: list[str] = [] + + class LinkLabel(Label): + def action_bell_message(self, message: str) -> None: + nonlocal messages + messages.append(f"label {message}") + + class HomeScreen(Screen[None]): + def compose(self) -> ComposeResult: + yield LinkLabel("[@click=app.bell_message('foo')]Ring the bell![/]") + yield LinkLabel("[@click=screen.bell_message('bar')]Ring the bell![/]") + yield LinkLabel("[@click=bell_message('baz')]Ring the bell![/]") + + def action_bell_message(self, message: str) -> None: + nonlocal messages + messages.append(f"screen {message}") + + class ScreenNamespace(App[None]): + def get_default_screen(self) -> HomeScreen: + return HomeScreen() + + def action_bell_message(self, message: str) -> None: + nonlocal messages + messages.append(f"app {message}") + + async with ScreenNamespace().run_test() as pilot: + await pilot.click(offset=(5, 0)) + await pilot.click(offset=(5, 1)) + await pilot.click(offset=(5, 2)) + assert messages == ["app foo", "screen bar", "label baz"] diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 358d64baba..ed99b2feef 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -149,7 +149,6 @@ async def test_update_of_document_posts_table_of_content_update_message() -> Non messages: list[str] = [] class TableOfContentApp(App[None]): - def compose(self) -> ComposeResult: yield Markdown("# One\n\n#Two\n") @@ -162,6 +161,57 @@ def log_table_of_content_update( async with TableOfContentApp().run_test() as pilot: assert messages == ["TableOfContentsUpdated"] - pilot.app.query_one(Markdown).update("") + await pilot.app.query_one(Markdown).update("") await pilot.pause() assert messages == ["TableOfContentsUpdated", "TableOfContentsUpdated"] + + +async def test_link_in_markdown_table_posts_message_when_clicked(): + """A link inside a markdown table should post a `Markdown.LinkClicked` + message when clicked. + + Regression test for https://github.com/Textualize/textual/issues/4683 + """ + + markdown_table = """\ +| Textual Links | +| ------------------------------------------------ | +| [GitHub](https://github.com/textualize/textual/) | +| [Documentation](https://textual.textualize.io/) |\ +""" + + class MarkdownTableApp(App): + messages = [] + + def compose(self) -> ComposeResult: + yield Markdown(markdown_table) + + @on(Markdown.LinkClicked) + def log_markdown_link_clicked( + self, + event: Markdown.LinkClicked, + ) -> None: + self.messages.append(event.__class__.__name__) + + app = MarkdownTableApp() + async with app.run_test() as pilot: + await pilot.click(Markdown, offset=(3, 3)) + assert app.messages == ["LinkClicked"] + + +async def test_markdown_quoting(): + # https://github.com/Textualize/textual/issues/3350 + links = [] + + class MyApp(App): + def compose(self) -> ComposeResult: + self.md = Markdown(markdown="[tété](tété)") + yield self.md + + def on_markdown_link_clicked(self, message: Markdown.LinkClicked): + links.append(message.href) + + app = MyApp() + async with app.run_test() as pilot: + await pilot.click(Markdown, offset=(0, 0)) + assert links == ["tété"] diff --git a/tests/test_message_pump.py b/tests/test_message_pump.py index a25bd53a79..90b840bf85 100644 --- a/tests/test_message_pump.py +++ b/tests/test_message_pump.py @@ -5,7 +5,7 @@ from textual.events import Key from textual.message import Message from textual.widget import Widget -from textual.widgets import Input +from textual.widgets import Button, Input, Label class ValidWidget(Widget): @@ -143,3 +143,28 @@ def on_input_changed(self) -> None: app.call_next(app.change_input) await pilot.pause() assert hits == 2 + + +async def test_prevent_default(): + """Test that prevent_default doesn't apply when a message is bubbled.""" + + app_button_pressed = False + + class MyButton(Button): + def _on_button_pressed(self, event: Button.Pressed) -> None: + event.prevent_default() + + class PreventApp(App[None]): + def compose(self) -> ComposeResult: + yield MyButton("Press me") + yield Label("No pressure") + + def on_button_pressed(self, event: Button.Pressed) -> None: + nonlocal app_button_pressed + app_button_pressed = True + self.query_one(Label).update("Ouch!") + + app = PreventApp() + async with app.run_test() as pilot: + await pilot.click(MyButton) + assert app_button_pressed diff --git a/tests/test_modal.py b/tests/test_modal.py new file mode 100644 index 0000000000..d0af4d6770 --- /dev/null +++ b/tests/test_modal.py @@ -0,0 +1,94 @@ +from textual.app import App, ComposeResult +from textual.containers import Grid +from textual.screen import ModalScreen +from textual.widgets import Button, Footer, Header, Label + +TEXT = """I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +I will permit it to pass over me and through me. +And when it has gone past, I will turn the inner eye to see its path. +Where the fear has gone there will be nothing. Only I will remain.""" + + +class QuitScreen(ModalScreen[bool]): # (1)! + """Screen with a dialog to quit.""" + + def compose(self) -> ComposeResult: + yield Grid( + Label("Are you sure you want to quit?", id="question"), + Button("Quit", variant="error", id="quit"), + Button("Cancel", variant="primary", id="cancel"), + id="dialog", + ) + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "quit": + self.dismiss(True) + else: + self.dismiss(False) + + +class ModalApp(App): + """An app with a modal dialog.""" + + BINDINGS = [("q", "request_quit", "Quit")] + + CSS = """ +QuitScreen { + align: center middle; +} + +#dialog { + grid-size: 2; + grid-gutter: 1 2; + grid-rows: 1fr 3; + padding: 0 1; + width: 60; + height: 11; + border: thick $background 80%; + background: $surface; +} + +#question { + column-span: 2; + height: 1fr; + width: 1fr; + content-align: center middle; +} + +Button { + width: 100%; +} + + """ + + def compose(self) -> ComposeResult: + yield Header() + yield Label(TEXT * 8) + yield Footer() + + def action_request_quit(self) -> None: + """Action to display the quit dialog.""" + + def check_quit(quit: bool) -> None: + """Called when QuitScreen is dismissed.""" + + if quit: + self.exit() + + self.push_screen(QuitScreen(), check_quit) + + +async def test_modal_pop_screen(): + # https://github.com/Textualize/textual/issues/4656 + + async with ModalApp().run_test() as pilot: + await pilot.pause() + # Check clicking the footer brings up the quit screen + await pilot.click(Footer) + assert isinstance(pilot.app.screen, QuitScreen) + # Check activating the quit button exits the app + await pilot.press("enter") + assert pilot.app._exit diff --git a/tests/test_pilot.py b/tests/test_pilot.py index 9451237fab..52a759416f 100644 --- a/tests/test_pilot.py +++ b/tests/test_pilot.py @@ -6,6 +6,7 @@ from textual.app import App, ComposeResult from textual.binding import Binding from textual.containers import Center, Middle +from textual.css.errors import StylesheetError from textual.pilot import OutOfBounds from textual.screen import Screen from textual.widgets import Button, Label @@ -395,3 +396,14 @@ async def test_pilot_resize_terminal(): await pilot.pause() assert app.size == (27, 15) assert app.screen.size == (27, 15) + + +async def test_fail_early(): + # https://github.com/Textualize/textual/issues/3282 + class MyApp(App): + CSS_PATH = "foo.tcss" + + app = MyApp() + with pytest.raises(StylesheetError): + async with app.run_test() as pilot: + await pilot.press("enter") diff --git a/tests/test_pipe.py b/tests/test_pipe.py new file mode 100644 index 0000000000..92593ac4d6 --- /dev/null +++ b/tests/test_pipe.py @@ -0,0 +1,21 @@ +import subprocess +import sys +from pathlib import Path + +import pytest + +skip_windows = pytest.mark.skipif( + sys.platform == "win32", reason="Syntax not supported on Windows" +) + + +@skip_windows +def test_deadlock(): + """Regression test for https://github.com/Textualize/textual/issues/4643""" + app_path = (Path(__file__) / "../deadlock.py").resolve().absolute() + result = subprocess.run( + f'echo q | "{sys.executable}" "{app_path}"', shell=True, capture_output=True + ) + print(result.stdout) + print(result.stderr) + assert result.returncode == 0 diff --git a/tests/test_query.py b/tests/test_query.py index 19aac12dbe..07f608824a 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -56,6 +56,11 @@ class App(Widget): # repeat tests to account for caching for repeat in range(3): + assert list(app.query_children()) == [main_view, help_view] + assert list(app.query_children("*")) == [main_view, help_view] + assert list(app.query_children("#help")) == [help_view] + assert list(main_view.query_children(".float")) == [sidebar] + assert list(app.query("Frob")) == [] assert list(app.query(".frob")) == [] assert list(app.query("#frob")) == [] diff --git a/tests/test_reactive.py b/tests/test_reactive.py index 8a22d70a13..ad2da5724d 100644 --- a/tests/test_reactive.py +++ b/tests/test_reactive.py @@ -735,7 +735,6 @@ def on_test_widget_test_message(self, event: TestWidget.TestMessage) -> None: message_senders.append(event._sender) class TestApp(App[None]): - def compose(self) -> ComposeResult: yield TestContainer() @@ -744,3 +743,43 @@ def compose(self) -> ComposeResult: pilot.app.query_one(TestWidget).make_reaction() await pilot.pause() assert message_senders == [pilot.app.query_one(TestWidget)] + + +async def test_mutate_reactive() -> None: + """Test explicitly mutating reactives""" + + watched_names: list[list[str]] = [] + + class TestWidget(Widget): + names: reactive[list[str]] = reactive(list) + + def watch_names(self, names: list[str]) -> None: + watched_names.append(names.copy()) + + class TestApp(App): + def compose(self) -> ComposeResult: + yield TestWidget() + + app = TestApp() + async with app.run_test(): + widget = app.query_one(TestWidget) + # watch method called on startup + assert watched_names == [[]] + + # Mutate the list + widget.names.append("Paul") + # No changes expected + assert watched_names == [[]] + # Explicitly mutate the reactive + widget.mutate_reactive(TestWidget.names) + # Watcher will be invoked + assert watched_names == [[], ["Paul"]] + # Make further modifications + widget.names.append("Jessica") + widget.names.remove("Paul") + # No change expected + assert watched_names == [[], ["Paul"]] + # Explicit mutation + widget.mutate_reactive(TestWidget.names) + # Watcher should be invoked + assert watched_names == [[], ["Paul"], ["Jessica"]] diff --git a/tests/test_rlock.py b/tests/test_rlock.py new file mode 100644 index 0000000000..40cc4281b2 --- /dev/null +++ b/tests/test_rlock.py @@ -0,0 +1,56 @@ +import asyncio + +import pytest + +from textual.rlock import RLock + + +async def test_simple_lock(): + lock = RLock() + # Starts not locked + assert not lock.is_locked + # Acquire the lock + await lock.acquire() + assert lock.is_locked + # Acquire a second time (should not block) + await lock.acquire() + assert lock.is_locked + + # Release the lock + lock.release() + # Should still be locked + assert lock.is_locked + # Release the lock + lock.release() + # Should be released + assert not lock.is_locked + + # Another release is a runtime error + with pytest.raises(RuntimeError): + lock.release() + + +async def test_multiple_tasks() -> None: + """Check RLock prevents other tasks from acquiring lock.""" + lock = RLock() + + started: list[int] = [] + done: list[int] = [] + + async def test_task(n: int) -> None: + started.append(n) + async with lock: + done.append(n) + + async with lock: + assert done == [] + task1 = asyncio.create_task(test_task(1)) + assert sorted(started) == [] + task2 = asyncio.create_task(test_task(2)) + await asyncio.sleep(0) + assert sorted(started) == [1, 2] + + await task1 + assert 1 in done + await task2 + assert 2 in done diff --git a/tests/test_screen_modes.py b/tests/test_screen_modes.py index 2c7cdcbb66..85d1a8b358 100644 --- a/tests/test_screen_modes.py +++ b/tests/test_screen_modes.py @@ -11,9 +11,7 @@ UnknownModeError, ) from textual.screen import ModalScreen, Screen -from textual.widgets import Footer, Header, Label, RichLog - -FRUITS = cycle("apple mango strawberry banana peach pear melon watermelon".split()) +from textual.widgets import Footer, Header, Label class ScreenBindingsMixin(Screen[None]): @@ -21,7 +19,7 @@ class ScreenBindingsMixin(Screen[None]): ("1", "one", "Mode 1"), ("2", "two", "Mode 2"), ("p", "push", "Push rnd scrn"), - ("o", "pop_screen", "Pop"), + ("o", "app.pop_screen", "Pop"), ("r", "remove", "Remove mode 1"), ] @@ -56,14 +54,12 @@ class FruitModal(ModalScreen[str], ScreenBindingsMixin): BINDINGS = [("d", "dismiss_fruit", "Dismiss")] def compose(self) -> ComposeResult: + FRUITS = cycle( + "apple mango strawberry banana peach pear melon watermelon".split() + ) yield Label(next(FRUITS)) -class FruitsScreen(ScreenBindingsMixin): - def compose(self) -> ComposeResult: - yield RichLog() - - @pytest.fixture def ModesApp(): class ModesApp(App[None]): diff --git a/tests/test_screens.py b/tests/test_screens.py index 83fcde4932..aef83dfdb4 100644 --- a/tests/test_screens.py +++ b/tests/test_screens.py @@ -5,7 +5,7 @@ import pytest from textual import work -from textual.app import App, ComposeResult, ScreenStackError +from textual.app import App, ComposeResult, ScreenError, ScreenStackError from textual.events import MouseMove from textual.geometry import Offset from textual.screen import Screen @@ -110,7 +110,7 @@ async def test_screens(): # Check screen stack is empty assert app.screen_stack == [] # Push a screen - app.push_screen("screen1") + await app.push_screen("screen1") # Check it is on the stack assert app.screen_stack == [screen1] # Check it is current @@ -119,21 +119,21 @@ async def test_screens(): assert app.children == (screen1,) # Switch to another screen - app.switch_screen("screen2") + await app.switch_screen("screen2") # Check it has changed the stack and that it is current assert app.screen_stack == [screen2] assert app.screen is screen2 assert app.children == (screen2,) # Push another screen - app.push_screen("screen3") + await app.push_screen("screen3") assert app.screen_stack == [screen2, screen3] assert app.screen is screen3 # Only the current screen is in children assert app.children == (screen3,) # Pop a screen - assert app.pop_screen() is screen3 + await app.pop_screen() assert app.screen is screen2 assert app.screen_stack == [screen2] @@ -293,15 +293,16 @@ def on_mount(self): async def test_dismiss_non_top_screen(): class MyApp(App[None]): async def key_p(self) -> None: - self.bottom, top = Screen(), Screen() + self.bottom = Screen() + top = Screen() await self.push_screen(self.bottom) await self.push_screen(top) app = MyApp() async with app.run_test() as pilot: await pilot.press("p") - with pytest.raises(ScreenStackError): - app.bottom.dismiss() + with pytest.raises(ScreenError): + await app.bottom.dismiss() async def test_dismiss_action(): @@ -496,3 +497,20 @@ async def action_exit(self) -> None: with pytest.raises(NoActiveWorker): async with app.run_test() as pilot: await pilot.press("x", "y") + + +async def test_default_custom_screen() -> None: + """Test we can override the default screen.""" + + class CustomScreen(Screen): + pass + + class CustomScreenApp(App): + def get_default_screen(self) -> Screen: + return CustomScreen() + + app = CustomScreenApp() + async with app.run_test(): + assert len(app.screen_stack) == 1 + assert isinstance(app.screen_stack[0], CustomScreen) + assert app.screen is app.screen_stack[0] diff --git a/tests/test_shutdown.py b/tests/test_shutdown.py new file mode 100644 index 0000000000..4810aa3943 --- /dev/null +++ b/tests/test_shutdown.py @@ -0,0 +1,18 @@ +from textual.app import App +from textual.containers import Horizontal +from textual.widgets import Footer, Tree + + +class TreeApp(App[None]): + def compose(self): + yield Horizontal(Tree("Dune")) + yield Footer() + + +async def test_shutdown(): + # regression test for https://github.com/Textualize/textual/issues/4634 + # Testing that an app with the footer doesn't deadlock + app = TreeApp() + print("Check for deadlock in test_shutdown.py") + async with app.run_test(): + pass diff --git a/tests/test_signal.py b/tests/test_signal.py index 7833ae70f4..c8562c4ab4 100644 --- a/tests/test_signal.py +++ b/tests/test_signal.py @@ -11,7 +11,7 @@ async def test_signal(): class TestLabel(Label): def on_mount(self) -> None: - def signal_result(): + def signal_result(_): nonlocal called called += 1 @@ -22,14 +22,14 @@ class TestApp(App): BINDINGS = [("space", "signal")] def __init__(self) -> None: - self.test_signal = Signal(self, "coffee ready") + self.test_signal: Signal[str] = Signal(self, "coffee ready") super().__init__() def compose(self) -> ComposeResult: yield TestLabel() def action_signal(self) -> None: - self.test_signal.publish() + self.test_signal.publish("foo") app = TestApp() async with app.run_test() as pilot: @@ -65,7 +65,7 @@ def test_signal_errors(): label = Label() # Check subscribing a non-running widget is an error with pytest.raises(SignalError): - test_signal.subscribe(label, lambda: None) + test_signal.subscribe(label, lambda _: None) def test_repr(): @@ -73,3 +73,38 @@ def test_repr(): app = App() test_signal = Signal(app, "test") assert isinstance(repr(test_signal), str) + + +async def test_signal_parameters(): + str_result: str | None = None + int_result: int | None = None + + class TestApp(App): + BINDINGS = [("space", "signal")] + + def __init__(self) -> None: + self.str_signal: Signal[str] = Signal(self, "str") + self.int_signal: Signal[int] = Signal(self, "int") + super().__init__() + + def action_signal(self) -> None: + self.str_signal.publish("foo") + self.int_signal.publish(3) + + def on_mount(self) -> None: + def on_str(my_str): + nonlocal str_result + str_result = my_str + + def on_int(my_int): + nonlocal int_result + int_result = my_int + + self.str_signal.subscribe(self, on_str) + self.int_signal.subscribe(self, on_int) + + app = TestApp() + async with app.run_test() as pilot: + await pilot.press("space") + assert str_result == "foo" + assert int_result == 3 diff --git a/tests/test_suspend.py b/tests/test_suspend.py index 8f4534dce1..b2d95cff0d 100644 --- a/tests/test_suspend.py +++ b/tests/test_suspend.py @@ -39,17 +39,17 @@ def resume_application_mode(self) -> None: calls.add("resume") class SuspendApp(App[None]): - def on_suspend(self) -> None: + def on_suspend(self, _) -> None: nonlocal calls calls.add("suspend signal") - def on_resume(self) -> None: + def on_resume(self, _) -> None: nonlocal calls calls.add("resume signal") def on_mount(self) -> None: - self.app_suspend_signal.subscribe(self, self.on_suspend) - self.app_resume_signal.subscribe(self, self.on_resume) + self.app_suspend_signal.subscribe(self, self.on_suspend, immediate=True) + self.app_resume_signal.subscribe(self, self.on_resume, immediate=True) async with SuspendApp(driver_class=HeadlessSuspendDriver).run_test( headless=False diff --git a/tests/test_tabs.py b/tests/test_tabs.py index 8d865552d2..0eef93f78a 100644 --- a/tests/test_tabs.py +++ b/tests/test_tabs.py @@ -491,3 +491,22 @@ async def test_mouse_navigation_messages(): "on_tabs_tab_activated", "on_tabs_tab_activated", ] + + +async def test_disabled_tab_is_not_activated_by_clicking_underline(): + """Regression test for https://github.com/Textualize/textual/issues/4701""" + + class DisabledTabApp(App): + def compose(self) -> ComposeResult: + yield Tabs( + Tab("Enabled", id="enabled"), + Tab("Disabled", id="disabled", disabled=True), + ) + + app = DisabledTabApp() + async with app.run_test() as pilot: + # Click the underline beneath the disabled tab + await pilot.click(Tabs, offset=(14, 2)) + tabs = pilot.app.query_one(Tabs) + assert tabs.active_tab is not None + assert tabs.active_tab.id == "enabled" diff --git a/tests/test_tooltips.py b/tests/test_tooltips.py index 3648615148..d6cc4b46fa 100644 --- a/tests/test_tooltips.py +++ b/tests/test_tooltips.py @@ -7,7 +7,6 @@ class TooltipApp(App[None]): - CSS = """ Static { width: 1fr; diff --git a/tests/test_widget_child_moving.py b/tests/test_widget_child_moving.py index f0ab58d303..f4c89b607f 100644 --- a/tests/test_widget_child_moving.py +++ b/tests/test_widget_child_moving.py @@ -137,9 +137,14 @@ async def test_move_before_end_of_child_list() -> None: async def test_move_before_permutations() -> None: """Test the different permutations of moving one widget before another.""" widgets = [Widget(id=f"widget-{n}") for n in range(10)] - perms = ((1, 0), (widgets[1], 0), (1, widgets[0]), (widgets[1], widgets[0])) + perms = ( + (1, 0), + (widgets[1], 0), + (1, widgets[0]), + (widgets[1], widgets[0]), + ) for child, target in perms: - async with App().run_test() as pilot: + async with App[None]().run_test() as pilot: container = Widget(*widgets) await pilot.app.mount(container) container.move_child(child, before=target) @@ -153,9 +158,11 @@ async def test_move_after_permutations() -> None: widgets = [Widget(id=f"widget-{n}") for n in range(10)] perms = ((0, 1), (widgets[0], 1), (0, widgets[1]), (widgets[0], widgets[1])) for child, target in perms: - async with App().run_test() as pilot: + async with App[None]().run_test() as pilot: container = Widget(*widgets) await pilot.app.mount(container) + await pilot.pause() + container.move_child(child, after=target) assert container._nodes[0].id == "widget-1" assert container._nodes[1].id == "widget-0" diff --git a/tests/test_widget_mounting.py b/tests/test_widget_mounting.py index 305fd7c827..7babed4781 100644 --- a/tests/test_widget_mounting.py +++ b/tests/test_widget_mounting.py @@ -19,7 +19,7 @@ async def test_mount_via_app() -> None: # Make a background set of widgets. widgets = [Static(id=f"starter-{n}") for n in range(10)] - async with App().run_test() as pilot: + async with App[None]().run_test() as pilot: with pytest.raises(WidgetError): await pilot.app.mount(SelfOwn()) @@ -114,3 +114,10 @@ async def test_mount_via_app() -> None: await pilot.app.mount_all(widgets) with pytest.raises(TooManyMatches): await pilot.app.mount(Static(), before="Static") + + +def test_mount_error() -> None: + """Mounting a widget on an un-mounted widget should raise an error.""" + with pytest.raises(MountError): + widget = Widget() + widget.mount(Static()) diff --git a/tests/test_widget_removing.py b/tests/test_widget_removing.py index 7ffb624d77..ce352857f5 100644 --- a/tests/test_widget_removing.py +++ b/tests/test_widget_removing.py @@ -78,7 +78,7 @@ async def test_remove_move_focus(): assert pilot.app.focused == buttons[9] -async def test_widget_remove_order(): +async def test_widget_remove_order() -> None: """A Widget.remove of a top-level widget should cause bottom-first removal.""" removals: list[str] = [] diff --git a/tests/test_xterm_parser.py b/tests/test_xterm_parser.py index f8d0c02942..2995738897 100644 --- a/tests/test_xterm_parser.py +++ b/tests/test_xterm_parser.py @@ -218,6 +218,7 @@ def test_mouse_click(parser, sequence, event_type, shift, meta): ("\x1b[<35;15;38M", False, False, 0), # Basic cursor movement ("\x1b[<39;15;38M", True, False, 0), # Shift held down ("\x1b[<43;15;38M", False, True, 0), # Meta held down + ("\x1b[<3;15;38M", False, False, 0), ], ) def test_mouse_move(parser, sequence, shift, meta, button): @@ -289,6 +290,7 @@ def test_mouse_event_detected_but_info_not_parsed(parser): assert len(events) == 0 +@pytest.mark.xfail() def test_escape_sequence_resulting_in_multiple_keypresses(parser): """Some sequences are interpreted as more than 1 keypress""" events = list(parser.feed("\x1b[2;4~")) diff --git a/tests/text_area/test_edit_via_api.py b/tests/text_area/test_edit_via_api.py index e217bfa210..e732680f0f 100644 --- a/tests/text_area/test_edit_via_api.py +++ b/tests/text_area/test_edit_via_api.py @@ -106,6 +106,27 @@ async def test_insert_character_near_cursor_maintain_selection_offset( assert text_area.selection == Selection.cursor(cursor_destination) +@pytest.mark.parametrize( + "cursor_location,insert_location,cursor_destination", + [ + ((1, 0), (0, 0), (2, 0)), # API insert before cursor row + ((0, 0), (0, 0), (1, 0)), # API insert right at cursor row + ((0, 0), (1, 0), (0, 0)), # API insert after cursor row + ], +) +async def test_insert_newline_around_cursor_maintain_selection_offset( + cursor_location, + insert_location, + cursor_destination +): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor(cursor_location) + text_area.insert("X\n", location=insert_location) + assert text_area.selection == Selection.cursor(cursor_destination) + + async def test_insert_newlines_start(): app = TextAreaApp() async with app.run_test(): diff --git a/tests/tree/test_tree_cursor.py b/tests/tree/test_tree_cursor.py new file mode 100644 index 0000000000..ada1a41c57 --- /dev/null +++ b/tests/tree/test_tree_cursor.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +from typing import Any + +from textual import on +from textual.app import App, ComposeResult +from textual.widgets import Tree +from textual.widgets.tree import NodeID, TreeNode + + +class TreeApp(App[None]): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.messages: list[tuple[str, NodeID]] = [] + + def compose(self) -> ComposeResult: + tree = Tree[str](label="tree") + self._node = tree.root.add_leaf("leaf") + tree.root.expand() + yield tree + + @property + def node(self) -> TreeNode[str]: + return self._node + + @on(Tree.NodeHighlighted) + @on(Tree.NodeSelected) + @on(Tree.NodeCollapsed) + @on(Tree.NodeExpanded) + def record_event( + self, + event: ( + Tree.NodeHighlighted[str] + | Tree.NodeSelected[str] + | Tree.NodeCollapsed[str] + | Tree.NodeExpanded[str] + ), + ) -> None: + self.messages.append((event.__class__.__name__, event.node.id)) + + +async def test_move_cursor() -> None: + """Test moving the cursor to a node (updating the highlighted node).""" + async with TreeApp().run_test() as pilot: + app = pilot.app + tree: Tree[str] = app.query_one(Tree) + node_to_move_to = app.node + tree.move_cursor(node_to_move_to) + await pilot.pause() + + # Note there are no Selected messages. We only move the cursor. + assert app.messages == [ + ("NodeExpanded", 0), # From the call to `tree.root.expand()` in compose + ("NodeHighlighted", 0), # From the initial highlight of the root node + ("NodeHighlighted", 1), # From the call to `tree.move_cursor` + ] + + +async def test_move_cursor_reset() -> None: + async with TreeApp().run_test() as pilot: + app = pilot.app + tree: Tree[str] = app.query_one(Tree) + tree.move_cursor(app.node) + tree.move_cursor(None) + await pilot.pause() + assert app.messages == [ + ("NodeExpanded", 0), # From the call to `tree.root.expand()` in compose + ("NodeHighlighted", 0), # From the initial highlight of the root node + ("NodeHighlighted", 1), # From the 1st call to `tree.move_cursor` + ("NodeHighlighted", 0), # From the call to `tree.move_cursor(None)` + ] + + +async def test_select_node() -> None: + async with TreeApp().run_test() as pilot: + app = pilot.app + tree: Tree[str] = app.query_one(Tree) + tree.select_node(app.node) + await pilot.pause() + assert app.messages == [ + ("NodeExpanded", 0), # From the call to `tree.root.expand()` in compose + ("NodeHighlighted", 0), # From the initial highlight of the root node + ("NodeHighlighted", 1), # From the `tree.select_node` call + ("NodeSelected", 1), # From the call to `tree.select_node` + ] + + +async def test_select_node_reset() -> None: + async with TreeApp().run_test() as pilot: + app = pilot.app + tree: Tree[str] = app.query_one(Tree) + tree.move_cursor(app.node) + tree.select_node(None) + await pilot.pause() + + # Notice no Selected messages. + assert app.messages == [ + ("NodeExpanded", 0), # From the call to `tree.root.expand()` in compose + ("NodeHighlighted", 0), # From the initial highlight of the root node + ("NodeHighlighted", 1), # From the `tree.move_cursor` call + ("NodeHighlighted", 0), # From the call to `tree.select_node(None)` + ] diff --git a/tests/tree/test_tree_messages.py b/tests/tree/test_tree_messages.py index 1262721100..448d883889 100644 --- a/tests/tree/test_tree_messages.py +++ b/tests/tree/test_tree_messages.py @@ -58,8 +58,8 @@ async def test_tree_node_selected_message() -> None: await pilot.press("enter") await pilot.pause() assert pilot.app.messages == [ - ("NodeExpanded", "test-tree"), ("NodeSelected", "test-tree"), + ("NodeExpanded", "test-tree"), ] @@ -151,8 +151,8 @@ async def test_tree_node_highlighted_message() -> None: await pilot.press("enter", "down") await pilot.pause() assert pilot.app.messages == [ - ("NodeExpanded", "test-tree"), ("NodeSelected", "test-tree"), + ("NodeExpanded", "test-tree"), ("NodeHighlighted", "test-tree"), ]