求助:emacs lsp-dart的company补全无法弹出

已安装lsp-dart,可以进行跳转等,但company补全似乎有问题。 比如输入一个对像名称,再按".",不会弹出其成员,只有再连续输出3个字符,才会弹出。 但相同配置在C++下就没这个问题。

有没有人遇到跟我一样的问题呀? 或者看下有没有解决的思路

补充一下,如果我在输入"."后,执行M-x company-complete,是能够正常弹出补全窗口的,所以补全本身应该没问题,但不明白为什么C++下行为正常,dart下就无法弹出

举例:

// c++

class SomeType {
public:
    void func();
};

SomeType obj;

obj. // ---> 输入obj.后,company即自动弹出补全窗口,包含func选项
class SomeType {
  SomeType();

  void func1() {
    print("Hello world");
  }
}

void main() {
  var x = SomeType();
  x. // --> 此时不会弹出补全
}

补充最小复现配置:

(if (eq system-type 'darwin)
  (setq
    mac-command-modifier 'control
    mac-option-modifier 'meta))

(defvar bootstrap-version)
(let
  (
    (bootstrap-file
      (expand-file-name "straight/repos/straight.el/bootstrap.el"
        user-emacs-directory))
    (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
      (url-retrieve-synchronously
        "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
        'silent
        'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package 'use-package)

(use-package
  lsp-mode
  :ensure t
  :defer t
  :straight t
  :config
  (setq lsp-signature-function 'lsp-signature-posframe)
  (setq lsp-modeline-diagnostics-scope :file)
  (setq lsp-lens-enable t)
  :ensure t)


(use-package
  lsp-ui
  :ensure t
  :defer t
  :straight t
  :hook (lsp-mode . lsp-ui-mode)
  :config
  (setq lsp-ui-doc-use-webkit t)
  (setq lsp-ui-doc-max-width 60)
  (setq lsp-ui-doc-show-with-mouse nil)
  (setq lsp-ui-doc-show-with-cursor t)
  (setq lsp-ui-doc-position 'at-point)
  (setq lsp-ui-doc-delay 1.5))

(use-package
  lsp-dart
  :ensure t
  :straight t
  :hook
  (dart-mode . lsp-deferred))


(use-package
  company-posframe
  :requires posframe
  :ensure t
  :straight t
  :diminish company-posframe-mode)


(use-package
  company
  :straight t
  :ensure t
  :defer t
  :config
  (company-posframe-mode 1)
  (setq company-idle-delay 0)
  (setq company-show-quick-access t)
  (setq company-selection-wrap-around t))


(use-package
  cc-mode
  :ensure t
  :defer t
  :straight t
  :hook
  (c++-mode . lsp-deferred))
  1. 从标题看不出你是在分享还是求助。
  2. 不要问否有人跟你遇到一样的问题(这个问题的答案是「有」/「没有」),只管把问题描述清楚。
  3. 确保问题在 emacs -Q 上能复现。
  4. 最好提供能复现问题的最小代码。

设置一下company-insertion-on-trigger试试?默认是nil,改成t。

不起作用,我用(debug-on-entry 'company-capf)看了下,company-capf确实是有调用的,但不知为何没有后续结果了

可以看看lsp-mode的log,看输入"."之后有没有补全数据从dart的lsp server返回来。

要调试也可以从lsp-mode里有个名字大概是lsp-completion-at-point的函数入手看看,这个函数会从lsp server获取补全信息,也是company-capf最终会调用的方法。

1 个赞
[Trace - 10:13:18 PM] Sending notification 'textDocument/didChange'.
Params: {
  "textDocument": {
    "uri": "file:///Users/user/code/test2e/dart/my_app/lib/main.dart",
    "version": 159
  },
  "contentChanges": [
    {
      "range": {
        "start": {
          "line": 14,
          "character": 3
        },
        "end": {
          "line": 14,
          "character": 3
        }
      },
      "rangeLength": 0,
      "text": "."
    }
  ]
}


[Trace - 10:13:18 PM] Received request 'window/workDoneProgress/create - (70).
Params: {
  "token": "ANALYZING"
}


[Trace - 10:13:18 PM] Sending response 'window/workDoneProgress/create - (70)'. Processing request took 0ms
Params: {
  "jsonrpc": "2.0",
  "id": 70,
  "result": null
}

似乎是返回了null


对比了下dart和c++的日志,应该是没有继续请求,c++也请求了textDocument/didChange,但紧接着就请求了textDocument/completion,textDocument/completion最后返回了补全结果

输入 3 个字符才开始补全是 company-minimum-prefix-length 决定的。

但是 lsp server 也可以决定何时触发补全,比如输入 . 的时候,这个字符就叫做 trigger character。

就是说 dart server 不认为 . 是 trigger character,所以没有自动触发补全。

以下是 lsp pyright 日志信息:

  1. lsp 初始化,下发 trigger characters
[Trace - 10:57:42 PM] Received response 'initialize - (1)' in 2195ms.
Result: {
  "capabilities": {
    "textDocumentSync": 2,
    "definitionProvider": {
      "workDoneProgress": true
    },
    "declarationProvider": {
      "workDoneProgress": true
    },
    ...
    "completionProvider": {
      "triggerCharacters": [
        ".",
        "[",
        "\"",
        "'"
      ],
      ...
  1. 输入 . 触发补全请求
[Trace - 10:59:21 PM] Sending request 'textDocument/completion - (18)'.
Params: {
  "textDocument": {
    "uri": "file:///path/to/test_py/test.py"
  },
  "position": {
    "line": 30,
    "character": 13
  },
  "context": {
    "triggerKind": 2,
    "triggerCharacter": "."
  }
}
  1. 补全响应
[Trace - 10:59:21 PM] Received response 'textDocument/completion - (18)' in 87ms.
Result: {
  "items": [
    {
      "label": "__doc__",
      "kind": 6,
      "data": {
        "workspacePath": "/path/to/test_py",
        "filePath": "/path/to/test_py/test.py",
        "position": {
          "line": 30,
          "character": 13
        },
        "symbolLabel": "__doc__"
      },
      "sortText": "11.9999.__doc__"
    },
    ...

但是用vscode则可以,我看了插件,背后也是连接的dart的lsp-server

你先看看日志,dart server 有没有下发 trigger characters。如果没有,那是 vscode 有做优化。

在 Emacs 这端其实是可以自行检测 trigger character 并发起请求的,我目前用 lsp-bridge 就自己做了这方面的改动。

lsp初始化时的日志没有包括trigger characters的信息

[Trace - 11:34:49 PM] Received response 'initialize - (40)' in 255ms.
Result: {
  "capabilities": {
    "callHierarchyProvider": true,
    "colorProvider": {
      "documentSelector": [
        {
          "language": "dart",
          "scheme": "file"
        }
      ]
    },
    "documentHighlightProvider": true,
    "documentSymbolProvider": true,
    "executeCommandProvider": {
      "commands": [
        "edit.sortMembers",
        "edit.organizeImports",
        "edit.fixAll",
        "edit.sendWorkspaceEdit",
        "refactor.perform",
        "refactor.validate",
        "dart.logAction",
        "dart.refactor.convert_all_formal_parameters_to_named",
        "dart.refactor.convert_selected_formal_parameters_to_named",
        "dart.refactor.move_selected_formal_parameters_left",
        "dart.refactor.move_top_level_to_file"
      ],
      "workDoneProgress": true
    },
    "inlayHintProvider": {
      "resolveProvider": null
    },
    "semanticTokensProvider": {
      "full": {
        "delta": null
      },
      "legend": {
        "tokenModifiers": [
          "documentation",
          "constructor",
          "declaration",
          "importPrefix",
          "instance",
          "static",
          "escape",
          "annotation",
          "control",
          "label",
          "interpolation",
          "void"
        ],
        "tokenTypes": [
          "annotation",
          "keyword",
          "class",
          "comment",
          "method",
          "variable",
          "parameter",
          "enum",
          "enumMember",
          "type",
          "source",
          "property",
          "namespace",
          "boolean",
          "number",
          "string",
          "function",
          "typeParameter"
        ]
      },
      "range": true
    },
    "textDocumentSync": {
      "change": 2,
      "openClose": true,
      "willSave": null,
      "willSaveWaitUntil": null
    },
    "workspace": {
      "fileOperations": {
        "willRename": {
          "filters": [
            {
              "pattern": {
                "glob": "**/*.dart",
                "matches": "file"
              },
              "scheme": "file"
            },
            {
              "pattern": {
                "glob": "**/",
                "matches": "folder"
              },
              "scheme": "file"
            }
          ]
        }
      },
      "workspaceFolders": {
        "changeNotifications": true,
        "supported": true
      }
    },
    "workspaceSymbolProvider": true
  },
  "serverInfo": {
    "name": "Dart SDK LSP Analysis Server",
    "version": "3.1.4"
  }
}

但看后面一些内容有,但不清楚是做什么的了

[Trace - 11:34:49 PM] Received request 'client/registerCapability - (2).
Params: {
  "registrations": [
    {
      "id": "0",
      "method": "textDocument/completion",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ],
        "resolveProvider": true,
        "triggerCharacters": [
          ".",
          "=",
          "(",
          "$",
          "\"",
          "'",
          "{",
          "/",
          ":"
        ]
      }
    },
    {
      "id": "1",
      "method": "textDocument/completion",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "yaml",
            "pattern": "**/pubspec.yaml",
            "scheme": "file"
          },
          {
            "language": "yaml",
            "pattern": "**/analysis_options.yaml",
            "scheme": "file"
          },
          {
            "language": "yaml",
            "pattern": "**/lib/{fix_data.yaml,fix_data/**.yaml}",
            "scheme": "file"
          }
        ],
        "resolveProvider": true
      }
    },
    {
      "id": "2",
      "method": "textDocument/hover",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "3",
      "method": "textDocument/signatureHelp",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ],
        "retriggerCharacters": [
          ","
        ],
        "triggerCharacters": [
          "("
        ]
      }
    },
    {
      "id": "4",
      "method": "textDocument/references",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "5",
      "method": "textDocument/formatting",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "6",
      "method": "textDocument/onTypeFormatting",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ],
        "firstTriggerCharacter": "}",
        "moreTriggerCharacter": [
          ";"
        ]
      }
    },
    {
      "id": "7",
      "method": "textDocument/rangeFormatting",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "8",
      "method": "textDocument/definition",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "9",
      "method": "textDocument/typeDefinition",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "10",
      "method": "textDocument/implementation",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "11",
      "method": "textDocument/codeAction",
      "registerOptions": {
        "codeActionKinds": [
          "source",
          "source.organizeImports",
          "source.fixAll",
          "source.sortMembers",
          "quickfix",
          "refactor"
        ],
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "12",
      "method": "textDocument/rename",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ],
        "prepareProvider": true
      }
    },
    {
      "id": "13",
      "method": "textDocument/foldingRange",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "14",
      "method": "textDocument/selectionRange",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    },
    {
      "id": "15",
      "method": "textDocument/prepareTypeHierarchy",
      "registerOptions": {
        "documentSelector": [
          {
            "language": "dart",
            "scheme": "file"
          }
        ]
      }
    }
  ]
}

我不确定是不是 lsp-moderegisterCapability 协议支持不完善。

你对比一下你那边的 C++ 日志,看它把 triggerCharacters 放在哪条协议里。

C++的类似这样

[Trace - 12:15:20 AM] Received response 'initialize - (48)' in 23ms.
Result: {
  "capabilities": {
    "astProvider": true,
    "callHierarchyProvider": true,
    "clangdInlayHintsProvider": true,
    "codeActionProvider": {
      "codeActionKinds": [
        "quickfix",
        "refactor",
        "info"
      ]
    },
    "compilationDatabase": {
      "automaticReload": true
    },
    "completionProvider": {
      "resolveProvider": null,
      "triggerCharacters": [
        ".",
        "<",
        ">",
        ":",
        "\"",
        "/",
        "*"
      ]
    },

看起来是放在 initialize 才有效。

又或者是 dart server 这边出错了,你前面贴的日志当中,其中 "id": "0" 可能导致 lsp-mode 无法正确解析:

[Trace - 11:34:49 PM] Received request 'client/registerCapability - (2).
Params: {
  "registrations": [
    {
      "id": "0",
      ...

lsp 官网给的 registerCapability 例子:

{
	"method": "client/registerCapability",
	"params": {
		"registrations": [
			{
				"id": "79eee87c-c409-4664-8102-e03263673f6f",
				"method": "textDocument/willSaveWaitUntil",
				"registerOptions": {
					"documentSelector": [
						{ "language": "javascript" }
					]
				}
			}
		]
	}
}

看来是没有什么好的解决方案了 : ( 谢谢

可以试试eglot,如果用的是Emacs29,已经内置了。

我的eglot配置没有dart相关的,用你的测试用dart文件,"x."后是可以直接出来补全的。

可以整理个最小可复现问题的配置,给lsp-dart和dart两边都提个issue让他们看看。看起来像是跟triggerCharacter有关系。

我追踪了 lsp-completion-at-point,发现是有调用的,但是这个函数调用实际上是返回了一个list,list第三项是一个函数,上层调用这个函数才真正去获取补全,不知为何上层没有调用。

用你的配置我这边 . 会触发补全。

image

差别就是我这边的 Dart 是 2019 年安装,那时的 triggerCharacters 是放在 initialize:

[Trace - 05:31:21 PM] Received response 'initialize - (1)' in 1335ms.
Result: {
  "capabilities": {
    "textDocumentSync": {
      "openClose": true,
      "change": 2,
      "willSave": null,
      "willSaveWaitUntil": null
    },
    "hoverProvider": true,
    "completionProvider": {
      "resolveProvider": true,
      "triggerCharacters": [
        ".",
        "=",
        "(",
        "$"
      ]
    },

:sweat: 我看下我版本多少

我最多只能回退到3.0.0,2022年版的,试了下还是不行。搞不懂vscode是怎么弄的

降级当然不是好办法,应该向 lsp-mode 或 dart 提 issue 或 pr。

如果你只想要一个临时的 workround,可以打印一下 lsp-completion-at-point 中的 trigger-chars 变量:

    (let* ((trigger-chars (-> (lsp--capability-for-method "textDocument/completion")
                              (lsp:completion-options-trigger-characters?)))
+          (_ (message "[debug][lsp-completion-at-point] trigger-chars: %S" trigger-chars))

如果确定是 nil,可以试试以下 advice:

(advice-add 'lsp-completion--looking-back-trigger-characterp :around
 (defun lsp-completion--looking-back-trigger-characterp@fix-dart-trigger-characters (orig-fn trigger-characters)
   (funcall orig-fn
            (if (and (derived-mode-p 'dart-mode) (not trigger-characters))
                ["." "=" "(" "$"]
                trigger-characters))))