-
Notifications
You must be signed in to change notification settings - Fork 825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds an interface for replacing prompt of an individual option in an OptionList
#2985
Adds an interface for replacing prompt of an individual option in an OptionList
#2985
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
Let's see what CI thinks :P
src/textual/widgets/_option_list.py
Outdated
Raises: | ||
IndexError: If there is no option of the given index. | ||
""" | ||
option = self._options[index] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a get_option_at_index() function already, could we use that here (And other functions that already exist for retrieving options) for consistency?
option = self._options[index] | |
option = self.get_option_at_index(index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea, for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is really good, implements the feature required in the issue very well.
I feel like we can do better to avoid rewriting code that already exists here. What do you think @nekeal ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BenMarkFrost I pushed the changes. Should I replace it also in the _remove_option
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which part of the code do you mean? The one that retrieves the option's index?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BenMarkFrost I pushed the changes. Should I replace it also in the
_remove_option
method?
Yes well spotted, I think that would be a good idea. I can also see it in _set_option_disabled()
Which part of the code do you mean? The one that retrieves the option's index?
This PR re-implements errors for retrieving options by index and ID, when they are already present in those dedicated functions:
get_option()
(which gets the option by option_id), and get_option_by_index()
.
The implementation in my opinion could be as simple as
def replace_option_prompt_at_index(index: int .......):
self.get_option_at_index(index).set_prompt(new_prompt)
def replace_option_prompt(id: str .......):
self.get_option(id).set_prompt(new_prompt)
This would leave error handling for non-existant options to their respective functions, and would mean we can remove the _replace_option_prompt()
function (as long as we move the refresh functions to the end of both of these too).
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me 🚀
Should I also change implementations of other methods so they use get_option()
and get_option_by_index()
.
edit: Actually I still need to do refresh()
and _refresh_content_tracking()
so it can be invoked inside of the _replace_option_prompt()
. Is it ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only if you have time / feel like it would be in scope, I think we can happily create another issue for that if it's too much work!
But yes ideally it should all be consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just pushed a commit which shows how the rest of the methods can be implemented. Could you have a look?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider addressing https://github.com/Textualize/textual/pull/2985/files#r1271291408.
1c9f079
to
555b4d2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor docstring changes, please!
prompt: The new prompt for the option. | ||
|
||
Returns: | ||
The `OptionList` instance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also document what gets raised if the option doesn't exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary if it's already documented in the get_option_index()
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say "yes" because users will see the documentation for replace_option_prompt
and they won't look at how it is implemented to figure out what may get raised.
This will also make sure this method is more aligned with all others, because all other methods say they will raise the error. It's just that we raise it a bit later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, you are right. I added Raises
section.
@willmcgugan I'd appreciate your input on this. Related to the discussion in #2986 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! I'd like @davep to have a quick look, as he wrote the original code...
68490e9
to
10c8b5f
Compare
Will do. Likely won't be until tomorrow at the earliest, or Monday morning when I get to my desk at the latest. Very keen to look it over. |
There is not much to look at. Don't let me leave sprints without a single merged PR 😉 |
LGTM. If tests pass, merge away! |
prompt: The new prompt for the option. | ||
|
||
Returns: | ||
The `OptionList` instance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say "yes" because users will see the documentation for replace_option_prompt
and they won't look at how it is implemented to figure out what may get raised.
This will also make sure this method is more aligned with all others, because all other methods say they will raise the error. It's just that we raise it a bit later.
src/textual/widgets/_option_list.py
Outdated
""" | ||
try: | ||
return self._option_ids[option_id] | ||
except: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the bare except
clause?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea why I missed it. It's limited to KeyError
now.
src/textual/widgets/_option_list.py
Outdated
prompt: The new prompt for the option. | ||
|
||
Raises: | ||
IndexError: If there is no option of the given index. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is incorrect, right? What self.get_option_at_index
might raise is OptionDoesNotExist
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! Thank you for pointing that out. It's fixed now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! Hits the right interface, good testing, attention to the docs. Works for me!
I've suggested some small improvements, mainly revolving around making the testing as comprehensive as possible.
@@ -789,6 +842,22 @@ def get_option(self, option_id: str) -> Option: | |||
f"There is no option with an ID of '{option_id}'" | |||
) from None | |||
|
|||
def get_option_index(self, option_id): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're introducing this as part of the public interface, and in service of replace_option_prompt
, I think I would reduce the duplication of code by tweaking remove_option
to be based on it too. I think it makes sense to deduplicate the code in any developer-facing method that's unlikely to be called frequently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should mention, same for other similar developer-facing methods. If that's what #2986 was in reference to then I'm in favour of that and would support it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice testing.
One thing I would have done here and I think we should do this is take into account that a prompt can span multiple lines. So I'd write some extra replacement tests where a prompt goes from more to fewer lines, and from fewer to more lines.
I'd also add some snapshot tests too, perhaps:
- Going from a single-line prompt to a different single-line prompt.
- Going from many lines to fewer.
- Going from one or more lines to more lines.
No, it wasn't intentional 😅 Just slow WiFi in a train and I didn't notice that push failed. Yeah, I will try to implement suggestions from @davep later today but I'm not sure if I'll get it working before the sprint ends. Are you ok with that? |
Hey @nekeal, sorry for the time it took me to reply. This is my original reply:
Absolutely. The sprint is an event to help people get started with in-person guidance. You are more than welcome to finish the PR “outside” the sprint, as you are absolutely more than welcome to keep contributing. I see you committed something else in the meantime. |
No problem at all 😅 |
Ok. When you are ready, feel free to re-request a review by clicking the arrows 🔄 next to my name at the top right. |
I've added some snapshot tests. I'm not sure if I should've created a new application for testing this particular case but let me know if I should reuse the existing one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
Sorry I took so long to review but I was away.
Hey @nekeal we're ready to merge this. Thank you so much :D |
6633a85
to
978e0a3
Compare
This simplifies error handling in a public interface
978e0a3
to
def2628
Compare
No, problem. The branch is rebased with |
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 Textualize#2985 actually has that, so here I note that I'll look to making that happen once that gets added in.
Thanks @nekeal ! |
@davep this has now been merged; I believe you wanted to use this elsewhere? |
Aye, will merge main into the command palette branch. Thanks all. |
Please review the following checklist.
This PR extends
OptionList
class with an interface for replacing prompts in individual options.Closes #2603