use crate::ClipboardItem;
use gpui_types::{Bounds, Pixels, Point, px};
use std::{any::Any, ops::Range};
#[derive(Debug)]
pub struct UTF16Selection {
pub range: Range<usize>,
pub reversed: bool,
}
#[expect(missing_docs)]
pub trait PlatformInputHandlerDelegate: Any {
fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection>;
fn marked_text_range(&mut self) -> Option<Range<usize>>;
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted: &mut Option<Range<usize>>,
) -> Option<String>;
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
);
fn unmark_text(&mut self);
fn paste(&mut self, item: ClipboardItem);
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
fn apple_press_and_hold_enabled(&mut self) -> bool;
fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize>;
fn set_selected_text_range(&mut self, range_utf16: Range<usize>);
fn element_bounds(&mut self) -> Option<Bounds<Pixels>>;
fn text_length_utf16(&mut self) -> Option<usize>;
fn query_accepts_text_input(&mut self) -> bool;
fn query_prefers_ime_for_printable_keys(&mut self) -> bool;
fn text_input_editable_range(&mut self) -> Option<Range<usize>>;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
#[allow(dead_code)]
pub struct PlatformInputHandler {
delegate: Box<dyn PlatformInputHandlerDelegate>,
}
#[allow(dead_code)]
#[expect(missing_docs)]
impl PlatformInputHandler {
pub fn from_delegate(delegate: Box<dyn PlatformInputHandlerDelegate>) -> Self {
Self { delegate }
}
pub fn delegate_as_any_mut(&mut self) -> &mut dyn Any {
self.delegate.as_any_mut()
}
pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
self.delegate.selected_text_range(ignore_disabled_input)
}
pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
self.delegate.marked_text_range()
}
pub fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted: &mut Option<Range<usize>>,
) -> Option<String> {
self.delegate.text_for_range(range_utf16, adjusted)
}
pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
self.delegate.replace_text_in_range(replacement_range, text);
}
pub fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
) {
self.delegate
.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range);
}
pub fn unmark_text(&mut self) {
self.delegate.unmark_text();
}
pub fn paste(&mut self, item: ClipboardItem) {
self.delegate.paste(item);
}
pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
self.delegate.bounds_for_range(range_utf16)
}
pub fn apple_press_and_hold_enabled(&mut self) -> bool {
self.delegate.apple_press_and_hold_enabled()
}
pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
self.delegate.character_index_for_point(point)
}
pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
self.delegate.set_selected_text_range(range_utf16);
}
pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
self.delegate.element_bounds()
}
pub fn text_length_utf16(&mut self) -> Option<usize> {
self.delegate.text_length_utf16()
}
pub fn query_accepts_text_input(&mut self) -> bool {
self.delegate.query_accepts_text_input()
}
pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
self.delegate.query_prefers_ime_for_printable_keys()
}
pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
self.delegate.text_input_editable_range()
}
pub fn compute_ime_candidate_bounds(
marked_range: Option<Range<usize>>,
selection: &UTF16Selection,
mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
) -> Option<Bounds<Pixels>> {
if let Some(marked_range) = marked_range {
let mut line_start = marked_range.start;
let caret = selection.range.end;
if let Some(caret_bounds) = bounds_for_range(caret..caret) {
for i in (marked_range.start..caret).rev() {
if let Some(b) = bounds_for_range(i..i) {
if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
line_start = i + 1;
break;
}
}
}
}
bounds_for_range(line_start..line_start)
} else {
let offset = if selection.reversed {
selection.range.start
} else {
selection.range.end
};
bounds_for_range(offset..offset)
}
}
pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
let marked_range = self.marked_text_range();
let selection = self.selected_text_range(true)?;
Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
self.bounds_for_range(range)
})
}
}