Skip to content

[Property Editor] Add APIs to support refactors from the Property Editor #9202

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

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

elliette
Copy link
Member

Work towards #8652

This PR:

This does not update the UI to show the available refactors. That will be follow-up.

Note: Fetching the available refactors is currently protected by the propertyEditorRefactors feature flag.

@elliette elliette requested a review from a team as a code owner May 21, 2025 20:31
@elliette elliette requested review from bkonyi and removed request for a team May 21, 2025 20:31
@elliette elliette requested review from kenzieschmoll and removed request for bkonyi May 21, 2025 23:08
Copy link
Member Author

Choose a reason for hiding this comment

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

Goldens change due to Flutter SDK update

@@ -821,7 +821,7 @@ class _RenderScrollSemantics extends RenderProxyBox {
if (child.isTagged(RenderViewport.excludeFromScrolling)) {
excluded.add(child);
} else {
if (!child.hasFlag(SemanticsFlag.isHidden)) {
if (!child.flagsCollection.isHidden) {
Copy link
Member Author

Choose a reason for hiding this comment

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

hasFlag is deprecated in latest Flutter version, see flutter/flutter#166101

@kenzieschmoll kenzieschmoll requested a review from DanTup May 22, 2025 22:09
Comment on lines 45 to 48
for (final method in LspMethod.values) {
if (method.methodName == methodName) return method;
}
return null;
Copy link
Member

Choose a reason for hiding this comment

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

can this be return LspMethod.values.firstWhereOrNull((method) => method.methodName == methodName)

Copy link
Contributor

Choose a reason for hiding this comment

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

For a minor perf increase I think you could also add something like:

static nameMap = LspMethod.values.asNameMap();

And then look it up as:

return nameMap[methodName];`

Copy link
Member Author

Choose a reason for hiding this comment

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

Done! Though we need the method name map instead of the name map, so I've added a static map for method name -> method to use for the look-up.

@@ -501,6 +528,105 @@ class EditableArgumentsResult with Serializable {
};
}

/// Constants for [CodeAction] prefixes used to filter the results returned by
/// an [LspMethod.codeAction] request.
abstract class CodeActionPrefixes {
Copy link
Member

Choose a reason for hiding this comment

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

can we add unit tests for all of the new logic in this file?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, added editor_client_test (also added test cases for editArgument and editableArguments)

@@ -99,6 +96,7 @@ void main() {
mockEditorClient.getEditableArguments(
textDocument: location.document,
position: location.position,
screenId: 'propertyEditorSidebar',
Copy link
Member

Choose a reason for hiding this comment

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

can we use the analytics constant here instead of the raw string?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, done!

Copy link
Contributor

@DanTup DanTup left a comment

Choose a reason for hiding this comment

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

Added a few comments. Mostly minor stuff, but the deserializing of command arguments I do think we need to remove because that's not public API, it should just be round-tripped whatever it is.

Comment on lines 45 to 48
for (final method in LspMethod.values) {
if (method.methodName == methodName) return method;
}
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

For a minor perf increase I think you could also add something like:

static nameMap = LspMethod.values.asNameMap();

And then look it up as:

return nameMap[methodName];`

static const line = 'line';
static const loggedAction = 'loggedAction';
Copy link
Contributor

Choose a reason for hiding this comment

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

I actually don't think DevTools should have any knowledge of these fields that appear within the command arguments. The exact command/arguments are an implementation detail so you should just be sending whatever the values of command and arguments back to executeCommand without ever really looking at them.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good, I've removed CodeActionArgument

Comment on lines 563 to 566
args: (map[Field.arguments] as List<Object?>? ?? <Object?>[])
.cast<Map<String, Object?>>()
.map(CodeActionArgument.fromJson)
.toList(),
Copy link
Contributor

Choose a reason for hiding this comment

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

So here, I think we just want args: map[Field.arguments] and leave it as Object?, removing the CodeActionArgument class entirely.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed! Thanks

class EditArgumentResponse {
EditArgumentResponse({required this.success, this.errorMessage, errorCode})
: _errorCode = errorCode;
/// Generic response representing whether a reqeust was a [success].
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: reqeust

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, thanks for catching!

Comment on lines 301 to 302
/// Requests that the Analysis Server makes a code edit for an argument.
Future<GenericApiResponse> applyRefactor({
Copy link
Contributor

Choose a reason for hiding this comment

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

The dartdoc is stale here (this isn't editing an argument), but maybe these should also just be called executeCommand since it's more generic than refactors.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated dartdoc and renamed to executeCommand

// in the Property Editor by default.
if (FeatureFlags.propertyEditorRefactors) {
// Get any supported refactors for the current position.
refactorsResult = await editorClient.getRefactors(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this await here means we don't show any of the editable fields until we also have the list of actions. I wonder if we should separate these, so that the editable fields would show immediately (perhaps with any previous actions buttons disabled) and then update the actions after. This might improve performance a little for the most likely case (using the editor and not the refactors).

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I've added a TODO for once I tackle displaying the refactor buttons, will decide whether to request the refactors separately then

/// via an [LspMethod.executeCommand] request.
///
/// For example, "Wrap with Center" or "Wrap with Container".
class CodeAction with Serializable {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would call this CodeActionCommand just to avoid some confusion (at least for me), because CodeActions according to LSP can actually be one of two things:

typedef CodeAction = CodeActionLiteral | Command

A CodeActionLiteral is a newer type that supports inline edits, but DTD clients do not support them, and therefore we should always be returning Commands to you here.

In analysis server code, I'm trying to consistently use CodeAction to mean the union of CodeActionLiteral and Command, and then those names explicitly where only one is allowed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for that context! I've renamed to CodeActionCommand


List<CodeAction> _extractRefactors(CodeActionResult? result) =>
(result?.actions ?? <CodeAction>[])
.where((action) => action.title != null && action.command != null)
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you seen data where any of these were null? In theory, you should only ever be getting Command back from textDocument/codeAction, so both fields should be non-null (except for bugs). If you get a CodeActionLiteral back (you should not, but other editor LSP clients may) then title should still always be non-null (per the LSP spec), although command could be null (and if not, would be a Command rather than a String).

Copy link
Member Author

Choose a reason for hiding this comment

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

No I haven't! I added this check just to be safe, though I could also make title and command non-nullable if we expect them to always be returned. WDYT?

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

Successfully merging this pull request may close these issues.

3 participants