Skip to content

Commit

Permalink
feat: implementing better scrolling for the editor
Browse files Browse the repository at this point in the history
  • Loading branch information
wllfaria committed May 10, 2024
1 parent 7e82ebb commit dcae09d
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 27 deletions.
34 changes: 28 additions & 6 deletions reqtui/src/text_object/text_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ use std::ops::{Add, Sub};
use crate::text_object::cursor::Cursor;
use ropey::Rope;

#[derive(Debug, PartialEq, Clone)]
pub enum LineBreak {
Lf,
Crlf,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Readonly;
#[derive(Debug, Clone, PartialEq)]
Expand All @@ -12,6 +18,7 @@ pub struct Write;
pub struct TextObject<State = Readonly> {
content: Rope,
state: std::marker::PhantomData<State>,
line_break: LineBreak,
}

impl<State> Default for TextObject<State> {
Expand All @@ -21,23 +28,30 @@ impl<State> Default for TextObject<State> {
TextObject {
content: Rope::from_str(&content),
state: std::marker::PhantomData,
line_break: LineBreak::Lf,
}
}
}

impl TextObject<Readonly> {
pub fn from(content: &str) -> TextObject<Readonly> {
let content = Rope::from_str(content);
let line_break = match content.line(0).to_string().contains("\r\n") {
true => LineBreak::Crlf,
false => LineBreak::Lf,
};
TextObject::<Readonly> {
content,
state: std::marker::PhantomData::<Readonly>,
line_break,
}
}

pub fn with_write(self) -> TextObject<Write> {
TextObject::<Write> {
content: self.content,
state: std::marker::PhantomData,
line_break: self.line_break,
}
}
}
Expand All @@ -49,6 +63,15 @@ impl TextObject<Write> {
self.content.insert_char(col_offset, c);
}

pub fn insert_newline(&mut self, cursor: &Cursor) {
let line = self.content.line_to_char(cursor.row());
let col_offset = line + cursor.col();
match self.line_break {
LineBreak::Lf => self.content.insert_char(col_offset, '\n'),
LineBreak::Crlf => self.content.insert(col_offset, "\r\n"),
}
}

pub fn erase_backwards_up_to_line_start(&mut self, cursor: &Cursor) {
if cursor.col().eq(&0) {
return;
Expand Down Expand Up @@ -81,11 +104,10 @@ impl TextObject<Write> {
pub fn line_len(&self, line: usize) -> usize {
let mut line_len = 0;
if let Some(line) = self.content.line(line).as_str() {
line_len = line.len();
line.contains('\r')
.then(|| line_len = line_len.saturating_sub(1));
line.contains('\n')
.then(|| line_len = line_len.saturating_sub(1));
match self.line_break {
LineBreak::Lf => line_len = line.len().saturating_sub(1),
LineBreak::Crlf => line_len = line.len().saturating_sub(2),
}
}
line_len
}
Expand All @@ -95,7 +117,7 @@ impl TextObject<Write> {
let next_line = self.content.line_to_char(cursor.row().add(1));
let col_offset = line + cursor.col();
self.content
.try_remove(col_offset.saturating_sub(1)..next_line.saturating_sub(1))
.try_remove(col_offset..next_line.saturating_sub(1))
.ok();
}

Expand Down
33 changes: 22 additions & 11 deletions tui/src/components/api_explorer/api_explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ use reqtui::{
net::request_manager::{ReqtuiNetRequest, ReqtuiResponse},
schema::types::{Directory, Request, RequestKind, Schema},
};
use std::{cell::RefCell, collections::HashMap, ops::Add, rc::Rc};
use std::{
cell::RefCell,
collections::HashMap,
ops::{Add, Sub},
rc::Rc,
};
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};

use super::req_editor::EditorMode;
Expand Down Expand Up @@ -346,17 +351,23 @@ impl Component for ApiExplorer<'_> {
.as_ref()
.is_some_and(|pane| pane.eq(&PaneFocus::Editor))
{
let editor_position = self.layout.req_editor;
let mut editor_position = self.editor.layout().content_pane;
editor_position.height = editor_position.height.sub(2);
let cursor = self.editor.cursor();
let row_with_offset = editor_position
.y
.add(cursor.row_with_offset() as u16)
.saturating_sub(self.editor.row_scroll() as u16)
.add(3);
let col_with_offset = editor_position
.x
.add(cursor.col_with_offset() as u16)
.add(1);
let row_with_offset = u16::min(
editor_position
.y
.add(cursor.row_with_offset() as u16)
.saturating_sub(self.editor.row_scroll() as u16),
editor_position.y.add(editor_position.height),
);
let col_with_offset = u16::min(
editor_position
.x
.add(cursor.col_with_offset() as u16)
.saturating_sub(self.editor.col_scroll() as u16),
editor_position.x.add(editor_position.width),
);
frame.set_cursor(col_with_offset, row_with_offset);
}

Expand Down
98 changes: 88 additions & 10 deletions tui/src/components/api_explorer/req_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::{
buffer::Buffer,
layout::{Constraint, Direction, Layout, Rect},
style::{Style, Stylize},
style::{Style, Styled, Stylize},
text::{Line, Span},
widgets::{Block, Borders, Paragraph, Tabs, Widget},
Frame,
Expand Down Expand Up @@ -47,8 +47,8 @@ pub enum ReqEditorTabs {

#[derive(Debug)]
pub struct ReqEditorLayout {
tabs_pane: Rect,
content_pane: Rect,
pub tabs_pane: Rect,
pub content_pane: Rect,
}

impl From<ReqEditorTabs> for usize {
Expand Down Expand Up @@ -104,6 +104,7 @@ pub struct ReqEditor<'a> {
styled_display: Vec<Line<'static>>,
editor_mode: EditorMode,
row_scroll: usize,
col_scroll: usize,
layout: ReqEditorLayout,
}

Expand Down Expand Up @@ -135,14 +136,23 @@ impl<'a> ReqEditor<'a> {
cursor: Cursor::default(),
editor_mode: EditorMode::Normal,
row_scroll: 0,
col_scroll: 0,
layout: build_layout(size),
}
}

pub fn layout(&self) -> &ReqEditorLayout {
&self.layout
}

pub fn row_scroll(&self) -> usize {
self.row_scroll
}

pub fn col_scroll(&self) -> usize {
self.col_scroll
}

pub fn resize(&mut self, new_size: Rect) {
self.layout = build_layout(new_size);
}
Expand Down Expand Up @@ -215,7 +225,8 @@ impl<'a> ReqEditor<'a> {
"~".fg(self.colors.bright.black),
)))
.take(size.height.into())
.collect::<Vec<_>>();
.map(|line| get_visible_spans(&line, self.col_scroll))
.collect::<Vec<Line>>();

Paragraph::new(lines_in_view).render(request_pane, buf);
}
Expand Down Expand Up @@ -285,7 +296,7 @@ impl Eventful for ReqEditor<'_> {
self.cursor.move_right(1);
}
(EditorMode::Insert, KeyCode::Enter, KeyModifiers::NONE) => {
self.body.insert_char('\n', &self.cursor);
self.body.insert_newline(&self.cursor);
self.cursor.move_to_newline_start();
}
(EditorMode::Insert, KeyCode::Backspace, KeyModifiers::NONE) => {
Expand Down Expand Up @@ -323,13 +334,34 @@ impl Eventful for ReqEditor<'_> {
}
(EditorMode::Normal, KeyCode::Char('0'), KeyModifiers::NONE) => {
self.cursor.move_to_line_start();
if self.cursor.col().saturating_sub(self.col_scroll).le(&0) {
self.col_scroll = self
.col_scroll
.saturating_sub(self.col_scroll.sub(self.cursor.col()));
}
}
(EditorMode::Normal, KeyCode::Char('$'), KeyModifiers::NONE) => {
let current_line_len = self.body.line_len(self.cursor.row());
self.cursor.move_to_line_end(current_line_len);
if self.cursor.col().saturating_sub(self.col_scroll).gt(&self
.layout
.content_pane
.width
.sub(1)
.into())
{
self.col_scroll = self.col_scroll.add(
self.cursor
.col()
.sub(self.layout.content_pane.width.sub(1) as usize),
);
}
}
(EditorMode::Normal, KeyCode::Char('h'), KeyModifiers::NONE)
| (EditorMode::Insert, KeyCode::Left, KeyModifiers::NONE) => {
if self.cursor.col().saturating_sub(self.col_scroll).eq(&0) {
self.col_scroll = self.col_scroll.saturating_sub(1);
}
self.cursor.move_left(1);
}
(EditorMode::Normal, KeyCode::Char('j'), KeyModifiers::NONE)
Expand All @@ -338,18 +370,23 @@ impl Eventful for ReqEditor<'_> {
if self.cursor.row().lt(&len_lines.saturating_sub(1)) {
self.cursor.move_down(1);
}
if self
.cursor
.row()
.gt(&self.layout.content_pane.height.into())
if self.cursor.row().saturating_sub(self.row_scroll).gt(&self
.layout
.content_pane
.height
.sub(2)
.into())
{
self.row_scroll += 1;
self.row_scroll = self.row_scroll.add(1);
}
let current_line_len = self.body.line_len(self.cursor.row());
self.cursor.maybe_snap_to_col(current_line_len);
}
(EditorMode::Normal, KeyCode::Char('k'), KeyModifiers::NONE)
| (EditorMode::Insert, KeyCode::Up, KeyModifiers::NONE) => {
if self.cursor.row().saturating_sub(self.row_scroll).eq(&0) {
self.row_scroll = self.row_scroll.saturating_sub(1);
}
self.cursor.move_up(1);
let current_line_len = self.body.line_len(self.cursor.row());
self.cursor.maybe_snap_to_col(current_line_len);
Expand All @@ -359,6 +396,15 @@ impl Eventful for ReqEditor<'_> {
let current_line_len = self.body.line_len(self.cursor.row());
if self.cursor.col().lt(&current_line_len.saturating_sub(1)) {
self.cursor.move_right(1);
if self.cursor.col().saturating_sub(self.col_scroll).gt(&self
.layout
.content_pane
.width
.sub(1)
.into())
{
self.col_scroll = self.col_scroll.add(1);
}
}
}
(EditorMode::Normal, KeyCode::Char('x'), KeyModifiers::NONE) => {
Expand Down Expand Up @@ -386,6 +432,19 @@ impl Eventful for ReqEditor<'_> {
(EditorMode::Normal, KeyCode::Char('G'), KeyModifiers::SHIFT) => {
let len_lines = self.body.len_lines();
self.cursor.move_to_row(len_lines.saturating_sub(1));
if self.cursor.row().saturating_sub(self.row_scroll).gt(&self
.layout
.content_pane
.height
.sub(2)
.into())
{
self.row_scroll = self.row_scroll.add(
self.cursor
.row()
.sub(self.layout.content_pane.height.sub(2) as usize),
);
}
}
(EditorMode::Normal, KeyCode::Char('D'), KeyModifiers::SHIFT) => {
self.body.erase_until_eol(&self.cursor);
Expand Down Expand Up @@ -454,3 +513,22 @@ fn build_preview_layout(size: Rect) -> [Rect; 2] {

[request_pane, statusline_pane]
}

fn get_visible_spans(line: &Line<'static>, scroll: usize) -> Line<'static> {
let mut scroll_remaining = scroll;
let mut new_spans = vec![];

for span in line.spans.iter() {
let span_len = span.content.len();
if scroll_remaining >= span_len {
scroll_remaining -= span_len;
continue;
} else {
let visible_content = span.content[scroll_remaining..].to_string();
new_spans.push(Span::styled(visible_content, span.style));
scroll_remaining = 0;
}
}

Line::from(new_spans)
}

0 comments on commit dcae09d

Please sign in to comment.