Skip to main content

gpui_cn/terminal/
ime.rs

1//! Platform text input for the terminal.
2//!
3//! A terminal has no editable document, so the input handler presents the
4//! composition buffer as the entire text. Committed text is sent to the
5//! program and the buffer is cleared.
6//!
7//! Derived from Herdr (Apache-2.0).
8
9use std::ops::Range;
10
11use gpui_kit::{Bounds, Context, EntityInputHandler, Pixels, Point, UTF16Selection, Window};
12
13use crate::terminal::input::TerminalInput;
14use crate::terminal::state::TerminalState;
15
16impl EntityInputHandler for TerminalState {
17    fn text_for_range(
18        &mut self,
19        range: Range<usize>,
20        adjusted: &mut Option<Range<usize>>,
21        _window: &mut Window,
22        _cx: &mut Context<Self>,
23    ) -> Option<String> {
24        let marked = self.marked_text.as_deref().unwrap_or_default();
25        let utf16: Vec<u16> = marked.encode_utf16().collect();
26        let start = range.start.min(utf16.len());
27        let end = range.end.min(utf16.len()).max(start);
28        if start != range.start || end != range.end {
29            *adjusted = Some(start..end);
30        }
31        Some(String::from_utf16_lossy(&utf16[start..end]))
32    }
33
34    fn selected_text_range(
35        &mut self,
36        _ignore_disabled_input: bool,
37        _window: &mut Window,
38        _cx: &mut Context<Self>,
39    ) -> Option<UTF16Selection> {
40        let length = self.marked_utf16_len();
41        Some(UTF16Selection {
42            range: length..length,
43            reversed: false,
44        })
45    }
46
47    fn marked_text_range(
48        &self,
49        _window: &mut Window,
50        _cx: &mut Context<Self>,
51    ) -> Option<Range<usize>> {
52        self.marked_text
53            .as_ref()
54            .map(|text| 0..text.encode_utf16().count())
55    }
56
57    fn unmark_text(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
58        if self.marked_text.take().is_some() {
59            cx.notify();
60        }
61    }
62
63    fn replace_text_in_range(
64        &mut self,
65        _range: Option<Range<usize>>,
66        text: &str,
67        _window: &mut Window,
68        cx: &mut Context<Self>,
69    ) {
70        self.marked_text = None;
71        if !text.is_empty() {
72            self.selection = None;
73            self.send(TerminalInput::Text(text.to_owned()), cx);
74        }
75        cx.notify();
76    }
77
78    fn replace_and_mark_text_in_range(
79        &mut self,
80        _range: Option<Range<usize>>,
81        new_text: &str,
82        _new_selected_range: Option<Range<usize>>,
83        _window: &mut Window,
84        cx: &mut Context<Self>,
85    ) {
86        self.marked_text = (!new_text.is_empty()).then(|| new_text.to_owned());
87        cx.notify();
88    }
89
90    /// The candidate window follows the cursor cell.
91    fn bounds_for_range(
92        &mut self,
93        _range_utf16: Range<usize>,
94        element_bounds: Bounds<Pixels>,
95        _window: &mut Window,
96        _cx: &mut Context<Self>,
97    ) -> Option<Bounds<Pixels>> {
98        let geometry = self.geometry?;
99        let cursor = self.frame().cursor;
100        Some(
101            geometry
102                .cell_bounds(cursor.column, cursor.row)
103                .intersect(&element_bounds.union(&geometry.grid_bounds())),
104        )
105    }
106
107    fn character_index_for_point(
108        &mut self,
109        _point: Point<Pixels>,
110        _window: &mut Window,
111        _cx: &mut Context<Self>,
112    ) -> Option<usize> {
113        None
114    }
115}
116
117impl TerminalState {
118    fn marked_utf16_len(&self) -> usize {
119        self.marked_text
120            .as_ref()
121            .map_or(0, |text| text.encode_utf16().count())
122    }
123
124    /// Text being composed by an input method, drawn at the cursor.
125    #[must_use]
126    pub fn marked_text(&self) -> Option<&str> {
127        self.marked_text.as_deref()
128    }
129}