1use super::*;
4use crate::AppExt as _;
5
6pub(super) fn display_text(field: &TextField) -> (chip::Chipped, bool) {
9 if field.content.is_empty() {
10 (chip::Chipped::plain(field.placeholder.clone()), true)
11 } else {
12 (chip::chipped(&field.content, &field.chips), false)
13 }
14}
15
16fn chip_runs(mut runs: Vec<TextRun>, chipped: &chip::Chipped, color: gpui::Hsla) -> Vec<TextRun> {
19 for ((slot, _), title) in chipped.slots.iter().zip(&chipped.titles) {
20 runs = caret::recoloured(runs, &(slot.start..title.end), color);
21 runs = caret::recoloured(runs, slot, gpui::transparent_black());
22 runs = underlined(runs, title);
23 }
24 runs
25}
26
27pub fn coloured(
42 text: &str,
43 spans: &[(Range<usize>, HighlightKind)],
44 base: &TextRun,
45 palette: &SyntaxPalette,
46) -> Vec<TextRun> {
47 if spans.is_empty() {
48 return vec![TextRun {
49 len: text.len(),
50 ..base.clone()
51 }];
52 }
53 let mut runs = Vec::with_capacity(spans.len() * 2 + 1);
54 let mut at = 0;
55 for (range, kind) in spans {
56 if range.start < at
57 || range.end <= range.start
58 || range.end > text.len()
59 || !text.is_char_boundary(range.start)
60 || !text.is_char_boundary(range.end)
61 {
62 continue;
63 }
64 if range.start > at {
65 runs.push(TextRun {
66 len: range.start - at,
67 ..base.clone()
68 });
69 }
70 runs.push(TextRun {
71 len: range.end - range.start,
72 color: palette.color(*kind),
73 ..base.clone()
74 });
75 at = range.end;
76 }
77 if at < text.len() {
78 runs.push(TextRun {
79 len: text.len() - at,
80 ..base.clone()
81 });
82 }
83 runs
84}
85
86pub fn underlined(runs: Vec<TextRun>, marked: &Range<usize>) -> Vec<TextRun> {
90 let mut out = Vec::with_capacity(runs.len() + 2);
91 let mut at = 0;
92 for run in runs {
93 let end = at + run.len;
94 for (start, stop, mark) in [
95 (at, end.min(marked.start), false),
96 (at.max(marked.start), end.min(marked.end), true),
97 (at.max(marked.end), end, false),
98 ] {
99 if stop <= start {
100 continue;
101 }
102 out.push(TextRun {
103 len: stop - start,
104 underline: mark.then(|| UnderlineStyle {
105 color: Some(run.color),
106 thickness: px(1.0),
107 wavy: false,
108 }),
109 ..run.clone()
110 });
111 }
112 at = end;
113 }
114 out
115}
116
117pub(super) fn lines_from(lines: &[WrappedLine]) -> impl Iterator<Item = (usize, &WrappedLine)> {
126 lines.iter().scan(0usize, |start, line| {
127 let at = *start;
128 *start = at + line.len() + 1;
129 Some((at, line))
130 })
131}
132
133pub(super) fn rows(lines: &[WrappedLine], line_height: Pixels) -> Vec<(Range<usize>, Pixels)> {
138 let mut out = Vec::new();
139 let mut top = px(0.);
140 for (start, line) in lines_from(lines) {
141 let mut row_start = start;
142 for boundary in line.wrap_boundaries() {
143 let at = start + line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index;
144 out.push((row_start..at, top));
145 row_start = at;
146 top += line_height;
147 }
148 out.push((row_start..start + line.len(), top));
149 top += line_height;
150 }
151 out
152}
153
154pub(super) fn position_for_offset(
156 lines: &[WrappedLine],
157 offset: usize,
158 line_height: Pixels,
159) -> Option<Point<Pixels>> {
160 let mut top = px(0.);
161 for (start, line) in lines_from(lines) {
162 if offset <= start + line.len() {
163 let local = line.position_for_index(offset.saturating_sub(start), line_height)?;
164 return Some(gpui::point(local.x, local.y + top));
165 }
166 top += line.size(line_height).height;
167 }
168 None
169}
170
171pub(super) fn offset_for_position(
173 lines: &[WrappedLine],
174 position: Point<Pixels>,
175 line_height: Pixels,
176) -> usize {
177 let mut top = px(0.);
178 let mut last = 0;
179 for (start, line) in lines_from(lines) {
180 let height = line.size(line_height).height;
181 last = start + line.len();
182 if position.y < top + height {
183 let local = gpui::point(position.x, position.y - top);
184 let (Ok(index) | Err(index)) = line.closest_index_for_position(local, line_height);
185 return start + index;
186 }
187 top += height;
188 }
189 last
190}
191
192pub(super) fn floor_boundary(text: &str, offset: usize) -> usize {
194 let mut offset = offset.min(text.len());
195 while !text.is_char_boundary(offset) {
196 offset -= 1;
197 }
198 offset
199}
200
201pub(super) fn selection_rows(
207 lines: &[WrappedLine],
208 range: &Range<usize>,
209 line_height: Pixels,
210) -> Vec<Bounds<Pixels>> {
211 rows(lines, line_height)
212 .into_iter()
213 .filter(|(row, _)| range.start <= row.end && range.end >= row.start)
214 .filter_map(|(row, top)| {
215 let left = if range.start <= row.start {
216 px(0.)
217 } else {
218 position_for_offset(lines, range.start, line_height)?.x
219 };
220 let right = position_for_offset(lines, range.end.min(row.end), line_height)?.x;
221 (right > left).then(|| {
222 Bounds::from_corners(
223 gpui::point(left, top),
224 gpui::point(right, top + line_height),
225 )
226 })
227 })
228 .collect()
229}
230
231impl Render for TextField {
232 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
233 if self.focus_handle.is_focused(_window) && cx.caret_blink() {
236 if self.blink.is_none() {
237 self.start_blink(cx);
238 }
239 } else {
240 self.blink = None;
241 self.caret_on = true;
242 }
243 let theme = Theme::of(cx);
244 let mut key_context = gpui::KeyContext::default();
245 key_context.add(KEY_CONTEXT);
246 if self.shape.is_multiline() {
247 key_context.add(MULTILINE_KEY_CONTEXT);
248 }
249 if let Some(extra) = self.key_context.clone() {
250 key_context.add(extra);
251 }
252 div()
253 .key_context(key_context)
254 .track_focus(&self.focus_handle(cx))
255 .cursor(CursorStyle::IBeam)
256 .on_action(cx.listener(Self::backspace))
257 .on_action(cx.listener(Self::delete))
258 .on_action(cx.listener(Self::left))
259 .on_action(cx.listener(Self::right))
260 .on_action(cx.listener(Self::select_left))
261 .on_action(cx.listener(Self::select_right))
262 .on_action(cx.listener(Self::select_all))
263 .on_action(cx.listener(Self::home))
264 .on_action(cx.listener(Self::end))
265 .on_action(cx.listener(Self::select_home))
266 .on_action(cx.listener(Self::select_end))
267 .on_action(cx.listener(Self::word_left))
268 .on_action(cx.listener(Self::word_right))
269 .on_action(cx.listener(Self::select_word_left))
270 .on_action(cx.listener(Self::select_word_right))
271 .on_action(cx.listener(Self::up))
272 .on_action(cx.listener(Self::down))
273 .on_action(cx.listener(Self::select_up))
274 .on_action(cx.listener(Self::select_down))
275 .on_action(cx.listener(Self::insert_newline))
276 .on_action(cx.listener(Self::undo))
277 .on_action(cx.listener(Self::redo))
278 .on_action(cx.listener(Self::delete_word_left))
279 .on_action(cx.listener(Self::delete_word_right))
280 .on_action(cx.listener(Self::delete_to_line_start))
281 .on_action(cx.listener(Self::delete_to_line_end))
282 .on_action(cx.listener(Self::show_character_palette))
283 .on_action(cx.listener(Self::paste))
284 .on_action(cx.listener(Self::cut))
285 .on_action(cx.listener(Self::copy))
286 .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
287 .on_mouse_down(MouseButton::Right, cx.listener(Self::on_right_mouse_down))
288 .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
289 .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
290 .on_scroll_wheel(cx.listener(Self::on_scroll_wheel))
291 .w_full()
292 .when(self.frame, |field| {
293 field
294 .px(px(10.0))
295 .py(px(7.0))
296 .rounded(px(Theme::button_radius()))
297 .bg(theme.input_bg)
298 .border_1()
299 .border_color(if self.focus_handle.contains_focused(_window, cx) {
300 theme.ring
301 } else {
302 theme.border
303 })
304 })
305 .text_size(px(self.metrics.size()))
306 .font_weight(self.metrics.weight)
307 .line_height(px(self.metrics.line_height()))
308 .text_color(theme.text)
309 .child(TextFieldElement { field: cx.entity() })
310 .children(self.edit_menu(_window, cx))
311 }
312}
313
314pub(super) struct TextFieldElement {
318 field: Entity<TextField>,
319}
320
321pub(super) struct FieldPrepaint {
322 lines: Vec<WrappedLine>,
323 shown: Shown,
324 glyphs: Vec<(Range<usize>, icons::Icon)>,
326 glyph_color: gpui::Hsla,
327 font_size: Pixels,
328 origin: Point<Pixels>,
330 cursor: Option<PaintQuad>,
331 covered: Option<Covered>,
332 matches: Vec<PaintQuad>,
334 selection: Vec<PaintQuad>,
336 overdraws: Vec<overdraw::Overdraw>,
338}
339
340struct Covered {
342 layout: std::sync::Arc<gpui::LineLayout>,
343 range: Range<usize>,
345 at: Point<Pixels>,
347 color: gpui::Hsla,
348}
349
350impl IntoElement for TextFieldElement {
351 type Element = Self;
352
353 fn into_element(self) -> Self::Element {
354 self
355 }
356}
357
358impl Element for TextFieldElement {
359 type RequestLayoutState = ();
360 type PrepaintState = FieldPrepaint;
361
362 fn id(&self) -> Option<ElementId> {
363 None
364 }
365
366 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
367 None
368 }
369
370 fn request_layout(
371 &mut self,
372 _id: Option<&GlobalElementId>,
373 _inspector_id: Option<&gpui::InspectorElementId>,
374 window: &mut Window,
375 cx: &mut App,
376 ) -> (LayoutId, ()) {
377 let mut style = Style::default();
378 style.size.width = relative(1.).into();
379 let field = self.field.read(cx);
380 let line_height = field.line_height();
383 let shape = field.shape;
384
385 let (min, max) = match shape {
386 Shape::Line => {
387 style.size.height = line_height.into();
388 return (window.request_layout(style, [], cx), ());
389 }
390 Shape::Rows(rows) => {
391 style.size.height = (line_height * rows.max(1) as f32).into();
392 return (window.request_layout(style, [], cx), ());
393 }
394 Shape::Grow { min, max } => (min.max(1), max.max(min.max(1))),
398 };
399
400 let text = display_text(field).0.text;
401 let text_style = window.text_style();
404 let font_size = text_style.font_size.to_pixels(window.rem_size());
405 let id = window.request_measured_layout(style, move |known, available, window, _cx| {
406 let wrap_width = known.width.or(match available.width {
411 gpui::AvailableSpace::Definite(width) => Some(width),
412 _ => None,
413 });
414 let run = TextRun {
415 len: text.len(),
416 font: text_style.font(),
417 color: text_style.color,
418 background_color: None,
419 underline: None,
420 strikethrough: None,
421 };
422 let count = window
423 .text_system()
424 .shape_text(text.clone(), font_size, &[run], wrap_width, None)
425 .map(|lines| {
426 lines
427 .iter()
428 .map(|line| line.wrap_boundaries().len() + 1)
429 .sum::<usize>()
430 })
431 .unwrap_or(1);
432 gpui::size(
433 wrap_width.unwrap_or(px(0.)),
434 line_height * count.clamp(min, max) as f32,
435 )
436 });
437 (id, ())
438 }
439
440 fn prepaint(
441 &mut self,
442 _id: Option<&GlobalElementId>,
443 _inspector_id: Option<&gpui::InspectorElementId>,
444 bounds: Bounds<Pixels>,
445 _request_layout: &mut Self::RequestLayoutState,
446 window: &mut Window,
447 cx: &mut App,
448 ) -> FieldPrepaint {
449 let theme = Theme::of(cx).clone();
450 let field = self.field.read(cx);
451 let (chipped, is_placeholder) = display_text(field);
452 let shown = chipped.shown.clone();
454 let selected_range = shown.range(&field.selected_range);
455 let cursor = shown.at(field.cursor_offset());
456 let shape = field.shape;
457 let marked_range = field.marked_range.as_ref().map(|range| shown.range(range));
458 let matches: Vec<_> = field
459 .matches
460 .iter()
461 .filter(|range| {
462 range.start <= range.end
463 && field.content.is_char_boundary(range.start)
464 && field.content.is_char_boundary(range.end)
465 })
466 .map(|range| shown.range(range))
467 .collect();
468 let scrolled = field.scroll;
469 let caret_shape = cx.caret_shape();
470 let caret_height = cx.caret_height();
471 let at_start = !shape.is_multiline() && !field.focus_handle.contains_focused(window, cx);
473 let follow_caret =
474 !at_start && (field.follow_caret || caret_shape != field.last_caret_shape);
475 let style = window.text_style();
476
477 let text = chipped.text.clone();
478 let text_color = if is_placeholder {
479 theme.text_faint
480 } else {
481 style.color
482 };
483
484 let run = TextRun {
485 len: text.len(),
486 font: style.font(),
487 color: text_color,
488 background_color: None,
489 underline: None,
490 strikethrough: None,
491 };
492 let runs = if is_placeholder {
496 vec![run]
497 } else {
498 let spans: Vec<_> = field
499 .spans
500 .iter()
501 .map(|(range, kind)| (shown.range(range), *kind))
502 .collect();
503 chip_runs(
504 coloured(&text, &spans, &run, &theme.syntax),
505 &chipped,
506 text_color,
507 )
508 };
509 let runs = match marked_range.as_ref() {
510 Some(marked) => underlined(runs, marked),
511 None => runs,
512 };
513 let match_wash = theme.warning.opacity(0.25);
515 let focused = field.focus_handle.contains_focused(window, cx);
516 let layers: Vec<_> = (focused && !selected_range.is_empty())
517 .then(|| (selected_range.clone(), theme.selection))
518 .into_iter()
519 .chain(matches.iter().map(|range| (range.clone(), match_wash)))
520 .collect();
521 let overdraws = match is_placeholder {
522 true => Vec::new(),
523 false => overdraw::overdraws(&runs, theme.bg, &layers),
524 };
525
526 let font_size = style.font_size.to_pixels(window.rem_size());
527 let line_height = field.line_height();
528 let wrap_width = shape.is_multiline().then_some(bounds.size.width);
531 let lines = window
532 .text_system()
533 .shape_text(text.clone(), font_size, &runs, wrap_width, None)
534 .map(|lines| lines.into_vec())
535 .unwrap_or_default();
536
537 let content_height: Pixels = lines.iter().map(|l| l.size(line_height).height).sum();
544 let content_width = lines.iter().map(|l| l.width()).fold(px(0.), Pixels::max);
545 let slot = (caret_shape != CaretShape::Bar)
548 .then(|| caret::zero_width(style.font(), font_size, window));
549 let follows = (!is_placeholder)
551 .then(|| {
552 lines_from(&lines)
553 .find_map(|(start, line)| {
554 (cursor <= start + line.len()).then(|| {
555 caret::character_advance(line, &line.text, cursor.saturating_sub(start))
556 })
557 })
558 .flatten()
559 })
560 .flatten();
561 let advance = slot.map(|slot| follows.unwrap_or(slot));
562 let hollow = !window.is_window_active();
563 let caret_width = caret_shape
564 .quad(
565 Bounds::new(
566 gpui::point(px(0.), px(0.)),
567 gpui::size(CARET_WIDTH, font_size),
568 ),
569 line_height,
570 caret_height,
571 advance.unwrap_or_default(),
572 theme.caret,
573 hollow,
574 )
575 .bounds
576 .size
577 .width;
578 let max = gpui::point(
580 match shape.is_multiline() {
581 true => px(0.),
582 false => (content_width + slot.unwrap_or_default() - bounds.size.width).max(px(0.)),
583 },
584 (content_height - bounds.size.height).max(px(0.)),
585 );
586 let mut scroll = gpui::point(
587 scrolled.x.clamp(px(0.), max.x),
588 scrolled.y.clamp(px(0.), max.y),
589 );
590 if follow_caret && let Some(at) = position_for_offset(&lines, cursor, line_height) {
591 if at.y < scroll.y {
592 scroll.y = at.y;
593 } else if at.y + line_height > scroll.y + bounds.size.height {
594 scroll.y = at.y + line_height - bounds.size.height;
595 }
596 if at.x < scroll.x {
599 scroll.x = at.x;
600 } else if at.x + caret_width > scroll.x + bounds.size.width {
601 scroll.x = at.x + caret_width - bounds.size.width;
602 }
603 scroll.x = scroll.x.clamp(px(0.), max.x);
604 scroll.y = scroll.y.clamp(px(0.), max.y);
605 }
606 if at_start {
607 scroll.x = px(0.);
608 }
609 self.field.update(cx, |field, _| {
610 field.scroll = scroll;
611 field.follow_caret = false;
612 field.last_caret_shape = caret_shape;
613 });
614 let origin = bounds.origin - scroll;
615
616 let matches = if is_placeholder {
617 Vec::new()
618 } else {
619 matches
620 .iter()
621 .flat_map(|range| selection_rows(&lines, range, line_height))
622 .map(|rect| fill(Bounds::new(origin + rect.origin, rect.size), match_wash))
623 .collect()
624 };
625
626 let caret_shown = caret_shape.shown(!hollow, crate::input::caret::inactive_caret(cx));
627 let covered =
628 (selected_range.is_empty() && follows.is_some() && caret_shape.cuts_out(hollow))
629 .then(|| {
630 let (start, line) =
631 lines_from(&lines).find(|(start, line)| cursor <= start + line.len())?;
632 let at = position_for_offset(&lines, cursor, line_height)?;
633 Some(Covered {
634 layout: line.unwrapped_layout.clone(),
635 range: cursor - start..next_boundary(&text, cursor) - start,
636 at: origin + at,
637 color: theme::legible(theme.bg, theme.caret, theme::LEGIBLE),
638 })
639 })
640 .flatten();
641 let (selection, cursor) = if selected_range.is_empty() {
642 let at = position_for_offset(&lines, cursor, line_height).unwrap_or_default();
643 (
644 Vec::new(),
645 caret_shown.then(|| {
646 caret_shape.quad(
647 Bounds::new(
650 origin + at + gpui::point(px(0.), (line_height - font_size) / 2.),
651 gpui::size(CARET_WIDTH, font_size),
652 ),
653 line_height,
654 caret_height,
655 advance.unwrap_or_default(),
656 theme.caret,
657 hollow,
658 )
659 }),
660 )
661 } else {
662 (
663 selection_rows(&lines, &selected_range, line_height)
664 .into_iter()
665 .map(|rect| {
666 fill(
667 Bounds::new(origin + rect.origin, rect.size),
668 theme.selection,
669 )
670 })
671 .collect(),
672 None,
673 )
674 };
675
676 FieldPrepaint {
677 lines,
678 shown,
679 glyphs: chipped.slots,
680 glyph_color: theme.text_muted,
681 font_size,
682 origin,
683 cursor,
684 covered,
685 matches,
686 selection,
687 overdraws,
688 }
689 }
690
691 fn paint(
692 &mut self,
693 _id: Option<&GlobalElementId>,
694 _inspector_id: Option<&gpui::InspectorElementId>,
695 bounds: Bounds<Pixels>,
696 _request_layout: &mut Self::RequestLayoutState,
697 prepaint: &mut Self::PrepaintState,
698 window: &mut Window,
699 cx: &mut App,
700 ) {
701 let focus_handle = self.field.read(cx).focus_handle.clone();
702 let caret_on = self.field.read(cx).caret_on;
703 window.handle_input(
704 &focus_handle,
705 ElementInputHandler::new(bounds, self.field.clone()),
706 cx,
707 );
708 let line_height = self.field.read(cx).line_height();
709 let dragged = self.field.clone();
719 window.on_mouse_event(move |event: &MouseMoveEvent, phase, _window, cx| {
720 if phase != DispatchPhase::Bubble || !event.dragging() {
725 return;
726 }
727 dragged.update(cx, |field, cx| {
728 field.drag_to(event.position, line_height, cx);
729 });
730 });
731 let lines = std::mem::take(&mut prepaint.lines);
732 let matches = std::mem::take(&mut prepaint.matches);
733 let selection = match focus_handle.contains_focused(window, cx) {
736 true => std::mem::take(&mut prepaint.selection),
737 false => Vec::new(),
738 };
739 let cursor = prepaint.cursor.take();
740 let origin = prepaint.origin;
741
742 window.with_content_mask(Some(gpui::ContentMask::new(bounds)), |window| {
745 for quad in selection.into_iter().chain(matches) {
746 window.paint_quad(quad);
747 }
748
749 let mut top = origin;
750 for line in &lines {
751 line.paint(top, line_height, gpui::TextAlign::Left, None, window, cx)
752 .ok();
753 top.y += line.size(line_height).height;
754 }
755 overdraw::paint(
756 lines.iter().map(|line| &***line),
757 origin,
758 line_height,
759 px(0.),
760 &prepaint.overdraws,
761 window,
762 );
763
764 if focus_handle.is_focused(window)
767 && caret_on
768 && let Some(cursor) = cursor
769 {
770 window.paint_quad(cursor);
771 if let Some(covered) = &prepaint.covered {
772 caret::paint_covered(
773 &covered.layout,
774 &covered.range,
775 covered.at,
776 line_height,
777 covered.color,
778 window,
779 );
780 }
781 }
782
783 for (slot, glyph) in &prepaint.glyphs {
784 let Some(data) = glyph.data() else { continue };
785 let (Some(start), Some(end)) = (
786 position_for_offset(&lines, slot.start, line_height),
787 position_for_offset(&lines, slot.end, line_height),
788 ) else {
789 continue;
790 };
791 let side = (end.x - start.x).min(prepaint.font_size);
792 let bounds = Bounds::new(
793 origin + start + gpui::point(px(0.), (line_height - side) / 2.),
794 gpui::size(side, side),
795 );
796 let path = SharedString::from(format!("field-glyph-{:p}", data.as_ptr()));
797 window
798 .paint_svg(
799 bounds,
800 path,
801 Some(data),
802 gpui::TransformationMatrix::unit(),
803 prepaint.glyph_color,
804 cx,
805 )
806 .ok();
807 }
808 });
809
810 let shown = std::mem::take(&mut prepaint.shown);
811 self.field.update(cx, |field, _| {
812 field.last_layout = lines;
813 field.last_shown = shown;
814 field.last_bounds = Some(bounds);
815 });
816 }
817}