好的,我来看下!
最近发现了 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 的时候, 同时开启这个离线语法检测服务。
目前发现一个问题,在 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 之外,是不是其它相关函数也有变动呢?才导致不认中文?或者我的配置需要修正?
发现失效,无法识别输入的中文,也是在更新到这个补丁的时候!
是的,这个是我提交的 ,你可以在自己的配置里加上以下代码,把获取边界的函数改回原来的(覆盖 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,就可以在 “今天我去换钱” 的情况下返回光标左侧对应词汇“换钱”的韩语提示)
感谢大佬的指点!
覆盖那个函数后,已经恢复之前的效果了!输入中文、韩文又可以出现补全窗口啦!
有时间的时候我一定学习一下这个函数!
可以想办法探测一下当前 buffer 对应的 lsp server 里面有没有 Wen Server, 如果有就去掉中文前缀, 如果没有就用 (bounds-of-thing-at-point 'symbol) , 可以对不同补全后端都可以坚固到。
我个人观点是, LSP这种本来就复杂的场景, 能不让用户自定义就不自定义, 自定义参数太多只会增加大家使用 LSP 服务的门槛。
这个好有意思
这个好有意思啊
lsp-bridge的强大,蕴含着太多的可能性!让作为用户的我,感到使用体验极好、乐在其中、很享受!
我才提交了一个补丁,既能满足 @twiddling Wen LSP Server 不需要中文前缀的需求, 又可以满足 @blove 需要中文前缀的需求, 这个补丁以后, @blove 不需要定制 acm-get-input-prefix-bound
函数。
太好啦!大佬太赞啦👍🏻
赞同, 改成让 “高级用户” 自己定义更准确,比如像 @blove 自己写后端,如果只是使用的话,确实不需要知道这个函数是啥。
我现在尝试给 wenls 加上中文补全,因此也得检测中文分词边界了(而且想用 lsp 返回的分词信息来计算边界),这部分 可能还得多碰到些奇怪的后端需求才能更清楚怎么来灵活处理边界, 。
其实可以考虑 Wen LSP Server 来处理 elisp 返回 symbol 的分词边界, 这样我就可以去掉 Fix issue #356 · manateelazycat/lsp-bridge@09e091a · GitHub 这种 workaround 的补丁。
lsp-bridge 中越少的 LSP Server 兼容代码越好。
是的,这个我先试试看
请问有什么好的方法关闭某种mode对lsp-bridge的hook吗?我在org-mode里面不想用wenls,但是想在org文件的代码块(比如python代码块)里面使用 lsp-bridge. 比较丑陋的方法是把 lsp-bridge-default-mode-hooks 里面的 org-mode 直接注释掉。
我晚上加一个选项,这样就不用硬改alist了
好的,感谢为这个我自己都还没谱的语言服务专门加一个开关,