zls icon indicating copy to clipboard operation
zls copied to clipboard

Inconsistent completion of function parentheses when snippets are disabled

Open castholm opened this issue 1 year ago • 1 comments

Zig Version

0.13.0

Zig Language Server Version

0.13.0

Client / Code Editor / Extensions

vscode-zig

Steps to Reproduce and Observed Behavior

I have zig.zls.enableSnippets set to false in the settings. Despite this, ZLS completes function call parentheses differently depending on the signature, sometimes inserting just the function name and other times inserting the function name followed by (). This inconsistent behavior is undesirable and hurts muscle memory. I disabled snippets because I prefer just completing the names without any automatic insertion of parentheses.

fn functionZero() void {}

fn functionOne(a: i32) void {
    _ = a;
}

fn functionAnytype(a: anytype) void {
    _ = a;
}

const Foo = struct {
    fn method(foo: Foo) void {
        _ = foo;
    }
};

pub fn main() void {
    const foo: Foo = .{};
    _ = &foo;

    // Below, '_' represents the position of the caret

    // "funZ_"
    // <tab>
    // "functionZero()_"

    // "funO_"
    // <tab>
    // "functionOne_"

    // "funA_"
    // <tab>
    // "functionAnytype()_"

    // "foo.m_"
    // <tab>
    // "foo.method()_"
}

Of note is that when snippets are enabled, completion of the anytype function places the caret after the inserted () and not inbetween, so this is a bug regardless of whether the user has enabled snippets.

Expected Behavior

When snippets are disabled, only the name should be inserted. The parentheses should never be inserted regardless of whether the call takes any arguments or not.

I fixed this problem for myself locally by making the following changes to completions.zig:

diff --git a/src/features/completions.zig b/src/features/completions.zig
index 599f781..9cc4ff9 100644
--- a/src/features/completions.zig
+++ b/src/features/completions.zig
@@ -329,7 +329,8 @@ fn functionTypeCompletion(
     const new_text = switch (new_text_format) {
         .only_name => func_name,
         .snippet => blk: {
-            if (use_snippets and builder.server.config.enable_argument_placeholders) {
+            if (!use_snippets) break :blk func_name;
+            if (builder.server.config.enable_argument_placeholders) {
                 break :blk try std.fmt.allocPrint(builder.arena, "{}", .{Analyser.fmtFunction(.{
                     .fn_proto = func,
                     .tree = &tree,
@@ -358,12 +359,10 @@ fn functionTypeCompletion(
                     }
 
                     // Non-self parameter, leave the cursor in the parentheses
-                    if (!use_snippets) break :blk func_name;
                     break :blk try std.fmt.allocPrint(builder.arena, "{s}(${{1:}})", .{func_name});
                 },
                 else => {
                     // Atleast one non-self parameter, leave the cursor in the parentheses
-                    if (!use_snippets) break :blk func_name;
                     break :blk try std.fmt.allocPrint(builder.arena, "{s}(${{1:}})", .{func_name});
                 },
             }

This is enough for me but I'm not sure if it's a good general solution.

Relevant log output

No response

castholm avatar Jul 04 '24 11:07 castholm

Appearantly, that behavior is desired:

add empty parentheses when there are no arguments even if snippets are disabled

https://github.com/zigtools/zls/pull/1763

nwormek avatar Sep 02 '24 09:09 nwormek

add empty parentheses when there are no arguments even if snippets are disabled

I still have parens with enable_snippets == false, but the problem is not snippets. Turns out, nvim adds completionItem.labelDetails.detail after completion text automatically. And in case of builtins and functions zls sets it to () here: https://github.com/zigtools/zls/blob/706e08fc80e6c21650f54e5aab91dbe8fb441f0f/src/features/completions.zig#L434

Workaround is to remove labelDetailsSupport client capability in nvim like so:

local capabilities = require("blink.cmp").get_lsp_capabilities()
capabilities.textDocument.completion.completionItem.labelDetailsSupport = false

ivanjermakov avatar Jun 21 '25 12:06 ivanjermakov