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

feat(clap_complete): Add ParseState to support more dynamic completion #5538

Closed
wants to merge 1 commit into from

Conversation

shannmu
Copy link
Contributor

@shannmu shannmu commented Jun 19, 2024

Related issue: #3920

Add ParseState to record the context during completion parsing. So when we do dynamic completion, we can use this context to specify what we need to complete and support more dynamic completion including --flag value

@epage
Copy link
Member

epage commented Jun 19, 2024

feat(clap_complete): Add ParseState to support more dynamic completion

The PR / commit title say this is a feature but this looks like its a refactor (code clean up with no user-visible change). Could you clarify what the end-user impact of this either in the type or in the description?

@@ -53,6 +53,7 @@ pub fn complete(
let mut current_cmd = &*cmd;
let mut pos_index = 1;
let mut is_escaped = false;
let mut _state = ParseState::Unknown;
Copy link
Member

Choose a reason for hiding this comment

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

Do we need an Unknown state vs ValueDone

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that we need an Unknown state. ValueDone means we recognize that the word is a subcommand, flag or positional. Unknown means we don't know what is meaning of the word, for example, --flag_unknon, we parse a word starting with -- but not a arg belong to the current_cmd

Copy link
Member

Choose a reason for hiding this comment

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

The first thing suspicious to me is that clap's parser doesn't need it, see

#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ParseState {
ValuesDone,
Opt(Id),
Pos(Id),
}

Its also not clear what value we are supposed to be getting out of Unknown. clap's ValuesDone is more of the default state of the state machine. When you aren't currently in a specialized option or positional state, you move back to that. It sounds like Unknown is your default state, but then how is that different than your ValueDone?

Granted, it might be clearer once the state is in use. We should only include in this PR what gets used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unknown and ValueDone are indeed both default states of the state machine for now, I will remove Unknown first.

@@ -53,6 +53,7 @@ pub fn complete(
let mut current_cmd = &*cmd;
let mut pos_index = 1;
let mut is_escaped = false;
let mut _state = ParseState::Unknown;
Copy link
Member

Choose a reason for hiding this comment

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

Why does _state start with a _?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_state is still an unused variable. Starting with _ is just to prevent warnings.

Copy link
Member

Choose a reason for hiding this comment

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

When presented with a warning like that, it would be good to consider carefully before "suppressing" it. In this case, the compiler was basically telling you that the commit is not atomic.

Comment on lines +79 to +108
} else if let Some((flag, value)) = arg.to_long() {
if let Ok(flag) = flag {
_state = if let Some(opt) = current_cmd.get_arguments().find(|a| {
a.get_long_and_visible_aliases()
.map_or(false, |v| v.into_iter().find(|s| *s == flag).is_some())
}) {
match opt.get_action() {
clap::ArgAction::Set | clap::ArgAction::Append => {
if value.is_some() {
ParseState::ValueDone
} else {
ParseState::Opt(opt.clone())
}
}
_ => {
if value.is_some() {
ParseState::Unknown
} else {
ParseState::ValueDone
}
}
}
} else {
ParseState::Unknown
};
}
} else if let Some(short) = arg.to_short() {
let mut short = short.clone();
let opt = short.next_flag();

Copy link
Member

Choose a reason for hiding this comment

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

I thought this PR was supposed to just be a refactor that added ParseState::ValueDone and ParseState::Pos. I believe the intention was to remove the use of passing is_escaped to complete_arg.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This PR is supposed to add a struct to record the context during parsing. Actually, ParseState::Opt is the key filed in this PR. We could use it to complete --flag bar and -f bar. And i think that ParseState could be expanded if there are other contexts.

I believe the intention was to remove the use of passing is_escaped to complete_arg.

Yes, we can do this. is_escaped is like a context variable for the word after --

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The PR / commit title say this is a feature but this looks like its a refactor (code clean up with no user-visible change). Could you clarify what the end-user impact of this either in the type or in the description?

This PR does not provide new features for users, but it provides a feature for developers to record more context. From the user's perspective, this PR should perhaps be labeled as a refactor.

Copy link
Member

Choose a reason for hiding this comment

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

This PR is supposed to add a struct to record the context during parsing.

That is not the reason I suggested making this PR and this not a mergeable direction for this PR. Commits need to be atomic which this is not. PRs can either be a single commit or multiple that "tell a story together".

a feature for developers to record more context.

That is a refactor, not a feature. The terms are user-facing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think this counts as a refactor either. it's the codebase for the new feature #5539. The reason I separated it is that I think this PR could may also serve as the code base for other features, if someone wants to support multivalue completion, he could add a field in ParseState and add some logic in complete function.
From the user's perspective, this PR is indeed not atomic. So perhaps I should merge the code from PR #5539 into this PR and then close it? I'll keep the two commits to make it easier for everyone to track.

@shannmu shannmu closed this Jun 29, 2024
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.

2 participants