-
(This question is a continuation topic for previous focus travelsal discussion #4227) I'm looking for a recommended/preferred approach for copying content from a focused child of a Markdown widget. In the following code I've added binding 'action_copy'.
Ideally the content would be the part of original markdown content that focused markdown child is rendering. import pyperclip
# from rich.console import ConsoleRenderable, RichCast
from rich.syntax import Syntax
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Horizontal
from textual.widgets import Markdown, Static
from textual.widgets._markdown import (
MarkdownBlock,
MarkdownBullet,
MarkdownFence,
)
EXAMPLE_MARKDOWN = """\
# Table of Contents
- [Section 1](#section-1)
- [Subsection A](#subsection-a)
- [Subsection B](#subsection-b)
- [Section 2](#section-2)
- [Subsection C](#subsection-c)
This is an example of Textual's `Markdown` widget.
## Features
Markdown syntax and extensions are supported.
- Typography *emphasis*, **strong**, `inline code` etc.
- Headers
- Lists (bullet and ordered)
- Syntax highlighted code blocks
Nested lists:
- 1st level item
- 2nd level item
- 3rd level item
Some code:
~~~python
def do_something():
pass
~~~
"""
class FocusableMarkdown(Markdown, can_focus=True, can_focus_children=False):
BINDINGS = [
Binding("enter", "enable_focus_children"),
Binding("escape", "disable_focus_children"),
Binding("tab,down,right", "focus_next", priority=True),
Binding("shift+tab,up,left", "focus_previous", priority=True),
Binding("c", "copy", "Copy", show=True, key_display="c", priority=True),
]
DEFAULT_CSS = """
FocusableMarkdown {
border: tall transparent;
}
FocusableMarkdown:focus {
border: tall $accent;
}
FocusableMarkdown > MarkdownBlock:focus {
background: $accent;
}
"""
def action_enable_focus_children(self) -> None:
self.can_focus_children = True
for child in self.children:
child.can_focus = True
self.children[0].focus()
def action_disable_focus_children(self) -> None:
# If a MarkdownFence currently has focus, remove the highlighting
if isinstance(self.screen.focused, MarkdownFence):
self.update_fence_highlighting(
fence=self.screen.focused,
highlight=False,
)
self.can_focus_children = False
for child in self.children:
child.can_focus = False
self.focus()
def action_focus_next(self) -> None:
if self.can_focus_children:
# If a MarkdownFence currently has focus, remove the highlighting
if isinstance(self.screen.focused, MarkdownFence):
self.update_fence_highlighting(
fence=self.screen.focused,
highlight=False,
)
self.screen.focus_next("FocusableMarkdown *")
# Now if the focused widget is a MarkdownFence, add the highlighting
if isinstance(self.screen.focused, MarkdownFence):
self.update_fence_highlighting(
fence=self.screen.focused, highlight=True
)
else:
self.screen.focus_next()
def action_focus_previous(self) -> None:
if self.can_focus_children:
# If a MarkdownFence currently has focus, remove the highlighting
if isinstance(self.screen.focused, MarkdownFence):
self.update_fence_highlighting(
fence=self.screen.focused,
highlight=False,
)
self.screen.focus_previous("FocusableMarkdown *")
# Now if the focused widget is a MarkdownFence, add the highlighting
if isinstance(self.screen.focused, MarkdownFence):
self.update_fence_highlighting(
fence=self.screen.focused, highlight=True
)
else:
self.screen.focus_previous()
def update_fence_highlighting(
self,
fence: MarkdownFence,
highlight: bool,
) -> None:
if highlight:
accent_color = "#0178D4"
block = Syntax(
fence.code,
lexer=fence.lexer,
word_wrap=False,
indent_guides=True,
padding=(1, 2),
theme=fence.theme,
background_color=accent_color,
)
else:
block = Syntax(
fence.code,
lexer=fence.lexer,
word_wrap=False,
indent_guides=True,
padding=(1, 2),
theme=fence.theme,
)
fence.get_child_by_type(Static).update(block)
def action_copy(self) -> None:
if self.has_focus:
content = self._markdown
else:
for child in self.children:
if child.has_focus:
content = self.parse_from_children(child)
print(f"{content=}")
pyperclip.copy(content)
def parse_from_children(self, block: MarkdownBlock, indent_depth: int = -2) -> str:
children = block.children
print(f"[{indent_depth}] {block=} {children=}")
if len(children) > 0:
text = ""
for child in children:
text += self.parse_from_children(child, indent_depth + 1)
return text
else:
if isinstance(block, MarkdownBullet):
return " " * indent_depth + "- "
renderable_type = type(block.renderable)
print(f"{renderable_type=}")
return str(block.renderable) + "\n"
class FocusableMarkdownApp(App):
CSS = """
FocusableMarkdown {
width: 50%;
margin: 1 1 1 1;
}
"""
def compose(self) -> ComposeResult:
with Horizontal():
yield FocusableMarkdown(EXAMPLE_MARKDOWN)
yield FocusableMarkdown(EXAMPLE_MARKDOWN)
if __name__ == "__main__":
app = FocusableMarkdownApp()
app.run() |
Beta Was this translation helpful? Give feedback.
Answered by
villekr
Mar 6, 2024
Replies: 1 comment 3 replies
-
The |
Beta Was this translation helpful? Give feedback.
3 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for pointing me to look for the solution from the correct place. Added one if-clause to parse_from_children and it works now.