Skip to content
This repository was archived by the owner on May 11, 2023. It is now read-only.

Commit 07deba3

Browse files
committed
terminal: Enhance tui editor
1 parent c83d8f2 commit 07deba3

4 files changed

Lines changed: 218 additions & 9 deletions

File tree

issue/src/tui.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use radicle_common::cobs::issue::{Issue, IssueId};
99
use radicle_common::project::Metadata;
1010
use radicle_terminal as term;
1111

12+
use term::tui::editor::{Editor};
1213
use term::tui::events::{InputEvent, Key};
1314
use term::tui::store::State;
1415
use term::tui::theme::Theme;
@@ -30,6 +31,7 @@ pub enum Action {
3031
Up,
3132
Down,
3233
Comment,
34+
Backspace,
3335
}
3436

3537
lazy_static! {
@@ -50,14 +52,15 @@ pub fn run(project: &Metadata, issues: IssueList) -> Result<(), Error> {
5052
("app.mode", Box::new(Mode::Normal)),
5153
("app.page.active", Box::new(Page::Overview as usize)),
5254
("app.tab.active", Box::new(Tab::Open as usize)),
55+
("app.editor", Box::new(Editor::new())),
5356
("app.editor.text", Box::new(String::new())),
5457
("project.name", Box::new(project.name.clone())),
5558
("project.issue.list", Box::new(issues)),
5659
("project.issue.active", Box::new(0_usize)),
5760
("project.issue.comment.active", Box::new(0_usize)),
5861
(
5962
"app.shortcuts",
60-
Box::new(vec![String::from("q quit"), String::from("? help")]),
63+
Box::new(vec![String::from("c comment"), String::from("q quit"), String::from("? help")]),
6164
),
6265
]);
6366

@@ -232,14 +235,25 @@ pub fn quit_application(state: &mut State) -> Result<(), Error> {
232235
}
233236

234237
pub fn clear_editor(state: &mut State) -> Result<(), Error> {
238+
state.set("app.editor", Box::new(Editor::new()));
235239
state.set("app.editor.text", Box::new(String::new()));
236240
Ok(())
237241
}
238242

239243
pub fn append_editor(state: &mut State, character: char) -> Result<(), Error> {
240244
let text = state.get::<String>("app.editor.text")?;
241245
let text = format!("{}{}", text, character);
242-
state.set("app.editor.text", Box::new(text));
246+
state.set("app.editor.text", Box::new(text.clone()));
247+
248+
let mut editor = Editor::new();
249+
editor.set_content(text);
250+
state.set("app.editor", Box::new(editor));
243251
Ok(())
244252
}
245253

254+
// pub fn navigate_editor(state: &mut State, key: Key) -> Result<(), Error> {
255+
// let text = state.get::<String>("app.editor.text")?;
256+
// let cursor_x = state.get::<usize>("app.editor.cursor.x")?;
257+
258+
// Ok(())
259+
// }

