652 lines · 20.1 KB · Rust
RawDownload
use std::ops::Range; use gpui::*;use unicode_segmentation::*; actions!(    text_input,    [        Backspace,        Delete,        Left,        Right,        SelectLeft,        SelectRight,        SelectAll,        Home,        End,        ShowCharacterPalette    ]); struct TextInput {    focus_handle: FocusHandle,    content: SharedString,    placeholder: SharedString,    selected_range: Range<usize>,    selection_reversed: bool,    marked_range: Option<Range<usize>>,    last_layout: Option<ShapedLine>,    last_bounds: Option<Bounds<Pixels>>,    is_selecting: bool,} impl TextInput {    fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {        if self.selected_range.is_empty() {            self.move_to(self.previous_boundary(self.cursor_offset()), cx);        } else {            self.move_to(self.selected_range.start, cx)        }    }     fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {        if self.selected_range.is_empty() {            self.move_to(self.next_boundary(self.selected_range.end), cx);        } else {            self.move_to(self.selected_range.end, cx)        }    }     fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {        self.select_to(self.previous_boundary(self.cursor_offset()), cx);    }     fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {        self.select_to(self.next_boundary(self.cursor_offset()), cx);    }     fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {        self.move_to(0, cx);        self.select_to(self.content.len(), cx)    }     fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {        self.move_to(0, cx);    }     fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {        self.move_to(self.content.len(), cx);    }     fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {        if self.selected_range.is_empty() {            self.select_to(self.previous_boundary(self.cursor_offset()), cx)        }        self.replace_text_in_range(None, "", cx)    }     fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {        if self.selected_range.is_empty() {            self.select_to(self.next_boundary(self.cursor_offset()), cx)        }        self.replace_text_in_range(None, "", cx)    }     fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) {        self.is_selecting = true;         if event.modifiers.shift {            self.select_to(self.index_for_mouse_position(event.position), cx);        } else {            self.move_to(self.index_for_mouse_position(event.position), cx)        }    }     fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut ViewContext<Self>) {        self.is_selecting = false;    }     fn on_mouse_move(&mut self, event: &MouseMoveEvent, cx: &mut ViewContext<Self>) {        if self.is_selecting {            self.select_to(self.index_for_mouse_position(event.position), cx);        }    }     fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {        cx.show_character_palette();    }     fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {        self.selected_range = offset..offset;        cx.notify()    }     fn cursor_offset(&self) -> usize {        if self.selection_reversed {            self.selected_range.start        } else {            self.selected_range.end        }    }     fn index_for_mouse_position(&self, position: Point<Pixels>) -> usize {        if self.content.is_empty() {            return 0;        }         let (Some(bounds), Some(line)) = (self.last_bounds.as_ref(), self.last_layout.as_ref())        else {            return 0;        };        if position.y < bounds.top() {            return 0;        }        if position.y > bounds.bottom() {            return self.content.len();        }        line.closest_index_for_x(position.x - bounds.left())    }     fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {        if self.selection_reversed {            self.selected_range.start = offset        } else {            self.selected_range.end = offset        };        if self.selected_range.end < self.selected_range.start {            self.selection_reversed = !self.selection_reversed;            self.selected_range = self.selected_range.end..self.selected_range.start;        }        cx.notify()    }     fn offset_from_utf16(&self, offset: usize) -> usize {        let mut utf8_offset = 0;        let mut utf16_count = 0;         for ch in self.content.chars() {            if utf16_count >= offset {                break;            }            utf16_count += ch.len_utf16();            utf8_offset += ch.len_utf8();        }         utf8_offset    }     fn offset_to_utf16(&self, offset: usize) -> usize {        let mut utf16_offset = 0;        let mut utf8_count = 0;         for ch in self.content.chars() {            if utf8_count >= offset {                break;            }            utf8_count += ch.len_utf8();            utf16_offset += ch.len_utf16();        }         utf16_offset    }     fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)    }     fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)    }     fn previous_boundary(&self, offset: usize) -> usize {        self.content            .grapheme_indices(true)            .rev()            .find_map(|(idx, _)| (idx < offset).then_some(idx))            .unwrap_or(0)    }     fn next_boundary(&self, offset: usize) -> usize {        self.content            .grapheme_indices(true)            .find_map(|(idx, _)| (idx > offset).then_some(idx))            .unwrap_or(self.content.len())    }     fn reset(&mut self) {        self.content = "".into();        self.selected_range = 0..0;        self.selection_reversed = false;        self.marked_range = None;        self.last_layout = None;        self.last_bounds = None;        self.is_selecting = false;    }} impl ViewInputHandler for TextInput {    fn text_for_range(        &mut self,        range_utf16: Range<usize>,        _cx: &mut ViewContext<Self>,    ) -> Option<String> {        let range = self.range_from_utf16(&range_utf16);        Some(self.content[range].to_string())    }     fn selected_text_range(        &mut self,        _ignore_disabled_input: bool,        _cx: &mut ViewContext<Self>,    ) -> Option<UTF16Selection> {        Some(UTF16Selection {            range: self.range_to_utf16(&self.selected_range),            reversed: self.selection_reversed,        })    }     fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {        self.marked_range            .as_ref()            .map(|range| self.range_to_utf16(range))    }     fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {        self.marked_range = None;    }     fn replace_text_in_range(        &mut self,        range_utf16: Option<Range<usize>>,        new_text: &str,        cx: &mut ViewContext<Self>,    ) {        let range = range_utf16            .as_ref()            .map(|range_utf16| self.range_from_utf16(range_utf16))            .or(self.marked_range.clone())            .unwrap_or(self.selected_range.clone());         self.content =            (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])                .into();        self.selected_range = range.start + new_text.len()..range.start + new_text.len();        self.marked_range.take();        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>>,        cx: &mut ViewContext<Self>,    ) {        let range = range_utf16            .as_ref()            .map(|range_utf16| self.range_from_utf16(range_utf16))            .or(self.marked_range.clone())            .unwrap_or(self.selected_range.clone());         self.content =            (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])                .into();        self.marked_range = Some(range.start..range.start + new_text.len());        self.selected_range = new_selected_range_utf16            .as_ref()            .map(|range_utf16| self.range_from_utf16(range_utf16))            .map(|new_range| new_range.start + range.start..new_range.end + range.end)            .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());         cx.notify();    }     fn bounds_for_range(        &mut self,        range_utf16: Range<usize>,        bounds: Bounds<Pixels>,        _cx: &mut ViewContext<Self>,    ) -> Option<Bounds<Pixels>> {        let last_layout = self.last_layout.as_ref()?;        let range = self.range_from_utf16(&range_utf16);        Some(Bounds::from_corners(            point(                bounds.left() + last_layout.x_for_index(range.start),                bounds.top(),            ),            point(                bounds.left() + last_layout.x_for_index(range.end),                bounds.bottom(),            ),        ))    }} struct TextElement {    input: View<TextInput>,} struct PrepaintState {    line: Option<ShapedLine>,    cursor: Option<PaintQuad>,    selection: Option<PaintQuad>,} impl IntoElement for TextElement {    type Element = Self;     fn into_element(self) -> Self::Element {        self    }} impl Element for TextElement {    type RequestLayoutState = ();     type PrepaintState = PrepaintState;     fn id(&self) -> Option<ElementId> {        None    }     fn request_layout(        &mut self,        _id: Option<&GlobalElementId>,        cx: &mut WindowContext,    ) -> (LayoutId, Self::RequestLayoutState) {        let mut style = Style::default();        style.size.width = relative(1.).into();        style.size.height = cx.line_height().into();        (cx.request_layout(style, []), ())    }     fn prepaint(        &mut self,        _id: Option<&GlobalElementId>,        bounds: Bounds<Pixels>,        _request_layout: &mut Self::RequestLayoutState,        cx: &mut WindowContext,    ) -> Self::PrepaintState {        let input = self.input.read(cx);        let content = input.content.clone();        let selected_range = input.selected_range.clone();        let cursor = input.cursor_offset();        let style = cx.text_style();         let (display_text, text_color) = if content.is_empty() {            (input.placeholder.clone(), hsla(0., 0., 0., 0.2))        } else {            (content.clone(), style.color)        };         let run = TextRun {            len: display_text.len(),            font: style.font(),            color: text_color,            background_color: None,            underline: None,            strikethrough: None,        };        let runs = if let Some(marked_range) = input.marked_range.as_ref() {            vec![                TextRun {                    len: marked_range.start,                    ..run.clone()                },                TextRun {                    len: marked_range.end - marked_range.start,                    underline: Some(UnderlineStyle {                        color: Some(run.color),                        thickness: px(1.0),                        wavy: false,                    }),                    ..run.clone()                },                TextRun {                    len: display_text.len() - marked_range.end,                    ..run.clone()                },            ]            .into_iter()            .filter(|run| run.len > 0)            .collect()        } else {            vec![run]        };         let font_size = style.font_size.to_pixels(cx.rem_size());        let line = cx            .text_system()            .shape_line(display_text, font_size, &runs)            .unwrap();         let cursor_pos = line.x_for_index(cursor);        let (selection, cursor) = if selected_range.is_empty() {            (                None,                Some(fill(                    Bounds::new(                        point(bounds.left() + cursor_pos, bounds.top()),                        size(px(2.), bounds.bottom() - bounds.top()),                    ),                    gpui::blue(),                )),            )        } else {            (                Some(fill(                    Bounds::from_corners(                        point(                            bounds.left() + line.x_for_index(selected_range.start),                            bounds.top(),                        ),                        point(                            bounds.left() + line.x_for_index(selected_range.end),                            bounds.bottom(),                        ),                    ),                    rgba(0x3311FF30),                )),                None,            )        };        PrepaintState {            line: Some(line),            cursor,            selection,        }    }     fn paint(        &mut self,        _id: Option<&GlobalElementId>,        bounds: Bounds<Pixels>,        _request_layout: &mut Self::RequestLayoutState,        prepaint: &mut Self::PrepaintState,        cx: &mut WindowContext,    ) {        let focus_handle = self.input.read(cx).focus_handle.clone();        cx.handle_input(            &focus_handle,            ElementInputHandler::new(bounds, self.input.clone()),        );        if let Some(selection) = prepaint.selection.take() {            cx.paint_quad(selection)        }        let line = prepaint.line.take().unwrap();        line.paint(bounds.origin, cx.line_height(), cx).unwrap();         if focus_handle.is_focused(cx) {            if let Some(cursor) = prepaint.cursor.take() {                cx.paint_quad(cursor);            }        }         self.input.update(cx, |input, _cx| {            input.last_layout = Some(line);            input.last_bounds = Some(bounds);        });    }} impl Render for TextInput {    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {        div()            .flex()            .key_context("TextInput")            .track_focus(&self.focus_handle)            .cursor(CursorStyle::IBeam)            .on_action(cx.listener(Self::backspace))            .on_action(cx.listener(Self::delete))            .on_action(cx.listener(Self::left))            .on_action(cx.listener(Self::right))            .on_action(cx.listener(Self::select_left))            .on_action(cx.listener(Self::select_right))            .on_action(cx.listener(Self::select_all))            .on_action(cx.listener(Self::home))            .on_action(cx.listener(Self::end))            .on_action(cx.listener(Self::show_character_palette))            .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))

Lines 1–500 of 652.