大家有在 mac 上面使用 emacs-rime 的吗?是怎么和 mac 的输入法协调使用的?

我想试试看 emacs-rime,但是我需要给他单独配置一个激活的按键么?这样就感觉好像有点割裂,其他地方使用一个按键激活,emacs 里面使用另一个。有没有办法统一呢?我在 mac 使用 rime.

我用 better touch tool 设置了【在非 emacs/ghostty 中按 C-SPC 时切换输入法,否则传递 C-SPC 给应用】

这样就全局统一用 C-SPC 切换了

1 个赞

karabiner-elements 可以让你对相同的快捷键在 emacs 里和 emacs 外进行不同的映射,我是把 Shift 键映射了。同时限制在 emacs 里系统输入法强制切换到英文输入法,这点用 hammperspoon 和 karabiner-elements 都可以做到,也有其它类似的软件。

如果觉得用外部改键麻烦,也可以考虑 key-echo。

key-echo: 单按修饰键来触发Emacs命令 - Emacs-general - Emacs China

emacs中输入法不是有统一输入法切换键C-\吗?

是的 我也是用的 karabiner-elements , 它可以根据当前系统输入法的状态,当前激活的app 是什么 对键做映射 通 过 frontmost_application_unless frontmost_application_if input_source_if input_source_unless 等对当前状态进行判断 ,符合条件才执行 key 映射。

squirrel.custom.yaml:

patch:
  app_options:
    com.google.Chrome:
      # 規避 https://github.com/rime/squirrel/issues/435
      inline: true
    ru.keepcoder.Telegram:
      # 規避 https://github.com/rime/squirrel/issues/475
      inline: true
    org.gnu.Emacs:
      ascii_mode: true
      no_inline: true

mac下直接全局切鼠须管就好了吧。。。挺好使

在macOS上不用 emacs-rime,直接使用鼠须管体验和系统一致。 通过 sis 实现自动在 C-x 是切为英文。

我的解法比较暴力,我把系统的输入法切换快捷键设置为 C-\ (emacs 的快捷键),然后 emacs 里面的改为 shift ,我假想 emacs 始终开着输入法,只是通过 shift 切换 ascii 模式。

有点掩耳盗铃,但逻辑上能说通…… :joy:

我不是使用 mac 的,在 linux 上我是直接在启动方式里面修改 emacs 的环境变量禁用系统输入法,只在 emacs 中使用 emacs-rime。

env XMODIFIERS= GTK_IM_MODULE=none emacs

感谢各位的思路。我最后搞定了。但是用了几天用不下去了,主要是因为 rime 在 emacs 里面会崩溃。崩溃后我的输入都丢了,不敢继续用了。我也不知道是不是因为我在使用 emacs31+ghostel的缘故。换回 Squirrel 了。

使用 Karabiner-Elements + Hammerspoon 组合,我的最佳实践

我的配置 https://github.com/SLin0218/nix-config.git

我习惯使用 Capslock + space 切换输入法,在 Emacs 中映射为 Ctrl + \

{
  "conditions": [
    {
      "bundle_identifiers": ["^org\\.gnu\\.Emacs$"],
      "type": "frontmost_application_if"
    }
  ],
  "from": {
    "key_code": "spacebar",
    "modifiers": {
      "mandatory": [
        "right_command",
        "right_control",
        "right_option",
        "right_shift"
      ]
    }
  },
  "to": [
    {
      "key_code": "backslash",
      "modifiers": ["left_control"]
    }
  ],
  "type": "basic"
}

Emacs 始终保持为默认输入法 ABC 避免冲突。并且会记住当前聚焦 App 的输入法,进行自动切换。

---@diagnostic disable: undefined-global
--- === IM Auto Switch ===

local obj = {}
obj.__index = obj

-- Metadata
obj.name = "IM"
obj.version = "1.1.0"
obj.author = "DengShiLin <[email protected]>"

