1use super::*;
4use crate::AppExt as _;
5
6pub(super) fn display_text(field: &TextField) -> (SharedString, bool) {
9 if field.content.is_empty() {
10 (field.placeholder.clone(), true)
11 } else {
12 (field.content.clone(), false)
13 }
14}
15
16pub fn coloured(
31 text: &str,
32 spans: &[(Range<usize>, HighlightKind)],
33 base: &TextRun,
34 palette: &SyntaxPalette,
35) -> Vec<TextRun> {
36 if spans.is_empty() {
37 return vec![TextRun {
38 len: text.len(),
39 ..base.clone()
40 }];
41 }
42 let mut runs = Vec::with_capacity(spans.len() * 2 + 1);
43 let mut at = 0;
44 for (range, kind) in spans {
45 if range.start < at
46 || range.end <= range.start
47 || range.end > text.len()
48 || !text.is_char_boundary(range.start)
49 || !text.is_char_boundary(range.end)
50 {
51 continue;
52 }
53 if range.start > at {
54 runs.push(TextRun {
55 len: range.start - at,
56 ..base.clone()
57 });
58 }
59 runs.push(TextRun {
60 len: range.end - range.start,
61 color: palette.color(*kind),
62 ..base.clone()
63 });
64 at = range.end;
65 }
66 if at < text.len() {
67 runs.push(TextRun {
68 len: text.len() - at,
69 ..base.clone()
70 });
71 }
72 runs
73}
74
75pub fn underlined(runs: Vec<TextRun>, marked: &Range<usize>) -> Vec<TextRun> {
79 let mut out = Vec::with_capacity(runs.len() + 2);
80 let mut at = 0;
81 for run in runs {
82 let end = at + run.len;
83 for (start, stop, mark) in [
84 (at, end.min(marked.start), false),
85 (at.max(marked.start), end.min(marked.end), true),
86 (at.max(marked.end), end, false),
87 ] {
88 if stop <= start {
89 continue;
90 }
91 out.push(TextRun {
92 len: stop - start,
93 underline: mark.then(|| UnderlineStyle {
94 color: Some(run.color),
95 thickness: px(1.0),
96 wavy: false,
97 }),
98 ..run.clone()
99 });
100 }
101 at = end;
102 }
103 out
104}
105
106pub(super) fn lines_from(lines: &[WrappedLine]) -> impl Iterator<Item = (usize, &WrappedLine)> {
115 lines.iter().scan(0usize, |start, line| {
116 let at = *start;
117 *start = at + line.len() + 1;
118 Some((at, line))
119 })
120}
121
122pub(super) fn rows(lines: &[WrappedLine], line_height: Pixels) -> Vec<(Range<usize>, Pixels)> {
127 let mut out = Vec::new();
128 let mut top = px(0.);
129 for (start, line) in lines_from(lines) {
130 let mut row_start = start;
131 for boundary in line.wrap_boundaries() {
132 let at = start + line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index;
133 out.push((row_start..at, top));
134 row_start = at;
135 top += line_height;
136 }
137 out.push((row_start..start + line.len(), top));
138 top += line_height;
139 }
140 out
141}
142
143pub(super) fn position_for_offset(
145 lines: &[WrappedLine],
146 offset: usize,
147 line_height: Pixels,
148) -> Option<Point<Pixels>> {
149 let mut top = px(0.);
150 for (start, line) in lines_from(lines) {
151 if offset <= start + line.len() {
152 let local = line.position_for_index(offset.saturating_sub(start), line_height)?;
153 return Some(gpui::point(local.x, local.y + top));
154 }
155 top += line.size(line_height).height;
156 }
157 None
158}
159
160pub(super) fn offset_for_position(
162 lines: &[WrappedLine],
163 position: Point<Pixels>,
164 line_height: Pixels,
165) -> usize {
166 let mut top = px(0.);
167 let mut last = 0;
168 for (start, line) in lines_from(lines) {
169 let height = line.size(line_height).height;
170 last = start + line.len();
171 if position.y < top + height {
172 let local = gpui::point(position.x, position.y - top);
173 let (Ok(index) | Err(index)) = line.closest_index_for_position(local, line_height);
174 return start + index;
175 }
176 top += height;
177 }
178 last
179}
180
181pub(super) fn floor_boundary(text: &str, offset: usize) -> usize {
183 let mut offset = offset.min(text.len());
184 while !text.is_char_boundary(offset) {
185 offset -= 1;
186 }
187 offset
188}
189
190pub(super) fn selection_rows(
196 lines: &[WrappedLine],
197 range: &Range<usize>,
198 line_height: Pixels,
199) -> Vec<Bounds<Pixels>> {
200 rows(lines, line_height)
201 .into_iter()
202 .filter(|(row, _)| range.start <= row.end && range.end >= row.start)
203 .filter_map(|(row, top)| {
204 let left = if range.start <= row.start {
205 px(0.)
206 } else {
207 position_for_offset(lines, range.start, line_height)?.x
208 };
209 let right = position_for_offset(lines, range.end.min(row.end), line_height)?.x;
210 (right > left).then(|| {
211 Bounds::from_corners(
212 gpui::point(left, top),
213 gpui::point(right, top + line_height),
214 )
215 })
216 })
217 .collect()
218}
219
220impl Render for TextField {
221 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
222 if self.focus_handle.is_focused(_window) && cx.caret_blink() {
225 if self.blink.is_none() {
226 self.start_blink(cx);
227 }
228 } else {
229 self.blink = None;
230 self.caret_on = true;
231 }
232 let theme = Theme::of(cx);
233 let mut key_context = gpui::KeyContext::default();
234 key_context.add(KEY_CONTEXT);
235 if self.shape.is_multiline() {
236 key_context.add(MULTILINE_KEY_CONTEXT);
237 }
238 if let Some(extra) = self.key_context.clone() {
239 key_context.add(extra);
240 }
241 div()
242 .key_context(key_context)
243 .track_focus(&self.focus_handle(cx))
244 .cursor(CursorStyle::IBeam)
245 .on_action(cx.listener(Self::backspace))
246 .on_action(cx.listener(Self::delete))
247 .on_action(cx.listener(Self::left))
248 .on_action(cx.listener(Self::right))
249 .on_action(cx.listener(Self::select_left))
250 .on_action(cx.listener(Self::select_right))
251 .on_action(cx.listener(Self::select_all))
252 .on_action(cx.listener(Self::home))
253 .on_action(cx.listener(Self::end))
254 .on_action(cx.listener(Self::select_home))
255 .on_action(cx.listener(Self::select_end))
256 .on_action(cx.listener(Self::word_left))
257 .on_action(cx.listener(Self::word_right))
258 .on_action(cx.listener(Self::select_word_left))
259 .on_action(cx.listener(Self::select_word_right))
260 .on_action(cx.listener(Self::up))
261 .on_action(cx.listener(Self::down))
262 .on_action(cx.listener(Self::select_up))
263 .on_action(cx.listener(Self::select_down))
264 .on_action(cx.listener(Self::insert_newline))
265 .on_action(cx.listener(Self::undo))
266 .on_action(cx.listener(Self::redo))
267 .on_action(cx.listener(Self::delete_word_left))
268 .on_action(cx.listener(Self::delete_word_right))
269 .on_action(cx.listener(Self::delete_to_line_start))
270 .on_action(cx.listener(Self::delete_to_line_end))
271 .on_action(cx.listener(Self::show_character_palette))
272 .on_action(cx.listener(Self::paste))
273 .on_action(cx.listener(Self::cut))
274 .on_action(cx.listener(Self::copy))
275 .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
276 .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
277 .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
278 .on_scroll_wheel(cx.listener(Self::on_scroll_wheel))
279 .w_full()
280 .when(self.frame, |field| {
281 field
282 .px(px(10.0))
283 .py(px(7.0))
284 .rounded(px(Theme::button_radius()))
285 .bg(theme.input_bg)
286 .border_1()
287 .border_color(if self.focus_handle.is_focused(_window) {
288 theme.ring
289 } else {
290 theme.border
291 })
292 })
293 .text_size(px(self.metrics.size()))
294 .font_weight(self.metrics.weight)
295 .line_height(px(self.metrics.line_height()))
296 .text_color(theme.text)
297 .child(TextFieldElement { field: cx.entity() })
298 }
299}
300
301pub(super) struct TextFieldElement {
305 field: Entity<TextField>,
306}
307
308pub(super) struct FieldPrepaint {
309 lines: Vec<WrappedLine>,
310 origin: Point<Pixels>,
312 cursor: Option<PaintQuad>,
313 matches: Vec<PaintQuad>,
315 selection: Vec<PaintQuad>,
317}
318
319impl IntoElement for TextFieldElement {
320 type Element = Self;
321
322 fn into_element(self) -> Self::Element {
323 self
324 }
325}
326
327impl Element for TextFieldElement {
328 type RequestLayoutState = ();
329 type PrepaintState = FieldPrepaint;
330
331 fn id(&self) -> Option<ElementId> {
332 None
333 }
334
335 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
336 None
337 }
338
339 fn request_layout(
340 &mut self,
341 _id: Option<&GlobalElementId>,
342 _inspector_id: Option<&gpui::InspectorElementId>,
343 window: &mut Window,
344 cx: &mut App,
345 ) -> (LayoutId, ()) {
346 let mut style = Style::default();
347 style.size.width = relative(1.).into();
348 let field = self.field.read(cx);
349 let line_height = field.line_height();
352 let shape = field.shape;
353
354 let (min, max) = match shape {
355 Shape::Line => {
356 style.size.height = line_height.into();
357 return (window.request_layout(style, [], cx), ());
358 }
359 Shape::Rows(rows) => {
360 style.size.height = (line_height * rows.max(1) as f32).into();
361 return (window.request_layout(style, [], cx), ());
362 }
363 Shape::Grow { min, max } => (min.max(1), max.max(min.max(1))),
367 };
368
369 let text = display_text(field).0;
370 let id = window.request_measured_layout(style, move |known, available, window, _cx| {
371 let text_style = window.text_style();
372 let font_size = text_style.font_size.to_pixels(window.rem_size());
373 let wrap_width = known.width.or(match available.width {
378 gpui::AvailableSpace::Definite(width) => Some(width),
379 _ => None,
380 });
381 let run = TextRun {
382 len: text.len(),
383 font: text_style.font(),
384 color: text_style.color,
385 background_color: None,
386 underline: None,
387 strikethrough: None,
388 };
389 let count = window
390 .text_system()
391 .shape_text(text.clone(), font_size, &[run], wrap_width, None)
392 .map(|lines| {
393 lines
394 .iter()
395 .map(|line| line.wrap_boundaries().len() + 1)
396 .sum::<usize>()
397 })
398 .unwrap_or(1);
399 gpui::size(
400 wrap_width.unwrap_or(px(0.)),
401 line_height * count.clamp(min, max) as f32,
402 )
403 });
404 (id, ())
405 }
406
407 fn prepaint(
408 &mut self,
409 _id: Option<&GlobalElementId>,
410 _inspector_id: Option<&gpui::InspectorElementId>,
411 bounds: Bounds<Pixels>,
412 _request_layout: &mut Self::RequestLayoutState,
413 window: &mut Window,
414 cx: &mut App,
415 ) -> FieldPrepaint {
416 let theme = Theme::of(cx).clone();
417 let field = self.field.read(cx);
418 let selected_range = field.selected_range.clone();
419 let cursor = field.cursor_offset();
420 let shape = field.shape;
421 let marked_range = field.marked_range.clone();
422 let matches: Vec<_> = field
423 .matches
424 .iter()
425 .filter(|range| {
426 range.start <= range.end
427 && field.content.is_char_boundary(range.start)
428 && field.content.is_char_boundary(range.end)
429 })
430 .cloned()
431 .collect();
432 let scrolled = field.scroll;
433 let caret_shape = cx.caret_shape();
434 let follow_caret = field.follow_caret || caret_shape != field.last_caret_shape;
435 let style = window.text_style();
436
437 let (text, is_placeholder) = display_text(field);
438 let text_color = if is_placeholder {
439 theme.text_faint
440 } else {
441 style.color
442 };
443
444 let run = TextRun {
445 len: text.len(),
446 font: style.font(),
447 color: text_color,
448 background_color: None,
449 underline: None,
450 strikethrough: None,
451 };
452 let runs = if is_placeholder {
456 vec![run]
457 } else {
458 coloured(&text, &field.spans, &run, &theme.syntax)
459 };
460 let runs = match marked_range.as_ref() {
461 Some(marked) => underlined(runs, marked),
462 None => runs,
463 };
464
465 let font_size = style.font_size.to_pixels(window.rem_size());
466 let line_height = field.line_height();
467 let wrap_width = shape.is_multiline().then_some(bounds.size.width);
470 let lines = window
471 .text_system()
472 .shape_text(text, font_size, &runs, wrap_width, None)
473 .map(|lines| lines.into_vec())
474 .unwrap_or_default();
475
476 let content_height: Pixels = lines.iter().map(|l| l.size(line_height).height).sum();
483 let content_width = lines.iter().map(|l| l.width()).fold(px(0.), Pixels::max);
484 let advance = (!is_placeholder && caret_shape != CaretShape::Bar)
485 .then(|| {
486 lines_from(&lines)
487 .find_map(|(start, line)| {
488 (cursor <= start + line.len()).then(|| {
489 caret::character_advance(line, &line.text, cursor.saturating_sub(start))
490 })
491 })
492 .flatten()
493 })
494 .flatten();
495 let caret_width = caret_shape
496 .quad(
497 Bounds::new(
498 gpui::point(px(0.), px(0.)),
499 gpui::size(CARET_WIDTH, font_size),
500 ),
501 advance,
502 theme.caret,
503 )
504 .bounds
505 .size
506 .width;
507 let end_padding = if caret_shape == CaretShape::Bar {
508 px(0.)
509 } else {
510 font_size / 2.
511 };
512 let max = gpui::point(
513 (content_width + end_padding - bounds.size.width).max(px(0.)),
514 (content_height - bounds.size.height).max(px(0.)),
515 );
516 let mut scroll = gpui::point(
517 scrolled.x.clamp(px(0.), max.x),
518 scrolled.y.clamp(px(0.), max.y),
519 );
520 if follow_caret && let Some(at) = position_for_offset(&lines, cursor, line_height) {
521 if at.y < scroll.y {
522 scroll.y = at.y;
523 } else if at.y + line_height > scroll.y + bounds.size.height {
524 scroll.y = at.y + line_height - bounds.size.height;
525 }
526 if at.x < scroll.x {
529 scroll.x = at.x;
530 } else if at.x + caret_width > scroll.x + bounds.size.width {
531 scroll.x = at.x + caret_width - bounds.size.width;
532 }
533 scroll.x = scroll.x.clamp(px(0.), max.x);
534 scroll.y = scroll.y.clamp(px(0.), max.y);
535 }
536 self.field.update(cx, |field, _| {
537 field.scroll = scroll;
538 field.follow_caret = false;
539 field.last_caret_shape = caret_shape;
540 });
541 let origin = bounds.origin - scroll;
542
543 let matches = if is_placeholder {
544 Vec::new()
545 } else {
546 matches
547 .iter()
548 .flat_map(|range| selection_rows(&lines, range, line_height))
549 .map(|rect| {
550 fill(
551 Bounds::new(origin + rect.origin, rect.size),
552 theme.warning.opacity(0.25),
553 )
554 })
555 .collect()
556 };
557
558 let (selection, cursor) = if selected_range.is_empty() {
559 let at = position_for_offset(&lines, cursor, line_height).unwrap_or_default();
560 (
561 Vec::new(),
562 Some(caret_shape.quad(
563 Bounds::new(
566 origin + at + gpui::point(px(0.), (line_height - font_size) / 2.),
567 gpui::size(CARET_WIDTH, font_size),
568 ),
569 advance,
570 theme.caret,
571 )),
572 )
573 } else {
574 (
575 selection_rows(&lines, &selected_range, line_height)
576 .into_iter()
577 .map(|rect| {
578 fill(
579 Bounds::new(origin + rect.origin, rect.size),
580 theme.selection,
581 )
582 })
583 .collect(),
584 None,
585 )
586 };
587
588 FieldPrepaint {
589 lines,
590 origin,
591 cursor,
592 matches,
593 selection,
594 }
595 }
596
597 fn paint(
598 &mut self,
599 _id: Option<&GlobalElementId>,
600 _inspector_id: Option<&gpui::InspectorElementId>,
601 bounds: Bounds<Pixels>,
602 _request_layout: &mut Self::RequestLayoutState,
603 prepaint: &mut Self::PrepaintState,
604 window: &mut Window,
605 cx: &mut App,
606 ) {
607 let focus_handle = self.field.read(cx).focus_handle.clone();
608 let caret_on = self.field.read(cx).caret_on;
609 window.handle_input(
610 &focus_handle,
611 ElementInputHandler::new(bounds, self.field.clone()),
612 cx,
613 );
614 let line_height = self.field.read(cx).line_height();
615 let dragged = self.field.clone();
625 window.on_mouse_event(move |event: &MouseMoveEvent, phase, _window, cx| {
626 if phase != DispatchPhase::Bubble || !event.dragging() {
631 return;
632 }
633 dragged.update(cx, |field, cx| {
634 field.drag_to(event.position, line_height, cx);
635 });
636 });
637 let lines = std::mem::take(&mut prepaint.lines);
638 let matches = std::mem::take(&mut prepaint.matches);
639 let selection = std::mem::take(&mut prepaint.selection);
640 let cursor = prepaint.cursor.take();
641 let origin = prepaint.origin;
642
643 window.with_content_mask(Some(gpui::ContentMask::new(bounds)), |window| {
646 for quad in matches.into_iter().chain(selection) {
647 window.paint_quad(quad);
648 }
649
650 let mut top = origin;
651 for line in &lines {
652 line.paint(top, line_height, gpui::TextAlign::Left, None, window, cx)
653 .ok();
654 top.y += line.size(line_height).height;
655 }
656
657 if focus_handle.is_focused(window)
660 && caret_on
661 && let Some(cursor) = cursor
662 {
663 window.paint_quad(cursor);
664 }
665 });
666
667 self.field.update(cx, |field, _| {
668 field.last_layout = lines;
669 field.last_bounds = Some(bounds);
670 });
671 }
672}