Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add markdown viewer plugin #34

Merged
merged 1 commit into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 37 additions & 27 deletions plugins/jpterm/txl_jpterm/footer.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from __future__ import annotations

from collections import defaultdict
from typing import ClassVar

import rich.repr
from rich.console import RenderableType
from rich.text import Text
from textual import events
from textual.reactive import Reactive, watch
from textual.reactive import Reactive
from textual.widget import Widget

from txl.base import Footer as AbstractFooter
Expand All @@ -18,7 +19,22 @@ class FooterMeta(type(AbstractFooter), type(Widget)):

@rich.repr.auto
class Footer(AbstractFooter, Widget, metaclass=FooterMeta):
"""A simple header widget which docks itself to the top of the parent container."""
"""A simple footer widget which docks itself to the bottom of the parent container."""

COMPONENT_CLASSES: ClassVar[set[str]] = {
"footer--description",
"footer--key",
"footer--highlight",
"footer--highlight-key",
}
"""
| Class | Description |
| :- | :- |
| `footer--description` | Targets the descriptions of the key bindings. |
| `footer--highlight` | Targets the highlighted key binding. |
| `footer--highlight-key` | Targets the key portion of the highlighted key binding. |
| `footer--key` | Targets the key portions of the key bindings. |
"""

DEFAULT_CSS = """
Footer {
Expand All @@ -42,27 +58,20 @@ class Footer(AbstractFooter, Widget, metaclass=FooterMeta):
}
"""

COMPONENT_CLASSES = {
"footer--description",
"footer--key",
"footer--highlight",
"footer--highlight-key",
}
highlight_key: Reactive[str | None] = Reactive(None)

def __init__(self) -> None:
super().__init__()
self._key_text: Text | None = None
self._bindings = None
self.auto_links = False

highlight_key: Reactive[str | None] = Reactive(None)

async def watch_highlight_key(self, value) -> None:
"""If highlight key changes we need to regenerate the text."""
self._key_text = None
self.refresh()

def on_mount(self) -> None:
watch(self.screen, "focused", self._focus_changed)
self.watch(self.screen, "focused", self._focus_changed)

def _focus_changed(self, focused: Widget | None) -> None:
self._key_text = None
Expand All @@ -81,7 +90,6 @@ def __rich_repr__(self) -> rich.repr.Result:

def make_key_text(self) -> Text:
"""Create text containing all the keys."""
bindings = self._bindings
base_style = self.rich_style
text = Text(
style=self.rich_style,
Expand All @@ -94,24 +102,24 @@ def make_key_text(self) -> Text:
highlight_key_style = self.get_component_rich_style("footer--highlight-key")
key_style = self.get_component_rich_style("footer--key")

if bindings is None:
bindings = [
binding
for (_namespace, binding) in self.app.namespace_bindings.values()
if binding.show
]
bindings = [
binding
for (_namespace, binding) in self.app.namespace_bindings.values()
if binding.show
]

action_to_bindings = defaultdict(list)
for binding in bindings:
action_to_bindings[binding.action].append(binding)

for action, bindings in action_to_bindings.items():
binding = bindings[0]
key_display = (
binding.key.upper()
if binding.key_display is None
else binding.key_display
)
if binding.key_display is None:
key_display = self.app.get_key_display(binding.key)
if key_display is None:
key_display = binding.key.upper()
else:
key_display = binding.key_display
hovered = self.highlight_key == binding.key
key_text = Text.assemble(
(f" {key_display} ", highlight_key_style if hovered else key_style),
Expand All @@ -120,15 +128,17 @@ def make_key_text(self) -> Text:
highlight_style if hovered else base_style,
),
meta={
"@click": f"app.check_bindings('{binding.key}')"
if self._bindings is None
else binding.action,
"@click": f"app.check_bindings('{binding.key}')",
"key": binding.key,
},
)
text.append_text(key_text)
return text

def _on_styles_updated(self) -> None:
self._key_text = None
self.refresh()

def post_render(self, renderable):
return renderable

Expand Down
9 changes: 9 additions & 0 deletions plugins/markdown_viewer/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
MIT License

