-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
647 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
""" | ||
Code browser example. | ||
Run with: | ||
python code_browser.py PATH | ||
""" | ||
|
||
import sys | ||
|
||
from rich.syntax import Syntax | ||
from rich.traceback import Traceback | ||
|
||
from textual.app import App, ComposeResult | ||
from textual.containers import Container, VerticalScroll | ||
from textual.reactive import var | ||
from textual.widgets import DirectoryTree, Footer, Header, Static | ||
|
||
|
||
class CodeBrowser(App): | ||
"""Textual code browser app.""" | ||
|
||
CSS_PATH = "code_browser.tcss" | ||
BINDINGS = [ | ||
("f", "toggle_files", "Toggle Files"), | ||
("q", "quit", "Quit"), | ||
] | ||
|
||
show_tree = var(True) | ||
|
||
def watch_show_tree(self, show_tree: bool) -> None: | ||
"""Called when show_tree is modified.""" | ||
self.set_class(show_tree, "-show-tree") | ||
|
||
def compose(self) -> ComposeResult: | ||
"""Compose our UI.""" | ||
path = "./" if len(sys.argv) < 2 else sys.argv[1] | ||
yield Header() | ||
with Container(): | ||
yield DirectoryTree(path, id="tree-view") | ||
with VerticalScroll(id="code-view"): | ||
yield Static(id="code", expand=True) | ||
yield Footer() | ||
|
||
def on_mount(self) -> None: | ||
self.query_one(DirectoryTree).focus() | ||
|
||
def on_directory_tree_file_selected( | ||
self, event: DirectoryTree.FileSelected | ||
) -> None: | ||
"""Called when the user click a file in the directory tree.""" | ||
event.stop() | ||
code_view = self.query_one("#code", Static) | ||
try: | ||
syntax = Syntax.from_path( | ||
str(event.path), | ||
line_numbers=True, | ||
word_wrap=False, | ||
indent_guides=True, | ||
theme="github-dark", | ||
) | ||
except Exception: | ||
code_view.update(Traceback(theme="github-dark", width=None)) | ||
self.sub_title = "ERROR" | ||
else: | ||
code_view.update(syntax) | ||
self.query_one("#code-view").scroll_home(animate=False) | ||
self.sub_title = str(event.path) | ||
|
||
def action_toggle_files(self) -> None: | ||
"""Called in response to key binding.""" | ||
self.show_tree = not self.show_tree | ||
|
||
|
||
if __name__ == "__main__": | ||
CodeBrowser().run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
Screen { | ||
background: $surface-darken-1; | ||
&:inline { | ||
height: 50vh; | ||
} | ||
} | ||
|
||
#tree-view { | ||
display: none; | ||
scrollbar-gutter: stable; | ||
overflow: auto; | ||
width: auto; | ||
height: 100%; | ||
dock: left; | ||
} | ||
|
||
CodeBrowser.-show-tree #tree-view { | ||
display: block; | ||
max-width: 50%; | ||
} | ||
|
||
|
||
#code-view { | ||
overflow: auto scroll; | ||
min-width: 100%; | ||
} | ||
#code { | ||
width: auto; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from __future__ import annotations | ||
|
||
try: | ||
import httpx | ||
except ImportError: | ||
raise ImportError("Please install httpx with 'pip install httpx' ") | ||
|
||
|
||
from textual import work | ||
from textual.app import App, ComposeResult | ||
from textual.containers import VerticalScroll | ||
from textual.widgets import Input, Markdown | ||
|
||
|
||
class DictionaryApp(App): | ||
"""Searches ab dictionary API as-you-type.""" | ||
|
||
CSS_PATH = "dictionary.tcss" | ||
|
||
def compose(self) -> ComposeResult: | ||
yield Input(placeholder="Search for a word") | ||
with VerticalScroll(id="results-container"): | ||
yield Markdown(id="results") | ||
|
||
def on_mount(self) -> None: | ||
"""Called when app starts.""" | ||
# Give the input focus, so we can start typing straight away | ||
self.query_one(Input).focus() | ||
|
||
async def on_input_changed(self, message: Input.Changed) -> None: | ||
"""A coroutine to handle a text changed message.""" | ||
if message.value: | ||
self.lookup_word(message.value) | ||
else: | ||
# Clear the results | ||
self.query_one("#results", Markdown).update("") | ||
|
||
@work(exclusive=True) | ||
async def lookup_word(self, word: str) -> None: | ||
"""Looks up a word.""" | ||
url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}" | ||
|
||
async with httpx.AsyncClient() as client: | ||
response = await client.get(url) | ||
try: | ||
results = response.json() | ||
except Exception: | ||
self.query_one("#results", Markdown).update(response.text) | ||
|
||
if word == self.query_one(Input).value: | ||
markdown = self.make_word_markdown(results) | ||
self.query_one("#results", Markdown).update(markdown) | ||
|
||
def make_word_markdown(self, results: object) -> str: | ||
"""Convert the results in to markdown.""" | ||
lines = [] | ||
if isinstance(results, dict): | ||
lines.append(f"# {results['title']}") | ||
lines.append(results["message"]) | ||
elif isinstance(results, list): | ||
for result in results: | ||
lines.append(f"# {result['word']}") | ||
lines.append("") | ||
for meaning in result.get("meanings", []): | ||
lines.append(f"_{meaning['partOfSpeech']}_") | ||
lines.append("") | ||
for definition in meaning.get("definitions", []): | ||
lines.append(f" - {definition['definition']}") | ||
lines.append("---") | ||
|
||
return "\n".join(lines) | ||
|
||
|
||
if __name__ == "__main__": | ||
app = DictionaryApp() | ||
app.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
Screen { | ||
background: $panel; | ||
} | ||
|
||
Input { | ||
dock: top; | ||
margin: 1 0; | ||
} | ||
|
||
#results { | ||
width: 100%; | ||
height: auto; | ||
} | ||
|
||
#results-container { | ||
background: $background 50%; | ||
margin: 0 0 1 0; | ||
height: 100%; | ||
overflow: hidden auto; | ||
border: tall $background; | ||
} | ||
|
||
#results-container:focus { | ||
border: tall $accent; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from pathlib import Path | ||
from sys import argv | ||
|
||
from textual.app import App, ComposeResult | ||
from textual.reactive import var | ||
from textual.widgets import Footer, MarkdownViewer | ||
|
||
|
||
class MarkdownApp(App): | ||
BINDINGS = [ | ||
("t", "toggle_table_of_contents", "TOC"), | ||
("b", "back", "Back"), | ||
("f", "forward", "Forward"), | ||
] | ||
|
||
path = var(Path(__file__).parent / "../../../README.md") | ||
|
||
@property | ||
def markdown_viewer(self) -> MarkdownViewer: | ||
"""Get the Markdown widget.""" | ||
return self.query_one(MarkdownViewer) | ||
|
||
def compose(self) -> ComposeResult: | ||
yield Footer() | ||
yield MarkdownViewer() | ||
|
||
async def on_mount(self) -> None: | ||
self.markdown_viewer.focus() | ||
try: | ||
await self.markdown_viewer.go(self.path) | ||
except FileNotFoundError: | ||
self.exit(message=f"Unable to load {self.path!r}") | ||
|
||
def action_toggle_table_of_contents(self) -> None: | ||
self.markdown_viewer.show_table_of_contents = ( | ||
not self.markdown_viewer.show_table_of_contents | ||
) | ||
|
||
async def action_back(self) -> None: | ||
await self.markdown_viewer.back() | ||
|
||
async def action_forward(self) -> None: | ||
await self.markdown_viewer.forward() | ||
|
||
|
||
if __name__ == "__main__": | ||
app = MarkdownApp() | ||
if len(argv) > 1 and Path(argv[1]).exists(): | ||
app.path = Path(argv[1]) | ||
app.run() |
Oops, something went wrong.