1use std::convert::TryInto;
37use std::iter::Iterator;
38use std::marker::PhantomData;
39
40use ratatui::{
41 buffer::Buffer,
42 layout::Rect,
43 style::{Modifier, Style},
44 text::Span,
45 widgets::{Block, StatefulWidget, Widget},
46};
47
48use modalkit::actions::*;
49use modalkit::editing::{
50 application::{ApplicationInfo, EmptyInfo},
51 buffer::{CursorGroupId, FollowersInfo, HighlightInfo},
52 completion::CompletionList,
53 context::{EditContext, Resolve},
54 cursor::Cursor,
55 rope::{CharOff, EditRope},
56 store::{SharedBuffer, Store},
57};
58use modalkit::errors::{EditError, EditResult, UIResult};
59use modalkit::prelude::*;
60
61use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
62
63use super::{ScrollActions, TerminalCursor, WindowOps};
64
65pub struct LeftGutterInfo {
67 text: String,
68 style: Style,
69}
70
71impl LeftGutterInfo {
72 pub fn new(text: String, style: Style) -> Self {
74 LeftGutterInfo { text, style }
75 }
76
77 fn render(&self, area: Rect, buf: &mut Buffer) {
78 let _ = buf.set_stringn(area.x, area.y, &self.text, area.width as usize, self.style);
79 }
80}
81
82pub struct RightGutterInfo {
84 text: String,
85 style: Style,
86}
87
88impl RightGutterInfo {
89 pub fn new(text: String, style: Style) -> Self {
91 RightGutterInfo { text, style }
92 }
93
94 fn render(&self, area: Rect, buf: &mut Buffer) {
95 let _ = buf.set_stringn(area.x, area.y, &self.text, area.width as usize, self.style);
96 }
97}
98
99pub struct TextBoxState<I: ApplicationInfo = EmptyInfo> {
101 buffer: SharedBuffer<I>,
102 group_id: CursorGroupId,
103 readonly: bool,
104
105 viewctx: ViewportContext<Cursor>,
106 term_cursor: (u16, u16),
107}
108
109pub struct TextBox<'a, I: ApplicationInfo = EmptyInfo> {
111 block: Option<Block<'a>>,
112 prompt: Span<'a>,
113 oneline: bool,
114 style: Style,
115
116 lgutter_width: u16,
117 rgutter_width: u16,
118
119 _pc: PhantomData<I>,
120}
121
122fn shift_corner_nowrap(cursor: &Cursor, corner: &mut Cursor, width: usize, height: usize) {
127 if cursor.y < corner.y {
128 corner.set_y(cursor.y);
129 } else if cursor.y >= corner.y + height {
130 corner.set_y(cursor.y - height + 1);
131 }
132
133 if cursor.x < corner.x {
134 corner.set_x(cursor.x);
135 } else if cursor.x >= corner.x + width {
136 corner.set_x(cursor.x - width + 1);
137 }
138}
139
140fn shift_corner_wrap(cursor: &Cursor, corner: &mut Cursor, height: usize) {
141 if cursor.y < corner.y {
142 corner.set_y(cursor.y);
143 corner.set_x(0);
144 } else if cursor.y >= corner.y + height {
145 corner.set_y(cursor.y - height + 1);
146 corner.set_x(0);
147 } else if cursor.y == corner.y && cursor.x < corner.x {
148 corner.set_x(0);
149 }
150}
151
152fn shift_corner_oneline(cursor: &Cursor, corner: &mut Cursor) {
153 if cursor < corner {
154 corner.set_y(cursor.y);
155 corner.set_x(cursor.x);
156 }
157}
158
159fn shift_corner(
160 viewctx: &mut ViewportContext<Cursor>,
161 cursor: &Cursor,
162 width: usize,
163 height: usize,
164) {
165 if viewctx.wrap {
166 shift_corner_wrap(cursor, &mut viewctx.corner, height);
167 } else {
168 shift_corner_nowrap(cursor, &mut viewctx.corner, width, height);
169 }
170}
171
172fn shift_cursor(cursor: &mut Cursor, corner: &Cursor, width: usize, height: usize) {
177 if cursor.y < corner.y {
178 cursor.set_y(corner.y);
179 } else if cursor.y >= corner.y + height {
180 cursor.set_y(corner.y + height - 1);
181 }
182
183 if cursor.x < corner.x {
184 cursor.set_x(corner.x);
185 } else if cursor.x >= corner.x + width {
186 cursor.set_x(corner.x + width - 1);
187 }
188}
189
190impl<I> TextBoxState<I>
191where
192 I: ApplicationInfo,
193{
194 pub fn new(buffer: SharedBuffer<I>) -> Self {
196 let mut viewctx = ViewportContext::default();
197 let group_id = buffer.write().unwrap().create_group();
198
199 viewctx.set_wrap(true);
200
201 TextBoxState {
202 buffer,
203 group_id,
204 readonly: false,
205
206 viewctx,
207 term_cursor: (0, 0),
208 }
209 }
210
211 pub fn buffer(&self) -> SharedBuffer<I> {
213 self.buffer.clone()
214 }
215
216 pub fn is_readonly(&self) -> bool {
218 self.readonly
219 }
220
221 pub fn set_readonly(&mut self, readonly: bool) {
223 self.readonly = readonly;
224 }
225
226 pub fn get(&self) -> EditRope {
228 self.buffer.read().unwrap().get().clone()
229 }
230
231 pub fn get_text(&self) -> String {
233 self.buffer.read().unwrap().get_text()
234 }
235
236 pub fn set_text<T: Into<EditRope>>(&mut self, t: T) {
238 self.buffer.write().unwrap().set_text(t)
239 }
240
241 pub fn reset(&mut self) -> EditRope {
243 self.buffer.write().unwrap().reset()
244 }
245
246 pub fn reset_text(&mut self) -> String {
248 self.buffer.write().unwrap().reset_text()
249 }
250
251 pub fn set_left_gutter(&mut self, line: usize, s: String, style: Option<Style>) {
253 let style = style.unwrap_or_default();
254 let info = LeftGutterInfo::new(s, style);
255
256 self.buffer.write().unwrap().set_line_info(line, info);
257 }
258
259 pub fn set_right_gutter(&mut self, line: usize, s: String, style: Option<Style>) {
261 let style = style.unwrap_or_default();
262 let info = RightGutterInfo::new(s, style);
263
264 self.buffer.write().unwrap().set_line_info(line, info);
265 }
266
267 pub fn set_wrap(&mut self, wrap: bool) {
269 self.viewctx.set_wrap(wrap);
270 }
271
272 pub fn set_term_info(&mut self, area: Rect) {
274 self.viewctx.dimensions = (area.width as usize, area.height as usize);
275 }
276
277 pub fn get_cursor(&mut self) -> Cursor {
279 self.buffer.write().unwrap().get_leader(self.group_id)
280 }
281
282 pub fn get_lines(&self) -> usize {
284 self.buffer.read().unwrap().get_lines()
285 }
286
287 pub fn has_lines(&self, max: usize) -> usize {
296 if self.viewctx.wrap {
297 let width = self.viewctx.get_width();
298 let mut count = 0;
299
300 if width == 0 {
301 return count;
302 }
303
304 let mut fline = false;
305
306 for line in self.buffer.read().unwrap().lines(0) {
307 count += 1;
308 let mut line_width = 0;
309 for c in line.chars(CharOff::from(0)) {
310 let c_width = c.width_cjk().unwrap_or(1);
311 if line_width + c_width > width {
312 count += 1;
313 line_width = c_width;
314 } else {
315 line_width += c_width;
316 }
317 }
318 fline |= line_width == width;
319
320 if count >= max {
321 return max;
322 }
323 }
324
325 if fline {
326 count += 1;
331 }
332
333 return count;
334 } else {
335 self.buffer.read().unwrap().get_lines().min(max)
336 }
337 }
338}
339
340macro_rules! c2cgi {
341 ($s: expr, $ctx: expr) => {
342 &($s.group_id, &$s.viewctx, $ctx)
343 };
344}
345
346impl<I> Editable<EditContext, Store<I>, I> for TextBoxState<I>
347where
348 I: ApplicationInfo,
349{
350 fn editor_command(
351 &mut self,
352 act: &EditorAction,
353 ctx: &EditContext,
354 store: &mut Store<I>,
355 ) -> EditResult<EditInfo, I> {
356 if self.readonly && !act.is_readonly(ctx) {
357 Err(EditError::ReadOnly)
358 } else {
359 self.buffer.editor_command(act, c2cgi!(self, ctx), store)
360 }
361 }
362}
363
364impl<I> Jumpable<EditContext, I> for TextBoxState<I>
365where
366 I: ApplicationInfo,
367{
368 fn jump(
369 &mut self,
370 list: PositionList,
371 dir: MoveDir1D,
372 count: usize,
373 ctx: &EditContext,
374 ) -> UIResult<usize, I> {
375 self.buffer.jump(list, dir, count, c2cgi!(self, ctx))
376 }
377}
378
379impl<I> Promptable<EditContext, Store<I>, I> for TextBoxState<I>
380where
381 I: ApplicationInfo,
382{
383 fn prompt(
384 &mut self,
385 _: &PromptAction,
386 _: &EditContext,
387 _: &mut Store<I>,
388 ) -> EditResult<Vec<(Action<I>, EditContext)>, I> {
389 Err(EditError::Failure("Not at a prompt".to_string()))
390 }
391}
392
393impl<I> Searchable<EditContext, Store<I>, I> for TextBoxState<I>
394where
395 I: ApplicationInfo,
396{
397 fn search(
398 &mut self,
399 dir: MoveDirMod,
400 count: Count,
401 ctx: &EditContext,
402 store: &mut Store<I>,
403 ) -> UIResult<EditInfo, I> {
404 self.buffer.search(dir, count, c2cgi!(self, ctx), store)
405 }
406}
407
408impl<I> ScrollActions<EditContext, Store<I>, I> for TextBoxState<I>
409where
410 I: ApplicationInfo,
411{
412 fn dirscroll(
413 &mut self,
414 dir: MoveDir2D,
415 size: ScrollSize,
416 count: &Count,
417 ctx: &EditContext,
418 _: &mut Store<I>,
419 ) -> EditResult<EditInfo, I> {
420 let count = ctx.resolve(count);
421
422 let height = self.viewctx.dimensions.1;
423 let rows = match size {
424 ScrollSize::Cell => count,
425 ScrollSize::HalfPage => count.saturating_mul(height) / 2,
426 ScrollSize::Page => count.saturating_mul(height),
427 };
428
429 let width = self.viewctx.dimensions.0;
430 let cols = match size {
431 ScrollSize::Cell => count,
432 ScrollSize::HalfPage => count.saturating_mul(width) / 2,
433 ScrollSize::Page => count.saturating_mul(width),
434 };
435
436 match (dir, self.viewctx.wrap) {
437 (MoveDir2D::Up, _) => self.viewctx.corner.up(rows),
438 (MoveDir2D::Down, _) => self.viewctx.corner.down(rows),
439 (MoveDir2D::Left, false) => self.viewctx.corner.left(cols),
440 (MoveDir2D::Right, false) => self.viewctx.corner.right(cols),
441 (MoveDir2D::Left | MoveDir2D::Right, true) => (),
442 };
443
444 let mut cursor = self.get_cursor();
451 let mut buffer = self.buffer.write().unwrap();
452 shift_cursor(&mut cursor, &self.viewctx.corner, width, height);
453 buffer.clamp(&mut cursor, c2cgi!(self, ctx));
454 shift_corner(&mut self.viewctx, &cursor, width, height);
455 buffer.set_leader(self.group_id, cursor);
456
457 Ok(None)
458 }
459
460 fn cursorpos(
461 &mut self,
462 pos: MovePosition,
463 axis: Axis,
464 _: &EditContext,
465 _: &mut Store<I>,
466 ) -> EditResult<EditInfo, I> {
467 if axis == Axis::Horizontal && self.viewctx.wrap {
468 return Ok(None);
469 }
470
471 let (width, height) = self.viewctx.dimensions;
472 let cursor = self.get_cursor();
473 shift_corner(&mut self.viewctx, &cursor, width, height);
474
475 match (axis, pos) {
476 (Axis::Horizontal, MovePosition::Beginning) => {
477 self.viewctx.corner.set_x(cursor.x);
478 },
479 (Axis::Horizontal, MovePosition::Middle) => {
480 let off = cursor.x.saturating_add(1).saturating_sub(width / 2);
481
482 self.viewctx.corner.set_x(off);
483 },
484 (Axis::Horizontal, MovePosition::End) => {
485 let off = cursor.x.saturating_add(1).saturating_sub(width);
486
487 self.viewctx.corner.set_x(off);
488 },
489 (Axis::Vertical, MovePosition::Beginning) => {
490 self.viewctx.corner.set_y(cursor.y);
491 },
492 (Axis::Vertical, MovePosition::Middle) => {
493 let off = cursor.y.saturating_add(1).saturating_sub(height / 2);
494
495 self.viewctx.corner.set_y(off);
496 },
497 (Axis::Vertical, MovePosition::End) => {
498 let off = cursor.y.saturating_add(1).saturating_sub(height);
499
500 self.viewctx.corner.set_y(off);
501 },
502 }
503
504 Ok(None)
505 }
506
507 fn linepos(
508 &mut self,
509 pos: MovePosition,
510 count: &Count,
511 ctx: &EditContext,
512 _: &mut Store<I>,
513 ) -> EditResult<EditInfo, I> {
514 let mut buffer = self.buffer.write().unwrap();
515 let max = buffer.get_lines();
516 let line = ctx.resolve(count).min(max).saturating_sub(1);
517
518 let height = self.viewctx.get_height();
519
520 buffer.set_leader(self.group_id, Cursor::new(line, 0));
521
522 match pos {
523 MovePosition::Beginning => {
524 self.viewctx.corner.set_y(line);
525 },
526 MovePosition::Middle => {
527 let off = line.saturating_add(1).saturating_sub(height / 2);
528
529 self.viewctx.corner.set_y(off);
530 },
531 MovePosition::End => {
532 let off = line.saturating_add(1).saturating_sub(height);
533
534 self.viewctx.corner.set_y(off);
535 },
536 }
537
538 Ok(None)
539 }
540}
541
542impl<I> Scrollable<EditContext, Store<I>, I> for TextBoxState<I>
543where
544 I: ApplicationInfo,
545{
546 fn scroll(
547 &mut self,
548 style: &ScrollStyle,
549 ctx: &EditContext,
550 store: &mut Store<I>,
551 ) -> EditResult<EditInfo, I> {
552 match style {
553 ScrollStyle::Direction2D(dir, size, count) => {
554 return self.dirscroll(*dir, *size, count, ctx, store);
555 },
556 ScrollStyle::CursorPos(pos, axis) => {
557 return self.cursorpos(*pos, *axis, ctx, store);
558 },
559 ScrollStyle::LinePos(pos, count) => {
560 return self.linepos(*pos, count, ctx, store);
561 },
562 }
563 }
564}
565
566impl<I> TerminalCursor for TextBoxState<I>
567where
568 I: ApplicationInfo,
569{
570 fn get_term_cursor(&self) -> Option<(u16, u16)> {
571 if self.viewctx.get_height() == 0 {
572 return None;
573 }
574
575 self.term_cursor.into()
576 }
577}
578
579impl<I> WindowOps<I> for TextBoxState<I>
580where
581 I: ApplicationInfo,
582{
583 fn dup(&self, _: &mut Store<I>) -> Self {
584 let buffer = self.buffer.clone();
585 let group_id = buffer.write().unwrap().create_group();
586
587 TextBoxState {
588 buffer,
589 group_id,
590 readonly: self.readonly,
591
592 viewctx: self.viewctx.clone(),
593 term_cursor: (0, 0),
594 }
595 }
596
597 fn close(&mut self, _: CloseFlags, _: &mut Store<I>) -> bool {
598 true
599 }
600
601 fn write(&mut self, _: Option<&str>, _: WriteFlags, _: &mut Store<I>) -> UIResult<EditInfo, I> {
602 if self.readonly {
603 return Err(EditError::ReadOnly.into());
604 } else {
605 return Ok(None);
606 }
607 }
608
609 fn draw(&mut self, area: Rect, buf: &mut Buffer, _: bool, _: &mut Store<I>) {
610 TextBox::new().render(area, buf, self);
611 }
612
613 fn get_completions(&self) -> Option<CompletionList> {
614 self.buffer.read().unwrap().get_completions(self.group_id)
615 }
616
617 fn get_cursor_word(&self, style: &WordStyle) -> Option<String> {
618 self.buffer.read().unwrap().get_cursor_word(self.group_id, style)
619 }
620
621 fn get_selected_word(&self) -> Option<String> {
622 self.buffer.read().unwrap().get_selected_word(self.group_id)
623 }
624}
625
626impl<'a, I> TextBox<'a, I>
627where
628 I: ApplicationInfo,
629{
630 pub fn new() -> Self {
632 TextBox {
633 block: None,
634 prompt: Span::default(),
635 oneline: false,
636 style: Style::default(),
637
638 lgutter_width: 0,
639 rgutter_width: 0,
640
641 _pc: PhantomData,
642 }
643 }
644
645 pub fn style(mut self, style: Style) -> Self {
647 self.style = style;
648 self
649 }
650
651 pub fn block(mut self, block: Block<'a>) -> Self {
653 self.block = Some(block);
654 self
655 }
656
657 pub fn oneline(mut self) -> Self {
662 self.oneline = true;
663 self
664 }
665
666 pub fn prompt(mut self, prompt: impl Into<Span<'a>>) -> Self {
668 self.prompt = prompt.into();
669 self
670 }
671
672 pub fn left_gutter(mut self, lw: u16) -> Self {
674 self.lgutter_width = lw;
675 self
676 }
677
678 pub fn right_gutter(mut self, rw: u16) -> Self {
680 self.rgutter_width = rw;
681 self
682 }
683
684 #[inline]
685 fn _highlight_followers(
686 &self,
687 line: usize,
688 start: usize,
689 end: usize,
690 (x, y): (u16, u16),
691 followers: &FollowersInfo,
692 buf: &mut Buffer,
693 ) {
694 let hlstyled = self.style.add_modifier(Modifier::REVERSED);
695 let cs = (line, start);
696 let ce = (line, end);
697
698 for follower in followers.query(cs..ce) {
699 let fx = x + (follower.value.x - start) as u16;
700 let fa = Rect::new(fx, y, 1, 1);
701 buf.set_style(fa, hlstyled);
702 }
703 }
704
705 #[inline]
706 fn _set_style(&self, start: usize, h1: usize, h2: usize, (x, y): (u16, u16), buf: &mut Buffer) {
707 let tx: u16 = x + (h1 - start) as u16;
708 let selwidth: u16 = (h2 - h1 + 1).try_into().unwrap();
709
710 let hlstyled = self.style.add_modifier(Modifier::REVERSED);
711 let selarea = Rect::new(tx, y, selwidth, 1);
712
713 buf.set_style(selarea, hlstyled);
714 }
715
716 #[inline]
717 fn _highlight_line(
718 &self,
719 line: usize,
720 start: usize,
721 end: usize,
722 (x, y): (u16, u16),
723 hls: &HighlightInfo,
724 buf: &mut Buffer,
725 ) {
726 for selection in hls.query_point(line) {
727 let (sb, se, shape) = &selection.value;
728
729 let maxcol = end.saturating_sub(1);
730 let range = start..end;
731
732 match shape {
733 TargetShape::CharWise => {
734 let x1 = if line == sb.y { sb.x.max(start) } else { start };
735 let x2 = if line == se.y {
736 se.x.min(maxcol)
737 } else {
738 maxcol
739 };
740
741 if range.contains(&x1) && range.contains(&x2) {
742 self._set_style(start, x1, x2, (x, y), buf);
743 }
744 },
745 TargetShape::LineWise => {
746 let hlstyled = self.style.add_modifier(Modifier::REVERSED);
747 let selwidth: u16 = (end - start).try_into().unwrap();
748 let selarea = Rect::new(x, y, selwidth, 1);
749
750 buf.set_style(selarea, hlstyled);
751 },
752 TargetShape::BlockWise => {
753 let lx = sb.x.min(se.x);
754 let rx = sb.x.max(se.x);
755
756 let x1 = lx.max(start);
757 let x2 = rx.min(maxcol);
758
759 if range.contains(&x1) && range.contains(&x2) {
760 self._set_style(start, x1, x2, (x, y), buf);
761 }
762 },
763 }
764 }
765 }
766
767 fn _render_lines_wrap(
768 &mut self,
769 area: Rect,
770 gutters: (Rect, Rect),
771 buf: &mut Buffer,
772 hinfo: HighlightInfo,
773 finfo: FollowersInfo,
774 state: &mut TextBoxState<I>,
775 ) {
776 let bot = area.bottom();
777 let x = area.left();
778 let mut y = area.top();
779
780 let height = area.height as usize;
781 let width = area.width as usize;
782
783 let cursor = state.get_cursor();
790 shift_corner_wrap(&cursor, &mut state.viewctx.corner, height);
791
792 let cby = state.viewctx.corner.y;
793 let cbx = state.viewctx.corner.x;
794
795 let text = state.buffer.read().unwrap();
796
797 let mut wrapped = Vec::new();
798 let mut sawcursor = false;
799
800 for (loff, s) in text.lines_at(cby, cbx).enumerate() {
801 if wrapped.len() >= height && sawcursor {
802 break;
803 }
804
805 let base = if loff == 0 { cbx } else { 0 };
806 let line = cby + loff;
807 let mut first = true;
808 let mut char_offset = 0;
809 let s_charlen = s.len();
810
811 while char_offset < s_charlen && (wrapped.len() < height || !sawcursor) {
812 let start_char = char_offset;
813
814 let mut full = false;
815 let mut num_chars = 0;
816 let mut cur_width = 0;
817 for (i, c) in s.chars(CharOff::from(start_char)).enumerate() {
818 let c_width = c.width_cjk().unwrap_or(1);
819 if cur_width + c_width > width {
820 full = true;
821 break;
822 }
823 cur_width += c_width;
824 num_chars = i + 1;
825 }
826
827 let end_char = start_char + num_chars;
828 let swrapped =
829 s.slice(CharOff::from(start_char)..CharOff::from(end_char)).to_string();
830 char_offset = end_char;
831
832 let start = base + start_char;
833 let end = base + end_char;
834 let slen = base + s_charlen;
835
836 full |= cur_width == width;
837 let last = end == slen && cursor.x == slen;
838 let cursor_line = line == cursor.y && ((start..end).contains(&cursor.x) || last);
839
840 if cursor_line && full && last {
841 wrapped.push((line, start, end, swrapped, false, first));
842 wrapped.push((line, end, end, " ".to_string(), true, first));
843 } else {
844 wrapped.push((line, start, end, swrapped, cursor_line, first));
845 }
846
847 sawcursor |= cursor_line;
848
849 first = false;
850 }
851
852 if s_charlen == 0 {
853 let cursor_line = line == cursor.y;
854 wrapped.push((line, base, base, s.to_string(), cursor_line, true));
855 sawcursor |= cursor_line;
856 }
857 }
858
859 if wrapped.len() > height {
860 let n = wrapped.len() - height;
861 let _ = wrapped.drain(..n);
862 let (line, start, _, _, _, _) = wrapped.first().unwrap();
863 state.viewctx.corner.set_y(*line);
864 state.viewctx.corner.set_x(*start);
865 }
866
867 for (line, start, end, s, cursor_line, first) in wrapped.into_iter() {
868 if y >= bot {
869 break;
870 }
871
872 if first {
873 let lgutter = text.get_line_info::<LeftGutterInfo>(line);
874 let rgutter = text.get_line_info::<RightGutterInfo>(line);
875
876 if let Some(lgi) = lgutter {
877 let lga = Rect::new(gutters.0.x, y, gutters.0.width, 0);
878 lgi.render(lga, buf);
879 }
880
881 if let Some(rgi) = rgutter {
882 let rga = Rect::new(gutters.1.x, y, gutters.1.width, 0);
883 rgi.render(rga, buf);
884 }
885 }
886
887 if cursor_line {
888 let coff = s[..s
889 .char_indices()
890 .map(|(i, _)| i)
891 .nth(cursor.x.saturating_sub(start))
892 .unwrap_or(s.len())]
893 .width_cjk() as u16;
894
895 state.term_cursor = (x + coff, y);
896 }
897
898 let _ = buf.set_stringn(x, y, s, width, self.style);
899
900 self._highlight_followers(line, start, end, (x, y), &finfo, buf);
901 self._highlight_line(line, start, end, (x, y), &hinfo, buf);
902
903 y += 1;
904 }
905 }
906
907 fn _render_lines_oneline(
908 &mut self,
909 area: Rect,
910 buf: &mut Buffer,
911 hinfo: HighlightInfo,
912 finfo: FollowersInfo,
913 state: &mut TextBoxState<I>,
914 ) {
915 let right = area.right();
916 let mut x = area.left();
917 let y = area.top();
918
919 let width = area.width as usize;
920
921 let cursor = state.get_cursor();
923 shift_corner_oneline(&cursor, &mut state.viewctx.corner);
924
925 let cby = state.viewctx.corner.y;
926 let cbx = state.viewctx.corner.x;
927
928 let text = state.buffer.read().unwrap();
929
930 let mut joined = Vec::new();
931 let mut sawcursor = false;
932 let mut len = 0;
933 let mut off = cbx;
934
935 for (loff, s) in text.lines_at(cby, cbx).enumerate() {
936 if len >= width && sawcursor {
937 break;
938 }
939
940 let base = if loff == 0 { cbx } else { 0 };
941 let line = cby + loff;
942 let slen = s.len();
943
944 while off < slen && (len <= width || !sawcursor) {
945 let start = off;
946 let end = (start + width).min(slen);
947 let swrapped = s.slice(CharOff::from(start)..CharOff::from(end));
948
949 off = end;
950
951 let start = base + start;
952 let end = base + end;
953 let slen = base + slen;
954
955 let full = end - start == width;
956 let last = end == slen && cursor.x == slen;
957 let cursor_line = line == cursor.y && ((start..end).contains(&cursor.x) || last);
958
959 let wlen = swrapped.len();
960
961 if cursor_line && full && last {
962 joined.push((line, start, end, swrapped, wlen, false));
963 joined.push((line, end, end, EditRope::from(" "), 1, true));
964 len += wlen + 1;
965 } else {
966 joined.push((line, start, end, swrapped, wlen, cursor_line));
967 len += wlen;
968 }
969
970 sawcursor |= cursor_line;
971 }
972
973 if slen == 0 {
974 let cursor_line = line == cursor.y;
975 joined.push((line, 0, 0, s, 0, cursor_line));
976 sawcursor |= cursor_line;
977 }
978
979 joined.push((line, slen, slen, EditRope::from("^J"), 2, false));
980 len += 2;
981
982 off = 0;
984 }
985
986 if !joined.is_empty() {
987 joined.pop();
989 len -= 2;
990 }
991
992 if len > width {
993 let mut n = 0;
994
995 for (idx, (_, ref mut start, _, ref mut s, slen, ref cursor_line)) in
996 joined.iter_mut().enumerate()
997 {
998 if len <= width {
999 break;
1000 }
1001
1002 let diff = len - width;
1003 n = idx;
1004
1005 if *cursor_line {
1006 let into = cursor.x - *start;
1007 let rm = diff.min(into);
1008 *s = s.slice(CharOff::from(rm)..);
1009 *start += rm;
1010 break;
1011 } else if *slen > diff {
1012 *s = s.slice(CharOff::from(diff)..);
1013 *start += diff;
1014 break;
1015 } else {
1016 len -= *slen;
1017 continue;
1018 }
1019 }
1020
1021 let _ = joined.drain(..n);
1022 let (line, start, _, _, _, _) = joined.first().unwrap();
1023 state.viewctx.corner.set_y(*line);
1024 state.viewctx.corner.set_x(*start);
1025 }
1026
1027 state.term_cursor = (x, y);
1028
1029 for (line, start, end, s, _, cursor_line) in joined.into_iter() {
1030 if x >= right {
1031 break;
1032 }
1033
1034 let s = s.to_string();
1035 let w = (right - x) as usize;
1036
1037 if cursor_line {
1038 let coff = s[..s
1039 .char_indices()
1040 .map(|(i, _)| i)
1041 .nth(cursor.x.saturating_sub(start))
1042 .unwrap_or(s.len())]
1043 .width_cjk() as u16;
1044
1045 state.term_cursor = (x + coff, y);
1046 }
1047
1048 let (xres, _) = buf.set_stringn(x, y, s, w, self.style);
1049
1050 self._highlight_followers(line, start, end, (x, y), &finfo, buf);
1051 self._highlight_line(line, start, end, (x, y), &hinfo, buf);
1052
1053 x = xres;
1054 }
1055 }
1056
1057 fn _render_lines_nowrap(
1058 &mut self,
1059 area: Rect,
1060 gutters: (Rect, Rect),
1061 buf: &mut Buffer,
1062 hinfo: HighlightInfo,
1063 finfo: FollowersInfo,
1064 state: &mut TextBoxState<I>,
1065 ) {
1066 let bot = area.bottom();
1067 let x = area.left();
1068 let mut y = area.top();
1069
1070 let height = area.height as usize;
1071 let width = area.width as usize;
1072
1073 let cursor = state.get_cursor();
1075 shift_corner_nowrap(&cursor, &mut state.viewctx.corner, width, height);
1076
1077 let cby = state.viewctx.corner.y;
1078 let cbx = state.viewctx.corner.x;
1079
1080 let text = state.buffer.read().unwrap();
1081 let mut line = cby;
1082 let mut lines = text.lines(line);
1083
1084 while y < bot {
1085 if let Some(s) = lines.next() {
1086 let lgutter = text.get_line_info::<LeftGutterInfo>(line);
1087 let rgutter = text.get_line_info::<RightGutterInfo>(line);
1088
1089 let slen = s.len();
1090 let start = cbx;
1091 let end = slen;
1092
1093 if let Some(lgi) = lgutter {
1094 let lga = Rect::new(gutters.0.x, y, gutters.0.width, 0);
1095 lgi.render(lga, buf);
1096 }
1097
1098 let s = s.slice(CharOff::from(start)..CharOff::from(end)).to_string();
1099
1100 if line == cursor.y && (start..=end).contains(&cursor.x) {
1101 let coff = s[..s
1102 .char_indices()
1103 .map(|(i, _)| i)
1104 .nth(cursor.x.saturating_sub(start))
1105 .unwrap_or(s.len())]
1106 .width_cjk() as u16;
1107
1108 state.term_cursor = (x + coff, y);
1109 }
1110
1111 if cbx < slen {
1112 let _ = buf.set_stringn(x, y, s, width, self.style);
1113 }
1114
1115 if let Some(rgi) = rgutter {
1116 let rga = Rect::new(gutters.1.x, y, gutters.1.width, 0);
1117 rgi.render(rga, buf);
1118 }
1119
1120 self._highlight_followers(line, start, end, (x, y), &finfo, buf);
1121 self._highlight_line(line, start, end, (x, y), &hinfo, buf);
1122
1123 y += 1;
1124 line += 1;
1125 } else {
1126 break;
1127 }
1128 }
1129 }
1130
1131 #[inline]
1132 fn _selection_intervals(&self, state: &mut TextBoxState<I>) -> HighlightInfo {
1133 state.buffer.write().unwrap().selection_intervals(state.group_id)
1134 }
1135
1136 #[inline]
1137 fn _follower_intervals(&self, state: &mut TextBoxState<I>) -> FollowersInfo {
1138 state.buffer.write().unwrap().follower_intervals(state.group_id)
1139 }
1140
1141 fn _render_lines(&mut self, area: Rect, buf: &mut Buffer, state: &mut TextBoxState<I>) {
1142 let hinfo = self._selection_intervals(state);
1143 let finfo = self._follower_intervals(state);
1144
1145 if self.oneline {
1146 state.set_term_info(area);
1147 self._render_lines_oneline(area, buf, hinfo, finfo, state);
1148 return;
1149 }
1150
1151 let (lgw, rgw) = if area.width <= self.lgutter_width + self.rgutter_width {
1152 (0, 0)
1153 } else {
1154 (self.lgutter_width, self.rgutter_width)
1155 };
1156 let textw = area.width - lgw - rgw;
1157 let lga = Rect::new(area.x, area.y, lgw, area.height);
1158 let texta = Rect::new(area.x + lgw, area.y, textw, area.height);
1159 let rga = Rect::new(area.x + lgw + textw, area.y, rgw, area.height);
1160 let gutters = (lga, rga);
1161
1162 state.set_term_info(texta);
1163
1164 if state.viewctx.wrap {
1165 self._render_lines_wrap(texta, gutters, buf, hinfo, finfo, state);
1166 } else {
1167 self._render_lines_nowrap(texta, gutters, buf, hinfo, finfo, state);
1168 }
1169 }
1170}
1171
1172impl<I> Default for TextBox<'_, I>
1173where
1174 I: ApplicationInfo,
1175{
1176 fn default() -> Self {
1177 TextBox::new()
1178 }
1179}
1180
1181impl<I> StatefulWidget for TextBox<'_, I>
1182where
1183 I: ApplicationInfo,
1184{
1185 type State = TextBoxState<I>;
1186
1187 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1188 let area = match self.block.take() {
1189 Some(block) => {
1190 let inner_area = block.inner(area);
1191 block.render(area, buf);
1192 inner_area
1193 },
1194 None => area,
1195 };
1196
1197 let plen = self.prompt.width() as u16;
1198 let gutter = Rect::new(area.x, area.y, plen, area.height);
1199
1200 let text_area =
1201 Rect::new(area.x + plen, area.y, area.width.saturating_sub(plen), area.height);
1202
1203 if text_area.width == 0 || text_area.height == 0 {
1204 return;
1205 }
1206
1207 let _ = buf.set_span(gutter.left(), gutter.top(), &self.prompt, gutter.width);
1209
1210 self._render_lines(text_area, buf, state);
1212 }
1213}
1214
1215#[cfg(test)]
1216mod tests {
1217 use super::*;
1218 use modalkit::editing::store::Store;
1219 use modalkit::env::vim::VimState;
1220
1221 macro_rules! mv {
1222 ($mt: expr) => {
1223 EditTarget::Motion($mt, Count::Contextual)
1224 };
1225 ($mt: expr, $c: expr) => {
1226 EditTarget::Motion($mt, Count::Exact($c))
1227 };
1228 }
1229
1230 macro_rules! dirscroll {
1231 ($tbox: expr, $d: expr, $s: expr, $c: expr, $ctx: expr, $store: expr) => {
1232 $tbox
1233 .scroll(&ScrollStyle::Direction2D($d, $s, $c), $ctx, &mut $store)
1234 .unwrap()
1235 };
1236 }
1237
1238 macro_rules! cursorpos {
1239 ($tbox: expr, $pos: expr, $axis: expr, $ctx: expr, $store: expr) => {
1240 $tbox
1241 .scroll(&ScrollStyle::CursorPos($pos, $axis), $ctx, &mut $store)
1242 .unwrap()
1243 };
1244 }
1245
1246 macro_rules! linepos {
1247 ($tbox: expr, $pos: expr, $c: expr, $ctx: expr, $store: expr) => {
1248 $tbox.scroll(&ScrollStyle::LinePos($pos, $c), $ctx, &mut $store).unwrap()
1249 };
1250 }
1251
1252 fn mkbox() -> (TextBoxState, Store<EmptyInfo>) {
1253 let mut store = Store::default();
1254 let buffer = store.load_buffer("".to_string());
1255
1256 (TextBoxState::new(buffer), store)
1257 }
1258
1259 fn mkboxstr(s: &str) -> (TextBoxState, EditContext, Store<EmptyInfo>) {
1260 let (mut b, mut store) = mkbox();
1261 let ctx = EditContext::from(VimState::<EmptyInfo>::default());
1262
1263 b.set_text(s);
1264 b.editor_command(&HistoryAction::Checkpoint.into(), &ctx, &mut store)
1265 .unwrap();
1266
1267 return (b, ctx, store);
1268 }
1269
1270 #[test]
1271 fn test_scroll_dir1d() {
1272 let (mut tbox, ctx, mut store) = mkboxstr(
1273 "1234567890\n\
1274 abcdefghij\n\
1275 klmnopqrst\n\
1276 uvwxyz,.<>\n\
1277 -_=+[{]}\\|\n\
1278 !@#$%^&*()\n\
1279 1234567890\n",
1280 );
1281
1282 tbox.set_wrap(false);
1283 tbox.set_term_info(Rect::new(0, 0, 6, 4));
1284
1285 dirscroll!(tbox, MoveDir2D::Down, ScrollSize::Cell, 4.into(), &ctx, store);
1287 assert_eq!(tbox.viewctx.corner, Cursor::new(4, 0));
1288 assert_eq!(tbox.get_cursor(), Cursor::new(4, 0));
1289
1290 dirscroll!(tbox, MoveDir2D::Up, ScrollSize::Cell, 2.into(), &ctx, store);
1291 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 0));
1292 assert_eq!(tbox.get_cursor(), Cursor::new(4, 0));
1293
1294 dirscroll!(tbox, MoveDir2D::Right, ScrollSize::Cell, 6.into(), &ctx, store);
1295 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 6));
1296 assert_eq!(tbox.get_cursor(), Cursor::new(4, 6));
1297
1298 dirscroll!(tbox, MoveDir2D::Left, ScrollSize::Cell, 2.into(), &ctx, store);
1299 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 4));
1300 assert_eq!(tbox.get_cursor(), Cursor::new(4, 6));
1301
1302 dirscroll!(tbox, MoveDir2D::Down, ScrollSize::HalfPage, Count::Contextual, &ctx, store);
1304 assert_eq!(tbox.viewctx.corner, Cursor::new(4, 4));
1305 assert_eq!(tbox.get_cursor(), Cursor::new(4, 6));
1306
1307 dirscroll!(tbox, MoveDir2D::Up, ScrollSize::HalfPage, Count::Contextual, &ctx, store);
1308 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 4));
1309 assert_eq!(tbox.get_cursor(), Cursor::new(4, 6));
1310
1311 dirscroll!(tbox, MoveDir2D::Right, ScrollSize::HalfPage, Count::Contextual, &ctx, store);
1312 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 7));
1313 assert_eq!(tbox.get_cursor(), Cursor::new(4, 7));
1314
1315 dirscroll!(tbox, MoveDir2D::Left, ScrollSize::HalfPage, Count::Contextual, &ctx, store);
1316 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 4));
1317 assert_eq!(tbox.get_cursor(), Cursor::new(4, 7));
1318
1319 dirscroll!(tbox, MoveDir2D::Down, ScrollSize::Page, Count::Contextual, &ctx, store);
1321 assert_eq!(tbox.viewctx.corner, Cursor::new(6, 4));
1322 assert_eq!(tbox.get_cursor(), Cursor::new(6, 7));
1323
1324 dirscroll!(tbox, MoveDir2D::Up, ScrollSize::Page, Count::Contextual, &ctx, store);
1325 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 4));
1326 assert_eq!(tbox.get_cursor(), Cursor::new(5, 7));
1327
1328 dirscroll!(tbox, MoveDir2D::Right, ScrollSize::Page, Count::Contextual, &ctx, store);
1329 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 9));
1330 assert_eq!(tbox.get_cursor(), Cursor::new(5, 9));
1331
1332 dirscroll!(tbox, MoveDir2D::Left, ScrollSize::Page, Count::Contextual, &ctx, store);
1333 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 3));
1334 assert_eq!(tbox.get_cursor(), Cursor::new(5, 8));
1335
1336 dirscroll!(tbox, MoveDir2D::Right, ScrollSize::Page, Count::Contextual, &ctx, store);
1338 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 9));
1339 assert_eq!(tbox.get_cursor(), Cursor::new(5, 9));
1340
1341 dirscroll!(tbox, MoveDir2D::Right, ScrollSize::Page, Count::Contextual, &ctx, store);
1342 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 9));
1343 assert_eq!(tbox.get_cursor(), Cursor::new(5, 9));
1344 }
1345
1346 #[test]
1347 fn test_scroll_cursorpos() {
1348 let (mut tbox, ctx, mut store) = mkboxstr(
1349 "1234567890\n\
1350 abcdefghij\n\
1351 klmnopqrst\n\
1352 uvwxyz,.<>\n\
1353 -_=+[{]}\\|\n\
1354 !@#$%^&*()\n\
1355 1234567890\n",
1356 );
1357
1358 tbox.set_wrap(false);
1359 tbox.set_term_info(Rect::new(0, 0, 4, 4));
1360
1361 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1363 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1364
1365 cursorpos!(tbox, MovePosition::Beginning, Axis::Vertical, &ctx, store);
1366 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1367 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1368
1369 cursorpos!(tbox, MovePosition::Middle, Axis::Vertical, &ctx, store);
1370 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1371 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1372
1373 cursorpos!(tbox, MovePosition::End, Axis::Vertical, &ctx, store);
1374 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1375 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1376
1377 cursorpos!(tbox, MovePosition::Beginning, Axis::Horizontal, &ctx, store);
1378 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1379 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1380
1381 cursorpos!(tbox, MovePosition::Middle, Axis::Horizontal, &ctx, store);
1382 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1383 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1384
1385 cursorpos!(tbox, MovePosition::End, Axis::Horizontal, &ctx, store);
1386 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1387 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1388
1389 let mov = mv!(MoveType::BufferLineOffset, 5);
1391 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1392 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1393 assert_eq!(tbox.get_cursor(), Cursor::new(4, 0));
1394
1395 let mov = mv!(MoveType::LineColumnOffset, 2);
1396 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1397 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1398 assert_eq!(tbox.get_cursor(), Cursor::new(4, 1));
1399
1400 cursorpos!(tbox, MovePosition::Beginning, Axis::Vertical, &ctx, store);
1401 assert_eq!(tbox.get_cursor(), Cursor::new(4, 1));
1402 assert_eq!(tbox.viewctx.corner, Cursor::new(4, 0));
1403
1404 cursorpos!(tbox, MovePosition::End, Axis::Vertical, &ctx, store);
1405 assert_eq!(tbox.get_cursor(), Cursor::new(4, 1));
1406 assert_eq!(tbox.viewctx.corner, Cursor::new(1, 0));
1407
1408 cursorpos!(tbox, MovePosition::Middle, Axis::Vertical, &ctx, store);
1409 assert_eq!(tbox.get_cursor(), Cursor::new(4, 1));
1410 assert_eq!(tbox.viewctx.corner, Cursor::new(3, 0));
1411
1412 let mov = mv!(MoveType::LineColumnOffset, 5);
1414 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1415 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1416 assert_eq!(tbox.get_cursor(), Cursor::new(4, 4));
1417
1418 cursorpos!(tbox, MovePosition::Beginning, Axis::Horizontal, &ctx, store);
1419 assert_eq!(tbox.get_cursor(), Cursor::new(4, 4));
1420 assert_eq!(tbox.viewctx.corner, Cursor::new(3, 4));
1421
1422 cursorpos!(tbox, MovePosition::End, Axis::Horizontal, &ctx, store);
1423 assert_eq!(tbox.get_cursor(), Cursor::new(4, 4));
1424 assert_eq!(tbox.viewctx.corner, Cursor::new(3, 1));
1425
1426 cursorpos!(tbox, MovePosition::Middle, Axis::Horizontal, &ctx, store);
1427 assert_eq!(tbox.get_cursor(), Cursor::new(4, 4));
1428 assert_eq!(tbox.viewctx.corner, Cursor::new(3, 3));
1429
1430 let mov = MoveType::FirstWord(MoveDir1D::Next);
1432 let act = EditorAction::Edit(EditAction::Motion.into(), mv!(mov, 0));
1433 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1434 cursorpos!(tbox, MovePosition::Beginning, Axis::Vertical, &ctx, store);
1435 assert_eq!(tbox.get_cursor(), Cursor::new(4, 0));
1436 assert_eq!(tbox.viewctx.corner, Cursor::new(4, 0));
1437 }
1438
1439 #[test]
1440 fn test_scroll_linepos() {
1441 let (mut tbox, ctx, mut store) = mkboxstr(
1442 "1234567890\n\
1443 abcdefghij\n\
1444 klmnopqrst\n\
1445 uvwxyz,.<>\n\
1446 -_=+[{]}\\|\n\
1447 !@#$%^&*()\n\
1448 1234567890\n",
1449 );
1450
1451 tbox.set_wrap(false);
1452 tbox.set_term_info(Rect::new(0, 0, 4, 4));
1453
1454 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1455 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1456
1457 linepos!(tbox, MovePosition::Beginning, Count::Exact(3), &ctx, store);
1459 assert_eq!(tbox.get_cursor(), Cursor::new(2, 0));
1460 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 0));
1461
1462 linepos!(tbox, MovePosition::Middle, Count::Exact(7), &ctx, store);
1464 assert_eq!(tbox.get_cursor(), Cursor::new(6, 0));
1465 assert_eq!(tbox.viewctx.corner, Cursor::new(5, 0));
1466
1467 linepos!(tbox, MovePosition::Middle, Count::Exact(1), &ctx, store);
1469 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1470 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1471
1472 linepos!(tbox, MovePosition::End, Count::Exact(1), &ctx, store);
1474 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1475 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1476
1477 linepos!(tbox, MovePosition::End, Count::Exact(2), &ctx, store);
1479 assert_eq!(tbox.get_cursor(), Cursor::new(1, 0));
1480 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1481
1482 linepos!(tbox, MovePosition::End, Count::Exact(3), &ctx, store);
1484 assert_eq!(tbox.get_cursor(), Cursor::new(2, 0));
1485 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1486
1487 linepos!(tbox, MovePosition::End, Count::Exact(4), &ctx, store);
1489 assert_eq!(tbox.get_cursor(), Cursor::new(3, 0));
1490 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1491
1492 linepos!(tbox, MovePosition::End, Count::Exact(5), &ctx, store);
1494 assert_eq!(tbox.get_cursor(), Cursor::new(4, 0));
1495 assert_eq!(tbox.viewctx.corner, Cursor::new(1, 0));
1496 }
1497
1498 #[test]
1499 fn test_reset_text() {
1500 let (mut tbox, ctx, mut store) = mkboxstr("foo\nbar\nbaz");
1501
1502 let mov = mv!(MoveType::BufferLineOffset, 3);
1503 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1504 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1505
1506 assert_eq!(tbox.get_text(), "foo\nbar\nbaz\n");
1507 assert_eq!(tbox.get_cursor(), Cursor::new(2, 0));
1508
1509 assert_eq!(tbox.reset_text(), "foo\nbar\nbaz\n");
1510 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1511
1512 assert_eq!(tbox.get_text(), "\n");
1513 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1514 }
1515
1516 #[test]
1517 fn test_render_nowrap() {
1518 let (mut tbox, ctx, mut store) = mkboxstr("foo\nbar\nbaz\nquux 1 2 3 4 5");
1519
1520 tbox.set_wrap(false);
1521
1522 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
1523 let area = Rect::new(0, 8, 10, 2);
1524
1525 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1526
1527 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1528 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1529 assert_eq!(tbox.get_term_cursor(), (2, 8).into());
1530
1531 let mov = mv!(MoveType::BufferLineOffset, 4);
1533 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1534 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1535
1536 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1537
1538 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 0));
1539 assert_eq!(tbox.get_cursor(), Cursor::new(3, 0));
1540 assert_eq!(tbox.get_term_cursor(), (2, 9).into());
1541
1542 let mov = mv!(MoveType::LineColumnOffset, 14);
1544 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1545 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1546
1547 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1548
1549 assert_eq!(tbox.viewctx.corner, Cursor::new(2, 6));
1550 assert_eq!(tbox.get_cursor(), Cursor::new(3, 13));
1551 assert_eq!(tbox.get_term_cursor(), (9, 9).into());
1552
1553 let mov = mv!(MoveType::BufferByteOffset, 0);
1555 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1556 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1557
1558 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1559
1560 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1561 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1562 assert_eq!(tbox.get_term_cursor(), (2, 8).into());
1563 }
1564
1565 #[test]
1566 fn test_wide_char_cursor() {
1567 let (mut tbox, ctx, mut store) = mkboxstr("세계를 향한 대화\n");
1568
1569 let area = Rect::new(0, 0, 20, 20);
1570 let mut buffer = Buffer::empty(area);
1571
1572 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1574
1575 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1576 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1577 assert_eq!(tbox.get_term_cursor(), (2, 0).into());
1578
1579 let mov = mv!(MoveType::Column(MoveDir1D::Next, false), 7);
1581 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1582 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1583
1584 TextBox::new().prompt("> ").oneline().render(area, &mut buffer, &mut tbox);
1586
1587 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1588 assert_eq!(tbox.get_cursor(), Cursor::new(0, 7));
1589 assert_eq!(tbox.get_term_cursor(), (14, 0).into());
1590 tbox.set_wrap(true);
1592 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1593
1594 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1595 assert_eq!(tbox.get_cursor(), Cursor::new(0, 7));
1596 assert_eq!(tbox.get_term_cursor(), (14, 0).into());
1597
1598 tbox.set_wrap(true);
1600 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1601
1602 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1603 assert_eq!(tbox.get_cursor(), Cursor::new(0, 7));
1604 assert_eq!(tbox.get_term_cursor(), (14, 0).into());
1605 }
1606
1607 #[test]
1608 fn test_wide_char_wrap() {
1609 let (mut tbox, ctx, mut store) = mkboxstr("세계를 향한대화\n");
1610
1611 let area = Rect::new(0, 0, 10, 10);
1612 let mut buffer = Buffer::empty(area);
1613
1614 let mut expected = buffer.clone();
1615 expected.set_string(0, 0, "> 세계를 ", Style::new());
1616 expected.set_string(0, 1, " 향한대화", Style::new());
1617
1618 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1620
1621 assert_eq!(buffer, expected);
1622 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1623 assert_eq!(tbox.get_cursor(), Cursor::new(0, 0));
1624 assert_eq!(tbox.get_term_cursor(), (2, 0).into());
1625 assert_eq!(tbox.has_lines(5), 3);
1626
1627 let mov = mv!(MoveType::Column(MoveDir1D::Next, false), 3);
1629 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1630 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1631
1632 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1633
1634 assert_eq!(buffer, expected);
1635 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1636 assert_eq!(tbox.get_cursor(), Cursor::new(0, 3));
1637 assert_eq!(tbox.get_term_cursor(), (8, 0).into());
1638
1639 let mov = mv!(MoveType::Column(MoveDir1D::Next, false), 1);
1641 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1642 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1643
1644 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1645
1646 assert_eq!(buffer, expected);
1647 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1648 assert_eq!(tbox.get_cursor(), Cursor::new(0, 4));
1649 assert_eq!(tbox.get_term_cursor(), (2, 1).into());
1650
1651 let mov = mv!(MoveType::Column(MoveDir1D::Next, false), 3);
1653 let act = EditorAction::Edit(EditAction::Motion.into(), mov);
1654 tbox.editor_command(&act, &ctx, &mut store).unwrap();
1655
1656 TextBox::new().prompt("> ").render(area, &mut buffer, &mut tbox);
1657
1658 assert_eq!(buffer, expected);
1659 assert_eq!(tbox.viewctx.corner, Cursor::new(0, 0));
1660 assert_eq!(tbox.get_cursor(), Cursor::new(0, 7));
1661 assert_eq!(tbox.get_term_cursor(), (8, 1).into());
1662 }
1663}