* Fix `line_trim` only handling a single empty segment. It now removes any number of empty segments before performing the trim.
* Update the lockfile
* Tidy up
* Rewriting line_trim to support empty cells correctly
* Regression snapshot test for case where outline was applied to `Button` and did not render correctly.
* Update the changelog
* Add unit test for empty segments case
* Turn off AUTO_FOCUS on button outline snapshot test
* Remove debugging prints
* Fix issue with alignment generating empty segments
* Validate and update snapshots
* Revert some changes relating to trimming empty segments
* _styles_cache.py - In DEBUG mode, inspect Strips for non-control Segments which contain no text. There should be no empty Segments in Textual. If any are detected, we should investigate the source of them.
* _styles_cache.py - In DEBUG mode, inspect Strips for non-control Segments which contain no text. There should be no empty Segments in Textual. If any are detected, we should investigate the source of them.
* Remove types from docs
I use this in so many other projects, and I keep doing it within the textual
development environment, and get annoyed every time it isn't there. It's
handy so...
Yes, this does mean it'll fail when using Python 3.7; but it hardly seems
worth going to all the effort of trying to detect this inside the Makefile
when 3.7-support can't be that long for this world.
First we tested this by splitting the output by lines; that worked but it
could be faster. So now we're going to take the problematic buffer
size (32KiB), assume a reasonable worst case of every character heading out
being 3 bytes in size, and chunking the output based on that.
In other words, experiment for #2548 take two.
This is experimental. I think, ideally, we'll do this by finding an optimal
buffer size and writing based on that, but for the moment let's just try
line-by-line as that's easy.
This will be used to test against the tool I've made that lets me recreate
the issue documented in #2548; if this does the job we'll take this further.
Use a special value to flag a blank selection, add methods is_blank and clear, slightly refactor the way the widget is setup to avoid having attributes _initial_options and _options.
Implement exceptions to be raised when the widget is entering bad state.
* DataTable sort by function (or other callable)
The `DataTable` widget now takes the `by` argument instead of `columns`, allowing the table to also be sorted using a custom function (or other callable). This is a breaking change since it requires all calls to the `sort` method to include an iterable of key(s) (or a singular function/callable). Covers #2261 using [suggested function signature](https://github.com/Textualize/textual/pull/2512#issuecomment-1580277771) from @darrenburns on PR #2512.
* argument change and functionaloty update
Changed back to orinal `columns` argument and added a new `key` argument
which takes a function (or other callable). This allows the PR to NOT BE
a breaking change.
* better example for docs
- Updated the example file for the docs to better show the functionality
of the change (especially when using `columns` and `key` together).
- Added one new tests to cover a similar situation to the example
changes
* removed unecessary code from example
- the sort by clicked column function was bloat in my opinion
* requested changes
* simplify method and terminology
* combine key_wrapper and default sort
* Removing some tests from DataTable.sort as duplicates. Ensure there is test coverage of the case where a key, but no columns, is passed to DataTable.sort.
* Remove unused import
* Fix merge issues in CHANGELOG, update DataTable sort-by-key changelog PR link
---------
Co-authored-by: Darren Burns <darrenburns@users.noreply.github.com>
Co-authored-by: Darren Burns <darrenb900@gmail.com>
* Handle other Tabs widgets as DOM descendants of a TabbedContent
* Update CHANGELOG.md
* Ensure TabbedContent can identify messages from the associated Tabs so that it can ignore messages from other, irrelevant Tabs instances that may exist as descendants deeper in the DOM. Also adds some tests to ensure corresponding issues are fixed.
* Improve docstrings for Tabs.Cleared and corresponding handler inside TabbedContent - it now includes a note that the Cleared message is sent when all tabs are hidden.
Previously you couldn't call a threaded worker from another threaded worker because creating a worker was not thread safe (see #3472). However, the second threaded worker could already be called with self.call_from_thread, so we bake that in.
* Modifying two flaky animation tests, hopefully removing flakiness :)
* Make switch_mode return an AwaitMount
* Fix event issue
* Add AwaitComplete - a more generalised optionally awaitable object
* Use AwaitComplete in Tabs.remove_tab() and update tests accordingly.
* Update TabbedContent to use AwaitComplete instead of AwaitTabbedContent
* Simplifying - dont use optional awaitables where not required
* Update variable name
* Update a comment
* Add watcher for cursor blink to ensure when blink is switched off, the cursor immediately becomes visible. Ensure we turn of cursor blink inside the input suggetions snapshot test.
* Fix cursor blink reactive and disable cursor blink in the command palette snapshot test
* More progress
* Reworking AwaitComplete
* Some more work on tabs flakiness/race-conditions
* Ensure active tab is set correctly
* Simplify next tab assignment
* Simplify removing tabs logic
* Make button animation duration configurable; Switch off button animation in snapshot test
* Remove a flawed test
* Add awaits in some tests
* Docstrings
* Make active_effect_duration an instance attribute
* Fix a Tabs crash
* Await the tree reload when the path changes in DirectoryTree
* Change AwaitComplete _instances class attr to a set from a list
* Make AwaitComplete generic, AwaitComplete._wait_all is now private, and exposes timeout parameter
* Actually make AwaitComplete instances a set, not a list
* Update CHANGELOG.md regarding flaky-test adjacent changes, AwaitComplete, etc..
* Remove whitespace
* Use list() instead of useless comprehension, remove unused import
* Ensure loading indicator _start_time is initialised correctly
* Switch from time.sleep to asyncio.sleep in a notifications test, rework numbers to try and prevent flakiness
* Resolve deadlock by awaiting event on the event loop instead of in the message pump
* Renaming for clarity
* Debugging for remove_tab test flakiness
* Running all tests
* Updating snapshots
* Remove debugging prints
* Fix broken docstring, remove unused import
* Rename variable to make it clearer
* Add missing return type annotation
* Update src/textual/widgets/_tabbed_content.py
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Update src/textual/widgets/_tabbed_content.py
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Update src/textual/widgets/_tabs.py
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Scroll datatable cursor after refresh
* Add comment explaining use of call_after_refresh when scrolling data table cursor into view
* Add repr to AwaitComplete (auto-repr_
* Remove use of generics from AwaitComplete
* Update changelog and improve docstring
* Add a missing parameter from DirectoryTree.reset_node docstring.
Signed-off-by: Darren Burns <darrenb900@gmail.com>
* Improve docstring in DirectoryTree
Signed-off-by: Darren Burns <darrenb900@gmail.com>
* Rename parameter coroutine to coroutines in await_complete.py, since it's a variable length param.
---------
Signed-off-by: Darren Burns <darrenb900@gmail.com>
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
Instead of creating the link explicitly, we let terminal emulators auto-link to the file.
This came after a discussion about how/whether we should try to support linking to specific file lines/columns for TCSS files and after some research to see how that would be possible.
We decided to drop this feature when we couldn't find information in the standards for 'file://' regarding how to specify line/column numbers and after we found [this iTerm issue](https://gitlab.com/gnachman/iterm2/-/issues/9376) where the creator/maintainer of iTerm says that there is no standard API for opening a file to a particular line number.
We already kept track of the file and widget CSS was read from. Now, we also keep track of the class variable it comes from and we create some structure to transfer that information across the program.
* LinuxDriver: Exit if thread dies
If `run_input_thread()` dies, the whole application keeps running but becomes
unresponsive. It has to be killed by the user, leaving the terminal in an
unusable state.
This commit terminates the application instead and prints a traceback.
* LinuxDriver: Handle any exception from input thread
* LinuxDriver: Simpler traceback construction in input thread handler
* LinuxDriver: Catch exception from run_input_thread() in _run_input_thread()
* LinuxDriver: Remove unneeded import: Callable
---------
Co-authored-by: Random User <rndusr@example.org>
Adding will-change: filter seems to fix the issue with the Excalidraw diagrams on dark mode and recent Safari versions.
This change was also tested on Firefox and Chrome and it didn't break the diagrams in those browsers.
This fixes#3497.
* Remove the guard code which protects against `scrollbar-size-*:0`, update error messages indicating its supported now.
* Fix scrollbar region calculation to support zero-thickness scrollbars
* Add test ensuring zero-width scrollbars dont blow up
* Updating CHANGELOG regarding zero-thickness scrollbars
* Add note to scrollbar_size.md about zero width scrollbar
* Move tree-sitter and tree_sitter_languages to optional extras
* Update docs and GitHub action for moving syntax to extras
* Updating, remake lockfile
* Update snapshots from textual-dev change
* Improve warning when a language is set but tree-sitter not available
* Update CHANGELOG
* Add note on syntax extras
* Update lock
* Fixing IME alignment for Input widget. TextArea remains unfixed.
* Fix TextArea IME
* Prefix unused unpacked variables with underscore
* Updating IME preview location on scrolling in TextArea
* Add CHANGELOG entry for IME positioning fix
* Add CHANGELOG entry for new methods on Input and TextArea
* Test TextArea terminal cursor position update
* Tests for Input widget terminal cursor position updating
* Test for IME when content scrolled
In conversation we've decided that it's not that necessary to list all of
the IDs that are duplicates; with this in mind we can whittle down the work
being done to look for duplicates.
Co-authored-by: Will McGugan <willmcgugan@gmail.com>
The link in the doc[1] is not rendering properly,
because the method `textual.app.App.compose`
is ignored in the doc in the filter[2]. This commit
overrides the filter in the app.md file, generating
the doc for the "protocol" method and fixing the
link rendering in the guide.
[1]: https://textual.textualize.io/guide/app/#composing
[2]: textual/mkdocs-common.yml
Fix#3141
Simply put: until now the OptionList was adding the new options and then
checking if the ID was a duplicate. If some code was to then catch
DuplicateID and treat it as a pass this would then have an unintended
side-effect that the duplicate had been added anyway. The ultimate effect of
this being that once there was an attempt to add a duplicate, nothing more
could be added to that OptionList.
Fixes#3455.
Makes sure that cell padding can't be set to a negative value.
This also makes sure that we update the virtual size of the data table when the cell padding changes, otherwise it will go out of sync.
Related review comment: https://github.com/Textualize/textual/pull/3443#discussion_r1343910771
ExpectType grew its Widget bound and then it was moved out of the body of the class so that it could be referenced inside methods, because it was needed inside the body of 'only_one'.
Rather than open the command palette the moment we start looking for hits,
only show it when a hit comes in, or when we need to say that no matches
were found.
Fixes#3277.
* Add docstring and switch to tree-sitter-languages wheels - although the wheels arent working
* Adding highlights files
* Fix index error on SyntaxAwareDocument
* Narrowing highlighting scope
* Adding basic highlights for Markdown
* Using utf-8 byte length instead of codepoint count in syntax aware doc
* Start creating an ABC defining functionality required by Document impls
* Simplify tree-sitter logic
* Extracting more ABC
* Fix width calculation, add SyntaxTheme
* Ensure the highlight line style goes right to the very end
* Updating a docstring
* Renaming, and adding document width guide
* Ensuring that line number column toggling refreshes virtual size
* Ensuring that line number column toggling refreshes virtual size
* Width guide
* Fix focus event stopping
* Use release_mouse
* Improving a docstring
* Remove bash
* TextArea language snapshot testing
* Updating snapshots for TextArea since we now highlight more nodes
* Typing fixes
* Testing
* Adding tests
* Fixing language selection
* Refresh size on indent width change
* Testing, renaming, fixing display of selection
* Fix multibyte highlight glitch
* Fix deleting right with selection at end of document in TextArea
* Fixing utf-8 multibyte character issues
* Default location of text insertion is cursor position, add cursor_location properties
* Removing some debugging code
* Cursor location tests
* Updating snapshots
* Cached utf8 encoding
* TextArea selection snapshot testing
* Tidying docstrings and queries
* Updating selection snapshot output
* Binding for ESC to shift focus
* Only build the tree-sitter query once!
* Expand cursor scroll horizontal leeway in TextArea
* Property setter for cursor_location in TextArea shouldnt return value
* Avoiding NamedTuple subclassing - using type aliasing instead
* Tidying API, docstrings etc.
* Tidying the API and docstrings
* TextArea additional cursor tests
* Testing pageup and pagedown in TextArea
* Fix a faulty test
* Docstring in a test for TextArea edit
* Stop using DEFAULT_SYNTAX_THEME
* Docstrings
* Change cursor_destination to move_cursor, add more tests
* Remove faulty assertion
* Tidying cursor movement
* Tidying up, adding docstrings for component classes
* Fix a broken selection test
* Remove some unused highlighting machinery
* Fix some Python highlighting issues
* Make HTML syntax highlight nicely
* Create tag name for mismatching HTML end tag
* Add styling for YAML, update boolean styling
* Stylising toml types
* Styling floats
* JSON syntax highlighting
* Updating snapshots
* Syntax highlighting datetimes in TOML
* Namespace TOML errors in highlighting
* Add a move_cursor_relative method
* Update TOML TextArea snapshot for datetime highlighting support
* Adjusting selections
* At TextArea widget level, delete_range is insert_range of empty string
* Refactoring
* Dunder all, docstring fix
* Fix XFAIL
* Remove unused import
* More tests, tidying up
* Cleaning the API
* Docstrings for TextArea
* A bunch of docstrings, delete unused code
* More tidying and docstrings
* Cursor origin on document load, correctly handle delete word left/right when selection is non-empty, fix delete_line when selection spans multiple lines and is in reverse direction
* Moving things around
* Fixing dunder all to export DocumentBase
* Add docstring
* Record cursor width on programmatic insert since it can result in the cursor moving
* Typing fixes
* Fixing remaining typing issues with TextArea
* Add tree-sitter-languages stubs and fix typing issues in documents
* Fixing remaining typing issues with document
* Updating Syntax themes
* Improve highlighting, add initial TextArea docs page
* Add TextArea indent note
* Start TextArea guide inside reference
* Add TextArea to widget gallery
* Fleshing out TextArea docs
* Add note
* Fix TextArea programmatic insert/cursor interaction
* Improve a test
* Testing replacement within selection
* Testing double-width character keyboard navigation and deletion keybinds with active selections
* Testing "delete to start of line" TextArea binding
* Testing TextArea delete line methods and delete to end of line
* Testing shift selecting using keyboard in vertical direction
* Expand tests for home and end keybinds in TextArea
* Renaming tests, testing empty replace and insert
* Testing delete word left via API
* Testing delete word left via API
* Testing delete_word_left with tabs, and delete_word_right
* Remove unused variables
* Remove debugging width guide
* Fix snapshot report path
* Deleting word left/right interaction with line ends fixes, ensure cursor width recorded on all edits
* Docstring fixes
* Unpin textual snapshot library dependency (issue is fixed)
* Docstring fixes
* Fix recording cursor width
* Fix a docstring
* Add select_all to TextArea
* Remove unused tree-sitter stuff from .gitignore
* Line select
* Make word pattern private in TextArea
* Add blinking cursor to TextArea
* Renaming, adding missing return typing
* Add selection bindings
* Moving cursor left/right by word while selecting
* Change escape keybind description, TextArea
* Stripping whitespace when going word left/right
* Add missing annotation
* Cursor word right and left parity with PyCharm
* Use repaint=False for cursor blink
* Improve focus/blur styling
* A whole bunch of TextArea testing
* Simplify delete_left and delete_right
* Testing hiding line numbers in snapshot
* Adding snapshot test for unfocus styling
* Create initial snapshot for text-area unfocused
* Support shift+home, shift+end
* Document shift+home, shift+end
* Add Dracula syntax highlighting theme
* Small change to delete_line behaviour when multiple lines selected to match vscode/pycharm behaviour
* Add test for new delete line logic
* Delete line improvement
* Add extra test for delete_line multiple selection
* Test cursor "smart" home behaviour
* Fix typo
* Highlight matching brackets
* Update snapshot
* Update snapshot
* Fix xfails
* Simplify delete_word_left
* Catch correct exception to ensure support for Python 3.7
* Add styling for Markdown
* Add styles for Dracula for Markdown
* Remove unused _fix_direction.py
* Add docstring to EditResult
* Use default=0 in max inside Document
* Remove redundant actions
* Use cell-width aware expand tabs implementation from @willmcgugan
* Construct strip with cell length
* Some TextArea keyword-only arguments
* Begin moving over to TextAreaTheme #skipci
* Prepare queries inside document #skip-ci
* Add comment
* Refactoring
* TextAreaTheme styling
* Setting width of blank selected lines
* Building the highlight map in the text area
* Remove unused default css from TextArea
* Moving highlighting stylize into widget
* Moving syntax highlighting into TextArea widget
* Remove unused code
* Optimise imports
* Fix highlighting when initial text supplied to TextArea
* Rebuild highlight map when the theme changes
* Extending
* Restore themes
* Remove old comment, fix docstring
* Fixing docstrings
* Fixing mypy
* Fixing mypy issues in document
* Tidying things
* Updating version
* Add theme
* Fix VSCode theme bracket matching
* Only match brackets when theres no selection
* Highlighting tidying
* Fix markdown header highlighting
* Setting theme correctly in background
* Tidying module interface
* Merging main
* Fixing a bunch of typing problems
* Fixing more typing problems
* Correctly setting theme object
* mypy
* Small fix to bracket matching
* Improve a docstring
* Fix docstring
* Testing builtin and custom languages
* Unit testing theme stuff
* Reworking themes
* Error handling
* Improve error message
* Testing new theme setting approach, error handling
* Improvements/tests for theme and language setting
* Remove unused TextArea unfocus snapshot
* Update snapshot file
* Adding theme snapshot tests
* Add `function.call` style binding in dark vscode theme
* Renaming a test file
* Making active line clearer on vscode theme
* Renaming tests
* A whole lot of docs for TextArea
* Update wording in docs
* A bit more docs
* Example on adding Java as a custom language
* More custom language docs
* Finishing up custom themeing/syntax highlighting guide for TextArea
* Add note on potential issue
* Fix wording
* Add note on Apple Silicon Python 3.7 fallback
* Add another note on Apple Silicon Python 3.7 fallback
* Fix class names in example files
* Add some documentation for useful TextArea APIs
* TextArea docs improvements
* TextArea docs typo fix
* Note about extending TextArea
* Tab-stop support when spaces used for indent
* Docs update
* Text area blog post (#3356)
* Start blog post
* Add demo script to blog post
* Continuing the blog post
* Yet more writing for TextArea blog post
* Working on closing section
* Finishing up
* Update docs/blog/posts/text-area-learnings.md
Co-authored-by: Dave Pearson <davep@davep.org>
* Update docs/blog/posts/text-area-learnings.md
Co-authored-by: Dave Pearson <davep@davep.org>
* Typo fix
* Update docs/blog/posts/text-area-learnings.md
Co-authored-by: Dave Pearson <davep@davep.org>
---------
Co-authored-by: Dave Pearson <davep@davep.org>
* Remove redundant pass
* Add docstring
* Docs fix
* Simplify docs
* Improve docstring
* Add links in docstrings
---------
Co-authored-by: Dave Pearson <davep@davep.org>
The methods click/hover will now raise an error if they target a region that is outside of the target widget or completely outside of the screen. Then, they return a Boolean that indicates whether the click/hover hit the intended widget or not.
Related issue: #3349.
* Tweak progress bar docs.
There is no good reason as to why the progress bar can't be set back to its indeterminate state (and you could actually do it with code) so this removes the docstring that says that a progress bar can't go back to its indeterminate state.
Related issue: #3268
Related Discord message: https://discord.com/channels/1026214085173461072/1033754296224841768/1149742624002023594
* Use a special sentinal in ProgressBar.update
To comply with https://github.com/Textualize/textual/pull/3286#pullrequestreview-1628601324 we create a new type around a sentinel object and check whether we're using the sentinel before modifying the progress bar reactives.
Things that didn't quite work well:
- directly checking 'if parameter is not _sentinel:' won't satisfy type checkers because that condition doesn't restrict the type of 'parameter' to _not_ be 'UnsetParameter'.
- checking 'isinstance(parameter, float)' isn't enough because the user may call the method with an integer like '3' and then the isinstance check would fail.
- checking 'isinstance(parameter, (int, float))' works but looks a bit odd, plus it is not very general.
* Rework ProgressBar.update with a sentinel value.
* DataTable new rows can have auto height.
Related issue: #3122.
* Test auto height computation in DataTable.add_row
* Add snapshot test for add_row height=None.
* Extract some styles logic into auxiliary methods.
When adding a row with automatic height, I need to render the cells to compute their height. Instead of wasting that rendering, I want to do it well and then cache it, which means I need to reuse some of the logic of the other rendering methods. By extracting some logic, I'll be able to reuse it.
* Cache auxiliary cell renderings.
* Fix test import.
* Set row height to 0 when adding auto-height row.
* Remove superfluous cache clear.
* Fix cache/typing issue.
* Cache method to compute styles to render cell.
We extract this logic into a method for two reasons.
For one, having this as a method with an lru cache enables caching these auxiliary styles, which don't depend directly on the location of the cell, but instead depend on the values of 9 Boolean flags (making for a total of 512 possible combinations, versus the infinite number of different positions/states a cell can be in.
Secondly, having this as a method allows me to compute these styles more easily from within _update_dimensions when trying to salvage the renderings of the cells of a new row that may have been pre-rendered with the wrong height.
(See the following commits for more context.)
* Perform surgery on the datatable cache.
* Improve data table tests.
* Reduce cache size.
The first five parameters (is_header_cell, is_row_label_cell, is_fixed_style_cell, hover, and cursor) are the ones that change more frequently, so it is reasonable to fix the size of the cache at 32.
Related comment: https://github.com/Textualize/textual/pull/3213#discussion_r1326071862
* Clear cache with other caches.
Related comment: https://github.com/Textualize/textual/pull/3213#discussion_r1326071862.
* Mark the comment palette as existing
I've not checked of "Command menu" as that sounds like something slightly
different from the command palette.
* Reword the command palette part of the roadmap
* Reinstate the import of TimeoutError from asyncio
Fixes#3320
It looks like eaa749665f9b8271eff45be8e5e1e72ac8729b9e smuggled this change
in and caused the command palette to cease to work correctly on any version
of Python before 3.11.
This should make it work on all Pythons from 3.7 onward again.
* Update the CHANGELOG
Fixes#3299. Long story short: if a previous search was in the process of
stopping it looks like it could end up killing the timer for the next
search; given a fresh search resets the timer anyway there's no sense in
stopping the timer when we're being aborted.
* Collapsible container widget.
* Expose collapsible widget.
* Add collapsible container example
* Rename member variables as label and apply formatting
* Apply hover effect
* Apply formatting
* Add collapsible construction example with children.
* Wrap contents within Container and move _collapsed flag to Collapsible class from Summary for easier access.
* Add collapsible example that is expanded by default.
* Update collapsed property to be reactive
* Add footer to collapse and expand all with bound keys.
* Expose summary property of Collapsible
* Assign ids of ollapsed, expanded label instead of classes
* Add unit tests of Collapsible
* Rename class Summary to Title
* Rename variables of expanded/collapsed symbols and add it to arguments..
* Add documentation for Collapsible
* Update symbol ids of Collapsible title
* Update src/textual/widgets/_collapsible.py
Correct import path
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Sort module names in alphabetical order
* Clarify that collapsible is non-focusable in documentation.
* Add version hint
* Fix documentation of Collapsible.
* Add snapshot test for collapsible widget
* Stop on click event from Collapsible.
* Handle Title.Toggle event to prevent event in Contents from propagating to the children or parents Collapsible widgets.
* Update Collapsible default css to have 1 fraction of width instead of 100%
* Update Collapsible custom symbol snapshot
* Add Collapsible custom symbol snapshot as an example
* Update docs/widgets/collapsible.md
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Update src/textual/widgets/_collapsible.py
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Fix typo in Collapsible docs
* Rework collapsible documentation.
---------
Co-authored-by: Sunyoung Yoo <luysunyoung@aifactory.page>
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
The assert was for the benefit of type checkers; the code that needed that
hint was moved elsewhere by the recent tweak; but this wasn't tidied up.
This tidies that up.
We really don't need to document anything like this, I'll have done it by
habit, and having it there pulls it into the docs which then pollutes the
search results if someone is searching for what DEFAULT_CSS is all about.
As per the warning if you use the latest release of mkdocs-material:
WARNING - Action required: the format of the authors file changed.
All authors must now be located under the 'authors' key.
Please adjust 'docs/blog/.authors.yml' to match:
authors:
squidfunk:
avatar: https://avatars.githubusercontent.com/u/932156
description: Creator
name: Martin Donath
Note that this is for after:
Updating mkdocs-material (8.5.9+insiders.4.26.2
/Users/davep/develop/python/mkdocs-material-insiders -> 9.2.7)
It's also worth noting that our docs should now build regardless of
insiders' edition or not now; given that the blog module is part of the
mainstream release.
Adds missing fileno function to _PrintCapture class. This is needed
because _PrintCapture behaves like a normal stdin/stdout/stderr class
which provides this method.
If the mouse is hovering over the last option in an OptionList, and an
option is removed, the application will crash with an IndexError. The
problem was that the record of the hovered option needed to be cleared when
an option is removed (as it is during other changes).
Fixes#3270
While it's kinda cool... it's not really very helpful if you're doing things
via textual-web; all you're going to do is start to use storage on the host
machine, not the client machine (unless they're the same thing, of course).
Not getting carried away with this -- frogmouth exists after all -- but
allowing passing a different file on the command line does make it easier to
quickly test the Markdown widget.
Fixes the issue reported in #3094.
There's more to come on this, as rather than just fix that error, we'd also
like to go to the header that the anchor relates to. See #3094 for an
initial approach to this. This PR builds on the idea in a different way.
But before doing that wider part, this simply starts out by fixing the
reported bug.
This isn't 100% how GitHub's approach works, but the edge cases I can find
appear to be bugs or issues with how GitHub handle the more interesting
emoji when they appear in headers.
Long story short: they appear to just strip emoji for the most part, but if
an emoji is modified in some interesting way (think shrugging person vs
shrugging light-skinned woman with black hair) it looks like the final
codepoint "leaks" into the slug; nothing about this looks intentional, and
it's such a remote issue that it's hardly worth supporting.
This question crops up from time to time, often with people looking for
"focus" or "blur" and wondering why they don't bubble and so then wondering
how they can catch such events in ancestors in the DOM.
The Descendant prefix isn't obvious (I always forget what it is and need to
go hunting), so having the focus and blur events link to the descendant
events should help make them easier to discover.
We've recently changed the way the command list is cleared down when the
search term is modified, thus removing a source of "flashing" as the user
types; this pretty much involves *not* clearing down the previous hits until
the first new hit comes in. This is fine in all situations expect where the
last search was a "no matches" search.
In that situation the next search stats out saying "no matches". That's
correct, that's the result of the previous search, but in this case it's
unhelpful and potentially confusing. So this commit checks if that's the
state of the command list up front and clears that option from the list.
The asend back into the search routing was always showing a typing mismatch,
but I couldn't quite see what was going on; what made it even more confusing
was the code was working fine.
It looks like keeping hold of the "routine", and keeping that distinct from
the iterator of the results, is the trick here. It all still works *and* the
typing works out.
There's one typing error that's been with me for weeks now, and nothing I
did seemed to get to the heart of it. Finally, I think it's dawned on me.
Raising NotImplemented from the abstract base implementation confuses the
type checker as it's not seeing any sort of yield going on. This... this
solves it.
I'm not 100% sure this is the correct thing to do, advice online seems
patchy at best and the couple of things I've seen that do seem to address
this sort of situation seem to introduce other typing issues (a bare yield
being the main suggestion, which won't work as then it'll be yielding the
wrong type).
Gonna sit on this for now and see how I feel about it, or see if I can find
something relevant to this.
* Fix cursor position when clicking double-width char #2968
The cursor moved to the correct position only when clicking the first
index of the character. We now check if the click happened inside a
range of indices.
* Update changelog #2968
While I don't think this really makes a difference to anything, it makes
sense in the flow of the code to make it clear we're stopping as soon as
possible. We don't need more commands from any source as we have a brand new
query.
Mimicking 'App', we provide class variables TITLE and SUB_TITLE for the screen defaults and those can then be changed via the title and sub_title reactive attributes.
Related issue: #3195
An empty command list isn't really empty, it has a single disabled option
that shows that no matches were found; there's no point in allowing that to
be highlighted by the user.
For obvious reasons, every time the user typed a letter, the list of
already-completed commands needed to be cleared down before new ones got
added. While the code concerned with doing this was in the right place (when
a key was pressed), this had the unfortunate side-effect of making the list
appear to "flash" as the user typed the first few letters, especially if a
lot of hits were found near-instantly.
This commit delays the initial clearing-down of the content of the list,
keeping track of if the clear has been done already and only doing it at the
very last moment if it's needed.
Of course, they're not magic at all really, they're just fractional second
values that make sense in context; but giving them a name will help explain
what they're for.
This commit takes the handling of running command search tasks a wee bit
further, sending "down" the aborted status and cancelling the tasks as soon
as possible. There are still situations where this won't really make a
difference, and depending on how the command source is coded it could carry
on running for a while, but if a command source is coded to handle being
cancelled as soon as possible this will provide what's needed to benefit
from such an approach.
Note that it *does* mean that a developer writing a command source, which
awaits something, will need to handle a CancelledError; we should probably
see about talking about this in the docs.
* Add regression tests for #3053
* Traverse invisible containers when computing focus chain.
At the moment, we were completely bypassing invisible containers which meant that their visible children wouldn't be included in the focus chain.
* Make note of removed property.
* Add regression test for #3071.
* Fix#3071.
* Fix regression test for #3053.
* Optimize computation of focus chain.
Computing the focus chain was relying on the property 'visible' of nodes which may traverse the DOM up to find the visibility of a given node. Instead, we cache the visibility of the nodes we traverse and keep them in a stack, saving some of that computation.
Related issues: #3071
Related comments: https://github.com/Textualize/textual/pull/3070#issuecomment-1669683285
* Make test more robust.
* Make test more robust.
* Short-circuit disabled portions of DOM.
If a node is disabled, we will not be focusable, nor will its children, so we can skip it altogether.
Related review comment: https://github.com/Textualize/textual/pull/3070/files#r1300292492
* Simplify traversal.
The traversal code could be simplified after reordering some lines of code.
We also get rid of the visibility stack and instead keep everything in the same stack.
Related comments: https://github.com/Textualize/textual/pull/3070#pullrequestreview-1587295458
Keeping it as a FAQ makes sense, as it means that FAQtory will be able to
point to it, but now that we have the HOWTO, and it's more comprehensive, it
makes sense to direct the reader in that direction if they want something
more involved.
This allows one to use the 'disabled' attribute in tab panes to enable/disable a tab, which is particularly useful if you want to instantiate a tab that starts off as disabled, as seen in #3149.
For this to work faqtory needs to be a development dependency of Textual.
Textual still maintains support for Python 3.7; faqtory is Python 3.8 or
greater. So, for the moment, we're going to cheat a little and make it so
that you have to remember to run faqtory to rebuild FAQ.md.
All hail the walrus!
While it isn't designed to be used directly, it is something a developer
will be exposed to via the command source for the command palette, so it
should appear in the docs so it can be linked to.
Some things the developer may want to call will be sync methods, some might
be async methods. I think it makes sense to provide a wee helper here to
wrap such a call up in the right way.
This might need expanding a bit, I might also want to look at and consider
Textual's invoke helper, but for the moment this is working well so let's
experiment with this.
Just in the problematic tests. As an experiment for the moment. I've still
not quite got to the bottom of the core problem, as I've been seeing, but
there is an issue with testing the command palette: how to ensure that
there's actually matched commands before going on to test interaction, when
the sourcing of command matches is concurrent with anything else.
Here I reach in into the workers of the command palette and wait for them to
finish and *then* I go on to use the result.
Having been able to recreate the surface error locally, on a nice fast M2Pro
Mac, with a significant async sleep in the source, this fixed the error in
that situation. So let's see if that makes a dent in CI...
This is a simple command source for the command palette, that offers up some
of the more applicable actions within a Textual app. Here I also make it the
default source of commands for all Textual applications.
* Hide some members from the public docs.
See relevant issue: #3076.
Some methods need to be implemented to make the widget work but the user doesn't really care about them. For that matter, we can hide them from the public documentation.
* Use private handler to hide from docs.
Related comments: https://github.com/Textualize/textual/pull/3080#issuecomment-1671129733
It's looking like it's going to be almost impossible to test the exact
command chosen, every time, in CI, with all the timing issues. So let's make
life easier, for now anyway, and simply check that *something* was selected.
This was pinned by Darren a wee while back, I think, due to some other
problem. But this kills coverage. Right now I want coverage so I can see
what needs testing with the command palette. So let's unpin with a view to
pinning back again (or solving the main problem I guess) once I'm done.
This will help guard against anything causing two copies to be in the DOM at
once. It's unlikely, the code should work to make sure this doesn't happen,
but let's set this up to be a fail if it does.
When a new search term was created by an edit, the previous worker would get
cancelled by the nature of _gather_commands being exclusive; but if the user
edited the input such that it was empty the work would carry on. This
ensures that isn't the case.
Still haven't decided on the final style for this, but this helps to make
the command palette and the command list pop a little for a background
screen of similar colours.
To allow for maintaining the location of the highlight as we rebuild the
command list I'm probably going to need some method of tracking an ID for an
option, so I can find its new index back. There's no method in OptionList
right now for doing that; it's trivial, but it's not there. As it happens
the changes in #2985 actually has that, so here I note that I'll look to
making that happen once that gets added in.
The async reactive callbacks are now scheduled on the message pump of the watcher of the reactive instead of on the owner of the reactive attribute.
Related issues: #3036.
At some point this should really go into Rich itself, I think, as utility
method of Style. For the moment though, let's keep it close to hand as we
figure out if it's a good idea or not.
It remains to be seen if we'll keep it like this, but this is a useful
combination to use while I'm working on having the command palette a
"standard" part of a Textual application (I'm still minded to make it
optional and the develop binds it themselves -- we'll see where that
decision falls before final PR).
This adds an action to the App, and binds it to Ctrl+@, which in Mac
terminals at least equates to ctrl+space. This might not be the final
resting place for this, I'm not even sure if we should find it by default at
all. But for the purposes of further testing as I develop this that's fine.
This also adds support to the App class for running the user's choice of
command. With this change nobody needs to hook up the command palette with
Textual on their own any more, it's "out of the box".
What's needed by them now is hooking up a command provider.
So it turns out that App.screen gets updated before the mount event is
received, but the stack hasn't changed by that point. It's safer to work off
the stack.
During 2023-08-03 standup it was suggested to me that the preference might
be to have this work in a "click and go" way rather than a "click, review
then go" way. My preference is more for the latter, but I can see the desire
for the former too.
So this makes the former the default, but allows for the latter to be
configured by the developer.
This helps make it possible for the user to mouse to select a command and
then further mouse to run the command.
In doing this, because I was introducing a new element, I've done a revamp
of the layout and styling of the command palette. The result is more or less
the same as I had to start with, but this solves a couple of cosmetic issues
I was running into until now.
The code as was worked fine for nicely slow command sources. In fact I
thought it was going to be the slow ones that would give me the worst
problem. But having managed to handle that, it was the really fast ones that
showed a few issues.
Here I'm swapping back to just showing/hiding the loading indicator as
mounting and removing so fast was an issue.
And also I'm making sure that I flush the queue after all the tasks are
finished, otherwise it was easy to lose a lot of commands.
This is far from its final form, and right now to work it needs that the
calling code (which is in my test harness) receive the callable via the
screen callback system and make use of it. That's fine, it works, it just
means that as I get closer to making this part of Textual proper I'll need
to build such a mechanism into Screen.
I don't really see much need for this, now that development of this is well
under way. And even if we do want to expose this, I think we need to allow
setting it on the class, not on the instance.
In one case there was a hangover from before we added the `complete` option;
in the other two cases it also needed to be made clear that it's only a
no-op if there's no animation running or also scheduled.
* Fix the page up/down bindings for OptionList
I'd had page_up and page_down bound when the actual names of the bindings
are pageup and pagedown. This has always worked by sheer fluke because of
where OptionList inherits from and by the coincidence of the action names.
In other words: this commit has no substantive impact; but it does fix code
that wasn't helpful and also makes the documentation more correct.
* Correct the page up/down keys in the option list movement tests
Turns out the pilot lets you press keys that don't actually exist. Who
knew?!?
Not like this, but kinda like this. Just experimenting at the moment, hence
the random sleeps in the core of the generator (to sort of fake a slow
background source).
At the moment it does nothing more than grab a new UUID, but this gives us
scope for throwing in some sort of callout to the providers to let them know
we're done.
Just to help things stand out for the moment. At some point I think I'll
allow passing in custom styles, which will come from component classes or
something. For now though this makes it easier to see what's going on.
* blog post
* words
* words
* more words
* Update docs/blog/posts/rich-inspect.md
Co-authored-by: Dave Pearson <davep@davep.org>
* Update docs/blog/posts/rich-inspect.md
Co-authored-by: Dave Pearson <davep@davep.org>
---------
Co-authored-by: Dave Pearson <davep@davep.org>
* Remove duplicated Added section in the CHANGELOG
* Add the ability to stop a running animation
Adds stop_animation to the core animator class, and then exposes it via the
same named methods on App and Widget. Note that a request to stop an
animation that isn't running is treated as a no-op.
* Fix tests so they actually work and test things
This is what happens when you save time using -k to run one test, then add
more but keep just hitting cursor up to rerun the tests. O_o
* Add the ability to stop an animation and jump to the final value
This doesn't address the issue of stopping scheduled animations, that's to
come next, first I just wanted to get the basic approach in place and then
build out from there.
* Add full stopping support to the ScalarAnimation
* Tidy up various bits of documentation in Animator
While I'm in here and moving things around: being various bits of
documentation more in line with how we document these days, and also add
some missing documentation.
* Allow for the full stopping (with end-seeking) of scheduled animations
* Don't spin up a scheduled animation to then not use it
* Be super-careful about getting keys when stopping
* Pop rather than acquire and delete
* Don't implement anything in Animation.stop
See https://github.com/Textualize/textual/pull/3000#discussion_r1275074716
This change is based on the idea that if a widget is "overlay: screen" it's
totally outside the normal flow of things and so trying to see if it's
visually within its ancestors doesn't make a whole lot of sense as it might
deliberately not be. At which point the only sensible question we can ask is
"can the screen see it?".
See #2958.
* Make the default max-height of a DataTable 100%
See #2959
* Fix the demo
Adding `max-height: 100%;` to DataTable has worked everywhere, except in the
demo, where it makes the table just plain flat out disappear. It looks like
it's down to a bug in Textual's CSS, possibly. This fixes the demo for now,
and a standalone issue will follow that dives into what's going on with this
particular combination of container and datatable styling.
* Update the ChangeLog
* Update the FAQ
* Remove the implementation of notify on Screen
Screen inherits the one from Widget anyway. I always forget that Screen is a
Widget. O_o
* Remove imports that aren't needed any more
* Remove the empty title styling
The content of the style seems to have been removed, the class isn't
referenced any more, so it can go.
* Change the Notification interface and docs to reflect the changed code
A last-minute change to the notification PR changed the way that titles were
handled. If the intention is to take this new approach (as opposed to an
oversight in the change) then there's little point in allowing two forms of
empty title, while also documenting that an `None` title will default to the
severity.
This commit simplifies the interface for notifications and also modifies the
documentation to reflect the change that was made.
* Add a notification class and a class to hold notifications
This provides the core classes for holding information on a single
notification, and then on top of that a class for managing a collection of
notifications.
* WiP: End of day/week commit to pick up post-holiday
* Ask permission rather than forgiveness
Yes, this does go against all things Pythonic, but in this case it's likely
less costly to do the check first; moreover it works around the problem I
ran in to: https://github.com/Textualize/textual/issues/2863
* Move the handling of "I've seen this" into the toast rack
This way the interface becomes "here's a bunch of notifications, you go work
this out".
* Add a notify method to all widgets
* The removal time for a toast should be the time left
When it was per-screen, it made sense that it was the timeout; now that
we're carrying them over between screens we're going to make sure they're
only around for as long as they need to be.
* Carry notifications between screens
* Remove the test code
* Drop the borders from the toasts
Except for the title, keep that.
* Provide access to the notification timeout
* Remove the title panel from a Toast if the title is empty
* Make the Toast CSS classes "private"
Prefix with a - to reduce the chance of a clash with userspace.
* Refresh a docstring
* Stop widget leakage
The Toasts were removing themselves, but they're wrapped inside a helper
container that keeps them aligned right. So the problem was that the
alignment containers were leaking. This ensures that when a Toast goes away
it takes its parent with it.
* Make the alignment container hidden
This doesn't really make any difference, but it feels like it makes sense to
hide it if there's nothing to show -- it's purely for alignment.
* 🚚 Rename the toast container
This is about getting the toasts to align correctly (even when you do align
things, they don't really align as expected due to the way that a container
aligns the bounding box of all if its children, not the individual
children). However, I had this named after where it aligned them to; someone
using the system may wish to change that, so let's make the name more
generic.
* Improve ToastRack._toast_id
Add a docstring, and also change the format of the identity somewhat so that
it's even "more internal".
* Add some initial low-level notification testing
* Add initial testing of notifications within an application
* Add tests for notifying from the 3 main levels within the DOM
* Add a toast example to the docs and a snapshot test
This might not be the final form, but it'll do for the moment. I want to get
the snapshot test in place at least.
* Add a snapshot test for notifications persisting between screens
* Add some documentation for a Toast
This isn't going into the index, just yet. This is *technically* an internal
widget so I'm not sure how and where it makes sense to document it; if at
all. But let's get some documentation in here anyway.
* Flesh out the docstrings for the notify methods
* Add a missing docstring to the Notifications __init__ method
* Add snapshot tests for persisting notifications through mode switches
* Remove unused import
Looks like eglot/pyright tried to be "helpful" at some point and I didn't
notice.
* Correct the Toast severity level classes in the docs
Originally they weren't in the "internal" namespace, then I decided that
they should be so there's less chance of a clash with dev-space code; but I
forgot to reflect this in the docs.
This fixes that.
* Make the removal of notifications/toasts a two way thing
The addition of the ability to dismiss a toast by clicking on it had a flaw:
the notification->toast code had been written with things being one way. The
expiration of notifications happened in the notification handler, and the
expiration of Toasts was done in the Toast system, on purpose (notifications
might end up being routed via elsewhere so this needs to be done).
But... this meant that hand-removed Toasts kept coming back from the dead
when a new notification was raised iff the hand-removed ones hadn't yet
expired.
So here I add the ability the remove a notification from the notification
collection.
* Remove an unhelpful comment
Sort of a hangover from what was initially looking like it was going to be a
longer body of code. It doesn't really need explaining any more.
* Add in support to the notification collection
* Change the toast rack adder to be a general "show" method
This turns the method into one that further aids making the connection
between the notifications and the toasts two way. Now it makes sense that if
there are toasts for notifications that no longer exist, they also get
removed.
This makes it easier to add all sorts of clear options later on.
* Add a method to clear notifications
* Add an App method for clearing all existing notifications
* Add a missing docstring to _refresh_notifications
* Return the notification from the notify methods
It can be seen as, and used as, a handle of sorts (see unnotify); so return
it so people can use it.
* Add some more notifications unit testing
* Add some more app-level notification unit testing
* Style tweaks
* docs
* added notifications
* snapshots
---------
Co-authored-by: Will McGugan <willmcgugan@gmail.com>
* Fix Toggle Buttons on a selection list
* Update CHANGELOG.md
* Update dependencies
Mainly updating the now-external snapshot testing code so that the failure
report (which was to be expected) gets generated.
* Update snapshot tests
* Add testing for clicking on a SelectionList prompt or checkbox
* Cody tidy
---------
Co-authored-by: Dave Pearson <davep@davep.org>
Originally the request had been communicated as the following would be
errors:
- Non-async method, non-thread
- Async method, thread
As of
https://github.com/Textualize/textual/issues/2928#issuecomment-1634333775
it's been decided that the scope of the PR should be expanded to not only
guard against the first case, but also to extend the way the worker API
works to do the right thing in the second case.
This commit backs out the detection of the second case and flagging it as an
error.
Perhaps a hangover from testing some version of the Markdown widget at some
point in the past. Running a ripgrep for it over the repo shows no reference
to it.
* Updated DataTable.get_cell type hints to accept string keys (#2586)
Added DataTable.get_cell_coordinate
Added DataTable.get_row_index (#2587)
Added DataTable.get_column_index
* Added changelog entries for new DataTable methods
* Fix code style issues identified by Black
* Add unit tests for new DataTable methods
* Fix intermittent exception in _progress_bar.py
Exception raised if percentage changes before the _refresh_timer is instantiated.
* Minor fixes to PR for best practices
* WiP: Move the devtools and related code to `textual-dev` (#2834)
* Remove the textual script from the project file
This is moving into the textual-dev package.
* Remove the textual CLI code from Textual
This has all gone to live in textual-dev.
* Remove the devtools testing from Textual's unit tests
They've moved over to textual-dev instead.
* Remove the devtools server from Textual itself
The start of the process to remove as much of the core devtools as possible
from Textual.
* Switch the console docs example screenshot over to textual_dev
* Remove rednerables.py from Textual
* Remove the last parts of devtools from Textual
This is the last step. It remains to be seen if this is sustainable, but for
testing purposes this is the extreme case we're aiming for. I *think* this
will work though.
Hereon we'll be needing to do an editable install of textual-dev into
textual, and more generally and once this is "live" we'll be needing to make
sure that textual[dev] is installed when doing development work on textual
apps.
The thing that remains to be seen however is how this all works
with *developing* Textual itself. Will I always need to do an editable
install? Still got to figure that one out.
* Start to whittle down the pyproject file
The next step is to try and work out what can come out of the pyproject
file.
* Remove aiohttp from Textual
* Remove some more development dependencies we don't need any more
* Relock
* Remove the pointer to the previews directory
* Reintroduce the border preview snapshot test
* Reintroduce the color preview snapshot test
* Reinstate the key press for the border preview snapshot test
* Reintroduce the easing preview snapshot test
* Reintroduce the keys tool snapshot test
* Add pytest-asyncio as a development dependency
* Relock
* Pin the textual-dev version to 0.1.0 or later
Mostly to try and get the tests kicked off properly.
* Relock dependencies
* Further `textual-dev` changes (#2850)
* Remove the textual script from the project file
This is moving into the textual-dev package.
* Remove the textual CLI code from Textual
This has all gone to live in textual-dev.
* Remove the devtools testing from Textual's unit tests
They've moved over to textual-dev instead.
* Remove the devtools server from Textual itself
The start of the process to remove as much of the core devtools as possible
from Textual.
* Switch the console docs example screenshot over to textual_dev
* Remove rednerables.py from Textual
* Remove the last parts of devtools from Textual
This is the last step. It remains to be seen if this is sustainable, but for
testing purposes this is the extreme case we're aiming for. I *think* this
will work though.
Hereon we'll be needing to do an editable install of textual-dev into
textual, and more generally and once this is "live" we'll be needing to make
sure that textual[dev] is installed when doing development work on textual
apps.
The thing that remains to be seen however is how this all works
with *developing* Textual itself. Will I always need to do an editable
install? Still got to figure that one out.
* Start to whittle down the pyproject file
The next step is to try and work out what can come out of the pyproject
file.
* Remove aiohttp from Textual
* Remove some more development dependencies we don't need any more
* Relock
* Remove the pointer to the previews directory
* Reintroduce the border preview snapshot test
* Reintroduce the color preview snapshot test
* Reinstate the key press for the border preview snapshot test
* Reintroduce the easing preview snapshot test
* Reintroduce the keys tool snapshot test
* Add pytest-asyncio as a development dependency
* Relock
* Pin the textual-dev version to 0.1.0 or later
Mostly to try and get the tests kicked off properly.
* Relock dependencies
* Whitespace cleaning
* Swap mentions of textual[dev] to textual-dev
* Remove the dev extra
* Tweak README.md in response to PR review
* Tweak animation.md in response to PR review
* Tweak getting_started.md in response to PR review
* bump version
* lock
* drop dev
* more
* version bump
---------
Co-authored-by: Dave Pearson <davep@davep.org>
The terminal writer thread could send escape sequences when the terminal
was unable to process then; i.e. when not in virtual mode.
The following fixes have been made.
- Switch the terminal to virtual mode before the writer thread is
started and any control sequences are queued to the writer thread.
- Wait for the writer thread to finish before switching the terminal
out of virtual mode.
* Allow adding columns to populated data table
* Add snapshot test for adding columns/updating cells
* Update CHANGELOG.md
* Ensure we update virtual_size when cell updated
* Add a unit test for https://github.com/Textualize/textual/issues/2807
* Add a test for removing tabs in reverse
* Add a test for the messages sent when removing tabs in reverse
Marked as xfail for the moment, I suspect the root cause of #2807.
* Don't sent Changed when tab removal doesn't result in change
* Update the CHANGELOG
---------
Co-authored-by: Will McGugan <willmcgugan@gmail.com>
* Initial set of Markdown widget unit tests
Noting too crazy or clever to start with, initially something to just test
the basics and to ensure that the resulting Textual node list is what we'd
expect.
Really just the start of a testing framework for Markdown.
* Allow handling of an unknown token
This allow for a couple of things:
1. First and foremost this will let me test for unhandled tokens in testing.
2. This will also let applications support other token types.
* Update the Markdown testing to get upset about unknown token types
* Treat a code_block markdown token the same as a fence
I believe this should be a fine way to solve this. I don't see anything that
means that a `code_block` is in any way different than a fenced block that
has no syntax specified.
See #2781.
* Add a test for a code_block within Markdown
* Allow for inline fenced code and code blocks
See #2676
Co-authored-by: TomJGooding <101601846+TomJGooding@users.noreply.github.com>
* Update the ChangeLog
* Improve the external Markdown elements are added to the document
* Improve the testing of Markdown
Also add a test for the list inline code block
* Remove the unnecessary pause
* Stop list items in Markdown being added to the focus chain
See #2380
* Remove hint to pyright/pylance/pylint that it's okay to ignore the arg
---------
Co-authored-by: TomJGooding <101601846+TomJGooding@users.noreply.github.com>
Ideally this would use something like what #2786 intends to add, but
meanwhile this solves the problem of ghost highlights in extreme situations
of adding/removing tabs.
It's possible, unlikely but possible, that the content could get removed via
some other route, or out of sync, so allow for that. Don't get upset of the
content has gone away when we're removing the tab that was in charge of it.
It's possible that we might be being asked to switch from an item of content
that has actually been removed; there's no harm in making not finding the
old thing a no-op.
Merging in the core idea of #2762 given that this is an overhaul of Tabs in
general and also adding/removing/clearing tabs in particular.
Co-authored-by: blob42 <contact@blob42.xyz>
It's unlikely, but not impossible, that code could end up asking to make a
tab active that doesn't exist any more, so here we make things a wee bit
more robust and turn it into a no-op.
The tests touched in this commit are working fine in CI for GNU/Linux and
macOS; but fail on Windows as the message we need to come through doesn't
seem to be coming through.
Testing on Windows (11, in Parallels, on macOS) it seems that setting an
actual time for the pauses does the trick. I'm not sure why, I thought a
pause with no time ensured that all message queues were emptied before
coming out of the pause. Apparently not.
So this is an experiment to see if it'll pass in CI too.
Much like Tabs.Cleared, this indicates that all available tabs/panes have
been removed and the widget is now empty. This is especially important here
as the way we remove tabs is such that we can't await their removal and then
make the remove methods async (because Tabs doesn't allow for that).
So the approach taken here is to send a message from TabbedContent, and
delay it as much as possible, ideally once the action that's taking
place *has* taken place.
The reasoning is: a user may clear down all panes, then want to add some
back, possibly with IDs they've used before. The clear down might not have
fully happened, but we can't await it all, so the approach for the user
would be to wait until the Cleared message turns up *then* repopulate.
Do not keep an explicit registry of screens whose CSS has been parsed. Instead, grow methods in the file monitor and in the stylesheet to check if a given path is already being monitored/sourced and add the paths if it isn't.
Mimics (and actually simply returns) Tabs.tab_count. The idea being that if
people can now add and remove tabs from TabbedContent, they may want to be
able to keep track of how many tabs there are.
* Allow customising whether CSS or renderable colors show in datatable
* Simplify
* Add snapshot for style ordering in the DataTable
* Fix layering issue with text colours, add snapshot tests (DataTable)
* Update CHANGELOG.md
The branch being removed here seems to be trying to handle keyboard bindings
before handling the raw keyboard event. However, in the unit tests, even
when a binding is pressed, this code doesn't get called.
I strongly suspect this is code that predates changes that were made some
time ago in respect to the order in which bindings were processed and their
relationship to keystrokes.
After removing this all tests are passing just fine and hand-testing
`Input` (especially in the demo, for example, both non-password and password
incarnations) shows no problems either.
All evidence suggests that #2737 was incapable of hitting that branch of
code because it just could not be hit these days.
It looks like "visible_content" might have turned up after the tests were
added; so this adds testing for that to the tests (and gets coverage for
ContentSwitcher to 100%).
See #2720. I'm not 100% sure about this one; I don't think it's been
modified since the AUTO_FOCUS change was made, so I believe this will be
restoring the example to its intended state.
See #2718. The problem is that the work done on #2527 and related PRs has
changed the starting position of focus, which means that any code example
that has key presses in them that start out by tabbing to a control will be
off by one.
* blog post new release
* update words
* Update docs/blog/posts/release0-27-0.md
Co-authored-by: Dave Pearson <davep@davep.org>
---------
Co-authored-by: Dave Pearson <davep@davep.org>
The previous change actually broke some of the tests in test_disabled.py --
well actually it flat out broke one and caused all the others to run *very*
slowly. No clue why though.
But thinking about this some more, it does feel like delaying the refresh of
the node makes more sense.
* Option to ensure origin of widget is visible when calling scroll to center
* Update CHANGELOG.md
---------
Co-authored-by: Will McGugan <willmcgugan@gmail.com>
We do this inside _MessagePumpMeta.__new__ because this runs at 'import time', and thus is essentially the earliest we can figure out if this is not going to work.
* Cover the penultimate uncovered line in OptionList
Sort of moot, but worth a test that the control of a message is the option
list.
* Test OptionList hover over disabled option
I *would* have liked to have tested this sort of thing via snapshot tests,
but it seems that pilot.hover isn't quite behaving as desired there. But
this provides a useful test anyway.
It looks like mkdocstrings will fail out if it can't download the inv files.
Unless there's a setting where you can tell it "try, but if you can't grab
them just don't try and create links" the approach here seems reasonable and
has the same effect.
https://github.com/Textualize/textual/issues/2629#issuecomment-1566672594
for reference.
Yes, this does pretty much undo the "offline" part, but without this the
docs can't build if the builder doesn't have access to the insiders' edition
of mkdocs. Doing this as a first step to investigating if there's any
possible way to achieve what we want here:
- Anyone can build
- They can build offline
- They can build for maximum privacy
If not, this step at least hits the first requirement.
See #2629.
The test if a node was loaded wasn't being performed when loading the root.
This ensures that will happen. I suspect this is (no pun...) at the root of
the issue with https://github.com/Textualize/frogmouth/issues/50 even though
I can't see the route into how this happens, and can't recreate this at
will.
This feels like a worthwhile change to make anyway as it's a safer approach.
I'm struggling to recreate https://github.com/Textualize/frogmouth/issues/50
in a controlled way, but reviewing the code here makes me think that this is
a good idea anyway. While DirectoryTree should not end up in _populate_node
if a node has already been populated, it's also the case that it's an
all-or-nothing thing; it makes sense to clear out the children of the node
before populating it; at least in a belt-and-braces way.
* refactor(message): make control a property
* refactor(_tree): remove tree parameter on messages
* refactor(_directory_tree): remove tree parameter on message
* fix: tree message calls
* fix(_select): make Changed.control a property
* refactor(_on): control check
* refactor(_select): rename Changed.widget to select
* docs: changelog entry
* A few different types of validation
* Rename
* Fix test
* Updating validation framework
* Update lockfile
* Ensure validators can be None
* Reworking the API a little
* Convert Input.Changed to dataclass
* Add utility for getting failures as strings
* Update an example in Validator docstring
* Remove some redundant `pass`es
* Renaming variables
* Validating Input on submit, attaching result to Submitted event
* Testing various validation features
* Update snapshots and deps
* Styling unfocused -invalid Input differently
* Add snapshot test around input validation and associated styles
* Validation docs
* Tidying validation docs in Input widget reference
* Fix mypy issues
* Remove __bool__ from Failure, make validator field required
* Code review changes
* Improving error messages in Validators
While working on the SelectionList documentation I've noticed that even more
things have got lost from the docs relating to OptionList, likely lost when
widgets were removed from the API section of the docs.
This drags more OptionList-related types into the docs, thus providing more
links.
Up until now I've only been allowing tuples; mostly a hangover from the
initial take on this. Things have drifted a bit now and I feel it makes
sense to allow Selection instances in too.
Deciding if there was a change when turning all on or off by looking at the
before an after counts was fine, but it's not a sensible way of seeing if
there was a change during a toggle. So this swaps things up a bit and has
the core selection changing methods return a flag to say if they actually
made a change or not.
Adding back docstrings from overriding methods. Initially I was thinking it
made sense to keep them empty, allowing for any inheriting of the
docs (if/when our documentation generation system does that); but in most
cases there's a subtle difference in what's supported in terms of parameters
or return values so it makes sense to tweak the docs a wee bit.
The developer using this may wish to react to UI changes being made, but
they may also want to just know when the collection of selected values has
changed -- this could happen via code so won't get any UI/IO messages. So
this adds a message that is always sent when a change to the collection of
selected values happens.
It would be nice to just inherit form the OptionList messages, but the
naming of the properties wouldn't quite make sense, and there's also the
generic typing issue too. So here I start to spin up my own messages down
here.
Also, as an initial use of this, grab the OptionList highlight message and
turn it onto one of out own.
Mostly I feel it makes sense to have the value first, and the actual prompt
second (based on no reason at all); but given that Select does it prompt
then value, this should conform to the same approach.
The implicit type was creating mypy errors when defining bindings with
tuples. For example:
class MyApp(App):
BINDINGS = [("q", "quit", "Quit")]
Would give the error:
error: List item 0 has incompatible type "Tuple[str, str, str]"; expected "Binding" [list-item]
With #1719 in mind, and as an alternative to #2608, this allows for a child
class of DirectoryTree to specify how a fresh `Path` should be created. The
idea here being that whatever is created should be of the `Path` type, but
can have other abilities.
Now that I'm no longer having to dodge issues with getting component classes
before the DOM has spun up, I can go back to the simpler method of setting
up the selections.
This also means I can drop Mount handling.
More experimenting with overriding OptionList, and rather than trying to
swap out and around the prompt under the hood, I got to thinking that it
made more sense to perhaps override render_line.
So far so good...
I think I'm going to give up on basing this off OptionList. It's close
enough that inheriting from it and doing more makes some sense, but it's
also just far enough away that it's starting to feel like it's more work
that is worthwhile and it'll be easier to hand-roll something fresh.
Normally it's not a great idea to eat and hide exceptions within library
code; but I think it makes sense to make an exception here. This is a UI
element that lets the user navigate about a filesystem. If there is
something they don't have permission for, that should not cause an
exception, it should just give up with the best possible outcome.
If actually doing something with the exception is important, the developer
using this could use the filter to do tests and act accordingly.
See #2564.
Plan C; or is it plan D? Something like that. Anyway... in this approach we
keep a single "forever" async task worker per directory tree, which in turn
looks at the async Queue, and when a new node appears on it it starts a
short-lived thread to load the directory data.
This seems to be working fine on macOS. Next up is testing on Windows.
Turns out, there's a maximum number of threads you can have going in the
underlying pool, that's tied to the number of CPUs. As such, there was a
limit on how many directory trees you could have up and running before it
would start to block all sorts of operations in the surrounding
application (in Parallels on macOS, with the Windows VM appearing to have
just the one CPU, it would give up after 8 directory trees).
So here we move to a slightly different approach: have the main loader still
run "forever", but be an async task; it then in turn farms the loading out
to threads which close once the loading is done.
So far tested on macOS and behaves as expected. Next to test on Windows.
When the default screen is first created it was not getting the event ScreenResume. All other screens receive a ScreenResume when first created and _all_ screens (the default one and custom screens) receive this event when they become the active screen again, so this was kind of an edge case that needed the event to be posted by hand.
Related comments: https://github.com/Textualize/textual/pull/2581\#issuecomment-1550231559
After deciding
https://github.com/Textualize/textual/pull/2545#issuecomment-1547544057 it
makes more sense to roll back to the state of `main` than to try and get to
where I want to be from where we've decided we didn't want to be.
Can't get there from here, so let's go rogue-like on this PR...
While the fix for #2557 likely isn't *the* fix (see #2582 for some context
around that), it is a fix that works for now. As such, with the change,
there was a double attempt to refresh the content tracking in the clearing
of options in the OptionList, which shouldn't be necessary.
This removes that.
This caches the virtual environment so we don't have to download it every time (the cache can be cleared from the repository > Actions > Caches (on the left).
We also split black formatting into a separate workflow.
This means we can run black ONLY when *.py files are changed.
It also means all other testing jobs don't need to _also_ check formatting.
Nothing here actually implements a selection list yet; this just sets out
the very basic framework of the widget, as it inherits form OptionList. The
key things here are:
1. It introduces a Selection class, which is an Option with a typed value.
2. The SelectionList is also typed and expects Selections of that type.
So far this is working fine, but there was an issue where, if the load of a
very large directory was started, and then the application was cancelled
right away, the application would close down but there would be a long pause
until the shell prompt came back, the cause presumably being that we were
waiting for that particular thread to end.
So here I make sure I check the cancelled state of the worker. This would
also suggest that, while I turned the use of iterdir into a loop so I could
throw the sleep in to emulate a slow directory load, I *actually* want to do
this in a loop so I can test the cancelled state as we stream in the
directory content.
Having got the initial version of background loading of nodes in the
directory tree working, this moves to a gentler approach where only so many
loads run at once, and a queue of jobs that need to be completed is kept.
This is an end-of-coding-session WiP commit; there's more to come on this.
But at the moment I'm happy with the way it's going.
Move the node population code into its own method, the idea here being that
the update happens in one call to call_from_thread rather than spawning lots
of calls to it.
This isn't the final form, not even close, this is more to help test out the
idea and how well it will work. Note the very deliberate sleep in the code
that's there to emulate loading from a slow blocking source.
This will be removed and tidied up before a final PR, of course. The main
aim here is to emulate a worst-case scenario so that the use of a worker can
be tried out with some confidence.
See #2456.
Until now the Tree.NodeExpanded and Tree.NodeCollapsed messages were only
sent out when changes were made to the tree by user interaction. This meant
that if any changes were made with the TreeNode expand, expand_all,
collapse, collapse_all, toggle or toggle_all API calls no messages would be
sent.
This PR corrects this.
The work here is, in part, required for #2456 (DirectoryTree lazy-loads
directory information on node expansion so if someone is expanding nodes
under code control the DirectoryTree never gets to know that it should load
a directory's content) and will build on #1644, essentially adding a missing
aspect to the latter PR.
As I work on what's to come (loading DirectoryTree with a worker), I'm going
to want to try and construct slow loads so I can test the effectiveness of
the changes. This means a desire to fake a very slow source of directory
information. So let's drop this into its own method so we can then do silly
things like add a sleep to really show stuff down.
I'll admit to not really following what the code does, so will really need
someone with a better understanding of the aim of this code to look over the
proposed fix; but based on a bunch of runs and hand-debugging, this seems to
do the job.
This passes all existing tests and also removes the reported error.
On the other hand I'm not confident that I'm *not* just masking an
underlying issue with this function.
Sometimes the user may wish to delete a given node, other times it might be
a requirement to just remove the children (this will be especially useful
for #2448 where we'll want to keep the directory entry itself, but remove
and recreate its contents).
See #2462.
Up until now there wasn't really a way for the tree to go empty, outside of
clear/reset. Now that we can remove nodes on the fly, that is possible. This
takes that into account.
See #2462.
This is for removing an individual node, via the node. Note that attempting
to remove the root node of a Tree is an error and will case
TreeNode.RemoveRootError to be raised.
See #2462.
Adding these references to the sub-widgets that make up a markdown document is necessary in order for the blocks to be able to post messages with a reference to the original document, which in turn is needed for the Message.control property to work.
A hangover from the previous DirectoryTree, where setting the path didn't
matter. This now sets it *after* calling Tree's __init__, thus ensuring the
line cache and other related things have been created.
Until now it wasn't really possible to know *which* DirectoryTree widget had
sent a given message; this makes it available by providing the `node`, which
in turn will provide the `tree`.
A couple of things come with this, at least one being a breaking change of
sorts:
- DirectoryTree now has a path attribute
- DirectoryTree.path is a reactive
- When DirectoryTree.path is assigned to it rebuilds the tree content
- DirectoryTree.path can be assigned a str or Path but always evaluates to a Path
- DirEntry.path is now typed as a Path
- DirEntry drops is_dir (Directory.path.is_dir() does that job)
- DirectoryTree.FileSelected.path is now always a Path
This is the first of what might be a few changes here; the main thrust of
this commit being to allow changing a DirectoryTree to view a different
directory, and also to move to a Path-first approach.
* Export types used in app.py
* Export more linked types/errors/classes.
* Remove custom template.
* Address review comments.
We need to have explicit 'Returns:' sections in properties if we want to link to the return type while https://github.com/mkdocstrings/python/issues/65 is open.
* Improve docs.
In doing so this removes the file similarity value, and as such the key that
the failure report was sorted on. This was done because, given how many
snapshot tests we have now, if lots failed, it would take a long time (often
many minutes) to compile the report.
The report is now sorted on the test name.
Now, no matter how many snapshots fail, the report should be produced pretty
much instantly.
This is a series of tests for checking styles that have sub-styles, or
sub-parts, or whatever the correct name would be; the testing being that if
!important is applied to the whole, that it works.
Starting with #2420 it became apparent that this didn't work as intended,
and once that work started it became obvious that it affected more than just
border.
So these tests test all of the styles that can be specified as a single
whole, or as a set of parts (sides, directions, etc).
See https://github.com/Textualize/textual/pull/2442#issuecomment-1529512891
This changes the original PR so that, rather than calling a private watcher
instead of a public, as originally issued, we now call public and private,
if they're both there.
If they are both there private is called first.
This change allows for private watch methods. By convention they start with
an underscore. If a reactive or var has a private watch method, it will be
used in preference to a public watch method.
With this change it becomes easier to have a private reactive/var whose
watcher is also private. For example:
_counter = var(0)
"""This is a private counter, it won't appear in the docs."
...
def _watch__counter(self) -> None:
"""Watch _counter, but don't appear in the docs either."
...
See #2382.
Currently, in the various TreeNode messages, and the handlers you'd write to
handle them, there's no way to easily know *which* tree sent the message and
so which tree the node belongs to.
This commit adds public access to the tree reference to the nodes, so that
in an event handler the developer can check the tree involved in the event.
See #2413.
* Make all containers 1fr
An unintended consequence of changes made to containers in v0.21.0 (#2377)
is something like #2385 so this commit sort of rolls that change back, and
solidifies how containers are styled by default in respect to their width
and height. Where appropriate the dimensions will be 1fr.
* Make the DEFAULT_CSS of ProgressBar less greedy
It was attempting to style a Vertical that is uses, but in doing so was
styling all Verticals rather than a Vertical within a ProgressBar.
This fixes that.
* Update the CHANGELOG for the container changes
* Move the ProgressBar CHANGELOG entry into the next release section
* Link the container change entry in the CHANGELOG to its PR
Now that the PR is PRd and we have a PR
* First prototype of PB.
* Repurpose UnderlineBar.
* Factor out 'Bar' widget.
* Revert "Factor out 'Bar' widget."
This reverts commit 0bb4871adf566416cfe590ac4396e0b79f84165c.
* Add Bar widget.
* Cap progress at 100%.
* Add skeleton for the ETA label.
[skip ci]
* Add ETA display.
* Improve docstrings.
* Directly compute percentage.
* Watch percentage changes directly.
[skip ci]
* Documentation.
* Make reactive percentage private.
Instead, we create a public read-only percentage property.
* Update griffe to fix documentation issue.
Related issues: #1572, https://github.com/mkdocstrings/griffe/issues/128.
Related PRs: https://github.com/mkdocstrings/griffe/pull/135.
* Add example and docs.
* Address review feedback.
[skip ci]
* More documentation.
* Add tests.
* Changelog.
* More tests.
* Fix/fake tests.
* Final tweaks.
* pass through unprocesses args
* omit symbols
* extract args
* posix exception
* timer updates
* another update
* just work on windows damn it
* remove updates
This keeps randomly failing in Windows in CI; multiple subsequent runs gets
it going in the end, normally one further fail at a time. So let's throw a
wee wait on the end and see if that helps.
Initially we went with a RadioSet being a simple container of RadioButtons,
with the user navigating the RadioButtons like you would any other set of
widgets. This was fine but it became pretty clear pretty quickly that having
to tab through a non-trivial collection of buttons in a set to get to the
next widget wasn't ideal.
This commit, satisfying #2368, takes over the navigation of the buttons
within the container, makes the container itself a focusable widget, and
sets up some new bindings to allow a more natural and efficient interaction
with the set.
With this commit a RadioSet becomes something you can tab into and out of
with just one keypress; navigation of the buttons within moves to being done
with the cursor keys instead.
See #2368.
* Remove hanging lines from docstrings.
Deleted hanging blank lines at the end of docstrings.
Regex pattern:
- find `\n\n( *)"""`
- replace with `\n$1"""`
This particular test seems rather flakey in GitHub's actions. In most cases
passing, with the odd fail here and there (normally when unrelated changed
are made -- I've had this fail when docs have been changed; zero code
changes!)
So, on the off chance that a little extra pause will help...
Ensures that the actual content gets changed when TabbedContent.active is
updated via code. Also adds more testing for TabbedContent.
See #2352 for details.
This makes `button` as obvious as `control`, and also ensures it has a type
in the docs too. While here I also add a couple of extra links to make the
docs more inter-linked.
This is, to some degree, rendered moot by #2332, but for the moment it still
feels worth doing. The initial intention was to make sure that non-scrolling
containers and their child classes don't have bindings that may mask other
uses for navigation keys. However, it was realised that the "problem"
affected more than just containers (hence #2332).
But... on the off chance we add any more default bindings to
`Widget` (unlikely, but still), this will mean that they don't leak into the
containers unless we intend them to.
See #2331.
The `Body` class inherited from `Container` rather than one of the scrolling
containers; until now it had worked because `Widget` provided the bindings
to make this happen, now that they've moved into `ScrollableContainer` that
stopped working.
The idea here is that not every widget will scroll, and as such not every
widget needs to have default bindings for calling the scrolling methods.
Generally scrolling is something done in a container.
These days we have *Scroll containers. As such it makes sense to introduce
the bindings in a common parent class for those containers.
This commit moves the binding from widget and creates that common parent
class, and then has HorizontalScroll and VerticalScroll inherit from it.
This is, it should be noted, a breaking change. Any code that creates a
scrolling widget that assumes that the bindings are just there, where that
widget doesn't inherit either from HorizontalScroll or VerticalScroll, will
suddenly find that scrolling with the keyboard is no longer possible.
See #2332.
Under normal circumstances the code wouldn't encounter this problem as
there's always a default screen; but a handful of tests that were testing
the screen stack broke after the recent additions relating to result
callbacks.
This cleans up that problem.
* Add regression test for #2229.
* Fix potential reactive-watch loop.
* Simplify regression test.
Labels are cheaper to use and the final visual result of the test won't depend on the directory it runs from.
* Simplify solution.
Turns out I didn't need a descriptor. :(
* Fail on empty tab.
It is possible for the same instance of a screen to get pushed onto the
screen stack multiple times; as such we really need to keep track of all the
callback requests.
So here I register a callback for every screen push and clean it up on every
screen pop; with those without callbacks being no-ops.
This is roughly how it should work. Having got this going and constructed
test code to go with it (outwith of this commit, not unit testing code, just
a test app to try out the ideas), I wanted to get this onto the forge for
further mulling over tomorrow.
The one sneaky/questionable thing here is that I'm sort of dumpster-diving
the screen stack to get the "parent" screen, to make the callback in
context. This both feels right and feels like a cheat. On the other hand
it's public for a reason, right?
Right?
I'm not 100% sure how to get this into the docs as I'd like, and it seems
we've moved the API docs around a lot in respect to widgets, recently (I'm
aware of this but aren't sure what the new layout is yet).
This at least starts to get things into the right place.
* Add a message for the tabbed content activated
* Add a docstring
* Testing tabbed content activated message
* Update changelog
* Add reference to the docs about TabbedContent.TabActivated
* Checking in remove_row progress
* Ensuring structures updated correctly when row deleted
* Clamping index
* Failed attempt
* Removing rows
* Update a type hint in DataTable
* Remove some code that wasnt required
* Use index syntax instead of get
* Add DataTable remove row test
* Snapshot tests for removing rows
* Add a docstring for DataTable.remove_row method
* Update changelog regarding DataTable.remove_row
* Add check_idle call to remove_row
* api docs
* more docstrings
* logs
* docs shakeup
* fix notes, added intro to all apis
* Remove defaults to
* add note to events
* note
* use fira code
* Add pageup and pagedown actions to DataTable, with no impls
* Pagedown moves DataTable cursor now
* Account for header height in pagedown action
* Page Up support in the DataTable
* Fix and off-by-1, ensure page up/down works on col cursor
* Add placeholder scroll home/end action handlers to datatable
* Add scroll home and scroll end
* Hide hover cursor when home or end is used
* Ensure home and end work correctly with all curosrs
* Testing home/end/pagedown/pageup cursor movement in DataTable
* Docstrings for new datatable actions
* Fix a broken unit test for the DataTable
Stemming from #2202 and implementing the solution decided in #2203. Pretty
much this change removes all the state change/handling being done on the DOM
and keeps the state internally.
* Menu skeleton
The vaguest of starts. Near the end of the day and I want to pick this up
later/tomorrow, so making sure it's on the forge before I go AFK.
* Initial design for populating the menu
One of the driving forces in the design here is that a menu option can have
a prompt that is more than one line in height; and different options can
have different height prompts. This has meant that I've had to finally get
to grips with Rich renderable types and related things.
The menu is going to lean heavily on the line API, and aims to be as
efficient as possible when it comes to having a very large menu. Large menus
are a bad idea! Nobody should be using large menus. On the other hand,
people will do it so let's allow for it.
Work in progress commit. Lots more to come.
* Add a reminder about width
* Make mypy and friend happy with OptionLineSpan.__contains__
* Fix typo
* Add a debug message
I'll remove this later, but I'd like to bubble up some debug stuff into my
own test app.
* Get scrolling working
After battling for ages to try and figure out why scrolling just wasn't
working at all, two things turned out to be at play:
1. If `overflow: hidden` you need force=True. Doh!
- I should know that too as I added force. O_o
2. Even if you do the above, it doesn't work as you'd expect *if* animation
is turned on. Turning that off made things work.
I've raised #2077 as a reminder to myself that I need to look into '2' some
more. The menus feel very snappy with animation off, but I suspect there
will be a call to allow animation during menu navigation so that'll need
sorting at some point I guess.
* Add support for home and end keys
Which, shockingly, go to the first and last items in the menu.
* Remind myself I need to remove the Debug message
Once I'm done with it.
* Make the MenuOption class a NamedTuple
There's no obvious benefit to it being a full class, but some benefits to it
being a NamedTuple.
* Add a Menu.OptionHighlighted message
* Add a missing argument to a docstring
* Fully type the option line segments list
I forgot to ensure that it was fully typed.
* Add a method for getting an option at a given position
* Better name for the location of the option
* Include the highlighted index in the OptionHighlighted message
* Add home and end to the binding docstring
* Add support for page up/down in a menu
* Rename OptionLineSegments to OptionLine
It's the details for a line of an option that hold the segments, amongst
other things. No point in repeating information here.
* Add support for a Menu.OptionSelected message
* Remove a TODO comment
It's not that it doesn't need doing yet; it's just that I've moved some TODO
stuff to a WIP document.
* Document the enter binding
* Add a reminder to myself about why animate is off
For a menu I feel that animate *should* be off, but if anyone is reading
this bit of code and feels it should be on, this will explain why it isn't.
* Explain OptionLineSpan a wee bit more
* Import Literal from typing_extensions rather than typing
This is needed for older Pythons.
* Move the prompt shapes calculation code into its own method
* Add a property for getting the option count of the menu
* Add a method of adding an option to the menu
* Highlight first option if no highlight and then movement
* Ensure the virtual height is recalculated on addition
* Remove the method for getting a specific option
There's little point in letting the user treat the menu as if it's an array;
they should know what they put into it and anything that happens *on* the
menu will result in a message which will point to the option anyway.
* Add a menu separator
This isn't the complete version of this; aside from the obvious fact that at
the moment it's just treated line an ordinary menu option (which we don't
want), the presence of a separator means the index of options will be thrown
off, from the user's point of view. The point being, a menu with 4 options
and a separator might look like this:
Option 1
Option 2
--------
Option 3
Option 4
I think the index of "Option 3" above should likely be 2 (starting from 0,
of course), not 3. This means I need to tweak the internals of the menu code
to take this into account while also keeping things efficient.
That's next up; but I wanted to get the core of this change in first so I
can noodle away and get the best approach to this.
* Finish off support for menu separators
Here I sort of add support for a menu having both content *and* prompts.
Content is anything that goes in the menu and results in lines being
rendered. Prompts are things that the end user actually gets to select from.
A menu option will have a prompt that has one or more lines. A menu
separator isn't an option but takes up one line.
* Add (back) a method for getting a particular option
Now that the menu content and the menu options are different lists, it's
possible to allow this again. While it still follows that menus shouldn't
really be treated like lists, there's no harm in providing this facility.
* Allow styling separators
* Fix how we tell the Rule to have no style
* Add a documentation line for the separator component class
* Apply default styling to the non-special options
* Set the default color to $text
This isn't actually working, but if I set it to an actual colour, it does
work. Need to dig into this more.
* Remove a TODO warning that isn't valid any more
* Have the menu option messages get the option via Menu.option
It did, and still could, pull directly from the _option property but one
step of indirection means that I can be sure anything "external" is going
via the public interface (yes, I know the message isn't really "external"
but it feels correct to treat it as such because it's for public
consumption).
* Make OptionLine just a Line and drop magic numbers for separators
Rather than overload the option index of the option line class with a magic
number to say that something isn't really an option, here I make the class
just about being a line, I keep the option_index but make it optional (no
pun intended); so that if it's `None` that means "this isn't related to a
menu option".
* Drop the assert that non-option content is a separator
I can't see much benefit in doing this in what should be a fairly tight
loop. This code relates to data that's all under the hood so we shouldn't
need to be quite so defensive.
* Remove unused import of Final
* Add a clear method
* Add support for disabled menu options
At the moment this is done in a way that, as the user navigates, the
disabled options *aren't* skipped. I'm still undecided about this. Your
traditional dropdown menus sometimes do that, sometimes don't do that. And
to make things even more interesting this menu can really be used as a
large-data-friendly listbox and I'm not sure we'd want that there.
This may change.
Also, at this point, I'm also working to keep the MenuOption class a
NamedTuple, which means it's read-only (I don't want the user messing with
things outside of the menu), which means there's interface methods for
changing the disabled state that copy the option and change the disabled
state.
Again, I'll see if I carry on liking this or not. So far I'm okay with this.
* Dial in the styles some more
* Rename some methods that use index to say index in the name
I'm going to be adding support for an id for options too, and want the user
to be able to either access an option via index or via ID. This is the first
step to allowing that.
* Remove an unnecessary inherit
Looks like this was a hangover from an early version of the message classes
and I didn't clean up.
* Fix copy/paste-o
Now there is forever evidence as to where I stole my homework from.
* Add support for menu option IDs
The idea here is that they're purely from, and purely for, the developer who
is creating the menu. Internally I don't care about them and don't
personally use them. However, there is without a doubt a good case for
allowing the developer to specify IDs for options so here's optional support
for that.
* Add a method to get a menu option via an ID
* Reduce property access and list access calls
* Have the content-tracker code do a little less work
* Drop the menu option data attribute
It wasn't going to work well, was going to cause a bunch of problems with
typing, and really it's easier to do by the dev by having them inherit from
MenuOption. So let's do that.
* Remove unused imports
* Add a TODO reminder about subclasses menu optons
* Add an initial bit of unit testing
Just the most basic test so far; it's the end of the day but I want to start
here.
* Allow the caller to use None as an alias for MenuSeparator
In doing so, overhaul how I type candidate menu content vs actual menu
content, setting up a couple of type aliases and making it easier to
maintain.
* Rename the parameter for Menu.add to better match other changes
* Swap MenuOption over to being a standard class
It would have been nice to keep it as a named tuple, but I want the
developer to be able to subclass and add their own properties to the
option (think attaching some random data to a menu option). The problem is
you can't subclass a named tuple.
So... standard class it is, with some reasonably defensive work to
discourage the developer from changing the prompt and the ID on the fly.
For obvious reasons I need to let them change the disabled state on the fly,
and this is where things end up being a little iffy. The only way (right
now) the menu will refresh when the disabled state changes is if the
developer does so via one of the methods on Menu. If they go toggling the
state on the option itself and hope that the menu will reflect this... no,
that's not going to happen.
I *could* make it happen by somehow capturing a reference to the parent menu
inside the menu option, but then things get circular and I don't like that.
* Test using None as an alternative to MenuSeparator
* Flesh out the initial menu unit tests
* Add a module docstring to the core menu unit test
* Add some testing for using subclassed menu options
* Add a property for getting an iterator of the options
* Add unit tests for option enabled/disabled
* Rename Menu -> OptionList (and friends)
The great renaming! We sort of had decided this was coming, but kept going
back and forth on if we should, what it should be, etc. Decision made today.
While this is mostly everything you want from a menu, it is foundational
enough that it needs to really be something else so it's a list of options.
Options; in a list. An OptionList.
* Add a test for adding more items to an option list later on
* Remove the debug message
I think I'm at a stage where I don't need to use it any more.
* Start of OptionList movement tests
End of day commit; more to come.
* Update the pyi for the Menu -> OptionList rename
Missed this during the grand rename.
* Tidy up a test
* Export the DuplicateID exception
* Add a test for creating a duplicate ID
* Add some more OptionList movement tests
* Allow scrollbars by default
Until the great renaming, this code was all about being menus, which
normally don't have scrollbars, and so I made a point of not having them on.
Now that this code is more about it being a list of stuff, which can be the
foundation for a menu, we want the bars there by default and any derived
menu widget can turn them off.
So here we go.
This introduces some issues that now need to be addressed. For one thing no
thought has been given to horizontal scrolling in this code (easy enough to
solve).
Also, weirdly though, the vertical scrollbars aren't quite reaching the
bottom when we highlight the last item. Wasn't expecting that, although I'm
sure there's a simple cause for that.
* Remove hover component class
I do want this, but not yet, so don't have it kicking around until I'm
actually doing something with it.
* Add missing items to the component classes docstring.
* Crop the lines that we draw
This in turn adds support for horizontal scrolling. We're not actually going
to support horizontal scrolling; in conversation with Will we've decided
that it will be *only* a vertical scrolling list, so options will be
rendered within the confines of the width.
* Allow for scrollbars by default
* Make scrolling to a non-highlight a nop rather than an error
Being able to call scroll_to_highlight even if nothing is highlighted is
useful; throwing an error when something isn't, isn't helpful. So let's make
that a no-op.
* Make a note that option ID tracking could be changed
* Ensure highlight is pulled into view on resize
It's possible that a resize might cause a highlight to partially, or even
totally, go out of view. This commit ensures that after such an event this
will be handled.
* Save an attribute access
* Microoptimise _refresh_content_tracking some more
* Reintroduce animation
But only if the vertical scrollbar is visible (see #2077 for context).
* Force a refresh when doing a specific add
* Add support for a mouse hover effect
* Highlighted a clicked option (where appropriate)
* Improve the style of a focused highlighted hovered option
* Reduce the number of attribute lookups in the line drawing method
* Simplify the way we handle page up/down at the margins
Rather than wrap around when doing page up/down, have them work as home/end
when at the margins.
* Remove unnecessary import
* Add some more option list movement tests
* Add tests for moving around an empty list
* Remove the debug message (again)
* Test moving when there are items but no highlight
* Ensure the mouse over gets cleared on clear
* Remove mouse hover logging code
It was useful while adding mouse hover support, but it's not needed now.
* Force a refresh of content tracking when doing a clear
* Rename some methods to talk about options
I want to add a `remove` for options, but widgets already have a `remove`.
So I could call it `remove_option` but then that's an imbalance with `add`.
So this renames `add` to `add_option`, and also renames `clear` to
`clear_options`.
* Add support for removing an option
* Add highlight wrapping back
I made some recent changes to highlight validation where more sensible in
the general sense, but broke the wrapping when using cursor keys to move
around. This takes that into account.
* Add tests for removing options
* Reduce the number of attribute lookups for spans
* Swap to watching highlighted to handle movement
I'd started out with an explicit refresh of the highlighted option, while
working on other things, and forgot to swap over to using a watch method.
This commit fixes that.
* Make a mouse-clicked option select that option too
* Add unit tests for option list messages
* Add unit tests for mouse hover events
* Clarify the point of the mouse click test
* Add an option list message test for highlighting a disabled option
* Add tests for interacting with disabled OptionList options
* Typo fixing
Try and make the docstring sound something approaching English.
* Fix the OptionMessage.__init__ docstring
* Add the API documentation for the OptionList
* Update the OptionTest message tests for initial highlight
Having changed things around a little regarding initial highlight, the unit
tests needed updating.
* Start the reference for the OptionList
I feel this needs a bit more work, but this feels like the core of what we
want to be emphasising.
* Add the OptionList to the gallary
* Try some extra pauses in OptionList tests
While the tests are all passing just fine locally, I'm getting the whole
whack-a-mole thing in CI that is mostly down to subtle timing issues. This
is a test to see if these extra pauses let the test apps settle down before
starting the meat of the testing.
* Try pausing in tests without setting a time
* Add snapshot tests for the OptionList examples
* Sort the bindings
* Add a docstring to the default CSS
* Explain that mouse_hovering_over can be None
* Turn mouse_hovering_over into an internal property
There was a reason that I had it as a reactive, at one point, but looking at
the final form of this code I can't see a use for it any more. So bring it
internal and make it cheaper to update.
* Update the CHANGELOG
* Update the mouse hover test after the changes to the tracking variable
* Tweak the descriptions of the hover tests
Now that I've changed this away from being a reactive.
* Tweak the OptionList hover tests some more
* Rename the up/down actions to cursor_up/down
Re: https://github.com/Textualize/textual/pull/2154#discussion_r1151587080
* Don't kick off an idle check if the widget isn't running
Added at Will's suggestion. :-P
* Simplify how we watch the vertical scrollbar status
Re: https://github.com/Textualize/textual/pull/2154#discussion_r1151593625
* Change the hover highlight to $boost
Re: https://github.com/Textualize/textual/pull/2154#discussion_r1151628190
* Add a custom exception for when an option can't be found
Re:
74a2d079b3 (r1151632957)
and 74a2d079b3 (r1151631495)
* Update tests for the new option list exceptions
* Remove the options property
We've decided it has little utility given the rest of the interface of the
widget.
Re: https://github.com/Textualize/textual/pull/2154#discussion_r1151630437
* Remove import of iterator
It's no longer required.
* Fix some option list unit tests after removing options property
* Crate Line.segments as a strip
Rather than recreate the strip every time around, just create it as a Strip
to start with. Also, in doing so, add the option meta up front rather than
every time we draw the line.
Re: https://github.com/Textualize/textual/pull/2154#discussion_r1151600239
* Correct a comment typo
* Simplify the Separator docstring
* Docstring wording tweak
* Remove the import of Segment
It's not needed any more
* Flesh out the OptionList reference some more
Things like the component classes, bindings and messages had been left out.
* Update snapshot tests
Nothing of consequence has changed but it looked like the change to how the
lines are originally constructed has resulted in an under-the-hood change to
the data that goes into a snapshot.
* Add a missing word to a docstring
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Fix a typo
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Fix a typo
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Document some raises that were missing
* Turn off animation
---------
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
* Using pseudoclass state in DataTable cache keys
* Use full pseudo-class state on tree cache key
* Adding tests for Widget.get_pseudo_class_state
* Test hiding hover cursor when mouse cursor leaves DataTable
* Update CHANGELOG.md
* Add tests for action parsing.
[skip ci]
* Fix action parsing issues.
Related issues: #2088.
* Simplify action parsing.
Turns out that we can just wrap the string that we want to parse as the arguments in '({string_here},)', as per @willmcgugan's comment in the PR review.
Related review comments: https://github.com/Textualize/textual/pull/2112\#issuecomment-1481015988
---------
Co-authored-by: Will McGugan <willmcgugan@gmail.com>
* Add Widget.border_title and border_subtitle.
Related issues: #1864
* Test setting border_(sub)title.
* Add border (sub)title references to StylesCache.
These internal references will make it easier for the instance of 'StylesCache' to know which border (sub)title to use, if/when needed.
* Add method to render border label.
* Add styles to align border (sub)title.
* Render border labels.
* Update styles template.
* Make new 'render_row' parameters optional.
* Add (sub)title border snapshot tests.
* Document border (sub)title and styles.
* Pass (sub)title directly as arguments.
Get rid of the watchers to make data flow easier to follow.
Related comment: https://github.com/Textualize/textual/pull/2064/files\#r1137746697
* Tweak example.
* Fix render_border_label.
This was wrong because border labels can be composed of multiple segments if they contain multiple styles. Additionally, we want to render a single blank space of padding around the title.
* Ensure we get no label when there's no space.
* Add tests for border label rendering.
* 'render_border_label' now returns iterable of segments.
* Add label to render_row.
* Fix calling signature in tests.
* Add padding to snapshot tests.
* Fix changelog.
* Update snapshot tests.
* Update snapshot tests.
* Border labels expand if there's no corners.
* Update CHANGELOG.md
* Fix docs.
* Remove irrelevant line.
* Fix snapshot tests.
* Don't share Console among tests.
* Simplify example in styles guide.
* Avoid expensive function call when possible.
* rewording
* positive branch first
* remove wasteful indirection
* fix changelog
---------
Co-authored-by: Will McGugan <willmcgugan@gmail.com>
I compiled a list of all widget methods that return 'None' and for which it _could_ make sense to make this change.
(I filtered out some methods, like watch and action methods.)
I tried choosing a subset of those methods, trying to only pick methods for which there weren't two things that could be returned (e.g., 'Widget.move_child' _could_ return either the widget or the child that was moved) and I also tried to only pick methods that have little or no parameters (e.g., 'Widget.animate' has many parameters and is typically called with quite a few.
These are all the 'Widget' methods for which this could make sense:
- 'move_child' (either return 'self' or the actual 'child' that was moved…)
- 'animate'
- 'scroll_to' / 'scroll_relate' / 'scroll_home' / 'scroll_end' / 'scroll_left' / 'scroll_right' / 'scroll_down' / 'scroll_up' / 'scroll_page_up' / 'scroll_page_down' / 'scroll_page_left' / 'scroll_page_right' / 'scroll_visible'
- 'refresh'
- 'focus' / 'reset_focus'
- 'capture_mouse' / 'release_mouse'
Additionally, I looked at each widget, and found these methods:
- 'Tree'
- 'TreeNode'
- 'expand' / 'expand_all' / 'collapse' / 'collapse_all' / 'toggle' / 'toggle_all'
- 'set_label'
- 'clear' / 'reset'
- 'select_node' (either return 'self' or the actual 'node' that was selected)
- 'scroll_to_line' / 'scroll_to_node'
- 'refresh_line'
- 'ToggleButton'
- 'toggle' (and 'action_toggle'?)
- 'TextLog'
- 'write'
- 'clear'
- 'Tabs'
- 'add_tab' / 'remove_tab'
- 'clear'
- 'Switch'
- 'toggle' (and 'action_toggle'?)
- 'Static'
- 'update'
- 'Pretty'
- 'update'
- 'Placeholder'
- 'cycle_variant'
- '_markdown.py'
- 'MarkdownBlock'
- 'set_content'
- 'MarkdownTableOfContents'
- 'set_table_of_contents'
- 'Input'
- 'insert_text_at_cursor'
- 'DirectoryTree'
- 'load_directory'
- 'DataTable'
- 'update_cell' / 'update_cell_at'
- 'clear'
- 'refresh_coordinate' / 'refresh_row' / 'refresh_column'
- 'sort'
- 'Button'
- 'press'
Related issues: #1908
Related discussions: #1817
* Remove _clock.py::sleep.
* Move _clock.py::get_time_no_wait to _time.py.
* Move _clock.py::get_time to _time.py.
* Remove async version of _time.py::get_time.
We started by removing '_time.py::get_time' because that was the async one and then I renamed 'get_time_no_wait' to 'get_time'.
* Make 'get_time' just an alias.
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name:Checkout repository
uses:actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name:Initialize CodeQL
uses:github/codeql-action/init@v2
with:
languages:${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name:Autobuild
uses:github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- Constant `Select.BLANK` to flag an empty selection https://github.com/Textualize/textual/pull/3614
- Added `restrict`, `type`, `max_length`, and `valid_empty` to Input https://github.com/Textualize/textual/pull/3657
### Changed
- CSS error reporting will no longer provide links to the files in question https://github.com/Textualize/textual/pull/3582
- inline CSS error reporting will report widget/class variable where the CSS was read from https://github.com/Textualize/textual/pull/3582
- Breaking change: Setting `Select.value` to `None` no longer clears the selection (See `Select.BLANK` and `Select.clear`) https://github.com/Textualize/textual/pull/3614
## [0.41.0] - 2023-10-31
### Fixed
- Fixed `Input.cursor_blink` reactive not changing blink state after `Input` was mounted https://github.com/Textualize/textual/pull/3498
- Fixed `Tabs.active` attribute value not being re-assigned after removing a tab or clearing https://github.com/Textualize/textual/pull/3498
- Fixed `DirectoryTree` race-condition crash when changing path https://github.com/Textualize/textual/pull/3498
- Fixed issue with `LRUCache.discard` https://github.com/Textualize/textual/issues/3537
- Fixed `DataTable` not scrolling to rows that were just added https://github.com/Textualize/textual/pull/3552
- Fixed cache bug with `DataTable.update_cell` https://github.com/Textualize/textual/pull/3551
- Fixed CSS errors being repeated https://github.com/Textualize/textual/pull/3566
- Fix issue with chunky highlights on buttons https://github.com/Textualize/textual/pull/3571
- Fixed `OptionList` event leakage from `CommandPalette` to `App`.
- Fixed crash in `LoadingIndicator` https://github.com/Textualize/textual/pull/3498
- Fixed crash when `Tabs` appeared as a descendant of `TabbedContent` in the DOM https://github.com/Textualize/textual/pull/3602
- Fixed the command palette cancelling other workers https://github.com/Textualize/textual/issues/3615
- Add setter for `TextArea.text` https://github.com/Textualize/textual/discussions/3525
- Added `key` argument to the `DataTable.sort()` method, allowing the table to be sorted using a custom function (or other callable) https://github.com/Textualize/textual/pull/3090
- Added `initial` to all css rules, which restores default (i.e. value from DEFAULT_CSS) https://github.com/Textualize/textual/pull/3566
- Added HorizontalPad to pad.py https://github.com/Textualize/textual/pull/3571
- Added `AwaitComplete` class, to be used for optionally awaitable return values https://github.com/Textualize/textual/pull/3498
### Changed
- Breaking change: `Button.ACTIVE_EFFECT_DURATION` classvar converted to `Button.active_effect_duration` attribute https://github.com/Textualize/textual/pull/3498
- Breaking change: `Input.blink_timer` made private (renamed to `Input._blink_timer`) https://github.com/Textualize/textual/pull/3498
- Breaking change: `Input.cursor_blink` reactive updated to not run on mount (now `init=False`) https://github.com/Textualize/textual/pull/3498
- Breaking change: `AwaitTabbedContent` class removed https://github.com/Textualize/textual/pull/3498
- Breaking change: `Tabs.remove_tab` now returns an `AwaitComplete` instead of an `AwaitRemove` https://github.com/Textualize/textual/pull/3498
- Breaking change: `Tabs.clear` now returns an `AwaitComplete` instead of an `AwaitRemove` https://github.com/Textualize/textual/pull/3498
- `TabbedContent.add_pane` now returns an `AwaitComplete` instead of an `AwaitTabbedContent` https://github.com/Textualize/textual/pull/3498
- `TabbedContent.remove_pane` now returns an `AwaitComplete` instead of an `AwaitTabbedContent` https://github.com/Textualize/textual/pull/3498
- `TabbedContent.clear_pane` now returns an `AwaitComplete` instead of an `AwaitTabbedContent` https://github.com/Textualize/textual/pull/3498
- `Tabs.add_tab` now returns an `AwaitComplete` instead of an `AwaitMount` https://github.com/Textualize/textual/pull/3498
- `DirectoryTree.reload` now returns an `AwaitComplete`, which may be awaited to ensure the node has finished being processed by the internal queue https://github.com/Textualize/textual/pull/3498
- `Tabs.remove_tab` now returns an `AwaitComplete`, which may be awaited to ensure the tab is unmounted and internal state is updated https://github.com/Textualize/textual/pull/3498
- `App.switch_mode` now returns an `AwaitMount`, which may be awaited to ensure the screen is mounted https://github.com/Textualize/textual/pull/3498
- Buttons will now display multiple lines, and have auto height https://github.com/Textualize/textual/pull/3539
- DataTable now has a max-height of 100vh rather than 100%, which doesn't work with auto
- Breaking change: empty rules now result in an error https://github.com/Textualize/textual/pull/3566
- Improved startup time by caching CSS parsing https://github.com/Textualize/textual/pull/3575
- Workers are now created/run in a thread-safe way https://github.com/Textualize/textual/pull/3586
## [0.40.0] - 2023-10-11
### Added
- Added `loading` reactive property to widgets https://github.com/Textualize/textual/pull/3509
## [0.39.0] - 2023-10-10
### Fixed
- `Pilot.click`/`Pilot.hover` can't use `Screen` as a selector https://github.com/Textualize/textual/issues/3395
- App exception when a `Tree` is initialized/mounted with `disabled=True` https://github.com/Textualize/textual/issues/3407
- Fixed `print` locations not being correctly reported in `textual console` https://github.com/Textualize/textual/issues/3237
- Fix location of IME and emoji popups https://github.com/Textualize/textual/pull/3408
- Fixed application freeze when pasting an emoji into an application on Windows https://github.com/Textualize/textual/issues/3178
- Fixed duplicate option ID handling in the `OptionList` https://github.com/Textualize/textual/issues/3455
- Fix crash when removing and updating DataTable cell at same time https://github.com/Textualize/textual/pull/3487
- Fixed fractional styles to allow integer values https://github.com/Textualize/textual/issues/3414
- Stop eating stdout/stderr in headless mode - print works again in tests https://github.com/Textualize/textual/pull/3486
### Added
- `OutOfBounds` exception to be raised by `Pilot` https://github.com/Textualize/textual/pull/3360
- `TextArea.cursor_screen_offset` property for getting the screen-relative position of the cursor https://github.com/Textualize/textual/pull/3408
- `Input.cursor_screen_offset` property for getting the screen-relative position of the cursor https://github.com/Textualize/textual/pull/3408
- Reactive `cell_padding` (and respective parameter) to define horizontal cell padding in data table columns https://github.com/Textualize/textual/issues/3435
- Added `TextArea.SelectionChanged` and `TextArea.Changed` messages https://github.com/Textualize/textual/pull/3442
- Added `wait_for_dismiss` parameter to `App.push_screen` https://github.com/Textualize/textual/pull/3477
- Allow scrollbar-size to be set to 0 to achieve scrollable containers with no visible scrollbars https://github.com/Textualize/textual/pull/3488
### Changed
- Breaking change: tree-sitter and tree-sitter-languages dependencies moved to `syntax` extra https://github.com/Textualize/textual/pull/3398
- `Pilot.click`/`Pilot.hover` now raises `OutOfBounds` when clicking outside visible screen https://github.com/Textualize/textual/pull/3360
- `Pilot.click`/`Pilot.hover` now return a Boolean indicating whether the click/hover landed on the widget that matches the selector https://github.com/Textualize/textual/pull/3360
- Added a delay to when the `No Matches` message appears in the command palette, thus removing a flicker https://github.com/Textualize/textual/pull/3399
- Timer callbacks are now typed more loosely https://github.com/Textualize/textual/issues/3434
## [0.38.1] - 2023-09-21
### Fixed
- Hotfix - added missing highlight files in build distribution https://github.com/Textualize/textual/pull/3370
## [0.38.0] - 2023-09-21
### Added
- Added a TextArea https://github.com/Textualize/textual/pull/2931
- Added :dark and :light pseudo classes
### Fixed
- Fixed `DataTable` not updating component styles on hot-reloading https://github.com/Textualize/textual/issues/3312
### Changed
- Breaking change: CSS in DEFAULT_CSS is now automatically scoped to the widget (set SCOPED_CSS=False) to disable
- Breaking change: Changed `Markdown.goto_anchor` to return a boolean (if the anchor was found) instead of `None` https://github.com/Textualize/textual/pull/3334
## [0.37.1] - 2023-09-16
### Fixed
- Fixed the command palette crashing with a `TimeoutError` in any Python before 3.11 https://github.com/Textualize/textual/issues/3320
- Fixed `Input` event leakage from `CommandPalette` to `App`.
## [0.37.0] - 2023-09-15
### Added
- Added the command palette https://github.com/Textualize/textual/pull/3058
- `Input` is now validated when focus moves out of it https://github.com/Textualize/textual/pull/3193
- Attribute `Input.validate_on` (and `__init__` parameter of the same name) to customise when validation occurs https://github.com/Textualize/textual/pull/3193
- Added `widgets.Collapsible` contributed by Sunyoung Yoo https://github.com/Textualize/textual/pull/2989
### Fixed
- Fixed a crash when removing an option from an `OptionList` while the mouse is hovering over the last option https://github.com/Textualize/textual/issues/3270
- Fixed a crash in `MarkdownViewer` when clicking on a link that contains an anchor https://github.com/Textualize/textual/issues/3094
- Fixed wrong message pump in pop_screen https://github.com/Textualize/textual/pull/3315
### Changed
- Widget.notify and App.notify are now thread-safe https://github.com/Textualize/textual/pull/3275
- Breaking change: Widget.notify and App.notify now return None https://github.com/Textualize/textual/pull/3275
- App.unnotify is now private (renamed to App._unnotify) https://github.com/Textualize/textual/pull/3275
- `Markdown.load` will now attempt to scroll to a related heading if an anchor is provided https://github.com/Textualize/textual/pull/3244
- `ProgressBar` explicitly supports being set back to its indeterminate state https://github.com/Textualize/textual/pull/3286
## [0.36.0] - 2023-09-05
### Added
- TCSS styles `layer` and `layers` can be strings https://github.com/Textualize/textual/pull/3169
- `App.return_code` for the app return code https://github.com/Textualize/textual/pull/3202
- Added `animate` switch to `Tree.scroll_to_line` and `Tree.scroll_to_node` https://github.com/Textualize/textual/pull/3210
- Added App.current_mode to get the current mode https://github.com/Textualize/textual/pull/3233
### Changed
- Reactive callbacks are now scheduled on the message pump of the reactable that is watching instead of the owner of reactive attribute https://github.com/Textualize/textual/pull/3065
- Callbacks scheduled with `call_next` will now have the same prevented messages as when the callback was scheduled https://github.com/Textualize/textual/pull/3065
- Added `cursor_type` to the `DataTable` constructor.
- Fixed `push_screen` not updating Screen.CSS styles https://github.com/Textualize/textual/issues/3217
- `DataTable.add_row` accepts `height=None` to automatically compute optimal height for a row https://github.com/Textualize/textual/pull/3213
### Fixed
- Fixed flicker when calling pop_screen multiple times https://github.com/Textualize/textual/issues/3126
- Fixed setting styles.layout not updating https://github.com/Textualize/textual/issues/3047
- Fixed flicker when scrolling tree up or down a line https://github.com/Textualize/textual/issues/3206
## [0.35.1]
### Fixed
- Fixed flash of 80x24 interface in textual-web
## [0.35.0]
### Added
- Ability to enable/disable tabs via the reactive `disabled` in tab panes https://github.com/Textualize/textual/pull/3152
- Textual-web driver support for Windows
### Fixed
- Could not hide/show/disable/enable tabs in nested `TabbedContent` https://github.com/Textualize/textual/pull/3150
## [0.34.0] - 2023-08-22
### Added
- Methods `TabbedContent.disable_tab` and `TabbedContent.enable_tab` https://github.com/Textualize/textual/pull/3112
- Methods `Tabs.disable` and `Tabs.enable` https://github.com/Textualize/textual/pull/3112
- Messages `Tab.Disabled`, `Tab.Enabled`, `Tabs.TabDisabled` and `Tabs.Enabled` https://github.com/Textualize/textual/pull/3112
- Methods `TabbedContent.hide_tab` and `TabbedContent.show_tab` https://github.com/Textualize/textual/pull/3112
- Methods `Tabs.hide` and `Tabs.show` https://github.com/Textualize/textual/pull/3112
- Messages `Tabs.TabHidden` and `Tabs.TabShown` https://github.com/Textualize/textual/pull/3112
- Added `ListView.extend` method to append multiple items https://github.com/Textualize/textual/pull/3012
### Changed
- grid-columns and grid-rows now accept an `auto` token to detect the optimal size https://github.com/Textualize/textual/pull/3107
- LoadingIndicator now has a minimum height of 1 line.
### Fixed
- Fixed auto height container with default grid-rows https://github.com/Textualize/textual/issues/1597
- Fixed `page_up` and `page_down` bug in `DataTable` when `show_header = False` https://github.com/Textualize/textual/pull/3093
- Fixed issue with visible children inside invisible container when moving focus https://github.com/Textualize/textual/issues/3053
## [0.33.0] - 2023-08-15
### Fixed
- Fixed unintuitive sizing behaviour of TabbedContent https://github.com/Textualize/textual/issues/2411
- Fixed relative units not always expanding auto containers https://github.com/Textualize/textual/pull/3059
- `MouseMove` events bubble up from widgets. `App` and `Screen` receive `MouseMove` events even if there's no Widget under the cursor. https://github.com/Textualize/textual/issues/2905
- Fixed click on double-width char https://github.com/Textualize/textual/issues/2968
### Changed
- Breaking change: `DOMNode.visible` now takes into account full DOM to report whether a node is visible or not.
- Breaking change: Creating a thread worker now requires that a `thread=True` keyword argument is passed https://github.com/Textualize/textual/pull/2938
- Breaking change: `Markdown.load` no longer captures all errors and returns a `bool`, errors now propagate https://github.com/Textualize/textual/issues/2956
- Breaking change: the default style of a `DataTable` now has `max-height: 100%` https://github.com/Textualize/textual/issues/2959
### Fixed
- Fixed a crash when a `SelectionList` had a prompt wider than itself https://github.com/Textualize/textual/issues/2900
- Fixed a bug where `Click` events were bubbling up from `Switch` widgets https://github.com/Textualize/textual/issues/2366
- Fixed a crash when using empty CSS variables https://github.com/Textualize/textual/issues/1849
- Fixed issue with tabs in TextLog https://github.com/Textualize/textual/issues/3007
- Fixed a bug with `DataTable` hover highlighting https://github.com/Textualize/textual/issues/2909
- call_after_refresh will preserve the sender within the callback https://github.com/Textualize/textual/pull/2806
### Added
- Added a method of allowing third party code to handle unhandled tokens in `Markdown` https://github.com/Textualize/textual/pull/2803
- Added `MarkdownBlock` as an exported symbol in `textual.widgets.markdown` https://github.com/Textualize/textual/pull/2803
### Changed
- Tooltips are now inherited, so will work with compound widgets
## [0.28.0] - 2023-06-19
### Added
- The devtools console now confirms when CSS files have been successfully loaded after a previous error https://github.com/Textualize/textual/pull/2716
- Class variable `CSS` to screens https://github.com/Textualize/textual/issues/2137
- Class variable `CSS_PATH` to screens https://github.com/Textualize/textual/issues/2137
- Added `cursor_foreground_priority` and `cursor_background_priority` to `DataTable` https://github.com/Textualize/textual/pull/2736
- Added Region.center
- Added `center` parameter to `Widget.scroll_to_region`
- Added `origin_visible` parameter to `Widget.scroll_to_region`
- Added `origin_visible` parameter to `Widget.scroll_to_center`
- Fixed setting `TreeNode.label` on an existing `Tree` node not immediately refreshing https://github.com/Textualize/textual/pull/2713
- Correctly implement `__eq__` protocol in DataTable https://github.com/Textualize/textual/pull/2705
- Fixed exceptions in Pilot tests being silently ignored https://github.com/Textualize/textual/pull/2754
- Fixed issue where internal data of `OptionList` could be invalid for short window after `clear_options` https://github.com/Textualize/textual/pull/2754
- Fixed `Tooltip` causing a `query_one` on a lone `Static` to fail https://github.com/Textualize/textual/issues/2723
- Nested widgets wouldn't lose focus when parent is disabled https://github.com/Textualize/textual/issues/2772
- Fixed the `Tabs``Underline` highlight getting "lost" in some extreme situations https://github.com/Textualize/textual/pull/2751
### Changed
- Breaking change: The `@on` decorator will now match a message class and any child classes https://github.com/Textualize/textual/pull/2746
- `Tabs.add_tab` is now optionally awaitable https://github.com/Textualize/textual/pull/2778
- `Tabs.add_tab` now takes `before` and `after` arguments to position a new tab https://github.com/Textualize/textual/pull/2778
- `Tabs.remove_tab` is now optionally awaitable https://github.com/Textualize/textual/pull/2778
- Breaking change: `Tabs.clear` has been changed from returning `self` to being optionally awaitable https://github.com/Textualize/textual/pull/2778
## [0.27.0] - 2023-06-01
### Fixed
- Fixed zero division error https://github.com/Textualize/textual/issues/2673
- Fix `scroll_to_center` when there were nested layers out of view (Compositor full_map not populated fully) https://github.com/Textualize/textual/pull/2684
- Fix crash when `Select` widget value attribute was set in `compose` https://github.com/Textualize/textual/pull/2690
- Issue with computing progress in workers https://github.com/Textualize/textual/pull/2686
- Issues with `switch_screen` not updating the results callback appropriately https://github.com/Textualize/textual/issues/2650
- Fixed incorrect mount order https://github.com/Textualize/textual/pull/2702
- `App.AUTO_FOCUS` to set auto focus on all screens https://github.com/Textualize/textual/issues/2594
- Option to `scroll_to_center` to ensure we don't scroll such that the top left corner of the widget is not visible https://github.com/Textualize/textual/pull/2682
- `Placeholder` now sets its color cycle per app https://github.com/Textualize/textual/issues/2590
- Footer now clears key highlight regardless of whether it's in the active screen or not https://github.com/Textualize/textual/issues/2606
- The default Widget repr no longer displays classes and pseudo-classes (to reduce noise in logs). Add them to your `__rich_repr__` method if needed. https://github.com/Textualize/textual/pull/2623
- Setting `Screen.AUTO_FOCUS` to `None` will inherit `AUTO_FOCUS` from the app instead of disabling it https://github.com/Textualize/textual/issues/2594
- Setting `Screen.AUTO_FOCUS` to `""` will disable it on the screen https://github.com/Textualize/textual/issues/2594
- Messages now have a `handler_name` class var which contains the name of the default handler method.
- `Message.control` is now a property instead of a class variable. https://github.com/Textualize/textual/issues/2528
- `Tree` and `DirectoryTree` Messages no longer accept a `tree` parameter, using `self.node.tree` instead. https://github.com/Textualize/textual/issues/2529
- Keybinding <kbd>right</kbd> in `Input` is also used to accept a suggestion if the cursor is at the end of the input https://github.com/Textualize/textual/pull/2604
- `Input.__init__` now accepts a `suggester` attribute for completion suggestions https://github.com/Textualize/textual/pull/2604
- Using `switch_screen` to switch to the currently active screen is now a no-op https://github.com/Textualize/textual/pull/2692
- Breaking change: removed `reactive.py::Reactive.var` in favor of `reactive.py::var` https://github.com/Textualize/textual/pull/2709/
### Removed
- `Placeholder.reset_color_cycle`
- Removed `Widget.reset_focus` (now called `Widget.blur`) https://github.com/Textualize/textual/issues/2642
## [0.26.0] - 2023-05-20
### Added
- Added `Widget.can_view`
### Changed
- Textual will now scroll focused widgets to center if not in view
## [0.25.0] - 2023-05-17
### Changed
- App `title` and `sub_title` attributes can be set to any type https://github.com/Textualize/textual/issues/2521
- `DirectoryTree` now loads directory contents in a worker https://github.com/Textualize/textual/issues/2456
- Only a single error will be written by default, unless in dev mode ("debug" in App.features) https://github.com/Textualize/textual/issues/2480
- Using `Widget.move_child` where the target and the child being moved are the same is now a no-op https://github.com/Textualize/textual/issues/1743
- Calling `dismiss` on a screen that is not at the top of the stack now raises an exception https://github.com/Textualize/textual/issues/2575
- `MessagePump.call_after_refresh` and `MessagePump.call_later` will now return `False` if the callback could not be scheduled. https://github.com/Textualize/textual/pull/2584
### Fixed
- Fixed `ZeroDivisionError` in `resolve_fraction_unit` https://github.com/Textualize/textual/issues/2502
- Fixed `TreeNode.expand` and `TreeNode.expand_all` not posting a `Tree.NodeExpanded` message https://github.com/Textualize/textual/issues/2535
- Fixed `TreeNode.collapse` and `TreeNode.collapse_all` not posting a `Tree.NodeCollapsed` message https://github.com/Textualize/textual/issues/2535
- Fixed `TreeNode.toggle` and `TreeNode.toggle_all` not posting a `Tree.NodeExpanded` or `Tree.NodeCollapsed` message https://github.com/Textualize/textual/issues/2535
- `footer--description` component class was being ignored https://github.com/Textualize/textual/issues/2544
- Pasting empty selection in `Input` would raise an exception https://github.com/Textualize/textual/issues/2563
- `Screen.AUTO_FOCUS` now focuses the first _focusable_ widget that matches the selector https://github.com/Textualize/textual/issues/2578
- `Screen.AUTO_FOCUS` now works on the default screen on startup https://github.com/Textualize/textual/pull/2581
- Fix for setting dark in App `__init__` https://github.com/Textualize/textual/issues/2583
- Fix issue with scrolling and docks https://github.com/Textualize/textual/issues/2525
- Fix not being able to use CSS classes with `Tab` https://github.com/Textualize/textual/pull/2589
### Added
- Class variable `AUTO_FOCUS` to screens https://github.com/Textualize/textual/issues/2457
- Added `NULL_SPACING` and `NULL_REGION` to geometry.py
## [0.24.1] - 2023-05-08
### Fixed
- Fix TypeError in code browser
## [0.24.0] - 2023-05-08
### Fixed
- Fixed crash when creating a `DirectoryTree` starting anywhere other than `.`
- Fixed line drawing in `Tree` when `Tree.show_root` is `True` https://github.com/Textualize/textual/issues/2397
- Fixed line drawing in `Tree` not marking branches as selected when first getting focus https://github.com/Textualize/textual/issues/2397
### Changed
- The DataTable cursor is now scrolled into view when the cursor coordinate is changed programmatically https://github.com/Textualize/textual/issues/2459
- run_worker exclusive parameter is now `False` by default https://github.com/Textualize/textual/pull/2470
- Added `always_update` as an optional argument for `reactive.var`
- Made Binding description default to empty string, which is equivalent to show=False https://github.com/Textualize/textual/pull/2501
- Modified Message to allow it to be used as a dataclass https://github.com/Textualize/textual/pull/2501
- Decorator `@on` accepts arbitrary `**kwargs` to apply selectors to attributes of the message https://github.com/Textualize/textual/pull/2498
### Added
- Property `control` as alias for attribute `tabs` in `Tabs` messages https://github.com/Textualize/textual/pull/2483
- `TabbedContent` now takes kwargs `id`, `name`, `classes`, and `disabled`, upon initialization, like other widgets https://github.com/Textualize/textual/pull/2497
- Added `-c` switch to `textual run` which runs commands in a Textual dev environment.
- Breaking change: standard keyboard scrollable navigation bindings have been moved off `Widget` and onto a new base class for scrollable containers (see also below addition) https://github.com/Textualize/textual/issues/2332
- `ScrollView` now inherits from `ScrollableContainer` rather than `Widget` https://github.com/Textualize/textual/issues/2332
- Containers no longer inherit any bindings from `Widget` https://github.com/Textualize/textual/issues/2331
- Added `ScrollableContainer`; a container class that binds the common navigation keys to scroll actions (see also above breaking change) https://github.com/Textualize/textual/issues/2332
### Fixed
- Fixed dark mode toggles in a "child" screen not updating a "parent" screen https://github.com/Textualize/textual/issues/1999
- Fixed "panel" border not exposed via CSS
- Fixed `TabbedContent.active` changes not changing the actual content https://github.com/Textualize/textual/issues/2352
- Fixed broken color on macOS Terminal https://github.com/Textualize/textual/issues/2359
## [0.20.1] - 2023-04-18
### Fix
- New fix for stuck tabs underline https://github.com/Textualize/textual/issues/2229
## [0.20.0] - 2023-04-18
### Changed
- Changed signature of Driver. Technically a breaking change, but unlikely to affect anyone.
- Breaking change: Timer.start is now private, and returns None. There was no reason to call this manually, so unlikely to affect anyone.
- A clicked tab will now be scrolled to the center of its tab container https://github.com/Textualize/textual/pull/2276
- Style updates are now done immediately rather than on_idle https://github.com/Textualize/textual/pull/2304
- `ButtonVariant` is now exported from `textual.widgets.button` https://github.com/Textualize/textual/issues/2264
- `HorizontalScroll` and `VerticalScroll` are now focusable by default https://github.com/Textualize/textual/pull/2317
- option `--port` to the command `textual console` to specify which port the console should connect to https://github.com/Textualize/textual/pull/2258
- `Widget.scroll_to_center` method to scroll children to the center of container widget https://github.com/Textualize/textual/pull/2255 and https://github.com/Textualize/textual/pull/2276
- Added `TabActivated` message to `TabbedContent` https://github.com/Textualize/textual/pull/2260
- Fixed order styles are applied in DataTable - allows combining of renderable styles and component classes https://github.com/Textualize/textual/pull/2272
- Fixed key combos with up/down keys in some terminals https://github.com/Textualize/textual/pull/2280
- Fix empty ListView preventing bindings from firing https://github.com/Textualize/textual/pull/2281
- Fix `get_component_styles` returning incorrect values on first call when combined with pseudoclasses https://github.com/Textualize/textual/pull/2304
- Fixed `active_message_pump.get` sometimes resulting in a `LookupError` https://github.com/Textualize/textual/issues/2301
## [0.19.1] - 2023-04-10
### Fixed
- Fix viewport units using wrong viewport size https://github.com/Textualize/textual/pull/2247
- Fixed layout not clearing arrangement cache https://github.com/Textualize/textual/pull/2249
## [0.19.0] - 2023-04-07
### Added
- Added support for filtering a `DirectoryTree` https://github.com/Textualize/textual/pull/2215
### Changed
- Allowed border_title and border_subtitle to accept Text objects
- Added additional line around titles
- When a container is auto, relative dimensions in children stretch the container. https://github.com/Textualize/textual/pull/2221
- DataTable page up / down now move cursor
### Fixed
- Fixed margin not being respected when width or height is "auto" https://github.com/Textualize/textual/issues/2220
- Fixed issue which prevent scroll_visible from working https://github.com/Textualize/textual/issues/2181
- Fixed missing tracebacks on Windows https://github.com/Textualize/textual/issues/2027
## [0.18.0] - 2023-04-04
### Added
- Added Worker API https://github.com/Textualize/textual/pull/2182
### Changed
- Breaking change: Markdown.update is no longer a coroutine https://github.com/Textualize/textual/pull/2182
### Fixed
- `RadioSet` is now far less likely to report `pressed_button` as `None` https://github.com/Textualize/textual/issues/2203
## [0.17.3] - 2023-04-02
### [Fixed]
- Fixed scrollable area not taking in to account dock https://github.com/Textualize/textual/issues/2188
- Fix for interaction between pseudo-classes and widget-level render caches https://github.com/Textualize/textual/pull/2155
### Changed
- DataTable now has height: auto by default. https://github.com/Textualize/textual/issues/2117
- Textual will now render strings within renderables (such as tables) as Console Markup by default. You can wrap your text with rich.Text() if you want the original behavior. https://github.com/Textualize/textual/issues/2120
- Some widget methods now return `self` instead of `None` https://github.com/Textualize/textual/pull/2102:
- Added Screen.ModalScreen which prevents App from handling bindings. https://github.com/Textualize/textual/pull/2139
- Added TEXTUAL_LOG env var which should be a path that Textual will write verbose logs to (textual devtools is generally preferred) https://github.com/Textualize/textual/pull/2148
First of all, thanks for taking the time to contribute to Textual!
## How can I contribute?
You can contribute to Textual in many ways:
1. [Report a bug](https://github.com/textualize/textual/issues/new?title=%5BBUG%5D%20short%20bug%20description&template=bug_report.md)
2. Add a new feature
3. Fix a bug
4. Improve the documentation
## Setup
To make a code or documentation contribution you will need to set up Textual locally.
You can follow these steps:
1. Make sure you have Poetry installed ([see instructions here](https://python-poetry.org))
2. Clone the Textual repository
3. Run `poetry shell` to create a virtual environment for the dependencies
4. Run `poetry install` to install all dependencies
5. Make sure the latest version of Textual was installed by running the command `textual --version`
6. Install the pre-commit hooks with the command `pre-commit install`
## Demo
Once you have Textual installed, run the Textual demo to get an impression of what Textual can do and to double check that everything was installed correctly:
```bash
python -m textual
```
## Guidelines
- Read any issue instructions carefully. Feel free to ask for clarification if any details are missing.
- Add docstrings to all of your code (functions, methods, classes, ...). The codebase should have enough examples for you to copy from.
- Write tests for your code.
- If you are fixing a bug, make sure to add regression tests that link to the original issue.
- If you are implementing a visual element, make sure to add _snapshot tests_. [See below](#snapshot-testing) for more details.
## Before opening a PR
Before you open your PR, please go through this checklist and make sure you've checked all the items that apply:
- [ ] Update the `CHANGELOG.md`
- [ ] Format your code with black (`make format`)
- [ ] All your code has docstrings in the style of the rest of the codebase
- [ ] Your code passes all tests (`make test`)
([Read this](#makefile-commands) if the command `make` doesn't work for you.)
## Updating and building the documentation
If you change the documentation, you will want to build the documentation to make sure everything looks like it should.
The command `make docs-serve-offline` should start a server that will let you preview the documentation locally and that should reload whenever you save changes to the documentation or the code files.
([Read this](#makefile-commands) if the command `make` doesn't work for you.)
We strive to write our documentation in a clear and accessible way so, if you find any issues with the documentation, we encourage you to open an issue where you can enumerate the things you think should be changed or added.
Opening an issue or a discussion is typically better than opening a PR directly.
That's because there are many subjective considerations that go into writing documentation and we cannot expect you, a well-intentioned external contributor, to be aware of those subjective considerations that we take into account when writing our documentation.
Of course, this does not apply to objective/technical issues with the documentation like bugs or broken links.
## After opening a PR
When you open a PR, your code will be reviewed by one of the Textual maintainers.
In that review process,
- We will take a look at all of the changes you are making
- We might ask for clarifications (why did you do X or Y?)
- We might ask for more tests/more documentation
- We might ask for some code changes
The sole purpose of those interactions is to make sure that, in the long run, everyone has the best experience possible with Textual and with the feature you are implementing/fixing.
Don't be discouraged if a reviewer asks for code changes.
If you go through our history of pull requests, you will see that every single one of the maintainers has had to make changes following a review.
## Snapshot testing
Snapshot tests ensure that visual things (like widgets) look like they are supposed to.
PR [#1969](https://github.com/Textualize/textual/pull/1969) is a good example of what adding snapshot tests looks like: it amounts to a change in the file `tests/snapshot_tests/test_snapshots.py` that should run an app that you write and compare it against a historic snapshot of what that app should look like.
When you create a new snapshot test, run it with `pytest -vv tests/snapshot_tests/test_snapshots.py`.
Because you just created this snapshot test, there is no history to compare against and the test will fail.
After running the snapshot tests, you should see a link that opens an interface in your browser.
This interface should show all failing snapshot tests and a side-by-side diff between what the app looked like when the test ran versus the historic snapshot.
Make sure your snapshot app looks like it is supposed to and that you didn't break any other snapshot tests.
If everything looks fine, you can run `make test-snapshot-update` to update the snapshot history with your new snapshot.
This will write to the file `tests/snapshot_tests/__snapshots__/test_snapshots.ambr`, which you should NOT modify by hand.
([Read this](#makefile-commands) if the command `make` doesn't work for you.)
## Join the community
Seems a little overwhelming?
Join our community on [Discord](https://discord.gg/Enf6Z3qhVr) to get help!
## Makefile commands
Textual has a `Makefile` file that contains the most common commands used when developing Textual.
([Read about Make and makefiles on Wikipedia.](https://en.wikipedia.org/wiki/Make_(software)))
If you don't have Make and you're on Windows, you may want to [install Make](https://stackoverflow.com/q/32127524/2828287).
- [Does Textual support images?](#does-textual-support-images)
- [How can I fix ImportError cannot import name ComposeResult from textual.app ?](#how-can-i-fix-importerror-cannot-import-name-composeresult-from-textualapp-)
- [How can I select and copy text in a Textual app?](#how-can-i-select-and-copy-text-in-a-textual-app)
- [How do I center a widget in a screen?](#how-do-i-center-a-widget-in-a-screen)
- [How do I pass arguments to an app?](#how-do-i-pass-arguments-to-an-app)
- [Why doesn't Textual look good on macOS?](#why-doesn't-textual-look-good-on-macos)
- [Why doesn't Textual support ANSI themes?](#why-doesn't-textual-support-ansi-themes)
<aname="does-textual-support-images"></a>
## Does Textual support images?
Textual doesn't have built in support for images yet, but it is on the [Roadmap](https://textual.textualize.io/roadmap/).
See also the [rich-pixels](https://github.com/darrenburns/rich-pixels) project for a Rich renderable for images that works with Textual.
Textual will not generate escape sequences for the 16 themeable *ANSI* colors.
This is an intentional design decision we took for for the following reasons:
- Not everyone has a carefully chosen ANSI color theme. Color combinations which may look fine on your system, may be unreadable on another machine. There is very little an app author or Textual can do to resolve this. Asking users to simply pick a better theme is not a good solution, since not all users will know how.
- ANSI colors can't be manipulated in the way Textual can do with other colors. Textual can blend colors and produce light and dark shades from an original color, which is used to create more readable text and user interfaces. Color blending will also be used to power future accessibility features.
Textual has a design system which guarantees apps will be readable on all platforms and terminals, and produces better results than ANSI colors.
There is currently a light and dark version of the design system, but more are planned. It will also be possible for users to customize the source colors on a per-app or per-system basis. This means that in the future you will be able to modify the core colors to blend in with your chosen terminal theme.
<hr>
Generated by [FAQtory](https://github.com/willmcgugan/faqtory)
Textual adds interactivity to [Rich](https://github.com/Textualize/rich) with an API inspired by modern web development.
On modern terminal software (installed by default on most systems), Textual apps can use **16.7 million** colors with mouse support and smooth flicker-free animation. A powerful layout engine and re-usable components makes it possible to build apps that rival the desktop and web experience.
On modern terminal software (installed by default on most systems), Textual apps can use **16.7 million** colors with mouse support and smooth flicker-free animation. A powerful layout engine and re-usable components makes it possible to build apps that rival the desktop and web experience.
## Compatibility
@ -37,10 +43,16 @@ Textual runs on Linux, macOS, and Windows. Textual requires Python 3.7 or above.
Install Textual via pip:
```
pip install "textual[dev]"
pip install textual
```
The addition of `[dev]` installs Textual development tools. See the [docs](https://textual.textualize.io/getting_started/) if you need help getting started.
If you plan on developing Textual apps, you should also install the development tools with the following command:
```
pip install textual-dev
```
See the [docs](https://textual.textualize.io/getting_started/) if you need help getting started.
## Demo
@ -56,6 +68,10 @@ python -m textual
Head over to the [Textual documentation](http://textual.textualize.io/) to start building!
## Join us on Discord
Join the Textual developers and community on our [Discord Server](https://discord.gg/Enf6Z3qhVr).
## Examples
The Textual repository comes with a number of examples you can experiment with or use as a template for your own projects.
The `textual` command has a few sub-commands to preview Textual styles.
<details>
<details>
<summary> 🎬 Easing reference </summary>
<hr>
This is the *easing* reference which demonstrates the easing parameter on animation, with both movement and opacity. You can run it with the following command:
Some terminal emulators have a translucent background feature which allows the desktop underneath to be partially visible.
This feature is unlikely to work with Textual, as the translucency effect requires the use of ANSI background colors, which Textual doesn't use.
Textual uses 16.7 million colors where available which enables consistent colors across all platforms and additional effects which aren't possible with ANSI colors.
For more information on ANSI colors in Textual, see [Why no Ansi Themes?](#why-doesnt-textual-support-ansi-themes).
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.
For instance, you may find it displays misaligned blocks and lines like this:
<imgwidth="1042"alt="Screenshot 2023-06-19 at 10 43 02"src="https://github.com/Textualize/textual/assets/554369/e61f3876-3dd1-4ac8-b380-22922c89c7d6">
You can (mostly) fix this by opening settings -> profiles > Text tab, and changing the font settings.
We have found that Menlo Regular font, with a character spacing of 1 and line spacing of 0.805 produces reasonable results.
If you want to use another font, you may have to tweak the line spacing until you get good results.
<imgwidth="737"alt="Screenshot 2023-06-19 at 10 44 00"src="https://github.com/Textualize/textual/assets/554369/0a052a93-b1fd-4327-9d33-d954b51a9ad2">
With these changes, Textual apps render more as intended:
<imgwidth="1042"alt="Screenshot 2023-06-19 at 10 43 23"src="https://github.com/Textualize/textual/assets/554369/a0c4aa05-c509-4ac1-b0b8-e68ce4433f70">
Even with this *fix*, Terminal.app has a few limitations.
It is limited to 256 colors, and can be a little slow compared to more modern alternatives.
Fortunately there are a number of free terminal emulators for macOS which produces high quality results.
We recommend any of the following terminals:
- [iTerm2](https://iterm2.com/)
- [Kitty](https://sw.kovidgoyal.net/kitty/)
- [WezTerm](https://wezfurlong.org/wezterm/)
### Terminal.app colors
<imgwidth="762"alt="Screenshot 2023-06-19 at 11 00 12"src="https://github.com/Textualize/textual/assets/554369/e0555d23-e141-4069-b318-f3965c880208">
### iTerm2 colors
<imgwidth="1002"alt="Screenshot 2023-06-19 at 11 00 25"src="https://github.com/Textualize/textual/assets/554369/9a8cde57-5121-49a7-a2e0-5f6fc871b7a6">
Textual will not generate escape sequences for the 16 themeable *ANSI* colors.
This is an intentional design decision we took for for the following reasons:
- Not everyone has a carefully chosen ANSI color theme. Color combinations which may look fine on your system, may be unreadable on another machine. There is very little an app author or Textual can do to resolve this. Asking users to simply pick a better theme is not a good solution, since not all users will know how.
- ANSI colors can't be manipulated in the way Textual can do with other colors. Textual can blend colors and produce light and dark shades from an original color, which is used to create more readable text and user interfaces. Color blending will also be used to power future accessibility features.
Textual has a design system which guarantees apps will be readable on all platforms and terminals, and produces better results than ANSI colors.
There is currently a light and dark version of the design system, but more are planned. It will also be possible for users to customize the source colors on a per-app or per-system basis. This means that in the future you will be able to modify the core colors to blend in with your chosen terminal theme.
{% with labels = function.labels or [(function.parameters._parameters_list and function.parameters._parameters_list[0].name == 'self') and 'method' or 'function'] %}
A (reasonable) criticism of async is that it tends to proliferate in your code. In order to `await` something, your functions must be `async` all the way up the call-stack. This tends to result in you making things `async` just to support that one call that needs it or, worse, adding `async` just-in-case. Given that going from `def` to `async def` is a breaking change there is a strong incentive to go straight there.
Before you know it, you have adopted a policy of "async all the things".
<!-- more -->
Textual is an async framework, but doesn't *require* the app developer to use the `async` and `await` keywords (but you can if you need to). This post is about how Textual accomplishes this async-agnosticism.
!!! info
See this [example](https://textual.textualize.io/guide/widgets/#attributes-down) from the docs for an async-less Textual app.
## An apology
But first, an apology! In a previous post I said Textual "doesn't do any IO of its own". This is not accurate. Textual responds to keys and mouse events (**I**nput) and writes content to the terminal (**O**utput).
Although Textual clearly does do IO, it uses `asyncio` mainly for *concurrency*. It allows each widget to update its part of the screen independently from the rest of the app.
## Await me (maybe)
The first no-async async technique is the "Await me maybe" pattern, a term first coined by [Simon Willison](https://simonwillison.net/2020/Sep/2/await-me-maybe/). This is particularly applicable to callbacks (or in Textual terms, message handlers).
The `await_me_maybe` function below can run a callback that is either a plain old function *or* a coroutine (`async def`). It does this by awaiting the result of the callback if it is awaitable, or simply returning the result if it is not.
```python
import asyncio
import inspect
def plain_old_function():
return "Plain old function"
async def async_function():
return "Async function"
async def await_me_maybe(callback):
result = callback()
if inspect.isawaitable(result):
return await result
return result
async def run_framework():
print(
await await_me_maybe(plain_old_function)
)
print(
await await_me_maybe(async_function)
)
if __name__ == "__main__":
asyncio.run(run_framework())
```
## Optionally awaitable
The "await me maybe" pattern is great when an async framework calls the app's code. The app developer can choose to write async code or not. Things get a little more complicated when the app wants to call the framework's API. If the API has *asynced all the things*, then it would force the app to do the same.
Textual's API consists of regular methods for the most part, but there are a few methods which are optionally awaitable. These are *not* coroutines (which must be awaited to do anything).
In practice, this means that those API calls initiate something which will complete a short time later. If you discard the return value then it won't prevent it from working. You only need to `await` if you want to know when it has finished.
The `mount` method is one such method. Calling it will add a widget to the screen:
```python
def on_key(self):
# Add MyWidget to the screen
self.mount(MyWidget("Hello, World!"))
```
In this example we don't care that the widget hasn't been mounted immediately, only that it will be soon.
!!! note
Textual awaits the result of mount after the message handler, so even if you don't *explicitly* await it, it will have been completed by the time the next message handler runs.
We might care if we want to mount a widget then make some changes to it. By making the handler `async` and awaiting the result of mount, we can be sure that the widget has been initialized before we update it:
Incidentally, I found there were very few examples of writing awaitable objects in Python. So here is the code for `AwaitMount` which is returned by the `mount` method:
```python
class AwaitMount:
"""An awaitable returned by mount() and mount_all()."""
Textual did initially "async all the things", which you might see if you find some old Textual code. Now async is optional.
This is not because I dislike async. I'm a fan! But it does place a small burden on the developer (more to type and think about). With the current API you generally don't need to write coroutines, or remember to await things. But async is there if you need it.
We're finding that Textual is increasingly becoming a UI to things which are naturally concurrent, so async was a good move. Concurrency can be a tricky subject, so we're planning some API magic to take the pain out of running tasks, threads, and processes. Stay tuned!
Join us on our [Discord server](https://discord.gg/Enf6Z3qhVr) if you want to talk about these things with the Textualize developers.
Broadly speaking, there are two types of contributions you can make to an Open Source project.
<!-- more -->
The first type is typically a bug fix, but could also be a documentation update, linting fix, or other change which doesn't impact core functionality.
Such a contribution is like *cake*.
It's a simple, delicious, gift to the project.
The second type of contribution often comes in the form of a new feature.
This contribution likely represents a greater investment of time and effort than a bug fix.
It is still a gift to the project, but this contribution is *not* cake.
A feature PR has far more in common with a puppy.
The maintainer(s) may really like the feature but hesitate to merge all the same.
They may even reject the contribution entirely.
This is because a feature PR requires an ongoing burden to maintain.
In the same way that a puppy needs food and walkies, a new feature will require updates and fixes long after the original contribution.
Even if it is an amazing feature, the maintainer may not want to commit to that ongoing work.

The chances of a feature being merged can depend on the maturity of the project.
At the beginning of a project, a maintainer may be delighted with a new feature contribution.
After all, having others join you to build something is the joy of Open Source.
And yet when a project gets more mature there may be a growing resistance to adding new features, and a greater risk that a feature PR is rejected or sits unappreciated in the PR queue.
So how should a contributor avoid this?
If there is any doubt, it's best to propose the feature to the maintainers before undertaking the work.
In all likelihood they will be happy for your contribution, just be prepared for them to say "thanks but no thanks".
Don't take it as a rejection of your gift: it's just that the maintainer can't commit to taking on a puppy.
There are other ways to contribute code to a project that don't require the code to be merged in to the core.
You could publish your change as a third party library.
Take it from me: maintainers love it when their project spawns an ecosystem.
You could also blog about how you solved your problem without an update to the core project.
Having a resource that can be googled for, or a maintainer can direct people to, can be a huge help.
What prompted me to think about this is that my two main projects, [Rich](https://github.com/Textualize/rich) and [Textual](https://github.com/Textualize/textual), are at quite different stages in their lifetime. Rich is relatively mature, and I'm unlikely to accept a puppy. If you can achieve what you need without adding to the core library, I am *probably* going to decline a new feature. Textual is younger and still accepting puppies — in addition to stick insects, gerbils, capybaras and giraffes.
!!! tip
If you are maintainer, and you do have to close a feature PR, feel free to link to this post.
---
Join us on the [Discord Server](https://discord.gg/Enf6Z3qhVr) if you want to discuss puppies and other creatures.
title: "Textual 0.16.0 adds TabbedContent and border titles"
authors:
- willmcgugan
---
# Textual 0.16.0 adds TabbedContent and border titles
Textual 0.16.0 lands 9 days after the previous release. We have some new features to show you.
<!-- more -->
There are two highlights in this release. In no particular order, the first is [TabbedContent](../../widgets/tabbed_content.md) which uses a row of *tabs* to navigate content. You will have likely encountered this UI in the desktop and web. I think in Windows they are known as "Tabbed Dialogs".
This widget combines existing [Tabs](../../widgets/tabs.md) and [ContentSwitcher](../../api/content_switcher.md) widgets and adds an expressive interface for composing. Here's a trivial example to use content tabs to navigate a set of three markdown documents:
```python
def compose(self) -> ComposeResult:
with TabbedContent("Leto", "Jessica", "Paul"):
yield Markdown(LETO)
yield Markdown(JESSICA)
yield Markdown(PAUL)
```
Here's an example of the UI you can create with this widget (note the nesting)!
The second highlight is a frequently requested feature (FRF?). Widgets now have the two new string properties, `border_title` and `border_subtitle`, which will be displayed within the widget's border.
You can set the alignment of these titles via [`border-title-align`](../../styles/border_title_align.md) and [`border-subtitle-align`](../../styles/border_subtitle_align.md). Titles may contain [Console Markup](https://rich.readthedocs.io/en/latest/markup.html), so you can add additional color and style to the labels.
Here's an example of a widget with a title:
<div>
--8<--"docs/blog/images/border-title.svg"
</div>
BTW the above is a command you can run to see the various border styles you can apply to widgets.
```
textual borders
```
## Container changes
!!! warning "Breaking change"
If you have an app that uses any container classes, you should read this section.
We've made a change to containers in this release. Previously all containers had *auto* scrollbars, which means that any container would scroll if its children didn't fit. With nested layouts, it could be tricky to understand exactly which containers were scrolling. In 0.16.0 we split containers in to scrolling and non-scrolling versions. So `Horizontal` will now *not* scroll by default, but `HorizontalScroll` will have automatic scrollbars.
## What else?
As always, see the [release notes](https://github.com/Textualize/textual/releases/tag/v0.16.0) for the full details on this update.
If you want to talk about this update or anything else Textual related, join us on our [Discord server](https://discord.gg/Enf6Z3qhVr).
Some files were not shown because too many files have changed in this diff
Show more