terminal/src/tui.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crossterm::terminal::{
1313
use tui::backend::{Backend, CrosstermBackend};
1414
use tui::Terminal;
1515

16+
pub mod editor;
1617
pub mod events;
1718
pub mod layout;
1819
pub mod spans;

terminal/src/tui/editor.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use std::cmp;
2+
// use unicode_segmentation::UnicodeSegmentation;
3+
4+
use tui::layout::Rect;
5+
6+
use super::events::Key;
7+
8+
pub struct Row {
9+
string: String,
10+
len: usize,
11+
}
12+
13+
impl From<&str> for Row {
14+
fn from(slice: &str) -> Self {
15+
Self {
16+
string: String::from(slice),
17+
len: slice.len()
18+
}
19+
}
20+
}
21+
22+
impl Row {
23+
pub fn render(&self, start: usize, end: usize) -> String {
24+
let end = cmp::min(end, self.string.len());
25+
let start = cmp::min(start, end);
26+
self.string.get(start..end).unwrap_or_default().to_string()
27+
}
28+
29+
// pub fn update_len(&mut self) {
30+
// self.len = self.string[..].graphemes(true).count();
31+
// }
32+
33+
// pub fn insert(&mut self, at: usize, c: char) {
34+
// if at >= self.len() {
35+
// self.string.push(c);
36+
// } else {
37+
// let mut result: String = self.string[..].graphemes(true).take(at).collect();
38+
// let remainder: String = self.string[..].graphemes(true).skip(at).collect();
39+
// result.push(c);
40+
// result.push_str(&remainder);
41+
// self.string = result;
42+
// }
43+
// self.update_len();
44+
// }
45+
46+
// pub fn len(&self) -> usize {
47+
// self.len
48+
// }
49+
}
50+
51+
#[derive(Default)]
52+
pub struct Document {
53+
rows: Vec<Row>,
54+
}
55+
56+
impl Document {
57+
pub fn new(content: String) -> Self {
58+
let mut rows = Vec::new();
59+
for value in content.lines() {
60+
rows.push(Row::from(value));
61+
}
62+
Self { rows }
63+
}
64+
65+
pub fn row(&self, index: usize) -> Option<&Row> {
66+
self.rows.get(index)
67+
}
68+
69+
pub fn is_empty(&self) -> bool {
70+
self.rows.is_empty()
71+
}
72+
73+
pub fn len(&self) -> usize {
74+
self.rows.len()
75+
}
76+
77+
// pub fn insert(&mut self, at: &Position, c: char) {
78+
// if at.y == self.len() {
79+
// let mut row = Row::default();
80+
// row.insert(0, c);
81+
// self.rows.push(row);
82+
// } else if at.y < self.len() {
83+
// let row = self.rows.get_mut(at.y).unwrap();
84+
// row.insert(at.x, c);
85+
// }
86+
// }
87+
}
88+
89+
#[derive(Default)]
90+
pub struct Position {
91+
pub x: usize,
92+
pub y: usize,
93+
}
94+
95+
pub struct Editor {
96+
cursor_position: Position,
97+
document: Document,
98+
}
99+
100+
impl Editor {
101+
pub fn new() -> Self {
102+
Self {
103+
cursor_position: Position::default(),
104+
document: Document::new(String::new()),
105+
}
106+
}
107+
108+
pub fn set_content(&mut self, content: String) {
109+
self.cursor_position = Position::default();
110+
self.document = Document::new(content);
111+
}
112+
113+
// pub fn clear(&mut self) {
114+
// self.cursor_position = Position::default();
115+
// self.document = Document::new(String::new())
116+
// }
117+
118+
pub fn render_row(&self, row: &Row, area: Rect) -> String {
119+
let start = 0;
120+
let end = area.width as usize;
121+
row.render(start, end)
122+
}
123+
124+
pub fn render_rows(&self, area: Rect) -> Vec<String> {
125+
let height = area.height;
126+
let mut rendered_rows = vec![];
127+
for terminal_row in 0..height - 1 {
128+
if let Some(row) = self.document.row(terminal_row as usize) {
129+
rendered_rows.push(self.render_row(row, area));
130+
}
131+
}
132+
rendered_rows
133+
}
134+
135+
fn refresh_screen(&self) -> Result<(), std::io::Error> {
136+
// Terminal::cursor_hide();
137+
// Terminal::cursor_position(&Position::default());
138+
139+
// if self.should_quit {
140+
// Terminal::clear_screen();
141+
// println!("Goodbye.\r");
142+
// } else {
143+
// self.draw_rows();
144+
// Terminal::cursor_position(&self.cursor_position);
145+
// }
146+
147+
// Terminal::cursor_show();
148+
// Terminal::flush()
149+
Ok(())
150+
}
151+
152+
pub fn move_cursor(&mut self, area: Rect, key: Key) {
153+
let Position { mut x, mut y } = self.cursor_position;
154+
let height = area.height.saturating_sub(1) as usize;
155+
let width = area.width.saturating_sub(1) as usize;
156+
157+
match key {
158+
Key::Up => y = y.saturating_sub(1),
159+
Key::Down => {
160+
if y < height {
161+
y = y.saturating_add(1)
162+
}
163+
}
164+
Key::Left => x = x.saturating_sub(1),
165+
Key::Right => {
166+
if x < width {
167+
x = x.saturating_add(1)
168+
}
169+
}
170+
// Key::PageUp => y = 0,
171+
// Key::PageDown => y = height,
172+
// Key::Home => x = 0,
173+
// Key::End => x = width,
174+
_ => (),
175+
}
176+
self.cursor_position = Position { x, y };
177+
}
178+
}

terminal/src/tui/window.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use tui::text::{Span, Spans};
99
use tui::widgets::{Block, Borders, Paragraph};
1010
use tui::Frame;
1111

12+
use super::editor::Editor;
1213
use super::layout;
1314
use super::layout::Padding;
1415
use super::spans;
@@ -78,6 +79,15 @@ where
7879
state: &State,
7980
) -> Result<(), Error> {
8081
let shortcuts = state.get::<Vec<String>>("app.shortcuts")?;
82+
// let page = state.get::<Vec<String>>("app.shortcuts")?;
83+
// let page = Page::try_from(*page)?;
84+
85+
// let shortcuts = match page {
86+
// Page::Detail => [vec![String::from("c comment")], *shortcuts].concat(),
87+
// _ => *shortcuts,
88+
// };
89+
// let shortcuts = [vec![String::from("c comment")], *shortcuts].concat();
90+
8191
let lengths = shortcuts
8292
.iter()
8393
.map(|s| s.len() as u16 + 2)
@@ -146,7 +156,7 @@ where
146156
state: &State,
147157
) -> Result<(), Error> {
148158
let mode = state.get::<Mode>("app.mode")?;
149-
let text = state.get::<String>("app.editor.text")?;
159+
let editor = state.get::<Editor>("app.editor")?;
150160

151161
if *mode == Mode::Editing {
152162
let title = String::from("Comment");
@@ -159,14 +169,20 @@ where
159169
template::block(theme, areas[0], Padding { top: 1, left: 4 }, true);
160170
frame.render_widget(block, inner);
161171

162-
let cursor_x = text.len() as u16;
163-
let text = spans::lines(&text, inner.width, 0);
164-
let input = Paragraph::new(text);
172+
let rows = editor.render_rows(inner);
173+
let rows = rows
174+
.iter()
175+
.map(|r| Spans::from(Span::raw(r)))
176+
.collect::<Vec<_>>();
177+
178+
// let cursor_x = text.len() as u16;
179+
// let text = spans::lines(&text, inner.width, 0);
180+
let input = Paragraph::new(rows);
165181
frame.render_widget(input, inner);
166182

167-
// Put cursor past the end of the input text
168-
// Move one line down, from the border to the input line
169-
frame.set_cursor(inner.x + cursor_x, inner.y);
183+
// // Put cursor past the end of the input text
184+
// // Move one line down, from the border to the input line
185+
// frame.set_cursor(inner.x + cursor_x, inner.y);
170186

171187
// Draw footer
172188
let title_w = title.len() as u16 + 2;

0 commit comments

Comments
 (0)