Files
oak-gpui/crates/gpui_elements/src/input/state_input_handler.rs
T

214 lines
8.0 KiB
Rust

use super::unicode::UnicodeString;
use crate::input::InputStateEvent;
use gpui::{Bounds, Context, EntityInputHandler, Pixels, Point, UTF16Selection, Window, point, px};
use std::ops::Range;
impl EntityInputHandler for super::InputState {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted_range: &mut Option<Range<usize>>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<String> {
let range = self.utf_range_16to8(&range_utf16);
let clamped_range =
range.start.min(self.content().len())..range.end.min(self.content().len());
adjusted_range.replace(self.utf_range_8to16(&clamped_range));
Some(self.content()[clamped_range].to_string())
}
fn selected_text_range(
&mut self,
_ignore_disabled_input: bool,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<UTF16Selection> {
Some(UTF16Selection {
range: self.utf_range_8to16(&self.selected_range),
reversed: self.selection_reversed,
})
}
fn marked_text_range(
&self,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<Range<usize>> {
self.marked_range
.as_ref()
.map(|range| self.utf_range_8to16(range))
}
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
self.marked_range = None;
}
fn replace_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.utf_range_16to8(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
let range = range.start.min(self.content().len())..range.end.min(self.content().len());
let text_to_insert = self.layout.sanitize_content(new_text);
// Record patch for undo before modifying content
self.push_undo_patch(range.clone(), text_to_insert.len());
// Update cached UTF-16 length incrementally if available
if let Some(cached_len) = self.cached_utf16_len {
let removed_utf16_len: usize = self.content()[range.clone()]
.chars()
.map(|c| c.len_utf16())
.sum();
let added_utf16_len: usize = text_to_insert.chars().map(|c| c.len_utf16()).sum();
self.cached_utf16_len = Some(cached_len - removed_utf16_len + added_utf16_len);
}
crate::input::replace_range(&mut self.content, range.clone(), &text_to_insert);
self.selected_range =
range.start + text_to_insert.len()..range.start + text_to_insert.len();
self.marked_range.take();
self.needs_layout = true;
self.pause_cursor_blink(cx);
cx.emit(InputStateEvent::TextChanged);
cx.notify();
}
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range_utf16: Option<Range<usize>>,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.utf_range_16to8(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
let range = range.start.min(self.content().len())..range.end.min(self.content().len());
let text_to_insert = self.layout.sanitize_content(new_text);
// Update cached UTF-16 length incrementally if available
if let Some(cached_len) = self.cached_utf16_len {
let removed_utf16_len: usize = self.content()[range.clone()]
.chars()
.map(|c| c.len_utf16())
.sum();
let added_utf16_len: usize = text_to_insert.chars().map(|c| c.len_utf16()).sum();
self.cached_utf16_len = Some(cached_len - removed_utf16_len + added_utf16_len);
}
crate::input::replace_range(&mut self.content, range.clone(), &text_to_insert);
if !text_to_insert.is_empty() {
self.marked_range = Some(range.start..range.start + text_to_insert.len());
} else {
self.marked_range = None;
}
self.selected_range = new_selected_range_utf16
.as_ref()
.map(|range_utf16| self.utf_range_16to8(range_utf16))
.map(|new_range| new_range.start + range.start..new_range.end + range.start)
.unwrap_or_else(|| {
range.start + text_to_insert.len()..range.start + text_to_insert.len()
});
self.needs_layout = true;
cx.emit(InputStateEvent::TextChanged);
cx.notify();
}
fn bounds_for_range(
&mut self,
range_utf16: Range<usize>,
bounds: Bounds<Pixels>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<Bounds<Pixels>> {
let range = self.utf_range_16to8(&range_utf16);
for line in &self.line_layouts {
if line.text_range.is_empty() {
if range.start == line.text_range.start {
return Some(Bounds::from_corners(
point(bounds.left(), bounds.top() + line.y_offset),
point(
bounds.left() + px(4.),
bounds.top() + line.y_offset + self.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, self.line_height)
.unwrap_or(point(px(0.), px(0.)));
let end_pos = wrapped
.position_for_index(local_end, self.line_height)
.unwrap_or_else(|| {
let last_line_y =
self.line_height * (line.visual_line_count - 1) as f32;
point(wrapped.width(), last_line_y)
});
let start_visual_line = (start_pos.y / self.line_height).floor() as usize;
let end_visual_line = (end_pos.y / self.line_height).floor() as usize;
if start_visual_line == end_visual_line {
return Some(Bounds::from_corners(
point(
bounds.left() + start_pos.x,
bounds.top() + line.y_offset + start_pos.y,
),
point(
bounds.left() + end_pos.x,
bounds.top() + line.y_offset + start_pos.y + self.line_height,
),
));
} else {
return Some(Bounds::from_corners(
point(
bounds.left() + start_pos.x,
bounds.top() + line.y_offset + start_pos.y,
),
point(
bounds.left() + wrapped.width(),
bounds.top() + line.y_offset + start_pos.y + self.line_height,
),
));
}
}
}
}
None
}
fn character_index_for_point(
&mut self,
point: Point<Pixels>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<usize> {
let index = self.index_for_position(point);
Some(self.utf_offset_8to16(index))
}
}