Skip to content

Commit 919b8ba

Browse files
authored
Trigger parameter hints on accepting a function-like completion item (swiftlang#1802)
* Trigger parameter hints on accepting a function-like completion * Test triggering parameter hints on accpeting function-like completion
1 parent 16352f0 commit 919b8ba

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed

src/configuration.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,16 @@ const configuration = {
525525
get outputChannelLogLevel(): string {
526526
return vscode.workspace.getConfiguration("swift").get("outputChannelLogLevel", "info");
527527
},
528+
parameterHintsEnabled(documentUri: vscode.Uri): boolean {
529+
const enabled = vscode.workspace
530+
.getConfiguration("editor.parameterHints", {
531+
uri: documentUri,
532+
languageId: "swift",
533+
})
534+
.get<boolean>("enabled");
535+
536+
return enabled === true;
537+
},
528538
};
529539

530540
const vsCodeVariableRegex = new RegExp(/\$\{(.+?)\}/g);

src/sourcekit-lsp/LanguageClientConfiguration.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,33 @@ export class LanguagerClientDocumentSelectors {
176176
}
177177
}
178178

179+
function addParameterHintsCommandsIfNeeded(
180+
items: vscode.CompletionItem[],
181+
documentUri: vscode.Uri
182+
): vscode.CompletionItem[] {
183+
if (!configuration.parameterHintsEnabled(documentUri)) {
184+
return items;
185+
}
186+
187+
return items.map(item => {
188+
switch (item.kind) {
189+
case vscode.CompletionItemKind.Function:
190+
case vscode.CompletionItemKind.Method:
191+
case vscode.CompletionItemKind.Constructor:
192+
case vscode.CompletionItemKind.EnumMember:
193+
return {
194+
command: {
195+
title: "Trigger Parameter Hints",
196+
command: "editor.action.triggerParameterHints",
197+
},
198+
...item,
199+
};
200+
default:
201+
return item;
202+
}
203+
});
204+
}
205+
179206
export function lspClientOptions(
180207
swiftVersion: Version,
181208
workspaceContext: WorkspaceContext,
@@ -199,6 +226,22 @@ export function lspClientOptions(
199226
middleware: {
200227
didOpen: activeDocumentManager.didOpen.bind(activeDocumentManager),
201228
didClose: activeDocumentManager.didClose.bind(activeDocumentManager),
229+
provideCompletionItem: async (document, position, context, token, next) => {
230+
const result = await next(document, position, context, token);
231+
232+
if (!result) {
233+
return result;
234+
}
235+
236+
if (Array.isArray(result)) {
237+
return addParameterHintsCommandsIfNeeded(result, document.uri);
238+
}
239+
240+
return {
241+
...result,
242+
items: addParameterHintsCommandsIfNeeded(result.items, document.uri),
243+
};
244+
},
202245
provideCodeLenses: async (document, token, next) => {
203246
const result = await next(document, token);
204247
return result?.map(codelens => {

test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
DidChangeWorkspaceFoldersNotification,
3737
DidChangeWorkspaceFoldersParams,
3838
LanguageClient,
39+
Middleware,
3940
State,
4041
StateChangeEvent,
4142
} from "vscode-languageclient/node";
@@ -597,6 +598,213 @@ suite("LanguageClientManager Suite", () => {
597598
]);
598599
});
599600

601+
suite("provideCompletionItem middleware", () => {
602+
const mockParameterHintsEnabled = mockGlobalValue(configuration, "parameterHintsEnabled");
603+
let document: MockedObject<vscode.TextDocument>;
604+
let middleware: Middleware;
605+
606+
setup(async () => {
607+
mockParameterHintsEnabled.setValue(() => true);
608+
609+
document = mockObject<vscode.TextDocument>({
610+
uri: vscode.Uri.file("/test/file.swift"),
611+
});
612+
613+
new LanguageClientToolchainCoordinator(
614+
instance(mockedWorkspace),
615+
{},
616+
languageClientFactoryMock
617+
);
618+
619+
await waitForReturnedPromises(languageClientMock.start);
620+
621+
middleware = languageClientFactoryMock.createLanguageClient.args[0][3].middleware!;
622+
});
623+
624+
test("adds parameter hints command to function completion items when enabled", async () => {
625+
const completionItemsFromLSP = async (): Promise<vscode.CompletionItem[]> => {
626+
return [
627+
{
628+
label: "post(endpoint: String, body: [String : Any]?)",
629+
detail: "NetworkRequest",
630+
kind: vscode.CompletionItemKind.EnumMember,
631+
},
632+
{
633+
label: "defaultHeaders",
634+
detail: "[String : String]",
635+
kind: vscode.CompletionItemKind.Property,
636+
},
637+
{
638+
label: "makeRequest(for: NetworkRequest)",
639+
detail: "String",
640+
kind: vscode.CompletionItemKind.Function,
641+
},
642+
{
643+
label: "[endpoint: String]",
644+
detail: "NetworkRequest",
645+
kind: vscode.CompletionItemKind.Method,
646+
},
647+
{
648+
label: "(endpoint: String, method: String)",
649+
detail: "NetworkRequest",
650+
kind: vscode.CompletionItemKind.Constructor,
651+
},
652+
];
653+
};
654+
655+
expect(middleware).to.have.property("provideCompletionItem");
656+
657+
const result = await middleware.provideCompletionItem!(
658+
instance(document),
659+
new vscode.Position(0, 0),
660+
{} as any,
661+
{} as any,
662+
completionItemsFromLSP
663+
);
664+
665+
expect(result).to.deep.equal([
666+
{
667+
label: "post(endpoint: String, body: [String : Any]?)",
668+
detail: "NetworkRequest",
669+
kind: vscode.CompletionItemKind.EnumMember,
670+
command: {
671+
title: "Trigger Parameter Hints",
672+
command: "editor.action.triggerParameterHints",
673+
},
674+
},
675+
{
676+
label: "defaultHeaders",
677+
detail: "[String : String]",
678+
kind: vscode.CompletionItemKind.Property,
679+
},
680+
{
681+
label: "makeRequest(for: NetworkRequest)",
682+
detail: "String",
683+
kind: vscode.CompletionItemKind.Function,
684+
command: {
685+
title: "Trigger Parameter Hints",
686+
command: "editor.action.triggerParameterHints",
687+
},
688+
},
689+
{
690+
label: "[endpoint: String]",
691+
detail: "NetworkRequest",
692+
kind: vscode.CompletionItemKind.Method,
693+
command: {
694+
title: "Trigger Parameter Hints",
695+
command: "editor.action.triggerParameterHints",
696+
},
697+
},
698+
{
699+
label: "(endpoint: String, method: String)",
700+
detail: "NetworkRequest",
701+
kind: vscode.CompletionItemKind.Constructor,
702+
command: {
703+
title: "Trigger Parameter Hints",
704+
command: "editor.action.triggerParameterHints",
705+
},
706+
},
707+
]);
708+
});
709+
710+
test("does not add parameter hints command when disabled", async () => {
711+
mockParameterHintsEnabled.setValue(() => false);
712+
713+
const completionItems = [
714+
{
715+
label: "makeRequest(for: NetworkRequest)",
716+
detail: "String",
717+
kind: vscode.CompletionItemKind.Function,
718+
},
719+
{
720+
label: "[endpoint: String]",
721+
detail: "NetworkRequest",
722+
kind: vscode.CompletionItemKind.Method,
723+
},
724+
];
725+
726+
const completionItemsFromLSP = async (): Promise<vscode.CompletionItem[]> => {
727+
return completionItems;
728+
};
729+
730+
const result = await middleware.provideCompletionItem!(
731+
instance(document),
732+
new vscode.Position(0, 0),
733+
{} as any,
734+
{} as any,
735+
completionItemsFromLSP
736+
);
737+
738+
expect(result).to.deep.equal(completionItems);
739+
});
740+
741+
test("handles CompletionList result format", async () => {
742+
const completionListFromLSP = async (): Promise<vscode.CompletionList> => {
743+
return {
744+
isIncomplete: false,
745+
items: [
746+
{
747+
label: "defaultHeaders",
748+
detail: "[String : String]",
749+
kind: vscode.CompletionItemKind.Property,
750+
},
751+
{
752+
label: "makeRequest(for: NetworkRequest)",
753+
detail: "String",
754+
kind: vscode.CompletionItemKind.Function,
755+
},
756+
],
757+
};
758+
};
759+
760+
const result = await middleware.provideCompletionItem!(
761+
instance(document),
762+
new vscode.Position(0, 0),
763+
{} as any,
764+
{} as any,
765+
completionListFromLSP
766+
);
767+
768+
expect(result).to.deep.equal({
769+
isIncomplete: false,
770+
items: [
771+
{
772+
label: "defaultHeaders",
773+
detail: "[String : String]",
774+
kind: vscode.CompletionItemKind.Property,
775+
},
776+
{
777+
label: "makeRequest(for: NetworkRequest)",
778+
detail: "String",
779+
kind: vscode.CompletionItemKind.Function,
780+
command: {
781+
title: "Trigger Parameter Hints",
782+
command: "editor.action.triggerParameterHints",
783+
},
784+
},
785+
],
786+
});
787+
});
788+
789+
test("handles null/undefined result from next middleware", async () => {
790+
mockParameterHintsEnabled.setValue(() => true);
791+
792+
const nullCompletionResult = async (): Promise<null> => {
793+
return null;
794+
};
795+
796+
const result = await middleware.provideCompletionItem!(
797+
instance(document),
798+
new vscode.Position(0, 0),
799+
{} as any,
800+
{} as any,
801+
nullCompletionResult
802+
);
803+
804+
expect(result).to.be.null;
805+
});
806+
});
807+
600808
suite("active document changes", () => {
601809
const mockWindow = mockGlobalObject(vscode, "window");
602810

0 commit comments

Comments
 (0)