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