Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiline Prompts #287

Merged
merged 7 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/shrs_line/src/line.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Core readline configuration

use std::{borrow::BorrowMut, io::Write, time::Duration, vec};
use std::{borrow::BorrowMut, io::Write, iter::repeat, time::Duration, vec};

use crossterm::{
cursor::SetCursorStyle,
Expand Down
178 changes: 118 additions & 60 deletions crates/shrs_line/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use std::{
cell::RefCell,
collections::HashMap,
fmt::Display,
io::{stdout, BufWriter, Stdout, Write},
io::{stdout, BufWriter, Write},
};

use crossterm::{
cursor::{self, MoveToColumn},
cursor::{self, MoveToColumn, MoveToNextLine, MoveToPreviousLine},
style::{ContentStyle, Print, PrintStyledContent, StyledContent},
terminal::{self, Clear, ScrollUp},
QueueableCommand,
Expand All @@ -18,7 +18,6 @@ use unicode_width::UnicodeWidthStr;
use crate::{
completion::Completion, cursor::CursorStyle, line::LineCtx, menu::Menu, prompt::Prompt,
};

/// Text to be rendered by painter
#[derive(Clone)]
pub struct StyledBuf {
Expand All @@ -34,10 +33,9 @@ impl StyledBuf {
}
}
pub fn new(content: &str, style: ContentStyle) -> Self {
Self {
content: content.to_string(),
styles: vec![style; content.chars().count()],
}
let mut s = Self::empty();
s.push(content, style);
s
}

pub fn push(&mut self, content: &str, style: ContentStyle) {
Expand All @@ -48,15 +46,24 @@ impl StyledBuf {
}
}

fn spans(&self) -> Vec<StyledContent<String>> {
let mut x: Vec<StyledContent<String>> = vec![];
for (i, c) in self.content.chars().enumerate() {
x.push(StyledContent::new(self.styles[i], c.to_string()));
pub fn lines(&self) -> Vec<Vec<StyledContent<String>>> {
let mut lines: Vec<Vec<StyledContent<String>>> = vec![];
let mut i = 0;
for line in self.content.split("\n") {
let mut x: Vec<StyledContent<String>> = vec![];

for c in line.chars() {
x.push(StyledContent::new(self.styles[i], c.to_string()));
i += 1;
}
i += 1;
lines.push(x);
}
x
lines
}

fn count_newlines(&self) -> u16 {
//can be simply changed to just the len(lines())-1
//kept for now
pub fn count_newlines(&self) -> u16 {
self.content
.chars()
.into_iter()
Expand All @@ -70,9 +77,8 @@ impl StyledBuf {
///
/// The length returned is the 'visual' length of the character, in other words, how many
/// terminal columns it takes up
pub fn content_len(&self) -> usize {
use unicode_width::UnicodeWidthStr;
UnicodeWidthStr::width(self.content.as_str())
pub fn content_len(&self) -> u16 {
UnicodeWidthStr::width(self.content.as_str()) as u16
}

pub fn change_style(&mut self, c_style: HashMap<usize, ContentStyle>, offset: usize) {
Expand All @@ -83,10 +89,17 @@ impl StyledBuf {
}
}
}
pub fn line_content_len(line: Vec<StyledContent<String>>) -> u16 {
let c = line
.iter()
.map(|x| x.content().as_str())
.collect::<String>();
UnicodeWidthStr::width(c.as_str()) as u16
}

impl Display for StyledBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.content);
write!(f, "{}", self.content)?;
Ok(())
}
}
Expand All @@ -108,7 +121,7 @@ pub struct Painter {
term_size: (u16, u16),
/// Current line the prompt is on
prompt_line: u16,
num_newlines: u16,
num_newlines: usize,
}

impl Painter {
Expand All @@ -124,6 +137,7 @@ impl Painter {
/// Clear screen and move prompt to the top
pub fn init(&mut self) -> crossterm::Result<()> {
self.prompt_line = 0;
self.num_newlines = 0;
self.term_size = terminal::size()?;

// advance to next row if cursor in middle of line
Expand Down Expand Up @@ -165,24 +179,23 @@ impl Painter {
self.prompt_line = self.prompt_line.saturating_sub(extra_lines);
}
}
let mut total_newlines = 0;
//newlines to account for when clearing and printing prompt
let prompt_left = prompt.as_ref().prompt_left(line_ctx);
let prompt_right = prompt.as_ref().prompt_right(line_ctx);
let mut prompt_right_rendered = false;
let render_prompt_right = |out: &mut BufWriter<Stdout>| -> anyhow::Result<()> {
let mut right_space = self.term_size.0;
right_space -= prompt_right.content_len() as u16;
out.queue(MoveToColumn(right_space))?;
for span in prompt_right.spans() {
out.queue(PrintStyledContent(span))?;
}
Ok(())
};
let prompt_left_lines = prompt_left.lines();
let prompt_right_lines = prompt_right.lines();
let styled_buf_lines = styled_buf.lines();

total_newlines += styled_buf.count_newlines();
total_newlines += prompt_left.count_newlines();
//need to also take into account extra lines needed for prompt_right
let total_newlines = (prompt_right_lines.len() - 1)
.max(styled_buf_lines.len() - 1 + prompt_left_lines.len() - 1);

//make sure num_newlines never gets smaller, and adds newlines to adjust for prompt
if self.num_newlines < total_newlines {
self.out
.borrow_mut()
.queue(ScrollUp((total_newlines - self.num_newlines) as u16))?;

self.num_newlines = total_newlines;
}

Expand All @@ -191,55 +204,101 @@ impl Painter {
.borrow_mut()
.queue(cursor::MoveTo(
0,
self.prompt_line.saturating_sub(self.num_newlines),
self.prompt_line.saturating_sub(self.num_newlines as u16),
))?
.queue(Clear(terminal::ClearType::FromCursorDown))?;

// render left prompt
let mut left_space = 0; // cursor position from left side of terminal
// cursor position from left side of terminal
let mut left_space = 0;

let mut ri = 0;
let mut li = 0;
let mut bi = 0;
//RENDER PROMPT
//loop through lines rendering prompt left and prompt right and only start rendering of
//buffer when prompt_left is out of lines

loop {
if li < prompt_left_lines.len() {
for span in prompt_left_lines[li].iter() {
self.out
.borrow_mut()
.queue(PrintStyledContent(span.clone()))?;
}

li += 1;
}
if bi < styled_buf_lines.len() && li >= prompt_left_lines.len() {
for span in styled_buf_lines[bi].iter() {
self.out
.borrow_mut()
.queue(PrintStyledContent(span.clone()))?;
}
bi += 1;
}

if ri < prompt_right_lines.len() {
let mut right_space = self.term_size.0;
right_space -= line_content_len(prompt_right_lines[ri].clone()) as u16;
self.out.borrow_mut().queue(MoveToColumn(right_space))?;
for span in prompt_right_lines[ri].iter() {
self.out
.borrow_mut()
.queue(PrintStyledContent(span.clone()))?;
}

for span in prompt_left.spans() {
self.out.borrow_mut().queue(PrintStyledContent(span))?;
ri += 1;
}
//no lines left in any of the styledbufs
if li == prompt_left_lines.len()
&& bi == styled_buf_lines.len()
&& ri == prompt_right_lines.len()
{
break;
}
self.out.borrow_mut().queue(MoveToNextLine(1))?;
}
//account for right prompt being longest and set position for cursor
//-1 to account for inline
if ri > bi + li.saturating_sub(1) {
self.out
.borrow_mut()
.queue(MoveToPreviousLine((ri - (bi + li - 1)) as u16))?;
}
//prompt space doesnt matter if there is going to be a carriage return
if total_newlines == 0 {
left_space += prompt_left.content_len();

//calculate left space
//prompt space is 0 if there is going to be a newline in the styled_buf
if styled_buf_lines.len().saturating_sub(1) == 0 {
//space is width of last line of prompt_left
left_space += UnicodeWidthStr::width(
prompt_left
.content
.split('\n')
.last()
.unwrap()
.chars()
.collect::<String>()
.as_str(),
);
}

//take last line of buf and get length for cursor
//take last line of buf to the cursor index and get length for cursor
let chars = styled_buf
.content
.as_str()
.split('\n')
.last()
.unwrap()
.chars()
.take(cursor_ind)
.collect::<String>();
left_space += UnicodeWidthStr::width(chars.as_str());
for span in styled_buf.spans() {
let content = span.content();
if span.content() == "\n" {
if !prompt_right_rendered {
render_prompt_right(&mut self.out.borrow_mut())?;
}
prompt_right_rendered = true;
self.out.borrow_mut().queue(Print("\r"))?;
}
self.out
.borrow_mut()
.queue(Print(StyledContent::new(*span.style(), content)))?;
}
if !prompt_right_rendered {
render_prompt_right(&mut self.out.borrow_mut())?;
}

// render menu
if menu.is_active() {
menu.render(&mut self.out.borrow_mut(), &self)?;
}

// self.out.queue(cursor::RestorePosition)?;
//move cursor to correct position
self.out
.borrow_mut()
.queue(cursor::MoveToColumn(left_space as u16))?;
Expand All @@ -255,7 +314,6 @@ impl Painter {
}

pub fn newline(&mut self) -> crossterm::Result<()> {
self.num_newlines = 0;
self.out.borrow_mut().queue(Print("\r\n"))?;
self.out.borrow_mut().flush()?;
Ok(())
Expand Down
73 changes: 71 additions & 2 deletions plugins/shrs_mux/src/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use std::{
process::{Child, ChildStderr, ChildStdout, Command, ExitStatus, Stdio},
};

use shrs::prelude::*;
use shrs::{
lang::{Lexer, Token},
prelude::*,
};

use crate::{
interpreter::{read_err, read_out},
Expand Down Expand Up @@ -52,7 +55,73 @@ impl Lang for MuxLang {
}

fn needs_line_check(&self, cmd: String) -> bool {
false
//TODO check if open quotes or brackets

if let Some(last_char) = cmd.chars().last() {
if last_char == '\\' {
return true;
}
};

let mut brackets: Vec<Token> = vec![];

let lexer = Lexer::new(cmd.as_str());

for t in lexer {
if let Ok(token) = t {
match token.1 {
Token::LBRACE => brackets.push(token.1),
Token::LPAREN => brackets.push(token.1),
Token::RPAREN => {
if let Some(bracket) = brackets.last() {
if bracket == &Token::LPAREN {
brackets.pop();
} else {
return false;
}
}
},
Token::RBRACE => {
if let Some(bracket) = brackets.last() {
if bracket == &Token::LBRACE {
brackets.pop();
} else {
return false;
}
}
},
Token::WORD(w) => {
if let Some(c) = w.chars().next() {
if c == '\'' {
if w.len() == 1 {
return true;
}
if let Some(e) = w.chars().last() {
return e != '\'';
} else {
return true;
}
}
if c == '\"' {
if w.len() == 1 {
return true;
}

if let Some(e) = w.chars().last() {
return e != '\"';
} else {
return true;
}
}
}
},

_ => (),
}
}
}

!brackets.is_empty()
}
}

Expand Down