Disable the IME on Windows when text input is unexpected (#51041)

Fixes #42444

- Changed `accepts_text_input` on the editor to be more precise.
Previously, it returned `true` only in insert mode. Now it also returns
`true` when an operator is pending.
- On Windows, we disable the IME whenever there is no input handler
which `accepts_text_input`.
- How this improves Vim mode: in insert mode, the IME is enabled; in
normal mode, it is disabled (command keys are not intercepted); when an
operator is pending, the IME is re-enabled.

Release Notes:

- On Windows, the IME is disabled in Vim normal and visual modes.
This commit is contained in:
John Tur authored and GitHub committed 2026-03-08 04:54:05 +00:00
1 parent b89152e516
commit 850943f89e
5 files changed
+111 -22

No files matched your search

+7 -1
View File
@@ -1233,6 +1233,7 @@ pub struct Editor {
autoindent_mode: Option<AutoindentMode>,
workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
input_enabled: bool,
expects_character_input: bool,
use_modal_editing: bool,
read_only: bool,
leader_id: Option<CollaboratorId>,
@@ -2469,6 +2470,7 @@ impl Editor {
collapse_matches: false,
workspace: None,
input_enabled: !is_minimap,
expects_character_input: !is_minimap,
use_modal_editing: full_mode,
read_only: is_minimap,
use_autoclose: true,
@@ -3365,6 +3367,10 @@ impl Editor {
self.input_enabled = input_enabled;
}
pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
self.expects_character_input = expects_character_input;
}
pub fn set_edit_predictions_hidden_for_vim_mode(
&mut self,
hidden: bool,
@@ -28409,7 +28415,7 @@ impl EntityInputHandler for Editor {
}
fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
self.input_enabled
self.expects_character_input
}
}
+7
View File
@@ -1062,6 +1062,13 @@ impl PlatformInputHandler {
pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
self.handler.accepts_text_input(window, cx)
}
#[allow(dead_code)]
pub fn query_accepts_text_input(&mut self) -> bool {
self.cx
.update(|window, cx| self.handler.accepts_text_input(window, cx))
.unwrap_or(true)
}
}
/// A struct representing a selection in a text buffer, in UTF16 characters.
+82 -21
View File
@@ -593,33 +593,63 @@ impl WindowsWindowInner {
}
pub(crate) fn update_ime_position(&self, handle: HWND, caret_position: POINT) {
let Some(ctx) = ImeContext::get(handle) else {
return;
};
unsafe {
let ctx = ImmGetContext(handle);
if ctx.is_invalid() {
return;
}
ImmSetCompositionWindow(
*ctx,
&COMPOSITIONFORM {
dwStyle: CFS_POINT,
ptCurrentPos: caret_position,
..Default::default()
},
)
.ok()
.log_err();
let config = COMPOSITIONFORM {
dwStyle: CFS_POINT,
ptCurrentPos: caret_position,
..Default::default()
};
ImmSetCompositionWindow(ctx, &config).ok().log_err();
let config = CANDIDATEFORM {
dwStyle: CFS_CANDIDATEPOS,
ptCurrentPos: caret_position,
..Default::default()
};
ImmSetCandidateWindow(ctx, &config).ok().log_err();
ImmReleaseContext(handle, ctx).ok().log_err();
ImmSetCandidateWindow(
*ctx,
&CANDIDATEFORM {
dwStyle: CFS_CANDIDATEPOS,
ptCurrentPos: caret_position,
..Default::default()
},
)
.ok()
.log_err();
}
}
fn update_ime_enabled(&self, handle: HWND) {
let ime_enabled = self
.with_input_handler(|input_handler| input_handler.query_accepts_text_input())
.unwrap_or(false);
if ime_enabled == self.state.ime_enabled.get() {
return;
}
self.state.ime_enabled.set(ime_enabled);
unsafe {
if ime_enabled {
ImmAssociateContextEx(handle, HIMC::default(), IACE_DEFAULT)
.ok()
.log_err();
} else {
if let Some(ctx) = ImeContext::get(handle) {
ImmNotifyIME(*ctx, NI_COMPOSITIONSTR, CPS_COMPLETE, 0)
.ok()
.log_err();
}
ImmAssociateContextEx(handle, HIMC::default(), 0)
.ok()
.log_err();
}
}
}
fn handle_ime_composition(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
let ctx = unsafe { ImmGetContext(handle) };
let result = self.handle_ime_composition_inner(ctx, lparam);
unsafe { ImmReleaseContext(handle, ctx).ok().log_err() };
result
let ctx = ImeContext::get(handle)?;
self.handle_ime_composition_inner(*ctx, lparam)
}
fn handle_ime_composition_inner(&self, ctx: HIMC, lparam: LPARAM) -> Option<isize> {
@@ -1123,6 +1153,7 @@ impl WindowsWindowInner {
});
self.state.callbacks.request_frame.set(Some(request_frame));
self.update_ime_enabled(handle);
unsafe { ValidateRect(Some(handle), None).ok().log_err() };
Some(0)
@@ -1205,6 +1236,36 @@ impl WindowsWindowInner {
}
}
struct ImeContext {
hwnd: HWND,
himc: HIMC,
}
impl ImeContext {
fn get(hwnd: HWND) -> Option<Self> {
let himc = unsafe { ImmGetContext(hwnd) };
if himc.is_invalid() {
return None;
}
Some(Self { hwnd, himc })
}
}
impl std::ops::Deref for ImeContext {
type Target = HIMC;
fn deref(&self) -> &HIMC {
&self.himc
}
}
impl Drop for ImeContext {
fn drop(&mut self) {
unsafe {
ImmReleaseContext(self.hwnd, self.himc).ok().log_err();
}
}
}
fn handle_key_event<F>(
wparam: WPARAM,
lparam: LPARAM,
+2
View File
@@ -52,6 +52,7 @@ pub struct WindowsWindowState {
pub callbacks: Callbacks,
pub input_handler: Cell<Option<PlatformInputHandler>>,
pub ime_enabled: Cell<bool>,
pub pending_surrogate: Cell<Option<u16>>,
pub last_reported_modifiers: Cell<Option<Modifiers>>,
pub last_reported_capslock: Cell<Option<Capslock>>,
@@ -142,6 +143,7 @@ impl WindowsWindowState {
min_size,
callbacks,
input_handler: Cell::new(input_handler),
ime_enabled: Cell::new(true),
pending_surrogate: Cell::new(pending_surrogate),
last_reported_modifiers: Cell::new(last_reported_modifiers),
last_reported_capslock: Cell::new(last_reported_capslock),
+13
View File
@@ -978,6 +978,7 @@ impl Vim {
editor.set_clip_at_line_ends(false, cx);
editor.set_collapse_matches(false);
editor.set_input_enabled(true);
editor.set_expects_character_input(true);
editor.set_autoindent(true);
editor.selections.set_line_mode(false);
editor.unregister_addon::<VimAddon>();
@@ -1346,6 +1347,15 @@ impl Vim {
}
}
fn expects_character_input(&self) -> bool {
if let Some(operator) = self.operator_stack.last() {
if operator.is_waiting(self.mode) {
return true;
}
}
self.editor_input_enabled()
}
pub fn editor_input_enabled(&self) -> bool {
match self.mode {
Mode::Insert => {
@@ -2058,6 +2068,7 @@ impl Vim {
clip_at_line_ends: self.clip_at_line_ends(),
collapse_matches: !HelixModeSetting::get_global(cx).0,
input_enabled: self.editor_input_enabled(),
expects_character_input: self.expects_character_input(),
autoindent: self.should_autoindent(),
cursor_offset_on_selection: self.mode.is_visual(),
line_mode: matches!(self.mode, Mode::VisualLine),
@@ -2075,6 +2086,7 @@ impl Vim {
editor.set_clip_at_line_ends(state.clip_at_line_ends, cx);
editor.set_collapse_matches(state.collapse_matches);
editor.set_input_enabled(state.input_enabled);
editor.set_expects_character_input(state.expects_character_input);
editor.set_autoindent(state.autoindent);
editor.set_cursor_offset_on_selection(state.cursor_offset_on_selection);
editor.selections.set_line_mode(state.line_mode);
@@ -2087,6 +2099,7 @@ struct VimEditorSettingsState {
clip_at_line_ends: bool,
collapse_matches: bool,
input_enabled: bool,
expects_character_input: bool,
autoindent: bool,
cursor_offset_on_selection: bool,
line_mode: bool,