-- 1. 用于记录每个 App 选择的输入法,刚开始为空
obj.appInputSources = {}
obj.cjk = "im.rime.inputmethod.Squirrel.Hans"

-- 固定输入法的 App 规则(在 init() 中动态初始化,以避免 Hammerspoon 启动时系统输入法列表未加载完毕的问题)
obj.fixedApps = {}

-- 面板/代理类应用(这些应用在获得和失去焦点时不会触发系统的 applicationWatcher)
obj.panelApps = {
	["Raycast"] = true,
	["Alfred"] = true,
	["Spotlight"] = true,
	["Search"] = true,
}

-- 调试日志开关
obj.debug = false

-- 辅助函数:输出调试日志
local function logDebug(fmt, ...)
	if obj.debug then
		print(string.format("[IM] " .. fmt, ...))
	end
end

-- 统一的自动切换输入法逻辑
function obj:switchInputForApp(appName)
	if not appName then
		return
	end
	local targetInput = obj.fixedApps[appName] or obj.appInputSources[appName] or obj.defaultLayout
	local currentInput = hs.keycodes.currentSourceID()
	if currentInput ~= targetInput then
		logDebug("Auto Switch (%s): %s -> %s", "Focus/Activate", appName, targetInput)
		local success = hs.keycodes.currentSourceID(targetInput)
		if not success then
			print(string.format("[IM] Warning: Failed to switch input source to %s", targetInput))
		end
	end
end

-- 监听 App 激活事件(常规应用)
local function applicationWatcher(appName, eventType, _)
	if eventType == hs.application.watcher.activated then
		obj:switchInputForApp(appName)
	end
end

-- 监听输入法切换事件(订阅系统通知,比 hs.keycodes.inputSourceChanged 更稳定)
local function inputSourceCallback(_, _, _)
	-- 优先获取 frontmostApplication (避免调用慢速的 accessibility 接口)
	local frontApp = hs.application.frontmostApplication()
	local frontAppName = frontApp and frontApp:name()

	-- 如果是面板类特殊应用,或者无法获取 frontmostApp 名字,则降级获取 focusedWindow (较慢)
	local focusedApp = nil
	local focusedAppName = nil
	if not frontAppName or obj.panelApps[frontAppName] then
		local focusedWindow = hs.window.focusedWindow()
		focusedApp = focusedWindow and focusedWindow:application()
		focusedAppName = focusedApp and focusedApp:name()
	end

	logDebug("Debug Focus: focusedWindowApp=%s, frontmostApp=%s", focusedAppName or "nil", frontAppName or "nil")

	-- 优先通过当前获得键盘焦点的窗口来获取当前活动 App
	local activeApp = focusedApp or frontApp
	if activeApp then
		local appName = activeApp:name()
		if appName then
			-- 如果是 Raycast、Alfred 等特殊面板应用,我们不记录其输入法状态(避免污染前台应用记录)
			-- 而是开启一个临时的轻量级监听定时器,等它失焦后切回先前活动 App 的输入法
			if obj.panelApps[appName] then
				if not obj.panelFocusTimer then
					logDebug("Started temporary focus timer for panel app: %s", appName)
					obj.panelFocusTimer = hs.timer.doEvery(0.1, function()
						local fApp = hs.application.frontmostApplication()
						local currentAppName = fApp and fApp:name()

						if not currentAppName or obj.panelApps[currentAppName] then
							local w = hs.window.focusedWindow()
							local app = w and w:application()
							local name = app and app:name()
							if name then
								currentAppName = name
							end
						end

						-- 如果焦点已经不是这些面板应用了,说明面板已关闭或失焦
						if currentAppName and not obj.panelApps[currentAppName] then
							logDebug("Panel app lost focus. Restoring input source for active app: %s", currentAppName)
							obj:switchInputForApp(currentAppName)
							-- 停止并清理定时器
							if obj.panelFocusTimer then
								obj.panelFocusTimer:stop()
								obj.panelFocusTimer = nil
							end
						end
					end)
				end
			else
				-- 正常应用,如果不是固定输入法的 App,则记录其输入法变更
				if not obj.fixedApps[appName] then
					local currentInput = hs.keycodes.currentSourceID()
					-- 如果当前记录值与实际输入法不一致,更新并记录
					if obj.appInputSources[appName] ~= currentInput then
						obj.appInputSources[appName] = currentInput
						logDebug("Recorded: %s -> %s", appName, currentInput)
					end
				end
			end
		end
	end
