lsp-bridge -- 速度最快的语法补全插件

我不用mode-line,你自己emacs -Q测试一下吧。

好的,我来试下!

大佬,emacs -Q没问题,能正常显示!

其他我最近也没装啥!能给点思路如何调试这块么!

论坛的回复框我已经帮大家写了怎么快速调试的方法了。

认真看一遍吧。

好的,我来看下!

最近发现了 GitHub - valentjn/ltex-ls: LTeX Language Server: LSP language server for LanguageTool with support for LaTeX , Markdown , and others , 周末的时候看看能否添加对它的支持, 对于我这种英文语法很差的人, LSP server 可以教我怎么写对英文长句。

可以在开启lsp-bridge English backend 的时候, 同时开启这个离线语法检测服务。

5 个赞

目前发现一个问题,在 adec95f 之后的更新,似乎 acm 只认英文了! 之前自己 hack 了 acm,可以在输入中文时,根据自定义 backend (关键字都是中文),出现补全提示窗。
而在 adec95f 之后的更新,hack acm 时也同步修正了最新的核心函数。但当输入非英文,比如中文,就不会出现补全提示窗口了!而输入英文,自定义 backend (关键字都是英文),依然会出现补全提示窗口!

以下是我当前的 hack 及配置,对于 adec95f 及之前的提交,还是有效果的!

