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

🎁 Add "Surround with snippet" to quick fix menu #152718

Merged

Conversation

babakks
Copy link
Contributor

@babakks babakks commented Jun 21, 2022

This PR fixes #150678

Summary of changes:

  • Thc core functionality of "Surround with snippet" is extracted into a new class, SurroundWithSnippet.
  • A class extending EditorAction2 is added to expose the command.
  • A class implementing CodeActionProvider is added to expose the command as a code action (quick fix).

@babakks babakks force-pushed the add-surround-with-snippet-to-quick-fixes branch from c38a5c4 to 6798db9 Compare June 21, 2022 09:03
@babakks babakks marked this pull request as ready for review June 21, 2022 09:31
@babakks
Copy link
Contributor Author

babakks commented Jun 21, 2022

@jrieken The workflow failure seems to be unrelated to the changes here.

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
@babakks babakks force-pushed the add-surround-with-snippet-to-quick-fixes branch from 6798db9 to 0dcd685 Compare June 21, 2022 13:44
@jrieken jrieken added this to the July 2022 milestone Jun 27, 2022
Copy link
Member

@jrieken jrieken left a comment

Choose a reason for hiding this comment

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

Thanks so far. This is already good but I think we can make this more smooth. Less click ftw!


export class SurroundWithSnippetCodeActionProvider extends Disposable implements CodeActionProvider {
private static readonly codeAction: CodeAction = {
kind: CodeActionKind.QuickFix.value,
Copy link
Member

Choose a reason for hiding this comment

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

The Refactor-type should be used here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
const snippets = await this.core.getSurroundableSnippets();
return {
actions: snippets.length ? [SurroundWithSnippetCodeActionProvider.codeAction] : [],
Copy link
Member

Choose a reason for hiding this comment

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

This will just invoke the picker but I think it would be better to directly show the snippets. Only show the picker when there is too much to pick from (>7 or so).

For each surroundable snippet you can use InsertSnippetAction and bind the snippet-argument accordingly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Introduced this constant value to limit the number of refactor menu items:

const MAX_SNIPPETS_ON_CODE_ACTIONS_MENU = 6;


async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
const model = this._editor.getModel();
Copy link
Member

Choose a reason for hiding this comment

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

Only extract this into a seprate function, e.g function surroundateSnippet(accessor, model) and leave the action as-is otherwise

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 didn't fully understand this commment. So, I took this as a hint to refactor from class-based to function-based implementation. So, I extracted the functions out and added an accessor: ServicesAccessor parameter to them to help resolve their dependencies.

}
}

registerEditorContribution(options.id, SurroundWithSnippetCodeActionProvider);
Copy link
Member

Choose a reason for hiding this comment

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

Oh, forget to mention that we need something better than an editor contribution. Those get created per editor but languageFeaturesService.codeActionProvider.register is gobal

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After going through different parts of the codebase, trying to find similar implementations, I've changed the solution from IEditorContribution to IWorkbenchContribution. I couldn't find any better solution with the same life-cycle as a workbench contribution, so please let me know if I should use another approach for this.

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
@jrieken
Copy link
Member

jrieken commented Jul 7, 2022

Another note: fresh from the press we are working on supporting snippets with workspace edits (#145374). So, this feature could be build even better, e.g don't have commands but use a workspace edit (for each surround-with snippet) that uses the new snippet edit support.

… contribution

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
@babakks
Copy link
Contributor Author

babakks commented Jul 7, 2022

@jrieken After changing from editor contribution to workbench contribution, strangely, context key data were not available to check the command precondition at canExecute function. So, I opted out of context-based checking and went toward implementing canExecute by using IEditorService.

You can check it by replacing the canExecute function with this:

function canExecute(accessor: ServicesAccessor): boolean {
    return accessor.get(IContextKeyService).contextMatchesRules(options.precondition);
}

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
@babakks
Copy link
Contributor Author

babakks commented Jul 7, 2022

Another note: fresh from the press we are working on supporting snippets with workspace edits (#145374). So, this feature could be build even better, e.g don't have commands but use a workspace edit (for each surround-with snippet) that uses the new snippet edit support.

As I see, we can use performSnippetEdit (which uses the newly defined SnippetController2.apply method) instead of directly calling the insert method of the SnippetController2 class. I mean change this:

SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText });

into this:

const selections = editor?.getSelections();
if (!selections) {
    return;
}
performSnippetEdit(editor, snippet.codeSnippet, selections);

@jrieken What do you think?

@babakks babakks requested a review from jrieken July 7, 2022 12:35
@jrieken
Copy link
Member

jrieken commented Jul 8, 2022

I was thinking different and that this can be done simpler. The code actions should not use the command property but the edit property. That workspace edit should have a single IWorkspaceTextEdit-edit which uses the new insertAsSnippet-flag. That's it. Than have a single "overflow"-command when there are too many snippets which simply invokes editor.action.surroundWithSnippet (e.g that one code action uses the command property)

