From 7ad8da8791b2fa262566f82986469ed5ae93bd69 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 11 Sep 2024 12:20:24 +0100 Subject: [PATCH 1/6] Add support for `completionList.applyKind` to determine how values from `completionList.itemDefaults` and `completion` are combined. Fixes https://github.com/microsoft/language-server-protocol/issues/1802 --- .../lsp/3.18/language/completion.md | 77 ++++++++++++++++++- _specifications/lsp/3.18/specification.md | 1 + 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/_specifications/lsp/3.18/language/completion.md b/_specifications/lsp/3.18/language/completion.md index 68fe55b3e..7f62e7ad0 100644 --- a/_specifications/lsp/3.18/language/completion.md +++ b/_specifications/lsp/3.18/language/completion.md @@ -166,6 +166,18 @@ export interface CompletionClientCapabilities { * @since 3.17.0 */ itemDefaults?: string[]; + + /** + * Specifies which fields of `CompletionList.applyKind` the client + * supports. If omitted, no properties are supported and all fields + * in a completion item will replace the defaults. + * + * Clients may only specify fields that have merge rules defined in the + * LSP spec. + * + * @since 3.18.0 + */ + applyKinds?: string[]; } } ``` @@ -338,7 +350,8 @@ export interface CompletionList { * be used if a completion item itself doesn't specify the value. * * If a completion list specifies a default value and a completion item - * also specifies a corresponding value, the one from the item is used. + * also specifies a corresponding value, the rules for combining these are + * defined by `applyKinds`, defaulting to "replace". * * Servers are only allowed to return default values if the client * signals support for this via the `completionList.itemDefaults` @@ -386,6 +399,68 @@ export interface CompletionList { data?: LSPAny; } + /** + * Specifies how fields from a completion item should be combined with those + * from `completionList.itemDefaults`. + * + * In unspecified, all fields will be treated as "replace". + * + * If a field's value is "replace", the value from a completion item will + * always be used instead of the value from `completionItem.itemDefaults`. + * + * If a field's value is "merge", the values will be merged using the rules + * defined against each field below. + * + * Servers are only allowed to return `applyKind` if the client + * signals support for this via the `completionList.applyKinds` + * capability. + * + * @since 3.18.0 + */ + applyKind?: { + /** + * Specifies whether commitCharacters on a completion will replace or be + * merged with those in `completionList.itemDefaults.commitCharacters`. + * + * If "replace", the commit characters from the completion item will + * always be used unless not provided, in which case those from + * `completionList.itemDefaults.commitCharacters` will be used. An empty + * list can be used if a completion item does not have any commit + * characters and also should not use those from + * `completionList.itemDefaults.commitCharacters`. + * + * If "merge" the commitCharacters for the completion will be the union + * of all values in both `completionList.itemDefaults.commitCharacters` + * and the completion's own `commitCharacters`. + * + * @since 3.18.0 + */ + commitCharacters?: "replace" | "merge"; + + /** + * Specifies whether data on a completion will replace or + * be merged with data from `completionList.itemDefaults.data`. + * + * If "replace", the data from the completion item will be used if + * provided, otherwise `completionList.itemDefaults.data` will be used. + * An empty object can be used if a completion item does not have any + * data but also should not use the value from + * `completionList.itemDefaults.data`. + * + * If "merge", a shallow merge will be performed between + * `completionList.itemDefaults.data` and the completion's own data + * using the following rules: + * + * - If a field is specified in `completion.data` it will be used as-is. + * - If a field is `null` in `completion.data`, it will remain `null`. + * - If a field is unspecified in `completion.data`, the same field from + * `completionList.itemDefaults.data` will be used. + * + * @since 3.18.0 + */ + data?: "replace" | "merge"; + } + /** * The completion items. */ diff --git a/_specifications/lsp/3.18/specification.md b/_specifications/lsp/3.18/specification.md index 0c6ffd51a..cec1e604b 100644 --- a/_specifications/lsp/3.18/specification.md +++ b/_specifications/lsp/3.18/specification.md @@ -737,6 +737,7 @@ Since 3.17 there is a meta model describing the LSP protocol: * Support for snippets in text document edits. * Support for debug message kind. * Client capability to enumerate properties that can be resolved for code lenses. +* Added support for `completionList.applyKind` to determine how values from `completionList.itemDefaults` and `completion` are combined. #### 3.17.0 (05/10/2022) From ab78abfe6b6c6952ba61d498a7d7195e7c442f15 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 11 Sep 2024 12:29:34 +0100 Subject: [PATCH 2/6] Update completion.md --- _specifications/lsp/3.18/language/completion.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/_specifications/lsp/3.18/language/completion.md b/_specifications/lsp/3.18/language/completion.md index 7f62e7ad0..9aae783b1 100644 --- a/_specifications/lsp/3.18/language/completion.md +++ b/_specifications/lsp/3.18/language/completion.md @@ -431,7 +431,9 @@ export interface CompletionList { * * If "merge" the commitCharacters for the completion will be the union * of all values in both `completionList.itemDefaults.commitCharacters` - * and the completion's own `commitCharacters`. + * and the completion's own `commitCharacters`. An empty list indicates + * the completion adds no additional commit characters and a value of + * `null` indicates the defaults should not be used. * * @since 3.18.0 */ @@ -451,8 +453,13 @@ export interface CompletionList { * `completionList.itemDefaults.data` and the completion's own data * using the following rules: * + * - If a completion's data is not specified, the data from + * `completionList.itemDefaults.data` will be used. + * - If a completion's data is null, the resulting data field will be + * null (defaults are not used). * - If a field is specified in `completion.data` it will be used as-is. - * - If a field is `null` in `completion.data`, it will remain `null`. + * - If a field is `null` in `completion.data`, it will remain `null` + * (default is not used). * - If a field is unspecified in `completion.data`, the same field from * `completionList.itemDefaults.data` will be used. * From 9886c0efa9a596b81a6a05456f844d8a43a4ddbb Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 11 Sep 2024 16:50:17 +0100 Subject: [PATCH 3/6] Simplify by removing distinction between null/undefined --- .../lsp/3.18/language/completion.md | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/_specifications/lsp/3.18/language/completion.md b/_specifications/lsp/3.18/language/completion.md index 9aae783b1..11b235bb8 100644 --- a/_specifications/lsp/3.18/language/completion.md +++ b/_specifications/lsp/3.18/language/completion.md @@ -405,8 +405,10 @@ export interface CompletionList { * * In unspecified, all fields will be treated as "replace". * - * If a field's value is "replace", the value from a completion item will - * always be used instead of the value from `completionItem.itemDefaults`. + * If a field's value is "replace", the value from a completion item (if + * provided) will always be used instead of the value from + * `completionItem.itemDefaults`. `null` values are treated the same as a + * value that was not provided. * * If a field's value is "merge", the values will be merged using the rules * defined against each field below. @@ -423,17 +425,15 @@ export interface CompletionList { * merged with those in `completionList.itemDefaults.commitCharacters`. * * If "replace", the commit characters from the completion item will - * always be used unless not provided, in which case those from - * `completionList.itemDefaults.commitCharacters` will be used. An empty - * list can be used if a completion item does not have any commit + * always be used unless not provided (or `null`), in which case those + * from `completionList.itemDefaults.commitCharacters` will be used. An + * empty list can be used if a completion item does not have any commit * characters and also should not use those from * `completionList.itemDefaults.commitCharacters`. * * If "merge" the commitCharacters for the completion will be the union * of all values in both `completionList.itemDefaults.commitCharacters` - * and the completion's own `commitCharacters`. An empty list indicates - * the completion adds no additional commit characters and a value of - * `null` indicates the defaults should not be used. + * and the completion's own `commitCharacters`. * * @since 3.18.0 */ @@ -444,24 +444,20 @@ export interface CompletionList { * be merged with data from `completionList.itemDefaults.data`. * * If "replace", the data from the completion item will be used if - * provided, otherwise `completionList.itemDefaults.data` will be used. - * An empty object can be used if a completion item does not have any - * data but also should not use the value from - * `completionList.itemDefaults.data`. + * provided (and not `null`), otherwise + * `completionList.itemDefaults.data` will be used. An empty object can + * be used if a completion item does not have any data but also should + * not use the value from `completionList.itemDefaults.data`. * * If "merge", a shallow merge will be performed between * `completionList.itemDefaults.data` and the completion's own data * using the following rules: * - * - If a completion's data is not specified, the data from - * `completionList.itemDefaults.data` will be used. - * - If a completion's data is null, the resulting data field will be - * null (defaults are not used). - * - If a field is specified in `completion.data` it will be used as-is. - * - If a field is `null` in `completion.data`, it will remain `null` - * (default is not used). - * - If a field is unspecified in `completion.data`, the same field from - * `completionList.itemDefaults.data` will be used. + * - If a completion's `data` field is not provided (or `null`), the + * data from `completionList.itemDefaults.data` will be used as-is. + * - If a completion's `data` is provided, each field will overwrite the + * field of the same name in `completionList.itemDefaults.data` but + * no merging of fields within that value will occur. * * @since 3.18.0 */ From 832f719dbdd875dbb4594aad995ac42361b6b21e Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Thu, 12 Sep 2024 10:49:19 +0100 Subject: [PATCH 4/6] Use a single client capability + add ApplyKind enum/type --- .../lsp/3.18/language/completion.md | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/_specifications/lsp/3.18/language/completion.md b/_specifications/lsp/3.18/language/completion.md index 11b235bb8..a0eda3d59 100644 --- a/_specifications/lsp/3.18/language/completion.md +++ b/_specifications/lsp/3.18/language/completion.md @@ -168,16 +168,13 @@ export interface CompletionClientCapabilities { itemDefaults?: string[]; /** - * Specifies which fields of `CompletionList.applyKind` the client - * supports. If omitted, no properties are supported and all fields - * in a completion item will replace the defaults. - * - * Clients may only specify fields that have merge rules defined in the - * LSP spec. + * Specifies whether the client supports `CompletionList.applyKind` to + * indicate how supported values from `completionList.itemDefaults` + * and `completion` will be combined. * * @since 3.18.0 */ - applyKinds?: string[]; + applyKindSupport?: boolean; } } ``` @@ -351,7 +348,8 @@ export interface CompletionList { * * If a completion list specifies a default value and a completion item * also specifies a corresponding value, the rules for combining these are - * defined by `applyKinds`, defaulting to "replace". + * defined by `applyKinds` (if the client supports it), defaulting to + * "replace". * * Servers are only allowed to return default values if the client * signals support for this via the `completionList.itemDefaults` @@ -406,15 +404,14 @@ export interface CompletionList { * In unspecified, all fields will be treated as "replace". * * If a field's value is "replace", the value from a completion item (if - * provided) will always be used instead of the value from - * `completionItem.itemDefaults`. `null` values are treated the same as a - * value that was not provided. + * provided and not `null`) will always be used instead of the value from + * `completionItem.itemDefaults`. * * If a field's value is "merge", the values will be merged using the rules * defined against each field below. * * Servers are only allowed to return `applyKind` if the client - * signals support for this via the `completionList.applyKinds` + * signals support for this via the `completionList.applyKindSupport` * capability. * * @since 3.18.0 @@ -437,10 +434,10 @@ export interface CompletionList { * * @since 3.18.0 */ - commitCharacters?: "replace" | "merge"; + commitCharacters?: ApplyKind; /** - * Specifies whether data on a completion will replace or + * Specifies whether the `data` field on a completion will replace or * be merged with data from `completionList.itemDefaults.data`. * * If "replace", the data from the completion item will be used if @@ -454,14 +451,16 @@ export interface CompletionList { * using the following rules: * * - If a completion's `data` field is not provided (or `null`), the - * data from `completionList.itemDefaults.data` will be used as-is. - * - If a completion's `data` is provided, each field will overwrite the - * field of the same name in `completionList.itemDefaults.data` but - * no merging of fields within that value will occur. + * entire `data` field from `completionList.itemDefaults.data` will be + * used as-is. + * - If a completion's `data` field is provided, each field will + * overwrite the field of the same name in + * `completionList.itemDefaults.data` but no merging of nested fields + * within that value will occur. * * @since 3.18.0 */ - data?: "replace" | "merge"; + data?: ApplyKind; } /** @@ -603,6 +602,32 @@ export interface CompletionItemLabelDetails { } ``` +
+ +```typescript +/** + * Defines how values from a set of defaults and an individual item will be + * merged. + */ +export namespace ApplyKind { + /** + * The value from the individual item (if provided and not `null`) will be + * used instead of the default. + */ + export const Replace: 'replace' = 'replace; + + /** + * The value from the item will be merged with the default. + * + * The specific rules for mergeing values are defined against each field + * that supports merging. + */ + export const Merge: 'merge' = 'merge'; +} + +export type ApplyKind = ApplyKind.Replace | ApplyKind.Merge; +``` +
```typescript From f094493e8ec12bdf3652e7d3c026e6cef2d7ed7b Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Thu, 12 Sep 2024 10:51:39 +0100 Subject: [PATCH 5/6] Make this clearer --- _specifications/lsp/3.18/language/completion.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/_specifications/lsp/3.18/language/completion.md b/_specifications/lsp/3.18/language/completion.md index a0eda3d59..f3a9a3682 100644 --- a/_specifications/lsp/3.18/language/completion.md +++ b/_specifications/lsp/3.18/language/completion.md @@ -171,6 +171,12 @@ export interface CompletionClientCapabilities { * Specifies whether the client supports `CompletionList.applyKind` to * indicate how supported values from `completionList.itemDefaults` * and `completion` will be combined. + * + * If a client supports `applyKind` it must support it for all fields + * that it supports that are listed in `CompletionList.applyKind`. This + * means when clients add support for new/future fields in completion + * items the MUST also support merge for them if those fields are + * defined in `CompletionList.applyKind`. * * @since 3.18.0 */ From 21ff7ad5e640d1ef55b6bd1754b50084aa3dba38 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 18 Sep 2024 11:53:40 +0100 Subject: [PATCH 6/6] Minor fixes - commitCharacters isn't allowed to be null so no point mentioning this - Fix missing quote - Fix error in ApplyKind type (either needs "typeof" or to use literals, and other things use literals) --- _specifications/lsp/3.18/language/completion.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_specifications/lsp/3.18/language/completion.md b/_specifications/lsp/3.18/language/completion.md index f3a9a3682..aaaaf769e 100644 --- a/_specifications/lsp/3.18/language/completion.md +++ b/_specifications/lsp/3.18/language/completion.md @@ -428,8 +428,8 @@ export interface CompletionList { * merged with those in `completionList.itemDefaults.commitCharacters`. * * If "replace", the commit characters from the completion item will - * always be used unless not provided (or `null`), in which case those - * from `completionList.itemDefaults.commitCharacters` will be used. An + * always be used unless not provided, in which case those from + * `completionList.itemDefaults.commitCharacters` will be used. An * empty list can be used if a completion item does not have any commit * characters and also should not use those from * `completionList.itemDefaults.commitCharacters`. @@ -620,7 +620,7 @@ export namespace ApplyKind { * The value from the individual item (if provided and not `null`) will be * used instead of the default. */ - export const Replace: 'replace' = 'replace; + export const Replace: 'replace' = 'replace'; /** * The value from the item will be merged with the default. @@ -631,7 +631,7 @@ export namespace ApplyKind { export const Merge: 'merge' = 'merge'; } -export type ApplyKind = ApplyKind.Replace | ApplyKind.Merge; +export type ApplyKind = 'replace' | 'merge'; ```