end

function obj:init()
	-- 获取默认布局 (增加 nil 安全检查与 fallback)
	local layouts = hs.keycodes.layouts(true)
	obj.defaultLayout = (layouts and #layouts > 0) and layouts[1] or "com.apple.keylayout.ABC"

	-- 设置默认的固定输入法规则
	local defaultFixedApps = {
		["微信"] = obj.cjk,
		["WeChat"] = obj.cjk,
		["WeLink"] = obj.cjk,
		["Emacs"] = obj.defaultLayout,
		["IntelliJ IDEA"] = obj.defaultLayout,
	}

	-- 将默认规则合并到 obj.fixedApps 中(不覆盖用户已有的设置)
	obj.fixedApps = obj.fixedApps or {}
	for k, v in pairs(defaultFixedApps) do
		if obj.fixedApps[k] == nil then
			obj.fixedApps[k] = v
		end
	end

	-- 创建 App 监听器 (绑定在 obj 上以防止垃圾回收)
	obj.appWatcher = hs.application.watcher.new(applicationWatcher)

	-- 创建输入法变更监听器 (绑定在 obj 上以防止垃圾回收)
	obj.inputWatcher = hs.distributednotifications.new(
		inputSourceCallback,
		"com.apple.Carbon.TISNotifySelectedKeyboardInputSourceChanged"
	)

	-- 自动启动
	obj:start()
end

function obj:start()
	if obj.appWatcher then
		obj.appWatcher:start()
	end
	if obj.inputWatcher then
		obj.inputWatcher:start()
	end
	return self
end

function obj:stop()
	if obj.appWatcher then
		obj.appWatcher:stop()
	end
	if obj.inputWatcher then
		obj.inputWatcher:stop()
	end
	if obj.panelFocusTimer then
		obj.panelFocusTimer:stop()
		obj.panelFocusTimer = nil
	end
	return self
end

return obj

可以试试rimel

设计哲学

Rimel极简主义

  • 一个文件、一个依赖、无 C 代码
  • 使用 read-event 循环,与 Emacs 内置 quail 完全相同的模式
  • 不借助 pre-command-hook/post-self-insert-hook 等hook
  • 合成过程自包含:函数进入时开始,返回时结束,无状态泄露
  • 适合想要最简单可用的 rime 输入法的用户

emacs-rime功能完备

  • 自带 C 动态模块(lib.c),独立于 liberime
  • 支持 5 种候选展示方式(minibuffer/message/popup/posframe/sidewindow)
  • 支持 predicates 系统(自动根据上下文切换中英文)
  • 支持 inline ASCII 模式
  • 使用 minor mode + overriding-terminal-local-map 处理合成期间按键
  • 适合需要深度定制和高级功能的用户

pyim + pyim-liberime框架化

  • pyim 是完整的输入法框架,liberime 只是其中一个后端
  • 支持多种输入方案(全拼、双拼、五笔等),rime 只是其一
  • 有独立的词频管理、词库、云输入等功能
  • pyim-liberime 仅 326 行,本质是 liberime-search() 的薄包装
  • 适合需要多输入法统一管理的用户

我用了这个 rimel,就是它崩溃的。Claude 说是因为 childframe 光标跟随对位置断言导致的崩溃。给出的建议是使用 echo area 但是用了一会也不习惯。

emacs-rimerimel 是两个不同的包,确定用的是同一个?

两个都用了。

有报错内容吗