  • only extract the getSurrounableSnippet function
  • leave SurroundWithAction unchanged otherwise
  • have a code action provider that uses getSurrounableSnippet and that uses workspace edits for its actions

…` method

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
…` resolves $SELECTION variable

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
…romEdits` method

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
@babakks
Copy link
Contributor Author

babakks commented Jul 14, 2022

@jrieken Thanks for the precise details. I've made the changes.

However, there was a bug, I think, with the SnippetSession.createEditsAndSnippetsFromEdits wherein variables were resolved after the replacement texts were rendered. So, I changed the order of statements and also added a test to verify this is now working:

suite('createEditsAndSnippetsFromEdits', function () {
    ...
    test('with $SELECTION variable', function () {
    ...

Could you please review the changes?

@babakks
Copy link
Contributor Author

babakks commented Jul 14, 2022

@jrieken Can't we also change the overflow command to use the new WorkspaceTextEdit approach? I mean, are we deliberately not changing the old behavior?

…rroundable snippets

* remove `canExecute` which isn't needed - the "framework" makes sure we only called when it makes sense

* tweak styles to my preference and lipstick, try to contain things over loose functions and objects,
@jrieken
Copy link
Member

jrieken commented Jul 15, 2022

@babakks There are some things that I want to tweak before merging - mostly code style/shape. It looks like you need to enable this for this PR.

@babakks
Copy link
Contributor Author

babakks commented Jul 15, 2022

The "Allow edits..." is enabled. Also, I now added your account to my fork collaborators.

Is there any other permission?

@babakks
Copy link
Contributor Author

babakks commented Jul 15, 2022

@jrieken I just merged this with your latest changes.

Copy link
Member

@jrieken jrieken left a comment

Choose a reason for hiding this comment

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

Thank @babakks

I have pushed a few style tweaks. Not to diss you but just to match my preferences since I will be maintaining this. Also note that I have another PR out that adds support for usage timestamps. With that the code actions can also be sorted based on frequently used snippets 🎉

@@ -535,6 +535,17 @@ export class SnippetSession {
const parser = new SnippetParser();
const snippet = new TextmateSnippet();

// snippet variables resolver
const resolver = new CompositeSnippetVariableResolver([
Copy link
Member

Choose a reason for hiding this comment

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

I believe this could have been fixed with some OvertypingCapturer-logic but this also seems reasonable.

@jrieken
Copy link
Member

jrieken commented Jul 15, 2022

The "Allow edits..." is enabled. Also, I now added your account to my fork collaborators.

It finally worked - ran into some permission issues before 🤷. Excited to see this land

@bpasero
Copy link
Member

bpasero commented Jul 15, 2022

Awesome 👏 !

@jrieken jrieken merged commit 241057e into microsoft:main Jul 15, 2022
@babakks babakks deleted the add-surround-with-snippet-to-quick-fixes branch July 15, 2022 10:08
@babakks
Copy link
Contributor Author

babakks commented Jul 15, 2022

@jrieken You're welcome. Thank you.

jrieken added a commit that referenced this pull request Jul 18, 2022
…quick-fixes

🎁 Add "Surround with snippet" to quick fix menu
@github-actions github-actions bot locked and limited conversation to collaborators Aug 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Show "Surround with snippet" in quick fix menu
3 participants