Skip to content
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

Merged

Conversation

nekeal
Copy link
Contributor

@nekeal nekeal commented Jul 22, 2023

Please review the following checklist.

  • Docstrings on all new or modified functions / classes
  • Updated documentation
  • Updated CHANGELOG.md (where appropriate)

This PR extends OptionList class with an interface for replacing prompts in individual options.

Closes #2603

Copy link
Contributor

@rodrigogiraoserrao rodrigogiraoserrao left a 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

Raises:
IndexError: If there is no option of the given index.
"""
option = self._options[index]
Copy link

@BenMarkFrost BenMarkFrost Jul 22, 2023

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?

Suggested change
option = self._options[index]
option = self.get_option_at_index(index)

Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

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 ?

Copy link
Contributor Author

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?

Copy link
Contributor Author

@nekeal nekeal Jul 22, 2023

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?

Copy link

@BenMarkFrost BenMarkFrost Jul 22, 2023

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?

Copy link
Contributor Author

@nekeal nekeal Jul 22, 2023

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?

Copy link

@BenMarkFrost BenMarkFrost Jul 22, 2023

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

Copy link
Contributor Author

@nekeal nekeal Jul 22, 2023

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?

Copy link
Contributor

@rodrigogiraoserrao rodrigogiraoserrao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@rodrigogiraoserrao rodrigogiraoserrao left a 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!

src/textual/widgets/_option_list.py Outdated Show resolved Hide resolved
prompt: The new prompt for the option.

Returns:
The `OptionList` instance.
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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.

@rodrigogiraoserrao
Copy link
Contributor

@willmcgugan I'd appreciate your input on this.
Not sure how we feel about the added level of indirection, which can be seen if we compare the replace_ methods directly with the remove_ methods, for example.

Related to the discussion in #2986

Copy link
Collaborator

@willmcgugan willmcgugan left a 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...

@nekeal nekeal force-pushed the feature/extend-optionlist branch from 68490e9 to 10c8b5f Compare July 22, 2023 14:16
@davep
Copy link
Contributor

davep commented Jul 22, 2023

I'd like @davep to have a quick look, as he wrote the original code...

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.

@nekeal
Copy link
Contributor Author

nekeal commented Jul 22, 2023

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 😉

@willmcgugan
Copy link
Collaborator

LGTM. If tests pass, merge away!

prompt: The new prompt for the option.

Returns:
The `OptionList` instance.
Copy link
Contributor

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.

"""
try:
return self._option_ids[option_id]
except:
Copy link
Contributor

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?

Copy link
Contributor Author

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.

prompt: The new prompt for the option.

Raises:
IndexError: If there is no option of the given index.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@davep davep left a 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):
Copy link
Contributor

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.

Copy link
Contributor

@davep davep Jul 23, 2023

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.

Copy link
Contributor

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:

  1. Going from a single-line prompt to a different single-line prompt.
  2. Going from many lines to fewer.
  3. Going from one or more lines to more lines.

@rodrigogiraoserrao
Copy link
Contributor

Hey @nekeal, I see you replied to my "request changes" but you haven't committed anything. I'm wondering if that's intentional 🤔
Either way, when you do commit your changes, please let us know if you intend to address @davep's suggestions or not!

Excellent work, so far!

@nekeal
Copy link
Contributor Author

nekeal commented Jul 23, 2023

Hey @nekeal, I see you replied to my "request changes" but you haven't committed anything. I'm wondering if that's intentional thinking Either way, when you do commit your changes, please let us know if you intend to address @davep's suggestions or not!

Excellent work, so far!

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?

@rodrigogiraoserrao
Copy link
Contributor

Hey @nekeal, sorry for the time it took me to reply.
Supposedly, I had replied already but apparently I didn't – instead, I edited your last comment and added my reply there...
Which makes no sense 🤣

This is my original reply:

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?

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.
Are you happy for me to go over the PR again or do you need more time to finish addressing the feedback?

@nekeal
Copy link
Contributor Author

nekeal commented Jul 25, 2023

Hey @nekeal, sorry for the time it took me to reply.
Supposedly, I had replied already but apparently I didn't – instead, I edited your last comment and added my reply there...
Which makes no sense 🤣

This is my original reply:

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?

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.
Are you happy for me to go over the PR again or do you need more time to finish addressing the feedback?

No problem at all 😅
I still need some time to add snapshot tests. I will l let you know when I'm finished.

@rodrigogiraoserrao
Copy link
Contributor

Ok. When you are ready, feel free to re-request a review by clicking the arrows 🔄 next to my name at the top right.

@nekeal
Copy link
Contributor Author

nekeal commented Jul 29, 2023

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.

Copy link
Contributor

@rodrigogiraoserrao rodrigogiraoserrao left a 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.

@rodrigogiraoserrao
Copy link
Contributor

Hey @nekeal we're ready to merge this.
The only thing left to do is make sure the changelog is updated correctly.
Would you mind merging main into your branch and then making sure your changelog entry ends up at the top?

Thank you so much :D

@nekeal
Copy link
Contributor Author

nekeal commented Aug 7, 2023

Hey @nekeal we're ready to merge this. The only thing left to do is make sure the changelog is updated correctly. Would you mind merging main into your branch and then making sure your changelog entry ends up at the top?

Thank you so much :D

No, problem. The branch is rebased with main now and hopefully ready to be merged 🚀

davep added a commit to davep/textual that referenced this pull request Aug 8, 2023
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.
@willmcgugan
Copy link
Collaborator

Thanks @nekeal !

@rodrigogiraoserrao rodrigogiraoserrao merged commit 49612e3 into Textualize:main Aug 8, 2023
19 checks passed
@rodrigogiraoserrao
Copy link
Contributor

@davep this has now been merged; I believe you wanted to use this elsewhere?

@davep
Copy link
Contributor

davep commented Aug 8, 2023

Aye, will merge main into the command palette branch. Thanks all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extend OptionList so that an option's prompt can be replaced in some way
5 participants