From b10d6c36e1a96692d8ae1a393d1dd3feea2e7ee3 Mon Sep 17 00:00:00 2001 From: temportalflux Date: Sun, 21 Jun 2026 14:18:37 -0400 Subject: [PATCH] implement remaining ime operations now that layout lines are stored add missing ime operations to textarea --- .../src/editable_text/input_state.rs | 12 ++- .../src/editable_text/shared_state.rs | 90 ++++++++++++++++++- .../src/editable_text/text_area_state.rs | 12 ++- 3 files changed, 105 insertions(+), 9 deletions(-) diff --git a/crates/gpui_elements/src/editable_text/input_state.rs b/crates/gpui_elements/src/editable_text/input_state.rs index 531e569324..a901549e27 100644 --- a/crates/gpui_elements/src/editable_text/input_state.rs +++ b/crates/gpui_elements/src/editable_text/input_state.rs @@ -125,19 +125,23 @@ impl EntityInputHandler for TextInputState { &mut self, range_utf16: Range, bounds: Bounds, - _window: &mut Window, + window: &mut Window, _cx: &mut Context, ) -> Option> { - unimplemented!() + self.internal + .ime_bounds_for_range(range_utf16, bounds, window) } fn character_index_for_point( &mut self, point: Point, - _window: &mut Window, + window: &mut Window, _cx: &mut Context, ) -> Option { - unimplemented!() + let index = self + .internal + .index_for_pixel_point(point, window.line_height()); + Some(self.internal.storage().utf_offset_8to16(index)) } } diff --git a/crates/gpui_elements/src/editable_text/shared_state.rs b/crates/gpui_elements/src/editable_text/shared_state.rs index be643359dc..044c72de79 100644 --- a/crates/gpui_elements/src/editable_text/shared_state.rs +++ b/crates/gpui_elements/src/editable_text/shared_state.rs @@ -4,7 +4,7 @@ use crate::editable_text::{ }; use gpui::{ App, Bounds, ClipboardItem, Entity, FocusHandle, Focusable, Hsla, NavigationDirection, Pixels, - Point, SharedString, TextRun, TextStyle, UTF16Selection, Window, WrappedLine, + Point, SharedString, TextRun, TextStyle, UTF16Selection, Window, WrappedLine, point, }; use std::{ops::Range, sync::Arc}; @@ -264,6 +264,40 @@ impl TextInputStateBase { lines } + + /// Returns the utf-8 character position of the start of the line that contains the provided pixel-point. + pub fn index_for_pixel_point(&self, point: Point, line_height: Pixels) -> usize { + let storage_len_utf8 = self.storage.content_utf8().len(); + if storage_len_utf8 == 0 { + return 0; + } + + for line in &self.layout_data.lines { + let y_offset = line.pos_y * line_height; + let line_height_total = line_height * line.num_visual_lines as f32; + + if point.y >= y_offset && point.y < y_offset + line_height_total { + if line.text_range.is_empty() { + return line.text_range.start; + } + let Some(wrapped) = &line.wrapped_line else { + return line.text_range.start; + }; + + let relative_y = point.y - y_offset; + let relative_point = gpui::point(point.x, relative_y); + + let closest_result = + wrapped.closest_index_for_position(relative_point, line_height); + + let local_idx = closest_result.unwrap_or_else(|closest| closest); + let clamped = local_idx.min(wrapped.text.len()); + return line.text_range.start + clamped; + } + } + + storage_len_utf8 + } } impl TextInputStateBase { @@ -384,6 +418,60 @@ impl TextInputStateBase { }) }; } + + pub fn ime_bounds_for_range( + &mut self, + range_utf16: Range, + bounds: Bounds, + window: &mut Window, + ) -> Option> { + let range = self.storage.utf_range_16to8(&range_utf16); + let line_height = window.line_height(); + + for line in &self.layout_data.lines { + let y_offset = line.pos_y * line_height; + if line.text_range.is_empty() { + if range.start == line.text_range.start { + return Some(Bounds::from_corners( + bounds.origin + point(Pixels::ZERO, y_offset), + bounds.origin + point(gpui::px(4.), y_offset + line_height), + )); + } + } else if line.text_range.contains(&range.start) { + if let Some(wrapped) = &line.wrapped_line { + let local_start = range.start - line.text_range.start; + let local_end = (range.end - line.text_range.start).min(wrapped.text.len()); + + let start_pos = wrapped + .position_for_index(local_start, line_height) + .unwrap_or(point(Pixels::ZERO, Pixels::ZERO)); + let end_pos = wrapped + .position_for_index(local_end, line_height) + .unwrap_or_else(|| { + let last_line_y = line_height * (line.num_visual_lines - 1) as f32; + point(wrapped.width(), last_line_y) + }); + + let start_visual_line = (start_pos.y / line_height).floor() as usize; + let end_visual_line = (end_pos.y / line_height).floor() as usize; + + if start_visual_line == end_visual_line { + return Some(Bounds::from_corners( + bounds.origin + start_pos + point(Pixels::ZERO, y_offset), + bounds.origin + point(end_pos.x, y_offset + start_pos.y + line_height), + )); + } else { + return Some(Bounds::from_corners( + bounds.origin + start_pos + point(Pixels::ZERO, y_offset), + bounds.origin + + point(wrapped.width(), y_offset + start_pos.y + line_height), + )); + } + } + } + } + None + } } impl TextInputStateBase { diff --git a/crates/gpui_elements/src/editable_text/text_area_state.rs b/crates/gpui_elements/src/editable_text/text_area_state.rs index 3661caa312..66e95d890d 100644 --- a/crates/gpui_elements/src/editable_text/text_area_state.rs +++ b/crates/gpui_elements/src/editable_text/text_area_state.rs @@ -125,19 +125,23 @@ impl EntityInputHandler for TextAreaState { &mut self, range_utf16: Range, bounds: Bounds, - _window: &mut Window, + window: &mut Window, _cx: &mut Context, ) -> Option> { - unimplemented!() + self.internal + .ime_bounds_for_range(range_utf16, bounds, window) } fn character_index_for_point( &mut self, point: Point, - _window: &mut Window, + window: &mut Window, _cx: &mut Context, ) -> Option { - unimplemented!() + let index = self + .internal + .index_for_pixel_point(point, window.line_height()); + Some(self.internal.storage().utf_offset_8to16(index)) } }