summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2021-11-25 16:21:59 +0100
committerGitHub <noreply@github.com>2021-11-25 16:21:59 +0100
commit6c6a4393f403359838ea9b32b09520de4f50c019 (patch)
treed1b553cab1a22c271be99bbc35a5e608bc96676d
parent9fb2c7ca168679905d79dcdaf686d40f4a6dc958 (diff)
This adds a UI for multiple users in panes (behind a feature flag) (#897)
* feat(ui): multiple users in panes * style(fmt): make rustfmt happy * style(fmt): make clippy happy
-rw-r--r--Cargo.lock1
-rw-r--r--default-plugins/status-bar/src/main.rs64
-rw-r--r--default-plugins/tab-bar/src/line.rs12
-rw-r--r--default-plugins/tab-bar/src/main.rs2
-rw-r--r--default-plugins/tab-bar/src/tab.rs8
-rw-r--r--zellij-server/Cargo.toml1
-rw-r--r--zellij-server/src/lib.rs14
-rw-r--r--zellij-server/src/panes/grid.rs13
-rw-r--r--zellij-server/src/panes/plugin_pane.rs42
-rw-r--r--zellij-server/src/panes/terminal_character.rs11
-rw-r--r--zellij-server/src/panes/terminal_pane.rs123
-rw-r--r--zellij-server/src/tab.rs313
-rw-r--r--zellij-server/src/ui/boundaries.rs11
-rw-r--r--zellij-server/src/ui/mod.rs1
-rw-r--r--zellij-server/src/ui/pane_boundaries_frame.rs491
-rw-r--r--zellij-server/src/ui/pane_contents_and_ui.rs165
-rw-r--r--zellij-tile/src/data.rs1
-rw-r--r--zellij-utils/src/shared.rs13
18 files changed, 1005 insertions, 281 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2210d2bd8..e454c20c2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2924,6 +2924,7 @@ dependencies = [
"url",
"wasmer",
"wasmer-wasi",
+ "zellij-tile",
"zellij-utils",
]
diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs
index f1b2197e4..1ecd87ab1 100644
--- a/default-plugins/status-bar/src/main.rs
+++ b/default-plugins/status-bar/src/main.rs
@@ -76,61 +76,55 @@ pub struct ColoredElements {
// that can be defined in the config perhaps
fn color_elements(palette: Palette) -> ColoredElements {
match palette.source {
- // "cyan" here is used as a background as a dirty hack
- // this is because the Palette struct doesn't have a "gray" section
- // and we can't use its "bg" because that is now dynamically taken from the terminal
- // and might often not actually fit the rest of the colorscheme
- //
- // to fix this, we need to restructure the Palette struct
PaletteSource::Default => ColoredElements {
- selected_prefix_separator: style!(palette.cyan, palette.green),
+ selected_prefix_separator: style!(palette.gray, palette.green),
selected_char_left_separator: style!(palette.black, palette.green).bold(),
selected_char_shortcut: style!(palette.red, palette.green).bold(),
selected_char_right_separator: style!(palette.black, palette.green).bold(),
selected_styled_text: style!(palette.black, palette.green).bold(),
- selected_suffix_separator: style!(palette.green, palette.cyan).bold(),
- unselected_prefix_separator: style!(palette.cyan, palette.fg),
+ selected_suffix_separator: style!(palette.green, palette.gray).bold(),
+ unselected_prefix_separator: style!(palette.gray, palette.fg),
unselected_char_left_separator: style!(palette.black, palette.fg).bold(),
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
unselected_char_right_separator: style!(palette.black, palette.fg).bold(),
unselected_styled_text: style!(palette.black, palette.fg).bold(),
- unselected_suffix_separator: style!(palette.fg, palette.cyan),
- disabled_prefix_separator: style!(palette.cyan, palette.fg),
- disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(),
- disabled_suffix_separator: style!(palette.fg, palette.cyan),
- selected_single_letter_prefix_separator: style!(palette.cyan, palette.green),
+ unselected_suffix_separator: style!(palette.fg, palette.gray),
+ disabled_prefix_separator: style!(palette.gray, palette.fg),
+ disabled_styled_text: style!(palette.gray, palette.fg).dimmed(),
+ disabled_suffix_separator: style!(palette.fg, palette.gray),
+ selected_single_letter_prefix_separator: style!(palette.gray, palette.green),
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
- selected_single_letter_suffix_separator: style!(palette.green, palette.cyan),
- unselected_single_letter_prefix_separator: style!(palette.cyan, palette.fg),
+ selected_single_letter_suffix_separator: style!(palette.green, palette.gray),
+ unselected_single_letter_prefix_separator: style!(palette.gray, palette.fg),
unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(),
- unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan),
- superkey_prefix: style!(palette.white, palette.cyan).bold(),
- superkey_suffix_separator: style!(palette.cyan, palette.cyan),
+ unselected_single_letter_suffix_separator: style!(palette.fg, palette.gray),
+ superkey_prefix: style!(palette.white, palette.gray).bold(),
+ superkey_suffix_separator: style!(palette.gray, palette.gray),
},
PaletteSource::Xresources => ColoredElements {
- selected_prefix_separator: style!(palette.cyan, palette.green),
+ selected_prefix_separator: style!(palette.gray, palette.green),
selected_char_left_separator: style!(palette.fg, palette.green).bold(),
selected_char_shortcut: style!(palette.red, palette.green).bold(),
selected_char_right_separator: style!(palette.fg, palette.green).bold(),
- selected_styled_text: style!(palette.cyan, palette.green).bold(),
- selected_suffix_separator: style!(palette.green, palette.cyan).bold(),
- unselected_prefix_separator: style!(palette.cyan, palette.fg),
- unselected_char_left_separator: style!(palette.cyan, palette.fg).bold(),
+ selected_styled_text: style!(palette.gray, palette.green).bold(),
+ selected_suffix_separator: style!(palette.green, palette.gray).bold(),
+ unselected_prefix_separator: style!(palette.gray, palette.fg),
+ unselected_char_left_separator: style!(palette.gray, palette.fg).bold(),
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
- unselected_char_right_separator: style!(palette.cyan, palette.fg).bold(),
- unselected_styled_text: style!(palette.cyan, palette.fg).bold(),
- unselected_suffix_separator: style!(palette.fg, palette.cyan),
- disabled_prefix_separator: style!(palette.cyan, palette.fg),
- disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(),
- disabled_suffix_separator: style!(palette.fg, palette.cyan),
+ unselected_char_right_separator: style!(palette.gray, palette.fg).bold(),
+ unselected_styled_text: style!(palette.gray, palette.fg).bold(),
+ unselected_suffix_separator: style!(palette.fg, palette.gray),
+ disabled_prefix_separator: style!(palette.gray, palette.fg),
+ disabled_styled_text: style!(palette.gray, palette.fg).dimmed(),
+ disabled_suffix_separator: style!(palette.fg, palette.gray),
selected_single_letter_prefix_separator: style!(palette.fg, palette.green),
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
selected_single_letter_suffix_separator: style!(palette.green, palette.fg),
- unselected_single_letter_prefix_separator: style!(palette.fg, palette.cyan),
+ unselected_single_letter_prefix_separator: style!(palette.fg, palette.gray),
unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(),
- unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan),
- superkey_prefix: style!(palette.cyan, palette.fg).bold(),
- superkey_suffix_separator: style!(palette.fg, palette.cyan),
+ unselected_single_letter_suffix_separator: style!(palette.fg, palette.gray),
+ superkey_prefix: style!(palette.gray, palette.fg).bold(),
+ superkey_suffix_separator: style!(palette.fg, palette.gray),
},
}
}
@@ -231,7 +225,7 @@ impl ZellijPlugin for State {
// [48;5;238m is gray background, [0K is so that it fills the rest of the line
// [m is background reset, [0K is so that it clears the rest of the line
- match self.mode_info.palette.cyan {
+ match self.mode_info.palette.gray {
PaletteColor::Rgb((r, g, b)) => {
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b);
}
diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs
index af78b0fcf..2e180845b 100644
--- a/default-plugins/tab-bar/src/line.rs
+++ b/default-plugins/tab-bar/src/line.rs
@@ -102,11 +102,11 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator:
// 238
// chars length plus separator length on both sides
let more_text_len = more_text.width() + 2 * separator.width();
- let left_separator = style!(palette.cyan, palette.orange).paint(separator);
+ let left_separator = style!(palette.gray, palette.orange).paint(separator);
let more_styled_text = style!(palette.black, palette.orange)
.bold()
.paint(more_text);
- let right_separator = style!(palette.orange, palette.cyan).paint(separator);
+ let right_separator = style!(palette.orange, palette.gray).paint(separator);
let more_styled_text = format!(
"{}",
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
@@ -132,11 +132,11 @@ fn right_more_message(
};
// chars length plus separator length on both sides
let more_text_len = more_text.width() + 2 * separator.width();
- let left_separator = style!(palette.cyan, palette.orange).paint(separator);
+ let left_separator = style!(palette.gray, palette.orange).paint(separator);
let more_styled_text = style!(palette.black, palette.orange)
.bold()
.paint(more_text);
- let right_separator = style!(palette.orange, palette.cyan).paint(separator);
+ let right_separator = style!(palette.orange, palette.gray).paint(separator);
let more_styled_text = format!(
"{}",
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
@@ -151,7 +151,7 @@ fn tab_line_prefix(session_name: Option<&str>, palette: Palette, cols: usize) ->
let prefix_text = " Zellij ".to_string();
let prefix_text_len = prefix_text.chars().count();
- let prefix_styled_text = style!(palette.white, palette.cyan)
+ let prefix_styled_text = style!(palette.white, palette.gray)
.bold()
.paint(prefix_text);
let mut parts = vec![LinePart {
@@ -161,7 +161,7 @@ fn tab_line_prefix(session_name: Option<&str>, palette: Palette, cols: usize) ->
if let Some(name) = session_name {
let name_part = format!("({}) ", name);
let name_part_len = name_part.width();
- let name_part_styled_text = style!(palette.white, palette.cyan).bold().paint(name_part);
+ let name_part_styled_text = style!(palette.white, palette.gray).bold().paint(name_part);
if cols.saturating_sub(prefix_text_len) >= name_part_len {
parts.push(LinePart {
part: format!("{}", name_part_styled_text),
diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs
index 936c18e48..5902050ae 100644
--- a/default-plugins/tab-bar/src/main.rs
+++ b/default-plugins/tab-bar/src/main.rs
@@ -112,7 +112,7 @@ impl ZellijPlugin for State {
}
len_cnt += bar_part.len;
}
- match self.mode_info.palette.cyan {
+ match self.mode_info.palette.gray {
PaletteColor::Rgb((r, g, b)) => {
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
}
diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs
index fdb498553..fc34b6c36 100644
--- a/default-plugins/tab-bar/src/tab.rs
+++ b/default-plugins/tab-bar/src/tab.rs
@@ -5,12 +5,12 @@ use zellij_tile::prelude::*;
use zellij_tile_utils::style;
pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart {
- let left_separator = style!(palette.cyan, palette.green).paint(separator);
+ let left_separator = style!(palette.gray, palette.green).paint(separator);
let tab_text_len = text.width() + 2 + separator.width() * 2; // 2 for left and right separators, 2 for the text padding
let tab_styled_text = style!(palette.black, palette.green)
.bold()
.paint(format!(" {} ", text));
- let right_separator = style!(palette.green, palette.cyan).paint(separator);
+ let right_separator = style!(palette.green, palette.gray).paint(separator);
let tab_styled_text = format!(
"{}",
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
@@ -22,12 +22,12 @@ pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart {
}
pub fn non_active_tab(text: String, palette: Palette, separator: &str) -> LinePart {
- let left_separator = style!(palette.cyan, palette.fg).paint(separator);
+ let left_separator = style!(palette.gray, palette.fg).paint(separator);
let tab_text_len = text.width() + 2 + separator.width() * 2; // 2 for left and right separators, 2 for the text padding
let tab_styled_text = style!(palette.black, palette.fg)
.bold()
.paint(format!(" {} ", text));
- let right_separator = style!(palette.fg, palette.cyan).paint(separator);
+ let right_separator = style!(palette.fg, palette.gray).paint(separator);
let tab_styled_text = format!(
"{}",
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml
index f38739f13..1855daea1 100644
--- a/zellij-server/Cargo.toml
+++ b/zellij-server/Cargo.toml
@@ -22,6 +22,7 @@ wasmer = "1.0.0"
wasmer-wasi = "1.0.0"
cassowary = "0.3.0"
zellij-utils = { path = "../zellij-utils/", version = "0.21.0" }
+zellij-tile = { path = "../zellij-tile/", version = "0.21.0" }
log = "0.4.14"
typetag = "0.1.7"
chrono = "0.4.19"
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 4d94607f4..cb8f050dc 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -11,7 +11,7 @@ mod ui;
mod wasm_vm;
use log::info;
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
use std::{
path::PathBuf,
sync::{Arc, Mutex, RwLock},
@@ -134,9 +134,15 @@ impl SessionState {
}
}
pub fn new_client(&mut self) -> ClientId {
- let mut clients: Vec<ClientId> = self.clients.keys().copied().collect();
- clients.sort_unstable();
- let next_client_id = clients.last().unwrap_or(&0) + 1;
+ let clients: HashSet<ClientId> = self.clients.keys().copied().collect();
+ let mut next_client_id = 1;
+ loop {
+ if clients.contains(&next_client_id) {
+ next_client_id += 1;
+ } else {
+ break;
+ }
+ }
self.clients.insert(next_client_id, None);
next_client_id
}
diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs
index e3752fef1..706e0e549 100644
--- a/zellij-server/src/panes/grid.rs
+++ b/zellij-server/src/panes/grid.rs
@@ -448,6 +448,9 @@ impl Grid {
pub fn render_full_viewport(&mut self) {
self.output_buffer.update_all_lines();
}
+ pub fn update_line_for_rendering(&mut self, line_index: usize) {
+ self.output_buffer.update_line(line_index);
+ }
pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) {
let mut next_tabstop = None;
for tabstop in self.horizontal_tabstops.iter() {
@@ -1057,6 +1060,16 @@ impl Grid {
self.add_character_at_cursor_position(terminal_character);
self.move_cursor_forward_until_edge(character_width);
}
+ pub fn get_character_under_cursor(&self) -> Option<TerminalCharacter> {
+ let absolute_x_in_line = self.get_absolute_character_index(self.cursor.x, self.cursor.y);
+ self.viewport
+ .get(self.cursor.y)
+ .and_then(|current_line| current_line.columns.get(absolute_x_in_line))
+ .copied()
+ }
+ pub fn get_absolute_character_index(&self, x: usize, y: usize) -> usize {
+ self.viewport.get(y).unwrap().absolute_character_index(x)
+ }
pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
let count_to_move = std::cmp::min(count, self.width - (self.cursor.x));
self.cursor.x += count_to_move;
diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs
index 6b1cb4168..36274b347 100644
--- a/zellij-server/src/panes/plugin_pane.rs
+++ b/zellij-server/src/panes/plugin_pane.rs
@@ -5,8 +5,9 @@ use std::unimplemented;
use crate::panes::PaneId;
use crate::pty::VteBytes;
use crate::tab::Pane;
-use crate::ui::pane_boundaries_frame::PaneFrame;
+use crate::ui::pane_boundaries_frame::{FrameParams, PaneFrame};
use crate::wasm_vm::PluginInstruction;
+use crate::ClientId;
use zellij_utils::pane_size::Offset;
use zellij_utils::position::Position;
use zellij_utils::shared::ansi_len;
@@ -27,7 +28,6 @@ pub(crate) struct PluginPane {
pub active_at: Instant,
pub pane_title: String,
frame: bool,
- frame_color: Option<PaletteColor>,
borderless: bool,
}
@@ -47,7 +47,6 @@ impl PluginPane {
send_plugin_instructions,
active_at: Instant::now(),
frame: false,
- frame_color: None,
content_offset: Offset::default(),
pane_title: title,
borderless: false,
@@ -152,17 +151,6 @@ impl Pane for PluginPane {
self.should_render = false;
let contents = buf_rx.recv().unwrap();
- // FIXME: This is a hack that assumes all fixed-size panes are borderless. This
- // will eventually need fixing!
- if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) {
- let frame = PaneFrame {
- geom: self.current_geom().into(),
- title: self.pane_title.clone(),
- color: self.frame_color,
- ..Default::default()
- };
- vte_output.push_str(&frame.render());
- }
for (index, line) in contents.lines().enumerate() {
let actual_len = ansi_len(line);
let line_to_print = if actual_len > self.get_content_columns() {
@@ -212,6 +200,28 @@ impl Pane for PluginPane {
None
}
}
+ fn render_frame(&mut self, _client_id: ClientId, frame_params: FrameParams) -> Option<String> {
+ // FIXME: This is a hack that assumes all fixed-size panes are borderless. This
+ // will eventually need fixing!
+ if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) {
+ let frame = PaneFrame::new(
+ self.current_geom().into(),
+ (0, 0), // scroll position
+ self.pane_title.clone(),
+ frame_params,
+ );
+ Some(frame.render())
+ } else {
+ None
+ }
+ }
+ fn render_fake_cursor(
+ &mut self,
+ _cursor_color: PaletteColor,
+ _text_color: PaletteColor,
+ ) -> Option<String> {
+ None
+ }
fn pid(&self) -> PaneId {
PaneId::Plugin(self.pid)
}
@@ -317,10 +327,6 @@ impl Pane for PluginPane {
fn set_content_offset(&mut self, offset: Offset) {
self.content_offset = offset;
}
- fn set_boundary_color(&mut self, color: Option<PaletteColor>) {
- self.frame_color = color;
- self.set_should_render(true);
- }
fn set_borderless(&mut self, borderless: bool) {
self.borderless = borderless;
}
diff --git a/zellij-server/src/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs
index c9de37603..e6a9d85cf 100644
--- a/zellij-server/src/panes/terminal_character.rs
+++ b/zellij-server/src/panes/terminal_character.rs
@@ -1,9 +1,11 @@
+use std::convert::From;
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::{Index, IndexMut};
use zellij_utils::vte::ParamsIter;
use crate::panes::alacritty_functions::parse_sgr_color;
+use zellij_tile::data::PaletteColor;
pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
character: ' ',
@@ -35,6 +37,15 @@ pub enum AnsiCode {
ColorIndex(u8),
}
+impl From<PaletteColor> for AnsiCode {
+ fn from(palette_color: PaletteColor) -> Self {
+ match palette_color {
+ PaletteColor::Rgb((r, g, b)) => AnsiCode::RgbCode((r, g, b)),
+ PaletteColor::EightBit(index) => AnsiCode::ColorIndex(index),
+ }
+ }
+}
+
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum NamedColor {
Black,
diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs
index f2974cee9..0a6942e95 100644
--- a/zellij-server/src/panes/terminal_pane.rs
+++ b/zellij-server/src/panes/terminal_pane.rs
@@ -7,6 +7,8 @@ use crate::panes::{
};
use crate::pty::VteBytes;
use crate::tab::Pane;
+use crate::ClientId;
+use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::os::unix::io::RawFd;
use std::time::{self, Instant};
@@ -20,7 +22,7 @@ use zellij_utils::{
pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10;
-use crate::ui::pane_boundaries_frame::PaneFrame;
+use crate::ui::pane_boundaries_frame::{FrameParams, PaneFrame};
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
pub enum PaneId {
@@ -42,9 +44,9 @@ pub struct TerminalPane {
selection_scrolled_at: time::Instant,
content_offset: Offset,
pane_title: String,
- frame: Option<PaneFrame>,
- frame_color: Option<PaletteColor>,
+ frame: HashMap<ClientId, PaneFrame>,
borderless: bool,
+ fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render
}
impl Pane for TerminalPane {
@@ -172,9 +174,7 @@ impl Pane for TerminalPane {
fn render_full_viewport(&mut self) {
// this marks the pane for a full re-render, rather than just rendering the
// diff as it usually does with the OutputBuffer
- if self.frame.is_some() {
- self.frame.replace(PaneFrame::default());
- }
+ self.frame.clear();
self.grid.render_full_viewport();
}
fn selectable(&self) -> bool {
@@ -187,14 +187,14 @@ impl Pane for TerminalPane {
if self.should_render() {
let mut vte_output = String::new();
let mut character_styles = CharacterStyles::new();
+ let content_x = self.get_content_x();
+ let content_y = self.get_content_y();
if self.grid.clear_viewport_before_rendering {
for line_index in 0..self.grid.height {
- let x = self.get_content_x();
- let y = self.get_content_y();
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m",
- y + line_index + 1,
- x + 1
+ content_y + line_index + 1,
+ content_x + 1
)); // goto row/col and reset styles
for _col_index in 0..self.grid.width {
vte_output.push(EMPTY_TERMINAL_CHARACTER.character);
@@ -202,6 +202,19 @@ impl Pane for TerminalPane {
}
self.grid.clear_viewport_before_rendering = false;
}
+ // here we clear the previous cursor locations by adding an empty style-less character
+ // in their location, this is done before the main rendering logic so that if there
+ // actually is another character there, it will be overwritten
+ for (y, x) in self.fake_cursor_locations.drain() {
+ // we need to make sure to update the line in the line buffer so that if there's
+ // another character there it'll override it and we won't create holes with our
+ // empty character
+ self.grid.update_line_for_rendering(y);
+ let x = content_x + x;
+ let y = content_y + y;