每个输入前都会自动加一个字符,有没有人遇到过?

赞,真希望这个能彻底解决了,不过我这里好久没出现过了……Emacs 、Mac一直也没升级过

他这个好像解决的是另一个问题 :laughing:

解决重复的补丁是

是同一个问题,原来的补丁有点bug,现在修好了。

1 个赞

:+1:

简述一下这个问题的发生条件:

  1. 使用 emacs NS port(官方 emacs 而非 emacs-macport)
  2. 开启了系统输入法 [1]
  3. 高频输入,特别是开着 corfu-auto、company-idle=0 时,只要卡一下使得 lisp call 还没跑完下一个键事件就来了就行。我用 citre 基本可以在 20 秒内复现这个问题。

问题原因有点懒得打,不过还是敲一下好了:打开系统输入法 [1, again],如果打开了 nsterm.m 里的 NS_KEYLOG,应该在每次按键的时候都能看到 firstRectForCharacterRange 的调用记录。在开了输入法的情况下,每次敲击都会导致 firstRectForCharacterRange 的一次调用。

firstRectForCharacterRange 会在内部调用一个叫 ns-in-echo-area 的 Lisp 函数(在 term/ns-win.el 里)。在特定情况下,ns-in-echo-area 会 throw,而 firstRectForCharacterRange 里对此没有保护,导致 keyDown 里的 remove event 操作没有完成,于是 event buffer 里会留存之前的 event,这也是为什么系统输入法也会收到额外的键(我一开始就是因为这一点怀疑 NS 相关代码有问题的)。(于是,想要加速复现还有一个办法是在 firstRectForCharacterRange 里 sleep 短暂的一段时间。)

找到问题 fix 就其实很简单了。

此外,这个问题是 emacs NS port 代码中的问题,emacs-macport (使用 Carbon 而非 Cocoa)是另一套实现,所以可能没有该问题。

[1] 置于英文状态也可。但若设为英文键盘则不会调用上述函数,也就不会有问题了。这可能是为什么有的人切换到 emacs-rime 后不再遇到问题的原因,大概是因为把系统键盘设为英文键盘了。

18 个赞

赞啊,困扰了大家好长时间,一直没人找到完美复现的方法,都在猜哈哈哈🤣

赞,辛苦大佬!被这个问题折磨好久了,不得切换到 pyim,现在终于解决了。

这就叫专业 :rofl:

哈哈,在这个场景,pyim起到了它设计的初衷:备用应急

1 个赞

小白一枚:请教大佬,这个补丁是不是只能通过重新build一次emacs才能生效?有办法热替换么,不是很想重新build一次。

更新:

在tg群里得到大佬回复:不能,只能使用编译好的emacs。

应用大神你的补丁后,重新编译emacs源代码,安装编译出来的 Emacs.app ,测试中文输入法没有出现这个问题了。是不是可以直接分发编译出来的这个 Emacs.app ?

肯定可以的。

BTW,这个问题昨天在 master 分支已经修了,emacs-plus 用户直接 brew install emacs-plus@29 就好。

5 个赞

也就是说不用 patch 啦?好,我这就再编译一遍(

必须大赞,坛子里藏龙卧虎啊 :+1:

感谢大佬,这就重新编译。

大赞,我今天也重新编译了 emacs-plus@29。

现在如果编译 28 应该打哪个补丁?

1

2

3

老bug了,说是升级至emacs29就好了

From 47b377f64bef8c3da519b3aa9c5c90b7199ba524 Mon Sep 17 00:00:00 2001
From: Po Lu <[email protected]>
Date: Sun, 13 Nov 2022 09:03:51 +0800
Subject: [PATCH] Prevent non-local exits from ns-in-echo-area

* src/nsterm.m (ns_in_echo_area_1):
(ns_in_echo_area_2):
(ns_in_echo_area): New functions.
([EmacsView firstRectForCharacterRange:]): Call them instead.
(syms_of_nsterm): New defsym.
---
 src/nsterm.m | 33 ++++++++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/src/nsterm.m b/src/nsterm.m
index 17f40dc7e37..507f2a9e7da 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -7056,6 +7056,36 @@ - (void)unmarkText
   processingCompose = NO;
 }
 
+static Lisp_Object
+ns_in_echo_area_1 (void *ptr)
+{
+  Lisp_Object in_echo_area;
+  specpdl_ref count;
+
+  count = SPECPDL_INDEX ();
+  specbind (Qinhibit_quit, Qt);
+  in_echo_area = safe_call (1, Qns_in_echo_area);
+
+  return unbind_to (count, in_echo_area);
+}
+
+static Lisp_Object
+ns_in_echo_area_2 (enum nonlocal_exit exit, Lisp_Object error)
+{
+  return Qnil;
+}
+
+static bool
+ns_in_echo_area (void)
+{
+  Lisp_Object in_echo_area;
+
+  in_echo_area
+    = internal_catch_all (ns_in_echo_area_1, NULL,
+			  ns_in_echo_area_2);
+
+  return !NILP (in_echo_area);
+}
 
 /* Used to position char selection windows, etc.  */
 - (NSRect)firstRectForCharacterRange: (NSRange)theRange
@@ -7069,7 +7099,7 @@ - (NSRect)firstRectForCharacterRange: (NSRange)theRange
   if (NS_KEYLOG)
     NSLog (@"firstRectForCharRange request");
 
-  if (WINDOWP (echo_area_window) && ! NILP (call0 (intern ("ns-in-echo-area"))))
+  if (WINDOWP (echo_area_window) && ns_in_echo_area ())
     win = XWINDOW (echo_area_window);
   else
     win = XWINDOW (FRAME_SELECTED_WINDOW (emacsframe));
@@ -11012,6 +11042,7 @@ Nil means use fullscreen the old (< 10.7) way.  The old way works better with
   DEFSYM (Qcondensed, "condensed");
   DEFSYM (Qreverse_italic, "reverse-italic");
   DEFSYM (Qexpanded, "expanded");
+  DEFSYM (Qns_in_echo_area, "ns-in-echo-area");
 
 #ifdef NS_IMPL_COCOA
   Fprovide (Qcocoa, Qnil);
-- 
2.41.0

无论你的 fix-ghost-key-v4.patch 还是 polu 的补丁都无法直接应用到 emacs-28.2。需要手动添加最后一行修改。

polu 的补丁打完之后编译失败,显示新添加的函数参数类型错误之类的,我没有细看,然后用了你的 v4 补丁。


我现在用的是 polu 的不定。

先前打补丁的过程出了点问题,实际上不管用哪个不定都需要修改:

- +  specpdl_ref count;
+ +  ptrdiff_t count;

28.2 里没有定义 specpdl_ref