使用lsp-mode时的一个坑,以及对call-process的困惑。

问题已经解决,不过或许有更好的解决方案。

我在写这个提问的过程中,自己慢慢理清了思路,最终比较丑陋的解决了问题。但是已经写了那么多了,不想删掉。我想,分享一下思考、疑惑的过程应该也是有价值的。


我在终端里能正常启动,lsp-mode跟lsp-ui。但是在GUI下就是不行!我想这应该是环境变量有问题,but我把终端里的所有环境变量都setenv到emacs里,还是不行。

下面是报错信息,这个报错信息在我刚装lsp-python-mode的时候出现过。那时是在终端里操作的,依然不错的原因是pyls没有安装。现在在GUI里,我执行(shell-command-to-string “which pyls”) --> /usr/local/bin/pyls,说明不是找不到pyls的缘故(或者这不能说明什么,ls-mode用的是另一种方式启动pyls的?)

Error running timer ‘lsp-ui-sideline--run’: (wrong-type-argument hash-table-p nil) [2 times]
Error in post-command-hook (lsp-ui-doc--make-request): (error "Buffer models.py has no process")
Error running timer ‘lsp-ui-sideline--run’: (wrong-type-argument hash-table-p nil) [8 times]

也许真的是启动方式不一样,我立刻想到了call-process,实验了一下,果然:

(call-process "which"
	      nil t "*scratch*"
	      "pyls")			; --> /usr/local/bin
(call-process "pyls"
	      nil t "*scratch*"
	      "-h")			; --> 报错,No such file or directory "pyls"

(shell-command-to-string "which pyls")	; --> /usr/local/bin
(shell-command-to-string "pyls -h")	; --> 正常执行,打印了帮助信息

我很好奇为什么它能which pyls出来,却不能执行pyls,难道call-process不走环境变量那一套? 突然明白了什么,`PATH环境变量给进程用来寻找cmd的,不是用来定位要启动的进程的。不知道这样理解是否正确。

然后我去注释掉(require 'lap-python-mode),自己写了那个define,cmd指定为pyls的绝对路径:

(lsp-define-stdio-client lsp-python "python"
			 (lsp-make-traverser #'(lambda (dir)
						 (when (directory-files dir nil ".git") ;hardcode,只有项目root目录才可以是python root
						   (directory-files
						    dir
						    nil
						    "\\(__init__\\|setup\\)\\.py"))))
			 '("/usr/local/bin/pyls"))

然后,这样就work了!但是是否有更好的解决方案?

我感觉这应该算是lsp-mode的坑,内部去执行一遍(shell-comamnd-to-string (format “which %s” cmd))来获取cmd的path,再用这个path去调用call-process是不是会更好一些?

另外我还是无法理解,如果环境变量不是关键因素,那么为什么终端里启动的emacs能辨析pyls而GUI的却不行? 我的意思是终端里的emacs为什么可以直接(call-process "pyls"... ...)?难道它不需要指定绝对路径吗?

感觉还是你PATH的问题……

但是为什么(call-process)执行"which pyls"能找到pyls。:joy:,这点让我困惑不已。

不懂 elisp,不过对你的疑惑有点判断。

交互式 shell 会读取系统环境变量,而 GUI 的一般却不会。

在 GUI 的 Emacs 里能执行 which pyls,不代表它就一定知道 pyls 具体位置在哪。

对于有些操作系统来说,/usr/local/bin 这个目录都不一定存在。你让它去读这个目录?


所以,这个问题有两种解决思路:

  1. 如果 GUI 有设定读取目录的选项,那就设置一下。

  2. 如果没有选项或者不确定,调用程序时,设定完整执行路径如 /usr/local/bin/pyls 是最好的解决办法。

这个解决思路应该适用于绝大部分的 GUI 和命令行程序。

其中的关键点是理解 交互式 shell

不太明白为啥(call-process "which" nil t "*scratch*" "pyls")能返回正确结果。

shell-command-to-string两个都能用是因为用的是shell的环境变量

你用的是什么系统?

用的是mac

Mac的话建议用这个包:exec-path-from-shell

交互式shell,让我放在潜意识里感受感受。

以前用IDEA的时候,貌似也有类似的问题,JDK是需要自己手动指定的。可能这样坑到一些入门小白了,后来IDEA干脆自己内置了一个JDK。

总之,感受感受。

谢谢,我去看看。听着名字貌似是个正确姿势。

Mac上GUI软件的环境变量一直有些问题,不过我直接用那个包傻瓜解决了,也没有多研究。

很强

谢谢二位 @casouri @ashfinal 。动车上 一直过隧道,恢复得慢。:joy:

  • call-process 用的是 Emacs 变量 exec-path
  • shell-command-to-string 相当于 (call-process "/bin/bash"),所以用的是环境变量 PATH
  • which(1) 用的也是环境变量 PATH(参见 which.c

程序启动时会自动获得环境变量,从终端中启动的 Emacs,它的 PATH 就是正在运行的 Shell 的;而从 Dock、Spotlight 等启动的 GUI Emacs 的 PATH 是操作系统赋予的。随后 Emacs 启动期间会用 PATH 初始化 exec-path

6 个赞

exec-pathPATH 保存一致就不会有这样的差异了。

正解啊~ exec-path跟path要保持一致。 call-process使用的是TM的exec-path :joy:

这就是“正确姿势”。姿势很重要。

我发现另外一个大坑也是浪费了我一个下午+一个晚上。
应该装全python-language-server[all]而不是python-language-server
而且lsp-python的Github上面并没有写全

1 个赞

看你的回复我又去github主页看了看,比之前多了不少东西。