;;;
;;; blove-lang-helper.el
;;;
;;; ~ defalias lsp-lang-toggle
;;;
;;;
;;; ~ defvar-local on/off
;;;
(defvar-local acm-enable-korean-helper nil)
(defvar-local acm-enable-us-eng-helper nil)
;;;
;;; ~ defcustom min-length
;;;
(defcustom acm-backend-korean-min-length 1
  "Minimum length of korean word."
  :type 'integer)
(defcustom acm-backend-us-eng-min-length 1
  "Minimum length of us english word."
  :type 'integer)
;;;
;;; ~ acm-icon-advice
;;;
;;; :: A. insert my-icon into the alist
(setq acm-icon-alist (append acm-icon-alist '(("korean" "material" "korean" "#ed6856"))))
(setq acm-icon-alist (append acm-icon-alist '(("us-eng" "material" "us-eng" "#50cddc"))))
;;; :: B. add my-svg-path
(setq korean-icon-path (expand-file-name "lisp/blove-extra/blove-icons/svg" user-emacs-directory))
;;; :: C. main acm-icon-filepath-advice
(advice-add 'acm-icon-filepath
			:override (lambda (collection name)
						(if (or (string-equal name "korean") (string-equal name "us-eng"))
							(concat (file-name-as-directory korean-icon-path) (format "%s_%s.svg" collection name))
						  (concat (file-name-as-directory acm-icon-dir) (format "%s_%s.svg" collection name))
						  )
						))
;;;
;;; ~ defun candidate
;;;
(defun acm-backend-language-candidates (min-length keyword dict the-icon)
  (let* ((candidates (list)))
	(when (>= (length keyword) min-length)
	  (dolist (candidate dict)
		(when (string-prefix-p (downcase keyword) candidate)
		  (add-to-list 'candidates (list :key candidate
										 :icon the-icon
										 :label candidate
										 :display-label candidate
										 :annotation (get-text-property 0 :initials candidate)
										 :backend "blove-lang")
					   t))))

	candidates))
;;;
;;; ~ defun lang-helper-toggle :: old
;;;
;; (defalias 'lsp-bridge-toggle-korean-helper #'acm-toggle-korean-helper)
;; (defun acm-toggle-korean-helper ()
;;   "Toggle korean helper."
;;   (interactive)
;;   (setq-local acm-enable-us-eng-helper nil)
;;   (setq-local the-icon "korean")
;;   (if acm-enable-korean-helper
;; 	  (message "Turn off korean helper.")
;; 	(message "Turn on korean helper."))
;;   (setq-local acm-enable-korean-helper (not acm-enable-korean-helper))
;;   )
;; (defalias 'lsp-bridge-toggle-us-eng-helper #'acm-toggle-us-eng-helper)
;; (defun acm-toggle-us-eng-helper ()
;;   "Toggle us english helper."
;;   (interactive)
;;   (setq-local acm-enable-korean-helper nil)
;;   (setq-local the-icon "us-eng")
;;   (if acm-enable-us-eng-helper
;; 	  (message "Turn off us english helper.")
;; 	(message "Turn on us english helper."))
;;   (setq-local acm-enable-us-eng-helper (not acm-enable-us-eng-helper))
;;   )
;;;
;;; ~ defun lang-helper-toggle :: new
;;;
;;; :: korean-helper
;;;
(defvar lsp-bridge-korean-helper-dict nil)
(defun lsp-bridge-toggle-korean-helper ()
  "Toggle korean helper."
  (interactive)
  (setq-local acm-enable-us-eng-helper nil)
  (setq-local the-icon "korean")
  (unless lsp-bridge-korean-helper-dict
    (setq lsp-bridge-korean-helper-dict (make-hash-table :test 'equal)))
  (if acm-enable-korean-helper
      (progn
        ;; Disable `lsp-bridge-mode' if it is enable temporality.
        (when (gethash (buffer-name) lsp-bridge-korean-helper-dict)
          (lsp-bridge-mode -1)
          (remhash (buffer-name) lsp-bridge-korean-helper-dict))
        (message "Turn off korean helper.")
		)
    ;; We enable `lsp-bridge-mode' temporality if current-mode not enable `lsp-bridge-mode' yet.
    (unless lsp-bridge-mode
      (puthash (buffer-name) t lsp-bridge-korean-helper-dict)
      (lsp-bridge-mode 1)
	  )
    (message "Turn on korean helper.")
	)
  (setq-local acm-enable-korean-helper (not acm-enable-korean-helper))
  )
;;;
;;; :: us-eng-helper
;;;
(defvar lsp-bridge-us-eng-helper-dict nil)
(defun lsp-bridge-toggle-us-eng-helper ()
  "Toggle us-eng helper."
  (interactive)
  (setq-local the-icon "us-eng")
  (setq-local acm-enable-korean-helper nil)
  (unless lsp-bridge-us-eng-helper-dict
    (setq lsp-bridge-us-eng-helper-dict (make-hash-table :test 'equal)))
  (if acm-enable-us-eng-helper
      (progn
        ;; Disable `lsp-bridge-mode' if it is enable temporality.
        (when (gethash (buffer-name) lsp-bridge-us-eng-helper-dict)
          (lsp-bridge-mode -1)
          (remhash (buffer-name) lsp-bridge-us-eng-helper-dict))
        (message "Turn off us-eng helper."))
    ;; We enable `lsp-bridge-mode' temporality if current-mode not enable `lsp-bridge-mode' yet.
    (unless lsp-bridge-mode
      (puthash (buffer-name) t lsp-bridge-us-eng-helper-dict)
      (lsp-bridge-mode 1))
    (message "Turn on us-eng helper."))
  (setq-local acm-enable-us-eng-helper (not acm-enable-us-eng-helper)))
;;;
;;; ~ add my backend
;;;
(add-to-list 'load-path (concat user-emacs-directory "lisp/blove-extra/lsp-bridge-fork/blove-lang-helper/blove-lang-zh"))
(add-to-list 'load-path (concat user-emacs-directory "lisp/blove-extra/lsp-bridge-fork/blove-lang-helper/blove-zh-lang"))
;;;
;;; ~ acm-update-candidates-advice
;;;
(defun acm-update-candidates-advice ()
  (let* ((keyword (acm-get-input-prefix))
		 (char-before-keyword (save-excursion
								(backward-char (length keyword))
								(acm-char-before)))
         (candidates (list))
         lsp-candidates
         path-candidates
         yas-candidates
         tabnine-candidates
         tempel-candidates
         mode-candidates
         citre-candidates)

	;; --- add language-helper for korean-helper / us-eng-helper --- begin

	(if (or acm-enable-korean-helper acm-enable-us-eng-helper)
		(progn
		  (setq-local fin-dict '())
		  (if acm-enable-korean-helper
			  (progn
				(require 'blove-backends-kor-zh-15000)
				(require 'blove-backends-zh-kor-15000)
				(setq fin-dict (append fin-dict blove-kor-zh-15000))
				(setq fin-dict (append fin-dict blove-zh-kor-15000))
				(setq candidates (acm-backend-language-candidates acm-backend-korean-min-length keyword fin-dict the-icon))
				)
			(progn
			  (if acm-enable-us-eng-helper
				  (progn
					(require 'blove-backends-us-zh-15000)
					(require 'blove-backends-zh-us-15000)
					(setq fin-dict (append fin-dict blove-us-zh-15000))
					(setq fin-dict (append fin-dict blove-zh-us-15000))
					(setq candidates (acm-backend-language-candidates acm-backend-us-eng-min-length keyword fin-dict the-icon))
					)
				)
			  )
			)
		  )
	  ;; --- add language-helper for korean-helper / us-eng-helper --- end
	  (progn
		;; from acm.el :: acm-update-candidates --- begin

		(when acm-enable-tabnine-helper
		  (require 'acm-backend-tabnine)
		  (setq tabnine-candidates (acm-backend-tabnine-candidates keyword)))

		(if acm-enable-english-helper
			;; Completion english if option `acm-enable-english-helper' is enable.
			(progn
			  (require 'acm-backend-english-data)
			  (require 'acm-backend-english)

			  (setq candidates (acm-backend-english-candidates keyword)))

		  (setq path-candidates (acm-backend-path-candidates keyword))
		  (if (> (length path-candidates) 0)
			  ;; Only show path candidates if prefix is valid path.
			  (setq candidates path-candidates)

			(when acm-enable-citre
			  (setq citre-candidates (acm-backend-citre-candidates keyword)))
			;; Fetch syntax completion candidates.
			(setq lsp-candidates (acm-backend-lsp-candidates keyword))
			(setq mode-candidates (append
								   (acm-backend-elisp-candidates keyword)
								   lsp-candidates
								   citre-candidates
								   (acm-backend-search-words-candidates keyword)
								   (acm-backend-telega-candidates keyword)))

			(when (or
				   ;; Show snippet candidates if lsp-candidates length is zero.
				   (zerop (length lsp-candidates))
				   ;; Don't search snippet if char before keyword is not in `acm-backend-lsp-completion-trigger-characters'.
				   (and (boundp 'acm-backend-lsp-completion-trigger-characters)
						(not (member char-before-keyword acm-backend-lsp-completion-trigger-characters))))
			  (setq yas-candidates (acm-backend-yas-candidates keyword))
			  (setq tempel-candidates (acm-backend-tempel-candidates keyword)))

			;; Insert snippet candidates in first page of menu.
			(setq candidates
				  (if (> (length mode-candidates) acm-snippet-insert-index)
					  (append (cl-subseq mode-candidates 0 acm-snippet-insert-index)
							  yas-candidates
							  tempel-candidates
							  (cl-subseq mode-candidates acm-snippet-insert-index)
							  tabnine-candidates)
					(append mode-candidates yas-candidates tempel-candidates tabnine-candidates)
					))))

		;; from acm.el :: acm-update-candidates --- end
		)
	  )
	candidates))
;;;
;;; :: use acm-update-candidates-advice
;;;
(advice-add 'acm-update-candidates :override #'acm-update-candidates-advice)
;;;
;;; ========== At-The-Bottom
;;;
(provide 'blove-lang-helper)

想问下,现在除了需要 hack acm-update-candidates 之外,是不是其它相关函数也有变动呢?才导致不认中文?或者我的配置需要修正?

在 adec95f 的提交可以实现以下效果,用输入法输入非英文,有补全

而在 adec95f 之后的更新,就无法实现这样的效果了,输入非英文,不弹出补全提示窗。

猜测,是不是因为红框内的提交,只使用 ASCII 作为 prefix 的原因呢? 而中文、韩文是需要 utf-8 的吧?只使用 ASCII 就不会捕获到更大范围的 utf-8 编码的中文或其它语种的文字?
发现失效,无法识别输入的中文,也是在更新到这个补丁的时候!

1 个赞

是的,这个是我提交的 :sweat_smile:,你可以在自己的配置里加上以下代码,把获取边界的函数改回原来的(覆盖 lsp-bridge 的这个函数)。

(defun acm-get-input-prefix-bound ()
  (bounds-of-thing-at-point 'symbol))

原因参考 lsp-bridge -- 速度最快的语法补全插件 - #2114,来自 twiddling

其实我觉得这个函数需要用户自己定义,它和补全后端的行为密切相关,例如想通过中文分词来补全就需要自己定义以上函数,使得它能自动分词(比如根据你图里的例子,如果输入“今天我去换钱”,就不会有韩文提示,只有在“换钱”前面有空格或者是行首才有,因为 (bounds-of-thing-at-point 'symbol) 获得的前缀是整个句子,但如果能对句子分词,只把”换钱“作为 prefix,就可以在 “今天我去换钱” 的情况下返回光标左侧对应词汇“换钱”的韩语提示)

2 个赞

感谢大佬的指点! :+1:t2:
覆盖那个函数后,已经恢复之前的效果了!输入中文、韩文又可以出现补全窗口啦!
有时间的时候我一定学习一下这个函数!:grin:

可以想办法探测一下当前 buffer 对应的 lsp server 里面有没有 Wen Server, 如果有就去掉中文前缀, 如果没有就用 (bounds-of-thing-at-point 'symbol) , 可以对不同补全后端都可以坚固到。

我个人观点是, LSP这种本来就复杂的场景, 能不让用户自定义就不自定义, 自定义参数太多只会增加大家使用 LSP 服务的门槛。

2 个赞

这个好有意思

这个好有意思啊

:grin:lsp-bridge的强大,蕴含着太多的可能性!让作为用户的我,感到使用体验极好、乐在其中、很享受!

我才提交了一个补丁,既能满足 @twiddling Wen LSP Server 不需要中文前缀的需求, 又可以满足 @blove 需要中文前缀的需求, 这个补丁以后, @blove 不需要定制 acm-get-input-prefix-bound 函数。

3 个赞

太好啦!大佬太赞啦👍🏻

赞同, 改成让 “高级用户” 自己定义更准确,比如像 @blove 自己写后端,如果只是使用的话,确实不需要知道这个函数是啥。

我现在尝试给 wenls 加上中文补全,因此也得检测中文分词边界了(而且想用 lsp 返回的分词信息来计算边界),这部分 可能还得多碰到些奇怪的后端需求才能更清楚怎么来灵活处理边界, :joy:

2 个赞

其实可以考虑 Wen LSP Server 来处理 elisp 返回 symbol 的分词边界, 这样我就可以去掉 Fix issue #356 · manateelazycat/lsp-bridge@09e091a · GitHub 这种 workaround 的补丁。

lsp-bridge 中越少的 LSP Server 兼容代码越好。

是的,这个我先试试看