Copyright (c) 2022-present David Brochart <david.brochart@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1 change: 1 addition & 0 deletions plugins/markdown_viewer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TXL plugin for a Markdown viewer
35 changes: 35 additions & 0 deletions plugins/markdown_viewer/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "txl_markdown_viewer"
description = "TXL plugin for a Markdown viewer"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
keywords = []
authors = [
{ name = "David Brochart", email = "david.brochart@gmail.com" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [
"txl",
]
dynamic = ["version"]

[project.urls]
Source = "https://github.com/davidbrochart/jpterm/plugins/markdown_viewer"

[project.entry-points.txl_component]
txl_markdown_viewer = "txl_markdown_viewer.components"

[tool.hatch.version]
path = "txl_markdown_viewer/__init__.py"
1 change: 1 addition & 0 deletions plugins/markdown_viewer/txl_markdown_viewer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.1.10"
63 changes: 63 additions & 0 deletions plugins/markdown_viewer/txl_markdown_viewer/components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import asyncio

from asphalt.core import Component, Context
from textual.containers import Container
from textual.widgets import MarkdownViewer as TextualMarkdownViewer

from txl.base import Contents, Editor, Editors, FileOpenEvent
from txl.hooks import register_component


class MarkdownViewerMeta(type(Editor), type(Container)):
pass


class MarkdownViewer(Editor, Container, metaclass=MarkdownViewerMeta):
def __init__(self, contents: Contents) -> None:
self.contents = contents
self.viewer = None
super().__init__()

async def on_open(self, event: FileOpenEvent) -> None:
await self.open(event.path)

async def open(self, path: str) -> None:
self.ytext = await self.contents.get(path, type="unicode")
self.focus()
await self.update_viewer()
self.ytext.observe(self.on_change)

async def update_viewer(self):
md = self.ytext.source
if self.viewer is not None:
self.viewer.remove()
self.viewer = TextualMarkdownViewer(md, show_table_of_contents=True)
self.mount(self.viewer)

def on_change(self, target, event):
asyncio.create_task(self.update_viewer())


class MarkdownViewerComponent(Component):
def __init__(self, register: bool = True):
super().__init__()
self.register = register

async def start(
self,
ctx: Context,
) -> None:
contents = await ctx.request_resource(Contents, "contents")

def markdown_viewer_factory():
return MarkdownViewer(contents)

if self.register:
editors = await ctx.request_resource(Editors, "editors")
editors.register_editor_factory(markdown_viewer_factory, [".md"])
else:
markdown_viewer = markdown_viewer_factory()
ctx.add_resource(markdown_viewer, name="markdown_viewer", types=Editor)


c = register_component("markdown_viewer", MarkdownViewerComponent)
2 changes: 1 addition & 1 deletion plugins/widgets/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ classifiers = [
]
dependencies = [
"txl",
"ypywidgets >=0.1.1",
"ypywidgets >=0.1.2",
]
dynamic = ["version"]

Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ dependencies = [
"txl_remote_terminals >=0.1.0",
"txl_remote_kernels >=0.1.0",
"txl_text_viewer >=0.1.0",
"txl_markdown_viewer >=0.1.0",
"txl_terminal >=0.1.0",
"txl_launcher>=0.1.0",
"txl_widgets>=0.1.0",
Expand All @@ -56,6 +57,7 @@ path = "jpterm/__init__.py"

[tool.hatch.envs.dev]
pre-install-commands = [
"pip install pre-commit",
"pip install textual[dev]",
"pip install -e ./txl",
"pip install -e ./plugins/cell",
Expand All @@ -74,6 +76,7 @@ pre-install-commands = [
"pip install -e ./plugins/remote_terminals",
"pip install -e ./plugins/remote_kernels",
"pip install -e ./plugins/text_viewer",
"pip install -e ./plugins/markdown_viewer",
"pip install -e ./plugins/terminal",
"pip install -e ./plugins/launcher",
"pip install -e ./plugins/widgets",
Expand Down Expand Up @@ -101,9 +104,10 @@ python_packages = [
"plugins/remote_kernels:txl_remote_kernels",
"plugins/terminal:txl_terminal",
"plugins/text_viewer:txl_text_viewer",
"plugins/markdown_viewer:txl_markdown_viewer",
"plugins/launcher:txl_launcher",
"plugins/widgets:txl_widgets",
".:jpterm:txl,txl_cell,txl_editors,txl_file_browser,txl_image_viewer,txl_jpterm,txl_kernel,txl_local_contents,txl_local_terminals,txl_local_kernels,txl_notebook,txl_notebook_editor,txl_notebook_viewer,txl_remote_contents,txl_remote_terminals,txl_remote_kernels,txl_terminal,txl_text_viewer,txl_launcher,txl_widgets"
".:jpterm:txl,txl_cell,txl_editors,txl_file_browser,txl_image_viewer,txl_jpterm,txl_kernel,txl_local_contents,txl_local_terminals,txl_local_kernels,txl_notebook,txl_notebook_editor,txl_notebook_viewer,txl_remote_contents,txl_remote_terminals,txl_remote_kernels,txl_terminal,txl_text_viewer,txl_launcher,txl_widgets,txl_markdown"
]

[tool.ruff]
Expand Down
2 changes: 1 addition & 1 deletion txl/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ classifiers = [
dependencies = [
"pluggy >=1.0.0,<2",
"asphalt >=4.11.0,<5",
"textual >=0.10.1,<1",
"textual >=0.11,<1",
"y-py >=0.5.5,<1",
]
dynamic = ["version"]
Expand Down