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: Text document selection range support (issue 651) #669

Conversation

SCWells72
Copy link
Contributor

@SCWells72 SCWells72 commented Dec 6, 2024

This PR integrates the LSP textDocument/selectionRange feature into the IDE's extendWordSelectionHandler EP that provides the Extend/Shrink Selection actions (which are among my absolute favorites in the IDE). Note that it does not yet include unit tests, but I do plan to add them once I know that there's interest in moving forward with it.

Here's an example of it working in TypeScript:

LSP_Extend_Shrink_Selection

and here it is in CSS:

LSP_Extend_Shrink_Selection_CSS

I'll add a few notes on things that I know will likely warrant further discussion.

@@ -14,6 +14,7 @@ The [LSPClientFeatures](https://github.com/redhat-developer/lsp4ij/blob/main/src
- [LSP documentSymbol feature](#lsp-documentSymbol-feature)
Copy link
Contributor Author

@SCWells72 SCWells72 Dec 6, 2024

Choose a reason for hiding this comment

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

I recommend that you hide whitespace differences because I ran several of the existing files through the Java formatter against the project's code style and it resulted in minor whitespace changes.

@@ -1105,6 +1140,7 @@ public void setServerCapabilities(@NotNull ServerCapabilities serverCapabilities

@Nullable
public TextDocumentServerCapabilityRegistry<? extends TextDocumentRegistrationOptions> getCapabilityRegistry(String method) {
// TODO: Can these be changed to else/if? Or can this be changed to switch?
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 know how often this methods gets called, but it looks like it can be changed to either if ... else if ... else if ... or ideally switch (method) to make it more efficient. I'm happy to do that as part of these changes if you'd like.

Copy link
Contributor

Choose a reason for hiding this comment

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

This method is called when language server is starting and receive some registerCapability (you can see those LSP trace in the LSP console (do a search with registerCapability). If you want update it, let's go.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks so much to have done this horrible change!

@angelozerr
Copy link
Contributor

Wow again an impressive PR. Let me some times to give yiu detailled review.

@angelozerr
Copy link
Contributor

@SCWells72 it would be nice also to update the LSPSupport markdown to enable the selectiinRange and add a detailled info about implementation and a little demo with some expl1nation line shortcut to use.

I could do that if you wish.

@SCWells72
Copy link
Contributor Author

I'll update LSPSupport.md now. I'll let you add a demo if that's okay.

@SCWells72
Copy link
Contributor Author

I've added a demo to LSPSupport.md as well. I used Presentation Assistant to show the default keybindings to make it clear what's happening. I can re-record without those if you'd prefer.

}

// Only if textDocument/selectionRange is supported for the file
return LSPFileSupport.getSupport(file).getSelectionRangeSupport() != null;
Copy link
Contributor

Choose a reason for hiding this comment

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

This check is every time not null. You need to check if there is a language server which support selectionrange:

// Check if the file can support the feature
return LanguageServiceAccessor.getInstance(project)
                .hasAny(file.getVirtualFile(), ls -> ls.getClientFeatures().getSelectionRangeFeature().isSelectionRangeSupported(file)));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please see what I've done in the latest commit. I looked for something that does what the new method isSupported(PsiFile, Predicate<LSPClientFeatures>) is doing but couldn't find anything quite like it. If something does exist, please let me know and I'll migrate to it; if not, perhaps that method should be moved somewhere more common for future needs? If so, where? LanguageServerAccessor? LSP4IJUtils? ???

}

// textDocument/selectionRanges has been collected correctly, create list of IJ SelectionDescriptor from LSP SelectionRange list
return selectionRangesFuture.getNow(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems method should never return null, replace with:

return selectionRangesFuture.getNow(Colletions.emptyList());

### Selection range

[textDocument/selectionRange](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange) is implemented with
the `extendWordSelectionHandler` extension point and used via the IDE's **Extend Selection** and **Shrink Selection** actions.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add shortcut sample in () to execute those actions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, but what does "in ()" mean? Is there already a list of supported keyboard shortcuts for LSP4IJ-integrated actions?

Copy link
Contributor

Choose a reason for hiding this comment

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

I wanted to say add (Ctrl+W) / (Ctrl+Shift+W)

…sting/better way to perform dry run enablement like I'm doing here. If so, I'll migrate to it; if not, perhaps isSupported() should be moved to a common location.
if (LSPRequestConstants.TEXT_DOCUMENT_CODE_ACTION.equals(method)) {
// register 'textDocument/codeAction' capability
return getCodeActionFeature().getCodeActionCapabilityRegistry();
if (method == null) {
Copy link
Contributor Author

@SCWells72 SCWells72 Dec 9, 2024

Choose a reason for hiding this comment

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

This is the result of changing this to use switch which should use a jump table instead of having to evaluate each and every condition. Note that the project formatter is responsible for deciding which case labels are single-line vs. multi-line here.

}

// TODO: This seems so incredibly boiler-plate that it should already exist somewhere common
private static boolean isSupported(@NotNull PsiFile file,
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 there not already something that does this in an encapsulated manner like this? If not, should this be moved into a common location?

Copy link
Contributor

Choose a reason for hiding this comment

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

The short answer is https://github.com/redhat-developer/lsp4ij/pull/669/files#r1877498632

Here a detailled explanation: when you need to check that an IJ feature is enabled (ex : to enable/disable Implementations menu item, selectionrange like you need for your PR) it MUST be very fast otherwise IJ will freeze.

You must never wait for starting language servers which can takes some times (some language servers can take 15sec to start it), so IJ could freeze during 15 sec.

Using document matcher requires to open a read action. In Ij Quarkus we use that to associate our Quarkus Language server to Java files only when the project have some Java class in the classpath.

Using hasAny is very fast. It checks:

  • if there is one or more language server which are associated to the file
  • if there is one language server has thecapability but ONLY when language server is started.

It means that if you execute Ctrl+W and language servers is starting, the action will be disable but I think it is better to loose the Ctrl+W feature and avoid freezing IJ.

}

// Only if textDocument/selectionRange is supported for the file
return isSupported(file, LSPClientFeatures::getSelectionRangeFeature);
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove your isSupported method with

        Project project = file.getProject();
        if (project.isDisposed()) {
            return false;
        }
        
        // Only if textDocument/selectionRange is supported for the file
        return LanguageServiceAccessor.getInstance(project)
                .hasAny(file.getVirtualFile(), ls -> ls.getClientFeatures().getSelectionRangeFeature().isSelectionRangeSupported(file));

}

// TODO: This seems so incredibly boiler-plate that it should already exist somewhere common
private static boolean isSupported(@NotNull PsiFile file,
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove this method

/**
* Selection range tests by emulating LSP 'textDocument/selectionRange' responses from the typescript-language-server.
*/
public class TypeScriptSelectionRangeTest extends LSPSelectionRangeFixtureTestCase {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks so much to have written tests, that's great!

@SCWells72
Copy link
Contributor Author

New PR for this work opened as #678.

@SCWells72 SCWells72 closed this Dec 10, 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