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: Client-side on-type formatting support #745

Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
192de8e
Fresh version of lsp_on_type_formatting branch that adds support for …
SCWells72 Jan 14, 2025
503ba8d
Merge branch 'main' into lsp_client_side_on_type_formatting
SCWells72 Jan 15, 2025
16788ba
Improvements and fixes based on testing client-side on-type formattin…
SCWells72 Jan 20, 2025
7912c4b
Merge branch 'main' into lsp_client_side_on_type_formatting_merged
SCWells72 Jan 20, 2025
1570e22
Disabling client-side format-on-close brace for jdtls because that la…
SCWells72 Jan 20, 2025
8141e65
Removed debugging statement that should not have been included in com…
SCWells72 Jan 21, 2025
18a7d8b
Merge branch 'main' into lsp_client_side_on_type_formatting_merged
SCWells72 Jan 24, 2025
8169857
Renamed the completion trigger handler to be more specific about its …
SCWells72 Jan 24, 2025
e376359
Added a client configuration for disabling the LSP "textDocument/onTy…
SCWells72 Jan 25, 2025
7ff8a26
Added a client configuration option for disabling the LSP "textDocume…
SCWells72 Jan 25, 2025
908b2a3
Merge branch 'lsp_server_on_type_formatting_config' into lsp_client_s…
SCWells72 Jan 25, 2025
bec2ea6
Merge branch 'redhat-developer:main' into lsp_client_side_on_type_for…
SCWells72 Jan 27, 2025
d74ebfe
Merged/integrated latest from main.
SCWells72 Jan 28, 2025
d6ce58f
Merge branch 'main' into lsp_client_side_on_type_formatting
SCWells72 Jan 28, 2025
638d54a
Updated to use the unified client configuration schema.
SCWells72 Jan 28, 2025
920db1a
Updated documentation for client-side on-type formatting behavior, co…
SCWells72 Jan 28, 2025
7fdfbc5
Updated the code block provider docs to be clear that code blocks are…
SCWells72 Jan 28, 2025
4fed3f3
Addressed PR review feedback regarding how the relevant language serv…
SCWells72 Jan 29, 2025
51a3acf
Feature doc improvements.
SCWells72 Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions docs/LSPApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,21 @@ public class MyLSPDocumentSymbolFeature extends LSPDocumentSymbolFeature {

## LSP Formatting Feature

| API | Description | Default Behaviour |
|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. <br/>This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
| boolean isRangeFormattingSupported(PsiFile file) | Returns `true` if the range formatting is supported for the given file and `false` otherwise. | Check the server capability |
| boolean isExistingFormatterOverrideable(PsiFile file) | Returns `true` if existing formatters are overrideable and `false` otherwise. | `false` |
| boolean isOnTypeFormattingEnabled(PsiFile file) | Whether or not server-side on-type formatting is enabled if `textDocument/onTypeFormatting` is supported by the server. | `true` |
| API | Description | Default Behaviour |
|----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. <br/>This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
| boolean isRangeFormattingSupported(PsiFile file) | Returns `true` if the range formatting is supported for the given file and `false` otherwise. | Check the server capability |
| boolean isExistingFormatterOverrideable(PsiFile file) | Returns `true` if existing formatters are overrideable and `false` otherwise. | `false` |
| boolean isOnTypeFormattingEnabled(PsiFile file) | Whether or not server-side on-type formatting is enabled if `textDocument/onTypeFormatting` is supported by the server. | `true` |
| boolean isFormatOnCloseBrace(PsiFile file) | Whether or not to format the file when close braces are typed. | `false` |
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are the new client configuration options for client-side on-type formatting.

| boolean getFormatOnCloseBraceCharacters(PsiFile file) | The specific close brace characters that should trigger on-type formatting in the file. | The language's standard close brace characters. |
| boolean getFormatOnCloseBraceScope(PsiFile file) | The scope that should be formatted when a close brace is typed. Allowed values are `CODE_BLOCK` and `FILE`. | `CODE_BLOCK` |
| boolean isFormatOnStatementTerminator(PsiFile file) | Whether or not to format the file when statement terminators are typed. | `false` |
| boolean getFormatOnStatementTerminatorCharacters(PsiFile file) | The specific statement terminator characters that should trigger on-type formatting in the file. | None. |
| boolean getFormatOnStatementTerminatorScope(PsiFile file) | The scope that should be formatted when a statement terminator is typed. Allowed values are `STATEMENT`, `CODE_BLOCK` and `FILE`. | `STATEMENT` |
| boolean isFormatOnCompletionTrigger(PsiFile file) | Whether or not to format the file when completion triggers are typed. | `false` |
| boolean getFormatOnCompletionTriggerCharacters(PsiFile file) | The specific completion trigger characters that should trigger on-type formatting in the file. | The language's standard completion trigger characters. |

Here is an example of code that allows executing the LSP formatter even if there is a specific formatter registered by an IntelliJ plugin:

Expand Down
61 changes: 61 additions & 0 deletions docs/LSPSupport.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,67 @@ Here is an example with the [Java Language Server](https://github.com/eclipse-jd

![textDocument/onTypeFormatting](./images/lsp-support/textDocument_onTypeFormatting.gif)

If desired &mdash; for example, for those who want to control when formatting is performed &mdash; server-side/LSP-based
Copy link
Contributor Author

@SCWells72 SCWells72 Jan 28, 2025

Choose a reason for hiding this comment

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

While updating the on-type formatting docs for this feature, I added details on how to disable server-side on-type formatting if desired.

on-type formatting can be disabled via client configuration as follows:

```json
{
"format": {
"onTypeFormatting": {
"serverSide": {
"enabled": false
}
}
}
}
```

#### Client-side On-Type Formatting
Copy link
Contributor Author

Choose a reason for hiding this comment

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

And I documented client-side on-type formatting extensively including the TypeScript server configuration example and a demo.


Not all language servers support the `textDocument/onTypeFormatting` feature. To provide an improved editor experience
for users of those that do not, LSP4IJ includes support for _client-side on-type formatting_. Client-side on-type
formatting can be enabled via client configuration with the following settings:

* `format.onTypeFormatting.clientSide.formatOnCloseBrace` - When set to `true`, formatting is automatically applied when a close brace character is typed. Defaults to `false`.
* `format.onTypeFormatting.clientSide.formatOnCloseBraceCharacters` - Specifies the exact characters that should treated as a close brace character for purposes of client-side on-type formatting. Defaults to the close brace characters for the language, typically `}`, `]`, and `)`.
* `format.onTypeFormatting.clientSide.formatOnCloseBraceScope` - Specifies the scope that should be formatted when a close brace is typed. Valid values are `CODE_BLOCK` and `FILE`. Defaults to `CODE_BLOCK`. `FILE` is most useful for language servers that do not support range formatting or yield incorrect results for range formatting.
* `format.onTypeFormatting.clientSide.formatOnStatementTerminator` - When set to `true`, formatting is automatically applied when a statement terminator character is typed. Defaults to `false`.
* `format.onTypeFormatting.clientSide.formatOnStatementTerminatorCharacters` - Specifies the exact characters that should treated as a statement terminator character for purposes of client-side on-type formatting. Defaults to empty and must be specified if `formatOnStatementTerminator` is enabled.
* `format.onTypeFormatting.clientSide.formatOnStatementTerminatorScope` - Specifies the scope that should be formatted when a statement terminator is typed. Valid values are `STATEMENT`, `CODE_BLOCK` and `FILE`. Defaults to `STATEMENT`. The other values are most useful for language servers that do not support range formatting or yield incorrect results for range formatting.
* `format.onTypeFormatting.clientSide.formatOnCompletionTrigger` - When set to `true`, formatting is automatically applied when a completion trigger character is typed. Defaults to `false`.
* `format.onTypeFormatting.clientSide.formatOnCompletionTriggerCharacters` - Specifies the exact characters that should treated as a completion trigger character for purposes of client-side on-type formatting. Defaults to the completion trigger characters specified by the language server.
* Note that there is no configurable scope for completion trigger-based formatting. Exactly the type completion trigger character is formatted. As above, support for this may vary by language server.

For example, the [TypeScript Language Server](./user-defined-ls/typescript-language-server.md) does not support server-side on-type formatting, but client-side
on-type formatting is included in its language server configuration template as:

```json
{
"format": {
"onTypeFormatting": {
"clientSide": {
"formatOnCloseBrace": true,
"formatOnStatementTerminator": true,
"formatOnStatementTerminatorCharacters": ";",
"formatOnCompletionTrigger": true
}
}
}
}
```

Here is an example for client-side on-type formatting with that configuration showing automatic indentation of a
statement continuation when the completion trigger character `.` is typed and automatic formatting of an entire code
block when the closing brace character `}` is typed for a surrounding conditional statement:

![Client-side on-type formatting](./images/lsp-support/clientSideOnTypeFormatting.gif)

#### Server-side / Client-side On-Type Formatting Relationship
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 also wanted to call out the relationship between server-side and client-side on-type formatting if the former is available in the language server and enabled and the latter is also enabled in config.

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 for having taken time to write this doc, it is excellent!


If server-side on-type formatting is supported by the language server and enabled _and_ client-side on-type formatting
is enabled for specific trigger characters, _only client-side on-type formatting will be applied_ when those specific
trigger characters are typed.

### Show Message

[window/showMessage](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showMessage) supports Markdown messages and clickable links.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void setServerCapabilities(@Nullable ServerCapabilities serverCapabilitie
}
}

// Client configuration settings
// Server-side on-type formatting

/**
* Whether or not server-side on-type formatting is enabled if <code>textDocument/onTypeFormatting</code> is
Expand All @@ -148,4 +148,123 @@ public boolean isOnTypeFormattingEnabled(@NotNull PsiFile file) {
// Default to enabled
return true;
}

// Client-side on-type formatting

/**
* Supported formatting scopes.
*/
public enum FormattingScope {
Copy link
Contributor Author

@SCWells72 SCWells72 Jan 14, 2025

Choose a reason for hiding this comment

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

Part of the config of this feature is the scope to which formatting should be applied when an enabled key has been typed. Because some language servers may not report very good code blocks (CSS doesn't seem to), for example, it may be desirable to enable this feature so that the entire file is formatted properly when certain keys are typed. The default are detailed below for each aspect of the feature.

/**
* The current statement if one can be identified.
*/
STATEMENT,
/**
* The current code block if one can be identified.
*/
CODE_BLOCK,
/**
* The current file.
*/
FILE
}

/**
* Whether or not to format on close brace using client-side on-type formatting. Defaults to false.
*
* @param file the file
* @return true if the file should be formatted when close braces are typed; otherwise false
*/
public boolean isFormatOnCloseBrace(@NotNull PsiFile file) {
// Default to disabled
return false;
}

/**
* The specific close brace characters that should trigger client-side on-type formatting.
*
* @param file the file
* @return the close brace characters that should trigger on-type formatting or null if the language's standard
* close brace characters should be used
*/
@Nullable
public String getFormatOnCloseBraceCharacters(@NotNull PsiFile file) {
// Default to the language's standard close brace characters
return null;
}

/**
* The scope that should be formatted using client-side on-type formatting when a close brace is typed. Allowed
* values are {@link FormattingScope#CODE_BLOCK CODE_BLOCK} and {@link FormattingScope#FILE FILE}. Defaults to
* {@link FormattingScope#CODE_BLOCK CODE_BLOCK}.
*
* @param file the file
* @return the format scope
*/
@NotNull
public FormattingScope getFormatOnCloseBraceScope(@NotNull PsiFile file) {
// Default to CODE_BLOCK
return FormattingScope.CODE_BLOCK;
}

/**
* Whether or not to format on statement terminator using client-side on-type formatting. Defaults to false.
*
* @param file the file
* @return true if the file should be formatted when statement terminators are typed; otherwise false
*/
public boolean isFormatOnStatementTerminator(@NotNull PsiFile file) {
// Default to disabled
return false;
}

/**
* The specific statement terminator characters that should trigger client-side on-type formatting.
*
* @param file the file
* @return the statement terminator characters that should trigger on-type formatting
*/
@Nullable
public String getFormatOnStatementTerminatorCharacters(@NotNull PsiFile file) {
// Default to none
return null;
}

/**
* The scope that should be formatted using client-side on-type formatting when a statement terminator is typed.
* Allowed values are {@link FormattingScope#STATEMENT STATEMENT}, {@link FormattingScope#CODE_BLOCK CODE_BLOCK},
* and {@link FormattingScope#FILE FILE}. Defaults to {@link FormattingScope#STATEMENT STATEMENT}.
*
* @param file the file
* @return the format scope
*/
@NotNull
public FormattingScope getFormatOnStatementTerminatorScope(@NotNull PsiFile file) {
// Default to STATEMENT
return FormattingScope.STATEMENT;
}

/**
* Whether or not to format using client-side on-type formatting on completion trigger. Defaults to false.
*
* @param file the file
* @return true if the file should be formatted when completion triggers are typed; otherwise false
*/
public boolean isFormatOnCompletionTrigger(@NotNull PsiFile file) {
// Default to disabled
return false;
}

/**
* The specific completion trigger characters that should trigger client-side on-type formatting.
*
* @param file the file
* @return the completion trigger characters that should trigger on-type formatting or null if the language's
* standard completion trigger characters should be used
*/
@Nullable
public String getFormatOnCompletionTriggerCharacters(@NotNull PsiFile file) {
// Default to the language's standard completion trigger characters
return null;
}
}
Loading
Loading