horizontal split prototype
This commit is contained in:
parent
9e204e0dcc
commit
29a12b5b2d
6 changed files with 146 additions and 32 deletions
158
src/main.rs
158
src/main.rs
|
|
@ -116,8 +116,7 @@ impl Display for TerminalCharacter {
|
|||
|
||||
impl ::std::fmt::Debug for TerminalCharacter {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.character);
|
||||
Ok(())
|
||||
write!(f, "{}", self.character)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,12 +131,13 @@ pub struct TerminalOutput {
|
|||
linebreak_indices: Vec<usize>, // linebreaks from line wrapping
|
||||
pending_ansi_code: Option<String>, // this is used eg. in a carriage return, where we need to preserve the style
|
||||
x_coords: u16,
|
||||
y_coords: u16,
|
||||
}
|
||||
|
||||
const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter { character: ' ', ansi_code: None };
|
||||
|
||||
impl TerminalOutput {
|
||||
pub fn new (pid: RawFd, ws: Winsize, x_coords: u16) -> TerminalOutput {
|
||||
pub fn new (pid: RawFd, ws: Winsize, x_coords: u16, y_coords: u16) -> TerminalOutput {
|
||||
TerminalOutput {
|
||||
pid,
|
||||
characters: vec![],
|
||||
|
|
@ -149,6 +149,7 @@ impl TerminalOutput {
|
|||
should_render: true,
|
||||
pending_ansi_code: None,
|
||||
x_coords,
|
||||
y_coords,
|
||||
}
|
||||
}
|
||||
pub fn handle_event(&mut self, event: VteEvent) {
|
||||
|
|
@ -233,7 +234,7 @@ impl TerminalOutput {
|
|||
let buffer_lines = &self.read_buffer_as_lines();
|
||||
let display_cols = &self.display_cols;
|
||||
for (row, line) in buffer_lines.iter().enumerate() {
|
||||
vte_output.push_str(&format!("\u{1b}[{};{}H\u{1b}[m", row + 1, self.x_coords + 1)); // goto row/col
|
||||
vte_output.push_str(&format!("\u{1b}[{};{}H\u{1b}[m", self.y_coords as usize + row + 1, self.x_coords + 1)); // goto row/col
|
||||
for (col, t_character) in line.iter().enumerate() {
|
||||
if (col as u16) < *display_cols {
|
||||
// in some cases (eg. while resizing) some characters will spill over
|
||||
|
|
@ -437,6 +438,13 @@ impl vte::Perform for TerminalOutput {
|
|||
terminal_character.ansi_code = self.pending_ansi_code.clone();
|
||||
if self.characters.len() == self.cursor_position {
|
||||
self.characters.push(terminal_character);
|
||||
|
||||
let start_of_last_line = self.index_of_beginning_of_line(self.cursor_position);
|
||||
let difference_from_last_newline = self.cursor_position - start_of_last_line;
|
||||
if difference_from_last_newline == self.display_cols as usize {
|
||||
self.linebreak_indices.push(self.cursor_position);
|
||||
}
|
||||
|
||||
} else if self.characters.len() > self.cursor_position {
|
||||
self.characters.remove(self.cursor_position);
|
||||
self.characters.insert(self.cursor_position, terminal_character);
|
||||
|
|
@ -455,7 +463,6 @@ impl vte::Perform for TerminalOutput {
|
|||
}
|
||||
}
|
||||
self.cursor_position += 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -677,20 +684,38 @@ pub fn sigwinch() -> (Box<OnSigWinch>, Box<SigCleanup>) {
|
|||
(Box::new(on_winch), Box::new(cleanup))
|
||||
}
|
||||
|
||||
fn split_horizontally_with_gap (rect: &Winsize) -> (Winsize, Winsize) {
|
||||
fn split_vertically_with_gap (rect: &Winsize) -> (Winsize, Winsize) {
|
||||
let width_of_each_half = (rect.ws_col - 1) / 2;
|
||||
let mut first_rect = rect.clone();
|
||||
let mut second_rect = rect.clone();
|
||||
if rect.ws_col % 2 == 0 {
|
||||
first_rect.ws_col = width_of_each_half + 1;
|
||||
} else {
|
||||
first_rect.ws_col = width_of_each_half;
|
||||
}
|
||||
second_rect.ws_col = width_of_each_half;
|
||||
(first_rect, second_rect)
|
||||
}
|
||||
|
||||
fn split_horizontally_with_gap (rect: &Winsize) -> (Winsize, Winsize) {
|
||||
let height_of_each_half = (rect.ws_row - 1) / 2;
|
||||
let mut first_rect = rect.clone();
|
||||
let mut second_rect = rect.clone();
|
||||
if rect.ws_row % 2 == 0 {
|
||||
first_rect.ws_row = height_of_each_half + 1;
|
||||
} else {
|
||||
first_rect.ws_row = height_of_each_half;
|
||||
}
|
||||
second_rect.ws_row = height_of_each_half;
|
||||
(first_rect, second_rect)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ScreenInstruction {
|
||||
Pty(RawFd, VteEvent),
|
||||
Render,
|
||||
AddTerminal(RawFd),
|
||||
HorizontalSplit(RawFd),
|
||||
VerticalSplit(RawFd),
|
||||
WriteCharacter(u8),
|
||||
ResizeLeft,
|
||||
ResizeRight,
|
||||
|
|
@ -703,6 +728,7 @@ struct Screen {
|
|||
pub send_screen_instructions: Sender<ScreenInstruction>,
|
||||
full_screen_ws: Winsize,
|
||||
vertical_separator: TerminalCharacter, // TODO: better
|
||||
horizontal_separator: TerminalCharacter, // TODO: better
|
||||
terminals: HashMap<RawFd, TerminalOutput>,
|
||||
active_terminal: Option<RawFd>,
|
||||
os_api: Box<dyn OsApi>,
|
||||
|
|
@ -716,21 +742,23 @@ impl Screen {
|
|||
send_screen_instructions: sender,
|
||||
full_screen_ws: full_screen_ws.clone(),
|
||||
vertical_separator: TerminalCharacter::new('│').ansi_code(String::from("\u{1b}[m")), // TODO: better
|
||||
horizontal_separator: TerminalCharacter::new('─').ansi_code(String::from("\u{1b}[m")), // TODO: better
|
||||
terminals: HashMap::new(),
|
||||
active_terminal: None,
|
||||
os_api,
|
||||
}
|
||||
}
|
||||
pub fn add_terminal(&mut self, pid: RawFd) {
|
||||
pub fn horizontal_split(&mut self, pid: RawFd) {
|
||||
if self.terminals.is_empty() {
|
||||
let x = 0;
|
||||
let new_terminal = TerminalOutput::new(pid, self.full_screen_ws.clone(), x);
|
||||
let y = 0;
|
||||
let new_terminal = TerminalOutput::new(pid, self.full_screen_ws.clone(), x, y);
|
||||
self.os_api.set_terminal_size_using_fd(new_terminal.pid, new_terminal.display_cols, new_terminal.display_rows);
|
||||
self.terminals.insert(pid, new_terminal);
|
||||
self.active_terminal = Some(pid);
|
||||
} else {
|
||||
// TODO: check minimum size of active terminal
|
||||
let (active_terminal_ws, active_terminal_x_coords) = {
|
||||
let (active_terminal_ws, active_terminal_x_coords, active_terminal_y_coords) = {
|
||||
let active_terminal = &self.get_active_terminal().unwrap();
|
||||
(
|
||||
Winsize {
|
||||
|
|
@ -739,12 +767,54 @@ impl Screen {
|
|||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
},
|
||||
active_terminal.x_coords
|
||||
active_terminal.x_coords,
|
||||
active_terminal.y_coords
|
||||
)
|
||||
};
|
||||
let (left_winszie, right_winsize) = split_horizontally_with_gap(&active_terminal_ws);
|
||||
let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&active_terminal_ws);
|
||||
let bottom_half_y = active_terminal_y_coords + top_winsize.ws_row + 1;
|
||||
let new_terminal = TerminalOutput::new(pid, bottom_winsize, active_terminal_x_coords, bottom_half_y);
|
||||
self.os_api.set_terminal_size_using_fd(new_terminal.pid, bottom_winsize.ws_col, bottom_winsize.ws_row);
|
||||
|
||||
{
|
||||
let active_terminal_id = &self.get_active_terminal_id().unwrap();
|
||||
let active_terminal = &mut self.terminals.get_mut(&active_terminal_id).unwrap();
|
||||
active_terminal.change_size(&top_winsize);
|
||||
}
|
||||
|
||||
self.terminals.insert(pid, new_terminal);
|
||||
let active_terminal_pid = self.get_active_terminal_id().unwrap();
|
||||
self.os_api.set_terminal_size_using_fd(active_terminal_pid, top_winsize.ws_col, top_winsize.ws_row);
|
||||
self.active_terminal = Some(pid);
|
||||
self.render();
|
||||
}
|
||||
}
|
||||
pub fn vertical_split(&mut self, pid: RawFd) {
|
||||
if self.terminals.is_empty() {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let new_terminal = TerminalOutput::new(pid, self.full_screen_ws.clone(), x, y);
|
||||
self.os_api.set_terminal_size_using_fd(new_terminal.pid, new_terminal.display_cols, new_terminal.display_rows);
|
||||
self.terminals.insert(pid, new_terminal);
|
||||
self.active_terminal = Some(pid);
|
||||
} else {
|
||||
// TODO: check minimum size of active terminal
|
||||
let (active_terminal_ws, active_terminal_x_coords, active_terminal_y_coords) = {
|
||||
let active_terminal = &self.get_active_terminal().unwrap();
|
||||
(
|
||||
Winsize {
|
||||
ws_row: active_terminal.display_rows,
|
||||
ws_col: active_terminal.display_cols,
|
||||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
},
|
||||
active_terminal.x_coords,
|
||||
active_terminal.y_coords
|
||||
)
|
||||
};
|
||||
let (left_winszie, right_winsize) = split_vertically_with_gap(&active_terminal_ws);
|
||||
let right_side_x = active_terminal_x_coords + left_winszie.ws_col + 1;
|
||||
let new_terminal = TerminalOutput::new(pid, right_winsize, right_side_x);
|
||||
let new_terminal = TerminalOutput::new(pid, right_winsize, right_side_x, active_terminal_y_coords);
|
||||
self.os_api.set_terminal_size_using_fd(new_terminal.pid, right_winsize.ws_col, right_winsize.ws_row);
|
||||
|
||||
{
|
||||
|
|
@ -791,16 +861,27 @@ impl Screen {
|
|||
let mut stdout = self.os_api.get_stdout_writer();
|
||||
for (_pid, terminal) in self.terminals.iter_mut() {
|
||||
if let Some(vte_output) = terminal.buffer_as_vte_output() {
|
||||
|
||||
// write boundaries
|
||||
if terminal.x_coords + terminal.display_cols < self.full_screen_ws.ws_col {
|
||||
let boundary_x_coords = terminal.x_coords + terminal.display_cols;
|
||||
let mut vte_output_boundaries = String::new();
|
||||
for row in 0..self.full_screen_ws.ws_row {
|
||||
for row in terminal.y_coords..=terminal.y_coords + terminal.display_rows {
|
||||
vte_output_boundaries.push_str(&format!("\u{1b}[{};{}H\u{1b}[m", row + 1, boundary_x_coords + 1)); // goto row/col
|
||||
vte_output_boundaries.push_str(&self.vertical_separator.to_string());
|
||||
}
|
||||
stdout.write_all(&vte_output_boundaries.as_bytes()).expect("cannot write to stdout");
|
||||
}
|
||||
if terminal.y_coords + terminal.display_rows < self.full_screen_ws.ws_row {
|
||||
let boundary_y_coords = terminal.y_coords + terminal.display_rows;
|
||||
let mut vte_output_boundaries = String::new();
|
||||
for col in terminal.x_coords..=terminal.x_coords + terminal.display_cols {
|
||||
vte_output_boundaries.push_str(&format!("\u{1b}[{};{}H\u{1b}[m", boundary_y_coords + 1, col + 1)); // goto row/col
|
||||
vte_output_boundaries.push_str(&self.horizontal_separator.to_string());
|
||||
}
|
||||
stdout.write_all(&vte_output_boundaries.as_bytes()).expect("cannot write to stdout");
|
||||
}
|
||||
|
||||
stdout.write_all(&vte_output.as_bytes()).expect("cannot write to stdout");
|
||||
}
|
||||
}
|
||||
|
|
@ -990,7 +1071,8 @@ impl Screen {
|
|||
}
|
||||
|
||||
enum PtyInstruction {
|
||||
SpawnTerminal,
|
||||
SpawnTerminalVertically,
|
||||
SpawnTerminalHorizontally,
|
||||
Quit
|
||||
}
|
||||
|
||||
|
|
@ -1011,7 +1093,7 @@ impl PtyBus {
|
|||
os_input,
|
||||
}
|
||||
}
|
||||
pub fn spawn_terminal(&mut self) {
|
||||
pub fn spawn_terminal_vertically(&mut self) {
|
||||
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal();
|
||||
task::spawn({
|
||||
let send_screen_instructions = self.send_screen_instructions.clone();
|
||||
|
|
@ -1031,7 +1113,29 @@ impl PtyBus {
|
|||
}
|
||||
}
|
||||
});
|
||||
self.send_screen_instructions.send(ScreenInstruction::AddTerminal(pid_primary)).unwrap();
|
||||
self.send_screen_instructions.send(ScreenInstruction::VerticalSplit(pid_primary)).unwrap();
|
||||
}
|
||||
pub fn spawn_terminal_horizontally(&mut self) {
|
||||
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal();
|
||||
task::spawn({
|
||||
let send_screen_instructions = self.send_screen_instructions.clone();
|
||||
let os_input = self.os_input.clone();
|
||||
async move {
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let mut vte_event_sender = VteEventSender::new(pid_primary, send_screen_instructions.clone());
|
||||
let mut first_terminal_bytes = ReadFromPid::new(&pid_primary, os_input);
|
||||
while let Some(bytes) = first_terminal_bytes.next().await {
|
||||
let bytes_is_empty = bytes.is_empty();
|
||||
for byte in bytes {
|
||||
vte_parser.advance(&mut vte_event_sender, byte);
|
||||
}
|
||||
if !bytes_is_empty {
|
||||
send_screen_instructions.send(ScreenInstruction::Render).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
self.send_screen_instructions.send(ScreenInstruction::HorizontalSplit(pid_primary)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1055,14 +1159,17 @@ pub fn start(mut os_input: Box<dyn OsApi>) {
|
|||
.name("pty".to_string())
|
||||
.spawn({
|
||||
move || {
|
||||
pty_bus.spawn_terminal();
|
||||
pty_bus.spawn_terminal_vertically();
|
||||
loop {
|
||||
let event = pty_bus.receive_pty_instructions
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
match event {
|
||||
PtyInstruction::SpawnTerminal => {
|
||||
pty_bus.spawn_terminal();
|
||||
PtyInstruction::SpawnTerminalVertically => {
|
||||
pty_bus.spawn_terminal_vertically();
|
||||
}
|
||||
PtyInstruction::SpawnTerminalHorizontally => {
|
||||
pty_bus.spawn_terminal_horizontally();
|
||||
}
|
||||
PtyInstruction::Quit => {
|
||||
break;
|
||||
|
|
@ -1089,8 +1196,11 @@ pub fn start(mut os_input: Box<dyn OsApi>) {
|
|||
ScreenInstruction::Render => {
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::AddTerminal(pid) => {
|
||||
screen.add_terminal(pid);
|
||||
ScreenInstruction::HorizontalSplit(pid) => {
|
||||
screen.horizontal_split(pid);
|
||||
}
|
||||
ScreenInstruction::VerticalSplit(pid) => {
|
||||
screen.vertical_split(pid);
|
||||
}
|
||||
ScreenInstruction::WriteCharacter(byte) => {
|
||||
screen.write_to_active_terminal(byte);
|
||||
|
|
@ -1123,8 +1233,10 @@ pub fn start(mut os_input: Box<dyn OsApi>) {
|
|||
send_screen_instructions.send(ScreenInstruction::ResizeRight).unwrap();
|
||||
} else if buffer[0] == 16 { // ctrl-p
|
||||
send_screen_instructions.send(ScreenInstruction::MoveFocus).unwrap();
|
||||
} else if buffer[0] == 8 { // ctrl-h
|
||||
send_pty_instructions.send(PtyInstruction::SpawnTerminalHorizontally).unwrap();
|
||||
} else if buffer[0] == 14 { // ctrl-n
|
||||
send_pty_instructions.send(PtyInstruction::SpawnTerminal).unwrap();
|
||||
send_pty_instructions.send(PtyInstruction::SpawnTerminalVertically).unwrap();
|
||||
} else if buffer[0] == 17 { // ctrl-q
|
||||
send_screen_instructions.send(ScreenInstruction::Quit).unwrap();
|
||||
send_pty_instructions.send(PtyInstruction::Quit).unwrap();
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ fn spawn_terminal () -> (RawFd, RawFd) {
|
|||
let pid_primary = fork_pty_res.master;
|
||||
let pid_secondary = match fork_pty_res.fork_result {
|
||||
ForkResult::Parent { child } => {
|
||||
// fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl");
|
||||
fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl");
|
||||
fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl");
|
||||
// fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl");
|
||||
child
|
||||
},
|
||||
ForkResult::Child => {
|
||||
|
|
|
|||
|
|
@ -24,13 +24,14 @@ pub fn starts_with_one_terminal () {
|
|||
let mut vte_parser = vte::Parser::new();
|
||||
let main_pid = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let fake_win_size = Winsize { // TODO: combine with above
|
||||
ws_col: 121,
|
||||
ws_row: 20,
|
||||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
};
|
||||
let mut terminal_output = TerminalOutput::new(main_pid, fake_win_size, x);
|
||||
let mut terminal_output = TerminalOutput::new(main_pid, fake_win_size, x, y);
|
||||
|
||||
for frame in output_frames.iter() {
|
||||
for byte in frame.iter() {
|
||||
|
|
@ -67,13 +68,14 @@ pub fn split_terminals_vertically() {
|
|||
let mut vte_parser = vte::Parser::new();
|
||||
let main_pid = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let fake_win_size = Winsize { // TODO: combine with above
|
||||
ws_col: 121,
|
||||
ws_row: 20,
|
||||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
};
|
||||
let mut terminal_output = TerminalOutput::new(main_pid, fake_win_size, x);
|
||||
let mut terminal_output = TerminalOutput::new(main_pid, fake_win_size, x, y);
|
||||
|
||||
for frame in output_frames.iter() {
|
||||
for byte in frame.iter() {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ expression: snapshot
|
|||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
Welcome to fish, the friendly interactive shell │
|
||||
│
|
||||
│
|
||||
⋊> ~/c/mosaic on main ⨯ │
|
||||
│
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ expression: snapshot
|
|||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
Welcome to fish, the friendly interactive shell │
|
||||
│
|
||||
│Welcome to fish, the friendly interactive shell
|
||||
⋊> ~/c/mosaic on main ⨯ │⋊> ~/c/mosaic on main ⨯ █ 19:40:00
|
||||
⋊> ~/c/mosaic on main ⨯ │⋊> ~/c/mosaic on main ⨯ 19:40:00
|
||||
│
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ expression: snapshot
|
|||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
Welcome to fish, the friendly interactive shell │
|
||||
│
|
||||
│Welcome to fish, the friendly interactive shell
|
||||
⋊> ~/c/mosaic on main ⨯ │⋊> ~/c/mosaic on main ⨯ 19:40:00
|
||||
│
|
||||
Bye from Mosaic!
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue