Skip to content

Commit acc29a4

Browse files
authored
🔀 Merge pull request #136 from davep/show-frontmatter
Add optional display of front matter
2 parents 691e22b + 0fd104a commit acc29a4

File tree

4 files changed

+120
-11
lines changed

4 files changed

+120
-11
lines changed

ChangeLog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Hike ChangeLog
22

3+
## Unreleased
4+
5+
**Released: WiP**
6+
7+
- Added a simple YAML front matter display.
8+
([#136](https://github.com/davep/hike/pull/136))
9+
310
## v1.1.4
411

512
**Released: 2025-09-09**

docs/source/configuration.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ command ([`ChangeCommandLineLocation`](#bindable-commands), bound to
2727
```{.textual path="docs/screenshots/basic_app.py" title="Command line on top" lines=40 columns=120 press="tab,d,ctrl+up,tab"}
2828
```
2929

30+
## Front matter display
31+
32+
By default Hike will show an expandable display of [YAML front
33+
matter](https://jekyllrb.com/docs/front-matter/), if it exists in the
34+
document being viewed. If you aren't ever interested in seeing the front
35+
matter this can be turned off with:
36+
37+
```json
38+
"show_front_matter": false
39+
```
40+
3041
## Keyboard bindings
3142

3243
Hike allows for a degree of configuration of its keyboard bindings;

src/hike/data/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ class Configuration:
5454
focus_viewer_on_load: bool = True
5555
"""Should the viewer get focus when a file is loaded?"""
5656

57+
show_front_matter: bool = True
58+
"""Should the viewer allow for the viewing of front matter?"""
59+
5760

5861
##############################################################################
5962
def configuration_file() -> Path:

src/hike/widgets/viewer.py

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@
1919
##############################################################################
2020
# MarkdownIt imports.
2121
from markdown_it import MarkdownIt
22+
from markdown_it.token import Token
2223
from mdit_py_plugins import front_matter
2324

2425
##############################################################################
2526
# Textual imports.
2627
from textual import on, work
2728
from textual.app import ComposeResult
29+
from textual.await_complete import AwaitComplete
2830
from textual.containers import Vertical
2931
from textual.events import Click
3032
from textual.message import Message
3133
from textual.reactive import var
32-
from textual.widgets import Label, Markdown, Rule
34+
from textual.widgets import Collapsible, Label, Markdown, Rule
35+
from textual.widgets.markdown import MarkdownBlock
3336

3437
##############################################################################
3538
# Textual enhanced imports.
@@ -108,6 +111,94 @@ class MarkdownScroll(EnhancedVerticalScroll):
108111
"""
109112

110113

114+
##############################################################################
115+
class FrontMatter(Collapsible):
116+
"""A widget to show the front matter of Markdown document."""
117+
118+
DEFAULT_CSS = """
119+
FrontMatter {
120+
padding: 0;
121+
border-top: none;
122+
display: none;
123+
background: transparent;
124+
125+
&.--exists {
126+
display: block;
127+
}
128+
129+
&.-collapsed {
130+
margin-bottom: 1;
131+
}
132+
133+
CollapsibleTitle {
134+
padding: 0;
135+
color: $accent;
136+
}
137+
138+
Contents {
139+
padding: 0;
140+
Label {
141+
padding: 0 0 0 2;
142+
}
143+
}
144+
}
145+
"""
146+
147+
front_matter: var[str | None] = var(None)
148+
"""The front matter to show."""
149+
150+
def __init__(self) -> None:
151+
super().__init__(Label(), Rule(), title="Front matter")
152+
153+
def _watch_front_matter(self) -> None:
154+
self.set_class(
155+
bool(self.front_matter) and load_configuration().show_front_matter,
156+
"--exists",
157+
)
158+
self.query_one(Label).update(self.front_matter or "")
159+
160+
161+
##############################################################################
162+
class HikeDown(Markdown):
163+
"""Hike's `Markdown` wrapper widget."""
164+
165+
DEFAULT_CSS = """
166+
HikeDown {
167+
background: transparent;
168+
}
169+
"""
170+
171+
front_matter: var[str | None] = var(None)
172+
"""The content of any front matter found in the Markdown file."""
173+
174+
def __init__(self) -> None:
175+
"""Initialise the widget."""
176+
super().__init__(
177+
open_links=False,
178+
parser_factory=lambda: MarkdownIt("gfm-like").use(
179+
front_matter.front_matter_plugin
180+
),
181+
)
182+
183+
def update(self, markdown: str) -> AwaitComplete:
184+
self.front_matter = None
185+
return super().update(markdown)
186+
187+
def unhandled_token(self, token: Token) -> MarkdownBlock | None:
188+
"""Handle tokens that Textual's Markdown didn't.
189+
190+
Args:
191+
token: The token to handle.
192+
193+
Returns:
194+
`None` or a `MarkdownBlock`.
195+
"""
196+
if token.type == "front_matter":
197+
self.front_matter = token.content
198+
return None
199+
return super().unhandled_token(token)
200+
201+
111202
##############################################################################
112203
class Viewer(Vertical, can_focus=False):
113204
"""The Markdown viewer widget."""
@@ -121,9 +212,6 @@ class Viewer(Vertical, can_focus=False):
121212
EnhancedVerticalScroll {
122213
background: transparent;
123214
}
124-
Markdown {
125-
background: transparent;
126-
}
127215
Rule {
128216
height: 1;
129217
margin: 0 !important;
@@ -165,13 +253,9 @@ def compose(self) -> ComposeResult:
165253
"""Compose the content of the viewer."""
166254
yield ViewerTitle()
167255
yield Rule(line_style="heavy")
256+
yield FrontMatter()
168257
with MarkdownScroll(id="document"):
169-
yield Markdown(
170-
open_links=False,
171-
parser_factory=lambda: MarkdownIt("gfm-like").use(
172-
front_matter.front_matter_plugin
173-
),
174-
)
258+
yield HikeDown()
175259

176260
def focus(self, scroll_visible: bool = True) -> Self:
177261
"""Focus the viewer.
@@ -360,9 +444,13 @@ async def _update_markdown(self, message: Loaded) -> None:
360444
Args:
361445
message: The message requesting the update.
362446
"""
447+
front_matter_had_focus = self.query_one(FrontMatter).has_focus_within
363448
self.query_one(ViewerTitle).location = self.location
364449
self._source = message.markdown
365-
await self.query_one(Markdown).update(message.markdown)
450+
await (hikedown := self.query_one(HikeDown)).update(message.markdown)
451+
if front_matter_had_focus and not hikedown.front_matter:
452+
self.query_one(MarkdownScroll).focus()
453+
self.query_one(FrontMatter).front_matter = hikedown.front_matter
366454
if (
367455
message.remember
368456
and self.location

0 commit comments

Comments
 (0)