mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-23 08:57:14 +03:00
feat(tabs): add ability to set tab name (#212)
* send all tabs in vec * works but no input filtering * add event types * add event handler for tab events * fmt fixups * update tab name in place, and escape rename works * rename handle_tab_event handle_tab_rename_keypress * handle empty new_name when renaming * fix(tabs): pad active tab name too * fix(tabs): report proper length * fix(tabs): always render active tab * style(fmt): rustfmt Co-authored-by: Aram Drevekenin <aram@poor.dev>
This commit is contained in:
parent
845478fe11
commit
44b0246e91
@ -5,6 +5,8 @@ parts:
|
||||
split_size:
|
||||
Fixed: 1
|
||||
plugin: tab-bar
|
||||
events:
|
||||
- Tab
|
||||
- direction: Vertical
|
||||
expansion_boundary: true
|
||||
- direction: Vertical
|
||||
|
@ -104,7 +104,7 @@ fn key_path(help: &Help) -> LinePart {
|
||||
len,
|
||||
)
|
||||
}
|
||||
InputMode::Tab => {
|
||||
InputMode::Tab | InputMode::RenameTab => {
|
||||
let mode_shortcut_text = "t ";
|
||||
let superkey = superkey_text.bold().on_magenta();
|
||||
let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black();
|
||||
|
@ -115,9 +115,7 @@ fn add_next_tabs_msg(
|
||||
title_bar: &mut Vec<LinePart>,
|
||||
cols: usize,
|
||||
) {
|
||||
while get_current_title_len(&title_bar) +
|
||||
// get_tabs_after_len(tabs_after_active.len()) >= cols {
|
||||
right_more_message(tabs_after_active.len()).len
|
||||
while get_current_title_len(&title_bar) + right_more_message(tabs_after_active.len()).len
|
||||
>= cols
|
||||
{
|
||||
tabs_after_active.insert(0, title_bar.pop().unwrap());
|
||||
|
@ -4,7 +4,7 @@ mod tab;
|
||||
use zellij_tile::*;
|
||||
|
||||
use crate::line::tab_line;
|
||||
use crate::tab::nameless_tab;
|
||||
use crate::tab::tab_style;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LinePart {
|
||||
@ -12,10 +12,25 @@ pub struct LinePart {
|
||||
len: usize,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum BarMode {
|
||||
Normal,
|
||||
Rename,
|
||||
}
|
||||
|
||||
impl Default for BarMode {
|
||||
fn default() -> Self {
|
||||
BarMode::Normal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
active_tab_index: usize,
|
||||
num_tabs: usize,
|
||||
tabs: Vec<TabData>,
|
||||
mode: BarMode,
|
||||
new_name: String,
|
||||
}
|
||||
|
||||
static ARROW_SEPARATOR: &str = "";
|
||||
@ -29,20 +44,32 @@ impl ZellijTile for State {
|
||||
set_max_height(1);
|
||||
self.active_tab_index = 0;
|
||||
self.num_tabs = 0;
|
||||
self.mode = BarMode::Normal;
|
||||
self.new_name = String::new();
|
||||
}
|
||||
|
||||
fn draw(&mut self, _rows: usize, cols: usize) {
|
||||
if self.num_tabs == 0 {
|
||||
if self.tabs.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut all_tabs: Vec<LinePart> = vec![];
|
||||
for i in 0..self.num_tabs {
|
||||
let tab = nameless_tab(i, i == self.active_tab_index);
|
||||
let mut active_tab_index = 0;
|
||||
for t in self.tabs.iter_mut() {
|
||||
let mut tabname = t.name.clone();
|
||||
if t.active && self.mode == BarMode::Rename {
|
||||
if self.new_name.is_empty() {
|
||||
tabname = String::from("Enter name...");
|
||||
} else {
|
||||
tabname = self.new_name.clone();
|
||||
}
|
||||
active_tab_index = t.position;
|
||||
} else if t.active {
|
||||
active_tab_index = t.position;
|
||||
}
|
||||
let tab = tab_style(tabname, t.active, t.position);
|
||||
all_tabs.push(tab);
|
||||
}
|
||||
|
||||
let tab_line = tab_line(all_tabs, self.active_tab_index, cols);
|
||||
|
||||
let tab_line = tab_line(all_tabs, active_tab_index, cols);
|
||||
let mut s = String::new();
|
||||
for bar_part in tab_line {
|
||||
s = format!("{}{}", s, bar_part.part);
|
||||
@ -50,8 +77,19 @@ impl ZellijTile for State {
|
||||
println!("{}\u{1b}[40m\u{1b}[0K", s);
|
||||
}
|
||||
|
||||
fn update_tabs(&mut self, active_tab_index: usize, num_tabs: usize) {
|
||||
self.active_tab_index = active_tab_index;
|
||||
self.num_tabs = num_tabs;
|
||||
fn update_tabs(&mut self) {
|
||||
self.tabs = get_tabs();
|
||||
}
|
||||
|
||||
fn handle_tab_rename_keypress(&mut self, key: Key) {
|
||||
self.mode = BarMode::Rename;
|
||||
match key {
|
||||
Key::Char('\n') | Key::Esc => {
|
||||
self.mode = BarMode::Normal;
|
||||
self.new_name.clear();
|
||||
}
|
||||
Key::Char(c) => self.new_name = format!("{}{}", self.new_name, c),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ pub fn active_tab(text: String, is_furthest_to_the_left: bool) -> LinePart {
|
||||
ARROW_SEPARATOR.black().on_magenta()
|
||||
};
|
||||
let right_separator = ARROW_SEPARATOR.magenta().on_black();
|
||||
let tab_styled_text = format!("{}{}{}", left_separator, text, right_separator)
|
||||
let tab_styled_text = format!("{} {} {}", left_separator, text, right_separator)
|
||||
.black()
|
||||
.bold()
|
||||
.on_magenta();
|
||||
let tab_text_len = text.chars().count() + 2; // 2 for left and right separators
|
||||
let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the text padding
|
||||
LinePart {
|
||||
part: format!("{}", tab_styled_text),
|
||||
len: tab_text_len,
|
||||
@ -27,26 +27,27 @@ pub fn non_active_tab(text: String, is_furthest_to_the_left: bool) -> LinePart {
|
||||
ARROW_SEPARATOR.black().on_green()
|
||||
};
|
||||
let right_separator = ARROW_SEPARATOR.green().on_black();
|
||||
let tab_styled_text = format!("{}{}{}", left_separator, text, right_separator)
|
||||
let tab_styled_text = format!("{} {} {}", left_separator, text, right_separator)
|
||||
.black()
|
||||
.bold()
|
||||
.on_green();
|
||||
let tab_text_len = text.chars().count() + 2; // 2 for the left and right separators
|
||||
let tab_text_len = text.chars().count() + 4; // 2 for the left and right separators, 2 for the text padding
|
||||
LinePart {
|
||||
part: format!("{}", tab_styled_text),
|
||||
len: tab_text_len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tab(text: String, is_active_tab: bool, is_furthest_to_the_left: bool) -> LinePart {
|
||||
if is_active_tab {
|
||||
active_tab(text, is_furthest_to_the_left)
|
||||
pub fn tab_style(text: String, is_active_tab: bool, position: usize) -> LinePart {
|
||||
let tab_text;
|
||||
if text.is_empty() {
|
||||
tab_text = format!("Tab #{}", position + 1);
|
||||
} else {
|
||||
non_active_tab(text, is_furthest_to_the_left)
|
||||
tab_text = text;
|
||||
}
|
||||
if is_active_tab {
|
||||
active_tab(tab_text, position == 0)
|
||||
} else {
|
||||
non_active_tab(tab_text, position == 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nameless_tab(index: usize, is_active_tab: bool) -> LinePart {
|
||||
let tab_text = format!(" Tab #{} ", index + 1);
|
||||
tab(tab_text, is_active_tab, index == 0)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
use crate::common::wasm_vm::EventType;
|
||||
use crate::panes::PositionAndSize;
|
||||
|
||||
fn split_space_to_parts_vertically(
|
||||
@ -180,6 +181,8 @@ pub struct Layout {
|
||||
pub plugin: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub expansion_boundary: bool,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<EventType>,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
|
@ -2,11 +2,13 @@
|
||||
//! as well as how they should be resized
|
||||
|
||||
use crate::common::{AppInstruction, SenderWithContext};
|
||||
use crate::layout::Layout;
|
||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
||||
use crate::pty_bus::{PtyInstruction, VteEvent};
|
||||
use crate::wasm_vm::{PluginInputType, PluginInstruction};
|
||||
use crate::{boundaries::Boundaries, panes::PluginPane};
|
||||
use crate::{layout::Layout, wasm_vm::PluginInstruction};
|
||||
use crate::{os_input_output::OsApi, utils::shared::pad_to_size};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
@ -51,6 +53,7 @@ fn split_horizontally_with_gap(rect: &PositionAndSize) -> (PositionAndSize, Posi
|
||||
pub struct Tab {
|
||||
pub index: usize,
|
||||
pub position: usize,
|
||||
pub name: String,
|
||||
panes: BTreeMap<PaneId, Box<dyn Pane>>,
|
||||
panes_to_hide: HashSet<PaneId>,
|
||||
active_terminal: Option<PaneId>,
|
||||
@ -64,6 +67,14 @@ pub struct Tab {
|
||||
expansion_boundary: Option<PositionAndSize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct TabData {
|
||||
/* subset of fields to publish to plugins */
|
||||
pub position: usize,
|
||||
pub name: String,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
// FIXME: Use a struct that has a pane_type enum, to reduce all of the duplication
|
||||
pub trait Pane {
|
||||
fn x(&self) -> usize;
|
||||
@ -170,6 +181,7 @@ impl Tab {
|
||||
pub fn new(
|
||||
index: usize,
|
||||
position: usize,
|
||||
name: String,
|
||||
full_screen_ws: &PositionAndSize,
|
||||
mut os_api: Box<dyn OsApi>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
@ -195,6 +207,7 @@ impl Tab {
|
||||
index,
|
||||
position,
|
||||
panes,
|
||||
name,
|
||||
max_panes,
|
||||
panes_to_hide: HashSet::new(),
|
||||
active_terminal: pane_id,
|
||||
@ -249,7 +262,11 @@ impl Tab {
|
||||
if let Some(plugin) = &layout.plugin {
|
||||
let (pid_tx, pid_rx) = channel();
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Load(pid_tx, plugin.clone()))
|
||||
.send(PluginInstruction::Load(
|
||||
pid_tx,
|
||||
plugin.clone(),
|
||||
layout.events.clone(),
|
||||
))
|
||||
.unwrap();
|
||||
let pid = pid_rx.recv().unwrap();
|
||||
let new_plugin = PluginPane::new(
|
||||
@ -546,7 +563,10 @@ impl Tab {
|
||||
}
|
||||
Some(PaneId::Plugin(pid)) => {
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Input(pid, input_bytes))
|
||||
.send(PluginInstruction::Input(
|
||||
PluginInputType::Normal(pid),
|
||||
input_bytes,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
_ => {}
|
||||
|
@ -198,6 +198,7 @@ pub enum ScreenContext {
|
||||
SwitchTabPrev,
|
||||
CloseTab,
|
||||
GoToTab,
|
||||
UpdateTabName,
|
||||
}
|
||||
|
||||
impl From<&ScreenInstruction> for ScreenContext {
|
||||
@ -236,6 +237,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev,
|
||||
ScreenInstruction::CloseTab => ScreenContext::CloseTab,
|
||||
ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab,
|
||||
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,4 +48,6 @@ pub enum Action {
|
||||
/// Close the current tab.
|
||||
CloseTab,
|
||||
GoToTab(u32),
|
||||
TabNameInput(Vec<u8>),
|
||||
SaveTabName,
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use crate::errors::ContextType;
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::pty_bus::PtyInstruction;
|
||||
use crate::screen::ScreenInstruction;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
use crate::wasm_vm::{EventType, PluginInputType, PluginInstruction};
|
||||
use crate::CommandIsExecuting;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -232,6 +232,28 @@ impl InputHandler {
|
||||
.send(ScreenInstruction::GoToTab(i))
|
||||
.unwrap();
|
||||
}
|
||||
Action::TabNameInput(c) => {
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Input(
|
||||
PluginInputType::Event(EventType::Tab),
|
||||
c.clone(),
|
||||
))
|
||||
.unwrap();
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::UpdateTabName(c))
|
||||
.unwrap();
|
||||
}
|
||||
Action::SaveTabName => {
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Input(
|
||||
PluginInputType::Event(EventType::Tab),
|
||||
vec![b'\n'],
|
||||
))
|
||||
.unwrap();
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::UpdateTabName(vec![b'\n']))
|
||||
.unwrap();
|
||||
}
|
||||
Action::NoOp => {}
|
||||
}
|
||||
|
||||
@ -265,6 +287,7 @@ pub enum InputMode {
|
||||
Tab,
|
||||
/// `Scroll` mode allows scrolling up and down within a pane.
|
||||
Scroll,
|
||||
RenameTab,
|
||||
}
|
||||
|
||||
/// Represents the contents of the help message that is printed in the status bar,
|
||||
@ -310,10 +333,14 @@ pub fn get_help(mode: InputMode) -> Help {
|
||||
keybinds.push(("←↓↑→".to_string(), "Move focus".to_string()));
|
||||
keybinds.push(("n".to_string(), "New".to_string()));
|
||||
keybinds.push(("x".to_string(), "Close".to_string()));
|
||||
keybinds.push(("r".to_string(), "Rename".to_string()));
|
||||
}
|
||||
InputMode::Scroll => {
|
||||
keybinds.push(("↓↑".to_string(), "Scroll".to_string()));
|
||||
}
|
||||
InputMode::RenameTab => {
|
||||
keybinds.push(("Enter".to_string(), "when done".to_string()));
|
||||
}
|
||||
}
|
||||
keybinds.push(("ESC".to_string(), "BACK".to_string()));
|
||||
keybinds.push(("q".to_string(), "QUIT".to_string()));
|
||||
|
@ -128,6 +128,13 @@ fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
||||
defaults.insert(Key::Char('n'), vec![Action::NewTab]);
|
||||
defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
|
||||
|
||||
defaults.insert(
|
||||
Key::Char('r'),
|
||||
vec![
|
||||
Action::SwitchToMode(InputMode::RenameTab),
|
||||
Action::TabNameInput(vec![0]),
|
||||
],
|
||||
);
|
||||
defaults.insert(Key::Char('q'), vec![Action::Quit]);
|
||||
defaults.insert(
|
||||
Key::Ctrl('g'),
|
||||
@ -155,6 +162,23 @@ fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
||||
);
|
||||
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
|
||||
}
|
||||
InputMode::RenameTab => {
|
||||
defaults.insert(
|
||||
Key::Char('\n'),
|
||||
vec![Action::SaveTabName, Action::SwitchToMode(InputMode::Tab)],
|
||||
);
|
||||
defaults.insert(
|
||||
Key::Ctrl('g'),
|
||||
vec![Action::SwitchToMode(InputMode::Normal)],
|
||||
);
|
||||
defaults.insert(
|
||||
Key::Esc,
|
||||
vec![
|
||||
Action::TabNameInput(vec![0x1b]),
|
||||
Action::SwitchToMode(InputMode::Tab),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(defaults)
|
||||
@ -178,6 +202,7 @@ pub fn key_to_actions(
|
||||
};
|
||||
match *mode {
|
||||
InputMode::Normal => mode_keybind_or_action(Action::Write(input)),
|
||||
InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)),
|
||||
_ => mode_keybind_or_action(Action::NoOp),
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,9 @@ use os_input_output::OsApi;
|
||||
use pty_bus::{PtyBus, PtyInstruction};
|
||||
use screen::{Screen, ScreenInstruction};
|
||||
use utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_ROOT_PLUGIN_DIR};
|
||||
use wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction};
|
||||
use wasm_vm::{
|
||||
wasi_stdout, wasi_write_string, zellij_imports, EventType, PluginInputType, PluginInstruction,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum ApiCommand {
|
||||
@ -419,6 +421,9 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
ScreenInstruction::GoToTab(tab_index) => {
|
||||
screen.go_to_tab(tab_index as usize)
|
||||
}
|
||||
ScreenInstruction::UpdateTabName(c) => {
|
||||
screen.update_active_tab_name(c);
|
||||
}
|
||||
ScreenInstruction::Quit => {
|
||||
break;
|
||||
}
|
||||
@ -438,6 +443,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
let store = Store::default();
|
||||
let mut plugin_id = 0;
|
||||
let mut plugin_map = HashMap::new();
|
||||
let handler_map: HashMap<EventType, String> =
|
||||
[(EventType::Tab, "handle_tab_rename_keypress".to_string())]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
move || loop {
|
||||
let (event, mut err_ctx) = receive_plugin_instructions
|
||||
@ -448,7 +458,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
send_pty_instructions.update(err_ctx);
|
||||
send_app_instructions.update(err_ctx);
|
||||
match event {
|
||||
PluginInstruction::Load(pid_tx, path) => {
|
||||
PluginInstruction::Load(pid_tx, path, events) => {
|
||||
let project_dirs =
|
||||
ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
||||
let plugin_dir = project_dirs.data_dir().join("plugins/");
|
||||
@ -490,6 +500,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
send_screen_instructions: send_screen_instructions.clone(),
|
||||
send_app_instructions: send_app_instructions.clone(),
|
||||
wasi_env,
|
||||
events,
|
||||
};
|
||||
|
||||
let zellij = zellij_imports(&store, &plugin_env);
|
||||
@ -514,32 +525,58 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
|
||||
buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap();
|
||||
}
|
||||
PluginInstruction::UpdateTabs(active_tab_index, num_tabs) => {
|
||||
for (instance, _) in plugin_map.values() {
|
||||
PluginInstruction::UpdateTabs(mut tabs) => {
|
||||
for (instance, plugin_env) in plugin_map.values() {
|
||||
if !plugin_env.events.contains(&EventType::Tab) {
|
||||
continue;
|
||||
}
|
||||
let handler = instance.exports.get_function("update_tabs").unwrap();
|
||||
handler
|
||||
.call(&[
|
||||
Value::I32(active_tab_index as i32),
|
||||
Value::I32(num_tabs as i32),
|
||||
])
|
||||
.unwrap();
|
||||
tabs.sort_by(|a, b| a.position.cmp(&b.position));
|
||||
wasi_write_string(
|
||||
&plugin_env.wasi_env,
|
||||
&serde_json::to_string(&tabs).unwrap(),
|
||||
);
|
||||
handler.call(&[]).unwrap();
|
||||
}
|
||||
}
|
||||
// FIXME: Deduplicate this with the callback below!
|
||||
PluginInstruction::Input(pid, input_bytes) => {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
|
||||
let handle_key = instance.exports.get_function("handle_key").unwrap();
|
||||
for key in input_bytes.keys() {
|
||||
if let Ok(key) = key {
|
||||
wasi_write_string(
|
||||
&plugin_env.wasi_env,
|
||||
&serde_json::to_string(&key).unwrap(),
|
||||
);
|
||||
handle_key.call(&[]).unwrap();
|
||||
PluginInstruction::Input(input_type, input_bytes) => {
|
||||
match input_type {
|
||||
PluginInputType::Normal(pid) => {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
let handle_key =
|
||||
instance.exports.get_function("handle_key").unwrap();
|
||||
for key in input_bytes.keys() {
|
||||
if let Ok(key) = key {
|
||||
wasi_write_string(
|
||||
&plugin_env.wasi_env,
|
||||
&serde_json::to_string(&key).unwrap(),
|
||||
);
|
||||
handle_key.call(&[]).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
PluginInputType::Event(event) => {
|
||||
for (instance, plugin_env) in plugin_map.values() {
|
||||
if !plugin_env.events.contains(&event) {
|
||||
continue;
|
||||
}
|
||||
let handle_key = instance
|
||||
.exports
|
||||
.get_function(handler_map.get(&event).unwrap())
|
||||
.unwrap();
|
||||
for key in input_bytes.keys() {
|
||||
if let Ok(key) = key {
|
||||
wasi_write_string(
|
||||
&plugin_env.wasi_env,
|
||||
&serde_json::to_string(&key).unwrap(),
|
||||
);
|
||||
handle_key.call(&[]).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(send_screen_instructions.send(ScreenInstruction::Render));
|
||||
}
|
||||
PluginInstruction::GlobalInput(input_bytes) => {
|
||||
|
@ -2,13 +2,14 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::str;
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
use super::{AppInstruction, SenderWithContext};
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::panes::PositionAndSize;
|
||||
use crate::pty_bus::{PtyInstruction, VteEvent};
|
||||
use crate::tab::Tab;
|
||||
use crate::tab::{Tab, TabData};
|
||||
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
|
||||
use crate::{layout::Layout, panes::PaneId};
|
||||
|
||||
@ -46,6 +47,7 @@ pub enum ScreenInstruction {
|
||||
SwitchTabPrev,
|
||||
CloseTab,
|
||||
GoToTab(u32),
|
||||
UpdateTabName(Vec<u8>),
|
||||
}
|
||||
|
||||
/// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes).
|
||||
@ -69,6 +71,7 @@ pub struct Screen {
|
||||
active_tab_index: Option<usize>,
|
||||
/// The [`OsApi`] this [`Screen`] uses.
|
||||
os_api: Box<dyn OsApi>,
|
||||
tabname_buf: String,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
@ -92,6 +95,7 @@ impl Screen {
|
||||
active_tab_index: None,
|
||||
tabs: BTreeMap::new(),
|
||||
os_api,
|
||||
tabname_buf: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,6 +107,7 @@ impl Screen {
|
||||
let tab = Tab::new(
|
||||
tab_index,
|
||||
position,
|
||||
String::new(),
|
||||
&self.full_screen_ws,
|
||||
self.os_api.clone(),
|
||||
self.send_pty_instructions.clone(),
|
||||
@ -246,6 +251,7 @@ impl Screen {
|
||||
let mut tab = Tab::new(
|
||||
tab_index,
|
||||
position,
|
||||
String::new(),
|
||||
&self.full_screen_ws,
|
||||
self.os_api.clone(),
|
||||
self.send_pty_instructions.clone(),
|
||||
@ -261,13 +267,36 @@ impl Screen {
|
||||
}
|
||||
|
||||
fn update_tabs(&self) {
|
||||
if let Some(active_tab) = self.get_active_tab() {
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::UpdateTabs(
|
||||
active_tab.position,
|
||||
self.tabs.len(),
|
||||
))
|
||||
.unwrap();
|
||||
let mut tab_data = vec![];
|
||||
let active_tab_index = self.active_tab_index.unwrap();
|
||||
for tab in self.tabs.values() {
|
||||
tab_data.push(TabData {
|
||||
position: tab.position,
|
||||
name: tab.name.clone(),
|
||||
active: active_tab_index == tab.index,
|
||||
});
|
||||
}
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::UpdateTabs(tab_data))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn update_active_tab_name(&mut self, buf: Vec<u8>) {
|
||||
let s = str::from_utf8(&buf).unwrap();
|
||||
match s {
|
||||
"\0" => {
|
||||
self.tabname_buf = String::new();
|
||||
}
|
||||
"\n" => {
|
||||
let new_name = self.tabname_buf.clone();
|
||||
let active_tab = self.get_active_tab_mut().unwrap();
|
||||
active_tab.name = new_name;
|
||||
self.update_tabs();
|
||||
self.render();
|
||||
}
|
||||
c => {
|
||||
self.tabname_buf.push_str(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,36 @@
|
||||
use crate::tab::TabData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::mpsc::{channel, Sender},
|
||||
};
|
||||
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
|
||||
use wasmer_wasi::WasiEnv;
|
||||
// use crate::utils::logging::debug_log_to_file;
|
||||
|
||||
use super::{
|
||||
input::handler::get_help, pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction,
|
||||
PaneId, SenderWithContext,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash, Eq, Serialize, Deserialize)]
|
||||
pub enum EventType {
|
||||
Tab,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PluginInputType {
|
||||
Normal(u32),
|
||||
Event(EventType),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PluginInstruction {
|
||||
Load(Sender<u32>, PathBuf),
|
||||
Load(Sender<u32>, PathBuf, Vec<EventType>),
|
||||
Draw(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
|
||||
Input(u32, Vec<u8>), // plugin id, input bytes
|
||||
Input(PluginInputType, Vec<u8>), // plugin id, input bytes
|
||||
GlobalInput(Vec<u8>), // input bytes
|
||||
Unload(u32),
|
||||
UpdateTabs(usize, usize), // num tabs, active tab
|
||||
UpdateTabs(Vec<TabData>), // num tabs, active tab
|
||||
Quit,
|
||||
}
|
||||
|
||||
@ -29,6 +41,7 @@ pub struct PluginEnv {
|
||||
pub send_app_instructions: SenderWithContext<AppInstruction>,
|
||||
pub send_pty_instructions: SenderWithContext<PtyInstruction>, // FIXME: This should be a big bundle of all of the channels
|
||||
pub wasi_env: WasiEnv,
|
||||
pub events: Vec<EventType>,
|
||||
}
|
||||
|
||||
// Plugin API ---------------------------------------------------------------------------------------------------------
|
||||
|
@ -7,7 +7,8 @@ pub trait ZellijTile {
|
||||
fn draw(&mut self, rows: usize, cols: usize) {}
|
||||
fn handle_key(&mut self, key: Key) {}
|
||||
fn handle_global_key(&mut self, key: Key) {}
|
||||
fn update_tabs(&mut self, active_tab_index: usize, num_active_tabs: usize) {}
|
||||
fn update_tabs(&mut self) {}
|
||||
fn handle_tab_rename_keypress(&mut self, key: Key) {}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@ -45,11 +46,18 @@ macro_rules! register_tile {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn update_tabs(active_tab_index: i32, num_active_tabs: i32) {
|
||||
pub fn update_tabs() {
|
||||
STATE.with(|state| {
|
||||
state.borrow_mut().update_tabs();
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn handle_tab_rename_keypress() {
|
||||
STATE.with(|state| {
|
||||
state
|
||||
.borrow_mut()
|
||||
.update_tabs(active_tab_index as usize, num_active_tabs as usize);
|
||||
.handle_tab_rename_keypress($crate::get_key());
|
||||
})
|
||||
}
|
||||
};
|
||||
|
@ -38,10 +38,19 @@ pub enum InputMode {
|
||||
Resize,
|
||||
Pane,
|
||||
Tab,
|
||||
RenameTab,
|
||||
Scroll,
|
||||
Exiting,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TabData {
|
||||
/* subset of fields to publish to plugins */
|
||||
pub position: usize,
|
||||
pub name: String,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
impl Default for InputMode {
|
||||
fn default() -> InputMode {
|
||||
InputMode::Normal
|
||||
@ -76,6 +85,10 @@ pub fn get_help() -> Help {
|
||||
deserialize_from_stdin().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_tabs() -> Vec<TabData> {
|
||||
deserialize_from_stdin().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn deserialize_from_stdin<T: DeserializeOwned>() -> Option<T> {
|
||||
let mut json = String::new();
|
||||
io::stdin().read_line(&mut json).unwrap();
|
||||
|
Loading…
Reference in New Issue
Block a user