From 1c6829f251a6d7f61b7fa5b13b0518cf6655ec3d Mon Sep 17 00:00:00 2001 From: Emirhan TALA Date: Wed, 7 May 2025 00:21:40 +0300 Subject: [PATCH 1/3] fix(DOM): Hyperlinks are overridden when renereded after Paragraph --- Cargo.toml | 15 +----------- src/backend/dom.rs | 59 +++++++++++++++++++++++++++++++++++++++------- src/error.rs | 6 +++++ 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a56f63a..2416487b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,20 +13,7 @@ include = ["src/**/*", "Cargo.*", "LICENSE", "README.md", "CHANGELOG.md"] edition = "2021" [dependencies] -web-sys = { version = "0.3.77", features = [ - 'Document', - 'Element', - 'HtmlElement', - 'Navigator', - 'Node', - 'Window', - 'Screen', - 'console', - 'KeyboardEvent', - 'CanvasRenderingContext2d', - 'HtmlCanvasElement', - 'Location', -] } +web-sys = { version = "0.3.77", features = ["Document", "Element", "HtmlElement", "Navigator", "Node", "Window", "Screen", "console", "KeyboardEvent", "CanvasRenderingContext2d", "HtmlCanvasElement", "Location", "HtmlCollection"] } compact_str = "0.9.0" ratatui = { version = "0.29", default-features = false, features = ["all-widgets"] } console_error_panic_hook = "0.1.7" diff --git a/src/backend/dom.rs b/src/backend/dom.rs index d888ca89..22aad615 100644 --- a/src/backend/dom.rs +++ b/src/backend/dom.rs @@ -196,18 +196,61 @@ impl DomBackend { /// Compare the current buffer to the previous buffer and updates the grid /// accordingly. fn update_grid(&mut self) -> Result<(), Error> { - for (y, line) in self.buffer.iter().enumerate() { - for (x, cell) in line.iter().enumerate() { - if cell.modifier.contains(HYPERLINK_MODIFIER) { - continue; + for y in 0..self.buffer.len() { + // Check if line needs rebuilding (any hyperlink changes) + let needs_rebuild = (0..self.buffer[y].len()).any(|x| { + let old_cell = &self.prev_buffer[y][x]; + let new_cell = &self.buffer[y][x]; + new_cell != old_cell && ( + new_cell.modifier.contains(HYPERLINK_MODIFIER) || + old_cell.modifier.contains(HYPERLINK_MODIFIER) + ) + }); + + if needs_rebuild { + // Rebuild entire line + let line = &self.buffer[y]; + let pre = self.grid.children().item(y as u32).ok_or(Error::UnableToRetrieveChildElement)?; + pre.set_inner_html(""); + + let mut x = 0; + while x < line.len() { + if line[x].modifier.contains(HYPERLINK_MODIFIER) { + // Handle hyperlink group + let start_x = x; + while x + 1 < line.len() && line[x + 1].modifier.contains(HYPERLINK_MODIFIER) { + x += 1; + } + + let hyperlink_cells = &line[start_x..=x]; + let anchor = create_anchor(&self.document, hyperlink_cells)?; + + for i in start_x..=x { + let span = create_span(&self.document, &line[i])?; + self.cells[y * line.len() + i] = span.clone(); + anchor.append_child(&span)?; + } + pre.append_child(&anchor)?; + } else { + // Handle regular cell + let span = create_span(&self.document, &line[x])?; + self.cells[y * line.len() + x] = span.clone(); + pre.append_child(&span)?; + } + x += 1; } - if cell != &self.prev_buffer[y][x] { - let elem = self.cells[y * self.buffer[0].len() + x].clone(); - elem.set_inner_html(cell.symbol()); - elem.set_attribute("style", &get_cell_style_as_css(cell))?; + } else { + // Just update changed cells directly + for x in 0..self.buffer[y].len() { + if self.buffer[y][x] != self.prev_buffer[y][x] { + let span = &self.cells[y * self.buffer[y].len() + x]; + span.set_inner_html(self.buffer[y][x].symbol()); + span.set_attribute("style", &get_cell_style_as_css(&self.buffer[y][x]))?; + } } } } + Ok(()) } } diff --git a/src/error.rs b/src/error.rs index e101d4dc..7fe686a6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,12 @@ pub enum Error { #[error("Unable to retrieve body")] UnableToRetrieveBody, + /// Unable to retrieve child element. + /// + /// This error occurs when `Element.children().item(index)` returns `None`. + #[error("Unable to retrieve child element")] + UnableToRetrieveChildElement, + /// Unable to retrieve canvas context. /// /// This error occurs when `canvas.get_context_with_context_options("2d")` From 3c09e978e58653e38885e53aa61297d880909d54 Mon Sep 17 00:00:00 2001 From: Emirhan TALA Date: Wed, 7 May 2025 12:33:49 +0300 Subject: [PATCH 2/3] refactor(DOM): Split grid update logic into two functions --- src/backend/dom.rs | 82 ++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/src/backend/dom.rs b/src/backend/dom.rs index 22aad615..5be7e529 100644 --- a/src/backend/dom.rs +++ b/src/backend/dom.rs @@ -207,46 +207,56 @@ impl DomBackend { ) }); - if needs_rebuild { - // Rebuild entire line - let line = &self.buffer[y]; - let pre = self.grid.children().item(y as u32).ok_or(Error::UnableToRetrieveChildElement)?; - pre.set_inner_html(""); - - let mut x = 0; - while x < line.len() { - if line[x].modifier.contains(HYPERLINK_MODIFIER) { - // Handle hyperlink group - let start_x = x; - while x + 1 < line.len() && line[x + 1].modifier.contains(HYPERLINK_MODIFIER) { - x += 1; - } + self.update_line(y, needs_rebuild)?; + } - let hyperlink_cells = &line[start_x..=x]; - let anchor = create_anchor(&self.document, hyperlink_cells)?; + Ok(()) + } - for i in start_x..=x { - let span = create_span(&self.document, &line[i])?; - self.cells[y * line.len() + i] = span.clone(); - anchor.append_child(&span)?; - } - pre.append_child(&anchor)?; - } else { - // Handle regular cell - let span = create_span(&self.document, &line[x])?; - self.cells[y * line.len() + x] = span.clone(); - pre.append_child(&span)?; + /// Updates a single line of the grid, either by completely rebuilding it + /// or by updating only the changed cells. + fn update_line(&mut self, y: usize, needs_rebuild: bool) -> Result<(), Error> { + let line = &self.buffer[y]; + let pre = self.grid.children().item(y as u32).ok_or(Error::UnableToRetrieveChildElement)?; + + // Rebuilding required when there is Hyperlink in the line + if needs_rebuild { + // Rebuild entire line + pre.set_inner_html(""); + + let mut x = 0; + while x < line.len() { + if line[x].modifier.contains(HYPERLINK_MODIFIER) { + // Handle hyperlink group + let start_x = x; + while x + 1 < line.len() && line[x + 1].modifier.contains(HYPERLINK_MODIFIER) { + x += 1; } - x += 1; - } - } else { - // Just update changed cells directly - for x in 0..self.buffer[y].len() { - if self.buffer[y][x] != self.prev_buffer[y][x] { - let span = &self.cells[y * self.buffer[y].len() + x]; - span.set_inner_html(self.buffer[y][x].symbol()); - span.set_attribute("style", &get_cell_style_as_css(&self.buffer[y][x]))?; + + let hyperlink_cells = &line[start_x..=x]; + let anchor = create_anchor(&self.document, hyperlink_cells)?; + + for i in start_x..=x { + let span = create_span(&self.document, &line[i])?; + self.cells[y * line.len() + i] = span.clone(); + anchor.append_child(&span)?; } + pre.append_child(&anchor)?; + } else { + // Handle regular cell + let span = create_span(&self.document, &line[x])?; + self.cells[y * line.len() + x] = span.clone(); + pre.append_child(&span)?; + } + x += 1; + } + } else { + // Just update changed cells directly + for x in 0..line.len() { + if self.buffer[y][x] != self.prev_buffer[y][x] { + let span = &self.cells[y * line.len() + x]; + span.set_inner_html(self.buffer[y][x].symbol()); + span.set_attribute("style", &get_cell_style_as_css(&self.buffer[y][x]))?; } } } From 03e2e4eb92ffc9c036d9fd74380c595a054f6be2 Mon Sep 17 00:00:00 2001 From: Emirhan TALA Date: Thu, 8 May 2025 09:30:00 +0300 Subject: [PATCH 3/3] feat(dom): add fn rerender_line() for handling hyperlink changes, improve documentation --- Cargo.toml | 16 +++++++++++++++- src/backend/dom.rs | 33 +++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2416487b..55caf6bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,21 @@ include = ["src/**/*", "Cargo.*", "LICENSE", "README.md", "CHANGELOG.md"] edition = "2021" [dependencies] -web-sys = { version = "0.3.77", features = ["Document", "Element", "HtmlElement", "Navigator", "Node", "Window", "Screen", "console", "KeyboardEvent", "CanvasRenderingContext2d", "HtmlCanvasElement", "Location", "HtmlCollection"] } +web-sys = { version = "0.3.77", features = [ + 'Document', + 'Element', + 'HtmlElement', + 'Navigator', + 'Node', + 'Window', + 'Screen', + 'console', + 'KeyboardEvent', + 'CanvasRenderingContext2d', + 'HtmlCanvasElement', + 'Location', + 'HtmlCollection', +] } compact_str = "0.9.0" ratatui = { version = "0.29", default-features = false, features = ["all-widgets"] } console_error_panic_hook = "0.1.7" diff --git a/src/backend/dom.rs b/src/backend/dom.rs index 5be7e529..c90a1069 100644 --- a/src/backend/dom.rs +++ b/src/backend/dom.rs @@ -197,8 +197,9 @@ impl DomBackend { /// accordingly. fn update_grid(&mut self) -> Result<(), Error> { for y in 0..self.buffer.len() { - // Check if line needs rebuilding (any hyperlink changes) - let needs_rebuild = (0..self.buffer[y].len()).any(|x| { + // Complete line re-rendering required when any hyperlink status changes (added or removed) + // This is necessary because hyperlinks require special DOM structure with anchor elements + let needs_rerender = (0..self.buffer[y].len()).any(|x| { let old_cell = &self.prev_buffer[y][x]; let new_cell = &self.buffer[y][x]; new_cell != old_cell && ( @@ -207,35 +208,38 @@ impl DomBackend { ) }); - self.update_line(y, needs_rebuild)?; + self.rerender_line(y, needs_rerender)?; } Ok(()) } - /// Updates a single line of the grid, either by completely rebuilding it - /// or by updating only the changed cells. - fn update_line(&mut self, y: usize, needs_rebuild: bool) -> Result<(), Error> { + /// Updates a grid line by comparing buffers. It fully re-renders the line if hyperlinks change; + /// otherwise, it updates only the modified cells. + fn rerender_line(&mut self, y: usize, needs_rerender: bool) -> Result<(), Error> { let line = &self.buffer[y]; let pre = self.grid.children().item(y as u32).ok_or(Error::UnableToRetrieveChildElement)?; - // Rebuilding required when there is Hyperlink in the line - if needs_rebuild { - // Rebuild entire line - pre.set_inner_html(""); + // Full re-render — rebuilds the entire line if hyperlink presence changes. + if needs_rerender { + // Required when hyperlinks are added or removed, as they need special DOM structure + pre.set_inner_html(""); // Clear existing content let mut x = 0; while x < line.len() { if line[x].modifier.contains(HYPERLINK_MODIFIER) { - // Handle hyperlink group + // Process contiguous hyperlink cells as a group let start_x = x; + // Find the end of the hyperlink sequence while x + 1 < line.len() && line[x + 1].modifier.contains(HYPERLINK_MODIFIER) { x += 1; } + // Create anchor element wrapping the hyperlink cells let hyperlink_cells = &line[start_x..=x]; let anchor = create_anchor(&self.document, hyperlink_cells)?; + // Create spans for each cell in the hyperlink and add to the anchor for i in start_x..=x { let span = create_span(&self.document, &line[i])?; self.cells[y * line.len() + i] = span.clone(); @@ -243,17 +247,18 @@ impl DomBackend { } pre.append_child(&anchor)?; } else { - // Handle regular cell + // Handle regular non-hyperlink cell let span = create_span(&self.document, &line[x])?; self.cells[y * line.len() + x] = span.clone(); pre.append_child(&span)?; } x += 1; } - } else { - // Just update changed cells directly + } else { // Partial update — updates only changed cells when hyperlinks are unchanged. + // No hyperlink structure changes are needed for x in 0..line.len() { if self.buffer[y][x] != self.prev_buffer[y][x] { + // Only update cells that have changed let span = &self.cells[y * line.len() + x]; span.set_inner_html(self.buffer[y][x].symbol()); span.set_attribute("style", &get_cell_style_as_css(&self.buffer[y][x]))?;