diff --git a/browsr/browsr.css b/browsr/browsr.css index fed6d02..d040e62 100644 --- a/browsr/browsr.css +++ b/browsr/browsr.css @@ -67,6 +67,8 @@ ConfirmationWindow { width: 100%; height: 100%; align: center middle; + layer: overlay; + background: $background 50%; } ConfirmationPopUp { @@ -84,3 +86,43 @@ ConfirmationPopUp Button { margin-top: 1; width: 100%; } + +/* -- ShortcutsPopUp -- */ + +ShortcutsWindow { + width: 100%; + height: 100%; + align: center middle; + layer: overlay; + background: $background 50%; +} + +ShortcutsPopUp { + background: $boost; + height: auto; + max-height: 80%; + max-width: 80; + min-width: 40; + border: wide $primary; + padding: 1 2; + margin: 1 2; + box-sizing: border-box; +} + +#shortcuts-header { + width: 100%; + content-align: center middle; + text-style: bold; + margin-bottom: 1; +} + +#shortcuts-table { + height: auto; + max-height: 20; + border: none; +} + +ShortcutsPopUp Button { + margin-top: 1; + width: 100%; +} diff --git a/browsr/browsr.py b/browsr/browsr.py index e5e84b1..11790a7 100644 --- a/browsr/browsr.py +++ b/browsr/browsr.py @@ -32,7 +32,7 @@ class Browsr(App[str]): CSS_PATH = "browsr.css" BINDINGS: ClassVar[list[BindingType]] = [ Binding(key="q", action="quit", description="Quit"), - Binding(key="d", action="toggle_dark", description="Dark Mode"), + Binding(key="d", action="toggle_dark", description="Toggle Dark Mode"), ] def __init__( @@ -74,6 +74,12 @@ def action_download_file(self) -> None: """ self.code_browser_screen.code_browser.download_file_workflow() + def action_copy_text(self) -> None: + """ + An action to copy text. + """ + self.code_browser_screen.code_browser.window_switcher.text_window.copy_selected_text() + app = Browsr( config_object=TextualAppContext( diff --git a/browsr/cli.py b/browsr/cli.py index c9232a4..76ebdfd 100644 --- a/browsr/cli.py +++ b/browsr/cli.py @@ -189,15 +189,18 @@ def browsr( ``` ## Key Bindings - - **`Q`** - Quit the application - - **`F`** - Toggle the file tree sidebar - - **`T`** - Toggle the rich theme for code formatting - - **`N`** - Toggle line numbers for code formatting - - **`D`** - Toggle dark mode for the application + - **`q`** - Quit the application + - **`f`** - Toggle the file tree sidebar + - **`t`** - Toggle the rich theme for code formatting + - **`n`** - Toggle line numbers for code formatting + - **`d`** - Toggle dark mode for the application - **`.`** - Parent Directory - go up one directory - - **`R`** - Reload the current directory - - **`C`** - Copy the current file or directory path to the clipboard - - **`X`** - Download the file from cloud storage + - **`r`** - Reload the current directory + - **`w`** - Toggle word wrap for code files + - **`c`** - Copy the current file or directory path to the clipboard + - **`shift+c`** - Copy the selected text to the clipboard + - **`x`** - Download the file from cloud storage + - **`?`** - View keyboard shortcuts """ extra_kwargs = {} if kwargs: diff --git a/browsr/config.py b/browsr/config.py index 5dc5562..5acac12 100644 --- a/browsr/config.py +++ b/browsr/config.py @@ -2,6 +2,7 @@ browsr configuration file """ +from collections import OrderedDict from os import getenv favorite_themes: list[str] = [ @@ -57,3 +58,63 @@ ".xv", ".pdf", ] + +textarea_default_theme = "vscode_dark" + +textarea_theme_map = OrderedDict( + [ + ("monokai", "monokai"), + ("dracula", "dracula"), + ("github-dark", "vscode_dark"), + ("solarized-light", "github_light"), + ("material", "vscode_dark"), + ("one-dark", "vscode_dark"), + ("solarized-dark", "vscode_dark"), + ("native", "vscode_dark"), + ("emacs", "vscode_dark"), + ("vim", "vscode_dark"), + ("paraiso-dark", "vscode_dark"), + ] +) + +language_map = { + "py": "python", + "pyi": "python", + "pyw": "python", + "md": "markdown", + "markdown": "markdown", + "json": "json", + "toml": "toml", + "yaml": "yaml", + "yml": "yaml", + "html": "html", + "htm": "html", + "css": "css", + "js": "javascript", + "mjs": "javascript", + "cjs": "javascript", + "rs": "rust", + "go": "go", + "sql": "sql", + "java": "java", + "sh": "bash", + "bash": "bash", + "zsh": "bash", + "xml": "xml", + "rss": "xml", + "svg": "xml", + "xsd": "xml", + "xslt": "xml", +} + +filename_map = { + "uv.lock": "toml", + "pyproject.toml": "toml", + "cargo.lock": "toml", + "cargo.toml": "toml", + "makefile": "bash", + "dockerfile": "bash", + "procfile": "yaml", + ".gitignore": "bash", + ".env": "bash", +} diff --git a/browsr/screens/code_browser.py b/browsr/screens/code_browser.py index bb74038..278541e 100644 --- a/browsr/screens/code_browser.py +++ b/browsr/screens/code_browser.py @@ -13,13 +13,14 @@ from textual.containers import Horizontal from textual.events import Mount from textual.widget import Widget -from textual.widgets import Footer, Header +from textual.widgets import DataTable, Footer, Header from textual_universal_directorytree import UPath from browsr.base import SortedBindingsScreen, TextualAppContext from browsr.utils import get_file_info from browsr.widgets.code_browser import CodeBrowser from browsr.widgets.files import CurrentFileInfoBar +from browsr.widgets.shortcuts import ShortcutsPopUp, ShortcutsWindow class CodeBrowserScreen(SortedBindingsScreen): @@ -27,25 +28,40 @@ class CodeBrowserScreen(SortedBindingsScreen): Code Browser Screen """ + LAYERS: ClassVar[list[str]] = ["default", "overlay"] + BINDINGS: ClassVar[list[BindingType]] = [ - Binding(key="f", action="toggle_files", description="Files"), - Binding(key="t", action="theme", description="Theme"), - Binding(key="n", action="linenos", description="Line Numbers"), - Binding(key="r", action="reload", description="Reload"), - Binding(key=".", action="parent_dir", description="Parent Directory"), + Binding(key="f", action="toggle_files", description="File Browser"), + Binding(key="t", action="theme", description="Toggle Theme"), + Binding(key="n", action="linenos", description="Toggle Line Numbers"), + Binding(key="r", action="reload", description="Reload", show=False), + Binding( + key=".", + action="parent_dir", + description="Parent Directory", + key_display=".", + show=False, + ), + Binding(key="w", action="toggle_wrap", description="Toggle Wrap", show=False), + Binding( + key="?", action="toggle_shortcuts", description="Shortcuts", key_display="?" + ), ] BINDING_WEIGHTS: ClassVar[dict[str, int]] = { - "ctrl+c": 1, - "q": 2, - "f": 3, - "t": 4, - "n": 5, - "d": 6, - "r": 995, - ".": 996, - "c": 997, - "x": 998, + "ctrl+c": 5, + "q": 10, + "f": 15, + "t": 20, + "n": 25, + "d": 30, + "r": 905, + ".": 910, + "c": 920, + "x": 925, + "w": 930, + "C": 935, + "?": 940, } def __init__( @@ -78,6 +94,7 @@ def __init__( else: self.file_information.file_info = get_file_info(self.config_object.path) self.footer = Footer() + self.shortcuts_window = ShortcutsWindow(id="shortcuts-container") def compose(self) -> Iterable[Widget]: """ @@ -87,6 +104,7 @@ def compose(self) -> Iterable[Widget]: yield self.code_browser yield self.info_bar yield self.footer + yield self.shortcuts_window @on(Mount) def start_up_app(self) -> None: @@ -157,8 +175,8 @@ def action_linenos(self) -> None: """ if self.code_browser.selected_file_path is None: return - self.code_browser.static_window.linenos = ( - not self.code_browser.static_window.linenos + self.code_browser.window_switcher.linenos = ( + not self.code_browser.window_switcher.linenos ) def action_reload(self) -> None: @@ -189,3 +207,21 @@ def action_reload(self) -> None: severity="information", timeout=1, ) + + def action_toggle_wrap(self) -> None: + """ + Toggle soft wrap for the text area. + """ + self.code_browser.window_switcher.text_window.soft_wrap = ( + not self.code_browser.window_switcher.text_window.soft_wrap + ) + + def action_toggle_shortcuts(self) -> None: + """ + Toggle the shortcuts window + """ + self.shortcuts_window.display = not self.shortcuts_window.display + if self.shortcuts_window.display: + popup = self.shortcuts_window.query_one(ShortcutsPopUp) + popup.update_shortcuts() + popup.query_one(DataTable).focus() diff --git a/browsr/utils.py b/browsr/utils.py index d8cab93..f8b7f24 100644 --- a/browsr/utils.py +++ b/browsr/utils.py @@ -39,8 +39,8 @@ def open_image(document: UPath, screen_width: float) -> Pixels: image_width = image.width image_height = image.height size_ratio = image_width / screen_width - new_width = min(int(image_width / size_ratio), image_width) - new_height = min(int(image_height / size_ratio), image_height) + new_width = int(image_width / size_ratio) + new_height = int(image_height / size_ratio) resized = image.resize((new_width, new_height)) return Pixels.from_image(resized) diff --git a/browsr/widgets/base.py b/browsr/widgets/base.py new file mode 100644 index 0000000..1e3289d --- /dev/null +++ b/browsr/widgets/base.py @@ -0,0 +1,80 @@ +""" +Base classes for widgets +""" + +from __future__ import annotations + +from typing import ClassVar + +from textual import on +from textual.binding import Binding, BindingType +from textual.containers import Container +from textual.message import Message + + +class BasePopUp(Container): + """ + Base class for popup widgets + """ + + can_focus = True + + BINDINGS: ClassVar[list[BindingType]] = [ + Binding("escape", "close", "Close", show=False), + ] + + class Toggle(Message): + """ + Toggle the popup visibility + """ + + def __init__(self, display: bool | None = None) -> None: + self.display = display + super().__init__() + + def action_close(self) -> None: + """ + Close the popup + """ + self.post_message(self.Toggle(display=False)) + + +class BaseOverlay(Container): + """ + Base class for overlay containers + """ + + can_focus = True + + BINDINGS: ClassVar[list[BindingType]] = [ + Binding("escape", "close", "Close", show=False), + ] + + def action_close(self) -> None: + """ + Close the overlay + """ + self.display = False + + def on_mount(self) -> None: + """ + On Mount + """ + self.display = False + + def watch_display(self, display: bool) -> None: + """ + Focus the overlay when it is displayed + """ + if display: + self.focus() + + @on(BasePopUp.Toggle) + def handle_toggle(self, message: BasePopUp.Toggle) -> None: + """ + Handle the toggle message from the popup + """ + if message.display is not None: + self.display = message.display + else: + self.display = not self.display diff --git a/browsr/widgets/code_browser.py b/browsr/widgets/code_browser.py index ccd824f..59b6429 100644 --- a/browsr/widgets/code_browser.py +++ b/browsr/widgets/code_browser.py @@ -7,16 +7,15 @@ import inspect import pathlib import shutil -from textwrap import dedent from typing import Any import pyperclip -from rich.markdown import Markdown from textual import on, work from textual.app import ComposeResult from textual.containers import Container from textual.events import Mount from textual.reactive import var +from textual.widget import Widget from textual.widgets import DirectoryTree from textual_universal_directorytree import ( UPath, @@ -27,7 +26,6 @@ TextualAppContext, ) from browsr.config import favorite_themes -from browsr.exceptions import FileSizeError from browsr.utils import ( get_file_info, handle_duplicate_filenames, @@ -59,10 +57,6 @@ class CodeBrowser(Container): force_show_tree = var(False) selected_file_path: UPath | None | var[None] = var(None) - hidden_table_view = var(False) - table_view_status = var(False) - static_window_status = var(False) - def __init__( self, config_object: TextualAppContext, @@ -93,6 +87,7 @@ def __init__( self.confirmation, id="confirmation-container" ) self.confirmation_window.display = False + self._last_display_state: dict[Widget, bool] = {} # Copy Pasting self._copy_function = pyperclip.determine_clipboard()[0] self._copy_supported = inspect.isfunction(self._copy_function) @@ -126,8 +121,16 @@ def bind_keys(self) -> None: """ if self._copy_supported: self.app.bind( - keys="c", action="copy_file_path", description="Copy Path", show=True + keys="c", action="copy_file_path", description="Copy Path", show=False + ) + self.app.bind( + keys="C", + action="copy_text", + description="Copy Text", + show=False, + key_display="shift+c", ) + if is_remote_path(self.initial_file_path): # type: ignore[arg-type] self.app.bind( keys="x", action="download_file", description="Download File", show=True @@ -168,8 +171,8 @@ def handle_table_view_display_toggle( """ Handle the table view display toggle. """ - self.datatable_window.display = self.table_view_status - self.window_switcher.vim_scroll.display = self.static_window_status + for widget, state in self._last_display_state.items(): + widget.display = state @on(DirectoryTree.FileSelected) def handle_file_selected(self, message: DirectoryTree.FileSelected) -> None: @@ -178,19 +181,7 @@ def handle_file_selected(self, message: DirectoryTree.FileSelected) -> None: """ self.selected_file_path = message.path # type: ignore[assignment] file_info = get_file_info(file_path=self.selected_file_path) # type: ignore[arg-type] - try: - self.static_window.handle_file_size( - file_info=file_info, max_file_size=self.config_object.max_file_size - ) - self.window_switcher.render_file(file_path=self.selected_file_path) # type: ignore[arg-type] - except FileSizeError as e: - error_message = self.static_window.handle_exception(exception=e) - error_syntax = self.static_window.text_to_syntax( - text=error_message, - file_path=self.selected_file_path, # type: ignore[arg-type] - ) - self.static_window.update(error_syntax) - self.window_switcher.switch_window(self.static_window) + self.window_switcher.render_file(file_path=self.selected_file_path) # type: ignore[arg-type] self.post_message(CurrentFileInfoBar.FileInfoUpdate(new_file=file_info)) @on(DoubleClickDirectoryTree.DirectoryDoubleClicked) @@ -234,24 +225,20 @@ def download_file_workflow(self) -> None: return elif is_remote_path(self.selected_file_path): handled_download_path = self._get_download_file_name() - prompt_message: str = dedent( - f""" - ## File Download - - **Are you sure you want to download that file?** - - **File:** `{self.selected_file_path}` - - **Path:** `{handled_download_path}` - """ + self.confirmation.prompt_download( + file_path=str(self.selected_file_path), + download_path=str(handled_download_path), ) - self.confirmation.download_message.update(Markdown(prompt_message)) - self.confirmation.refresh() - self.table_view_status = self.datatable_window.display - self.static_window_status = self.window_switcher.vim_scroll.display + self._last_display_state = { + self.datatable_window: self.datatable_window.display, + self.window_switcher.vim_scroll: ( + self.window_switcher.vim_scroll.display + ), + } self.datatable_window.display = False self.window_switcher.vim_scroll.display = False self.confirmation_window.display = True + self.confirmation_window.focus() @work(thread=True) def download_selected_file(self) -> None: diff --git a/browsr/widgets/confirmation.py b/browsr/widgets/confirmation.py index 6118570..c79ee87 100644 --- a/browsr/widgets/confirmation.py +++ b/browsr/widgets/confirmation.py @@ -1,14 +1,21 @@ +""" +Confirmation Widget +""" + +from __future__ import annotations + from textwrap import dedent from rich.markdown import Markdown from textual import on from textual.app import ComposeResult -from textual.containers import Container from textual.message import Message from textual.widgets import Button, Static +from browsr.widgets.base import BaseOverlay, BasePopUp + -class ConfirmationPopUp(Container): +class ConfirmationPopUp(BasePopUp): """ A Pop Up that asks for confirmation """ @@ -26,15 +33,6 @@ class ConfirmationWindowDownload(Message): Confirmation Window """ - class ConfirmationWindowDisplay(Message): - """ - Confirmation Window - """ - - def __init__(self, display: bool) -> None: - self.display = display - super().__init__() - class DisplayToggle(Message): """ TableView Display @@ -49,27 +47,36 @@ def compose(self) -> ComposeResult: yield Button("Yes", variant="success") yield Button("No", variant="error") + def prompt_download(self, file_path: str, download_path: str) -> None: + """ + Prompt the user to download a file + """ + prompt_message: str = dedent( + f""" + ## File Download + + **Are you sure you want to download that file?** + + **File:** `{file_path}` + + **Path:** `{download_path}` + """ + ) + self.download_message.update(Markdown(prompt_message)) + self.refresh() + @on(Button.Pressed) def handle_download_selection(self, message: Button.Pressed) -> None: """ Handle Button Presses """ - self.post_message(self.ConfirmationWindowDisplay(display=False)) + self.action_close() if message.button.variant == "success": self.post_message(self.ConfirmationWindowDownload()) self.post_message(self.DisplayToggle()) -class ConfirmationWindow(Container): +class ConfirmationWindow(BaseOverlay): """ Window containing the Confirmation Pop Up """ - - @on(ConfirmationPopUp.ConfirmationWindowDisplay) - def handle_confirmation_window_display( - self, message: ConfirmationPopUp.ConfirmationWindowDisplay - ) -> None: - """ - Handle Confirmation Window Display - """ - self.display = message.display diff --git a/browsr/widgets/shortcuts.py b/browsr/widgets/shortcuts.py new file mode 100644 index 0000000..dc5b2ab --- /dev/null +++ b/browsr/widgets/shortcuts.py @@ -0,0 +1,79 @@ +""" +Shortcuts Widget +""" + +from __future__ import annotations + +from typing import ClassVar + +from textual import on +from textual.app import ComposeResult +from textual.binding import Binding +from textual.widgets import Button, DataTable, Static + +from browsr.widgets.base import BaseOverlay, BasePopUp + + +class ShortcutsPopUp(BasePopUp): + """A Pop Up that displays keyboard shortcuts""" + + TRUSTED_ACTIONS: ClassVar[list[str]] = [ + "copy_file_path", + "copy_text", + "download_file", + "toggle_files", + "parent_dir", + "quit", + "reload", + "toggle_shortcuts", + "toggle_dark", + "linenos", + "theme", + "toggle_wrap", + ] + + IGNORED_KEYS: ClassVar[list[str]] = ["ctrl+q"] + + def compose(self) -> ComposeResult: + """Compose the Shortcuts Pop Up""" + yield Static("Keyboard Shortcuts", id="shortcuts-header") + yield DataTable(id="shortcuts-table") + yield Button("Close", variant="primary", id="close-shortcuts") + + def on_mount(self) -> None: + """Called when the widget is mounted""" + table = self.query_one(DataTable) + table.add_columns("Key", "Description") + table.cursor_type = "row" + self.update_shortcuts() + + def update_shortcuts(self) -> None: + """Update the shortcuts displayed in the table""" + table = self.query_one(DataTable) + table.clear() + rows = [] + for active_binding in self.app.active_bindings.values(): + binding = active_binding.binding + if not isinstance(binding, Binding): + continue + key = binding.key_display or binding.key + if binding.action not in self.TRUSTED_ACTIONS or key in self.IGNORED_KEYS: + continue + cells = [key, binding.description] + rows.append(cells) + sorted_rows = sorted(rows, key=lambda x: x[1]) + for row in sorted_rows: + table.add_row(*row) + + @on(Button.Pressed, "#close-shortcuts") + def handle_close(self) -> None: + """Handle the close button pressed""" + self.action_close() + + +class ShortcutsWindow(BaseOverlay): + """Window containing the Shortcuts Pop Up""" + + def compose(self) -> ComposeResult: + """Compose the Shortcuts Window""" + yield ShortcutsPopUp() diff --git a/browsr/widgets/windows.py b/browsr/widgets/windows.py index 303e611..cbfce20 100644 --- a/browsr/widgets/windows.py +++ b/browsr/widgets/windows.py @@ -4,35 +4,67 @@ from __future__ import annotations +import contextlib from json import JSONDecodeError -from typing import Any, ClassVar +from typing import Any, ClassVar, NamedTuple import orjson import pandas as pd +import pyperclip from art import text2art from numpy import nan from rich.markdown import Markdown from rich.syntax import Syntax from rich_pixels import Pixels from textual.app import ComposeResult +from textual.binding import Binding from textual.containers import Container from textual.message import Message from textual.reactive import Reactive, reactive from textual.widget import Widget -from textual.widgets import Static +from textual.widgets import Static, TextArea from textual_universal_directorytree import UPath from browsr.base import TextualAppContext -from browsr.config import favorite_themes, image_file_extensions +from browsr.config import ( + favorite_themes, + filename_map, + image_file_extensions, + language_map, + textarea_default_theme, + textarea_theme_map, +) from browsr.exceptions import FileSizeError from browsr.utils import ( ArchiveFileError, FileInfo, + get_file_info, open_image, ) from browsr.widgets.vim import VimDataTable, VimScroll +class FileToStringResult(NamedTuple): + result: str + error_occurred: bool + + +class ThemeVisibleMixin: + """ + Mixin for widgets with a theme + """ + + theme: Reactive[str] = reactive(favorite_themes[0]) + + +class LinenosVisibleMixin: + """ + Mixin for widgets with line numbers + """ + + linenos: Reactive[bool] = reactive(False) + + class BaseCodeWindow(Widget): """ Base code view widget @@ -50,10 +82,15 @@ def __init__(self, window: type[BaseCodeWindow], scroll_home: bool = False): self.scroll_home: bool = scroll_home super().__init__() - def file_to_string(self, file_path: UPath, max_lines: int | None = None) -> str: + def file_to_string( + self, file_path: UPath, max_lines: int | None = None + ) -> FileToStringResult: """ Load a file into a string + + Returns a tuple of the string and a boolean indicating if an exception occurred. """ + error_occurred = False try: if file_path.suffix in self.archive_extensions: message = f"Cannot render archive file {file_path}." @@ -61,9 +98,10 @@ def file_to_string(self, file_path: UPath, max_lines: int | None = None) -> str: text = file_path.read_text(encoding="utf-8") except Exception as e: text = self.handle_exception(exception=e) + error_occurred = True if max_lines: text = "\n".join(text.split("\n")[:max_lines]) - return text + return FileToStringResult(result=text, error_occurred=error_occurred) def file_to_image(self, file_path: UPath) -> Pixels: """ @@ -77,7 +115,7 @@ def file_to_json(self, file_path: UPath, max_lines: int | None = None) -> str: """ Load a file into a JSON object """ - code_str = self.file_to_string(file_path=file_path) + code_str = self.file_to_string(file_path=file_path).result try: code_obj = orjson.loads(code_str) code_str = orjson.dumps(code_obj, option=orjson.OPT_INDENT_2).decode( @@ -103,61 +141,26 @@ def handle_file_size(cls, file_info: FileInfo, max_file_size: int = 5) -> None: def handle_exception(cls, exception: Exception) -> str: """ Handle an exception - - This method is used to handle exceptions that occur when rendering a file. - When an uncommon exception occurs, the method will raise the exception. - - Parameters - ---------- - exception: Exception - The exception that occurred. - - Raises - ------ - Exception - If the exception is not one of the expected exceptions. - - Returns - ------- - str - The error message to display. """ font = "univers" - if isinstance(exception, ArchiveFileError): - error_message = ( - text2art("ARCHIVE", font=font) + "\n\n" + text2art("FILE", font=font) - ) - elif isinstance(exception, FileSizeError): - error_message = ( - text2art("FILE TOO", font=font) + "\n\n" + text2art("LARGE", font=font) - ) - elif isinstance(exception, PermissionError): - error_message = ( - text2art("PERMISSION", font=font) - + "\n\n" - + text2art("ERROR", font=font) - ) - elif isinstance(exception, UnicodeError): - error_message = ( - text2art("ENCODING", font=font) + "\n\n" + text2art("ERROR", font=font) - ) - elif isinstance(exception, FileNotFoundError): - error_message = ( - text2art("FILE NOT", font=font) + "\n\n" + text2art("FOUND", font=font) - ) - else: - raise exception from exception - return error_message + exception_map = { + ArchiveFileError: ("ARCHIVE", "FILE"), + FileSizeError: ("FILE TOO", "LARGE"), + PermissionError: ("PERMISSION", "ERROR"), + UnicodeError: ("ENCODING", "ERROR"), + FileNotFoundError: ("FILE NOT", "FOUND"), + } + for exc_type, (line1, line2) in exception_map.items(): + if isinstance(exception, exc_type): + return text2art(line1, font=font) + "\n\n" + text2art(line2, font=font) + raise exception from exception -class StaticWindow(Static, BaseCodeWindow): +class StaticWindow(Static, BaseCodeWindow, ThemeVisibleMixin, LinenosVisibleMixin): """ A static widget for displaying code. """ - linenos: Reactive[bool] = reactive(False) - theme: Reactive[str] = reactive(favorite_themes[0]) - rich_themes: ClassVar[list[str]] = favorite_themes def __init__( @@ -173,7 +176,7 @@ def file_to_markdown( Load a file into a Markdown """ return Markdown( - self.file_to_string(file_path, max_lines=max_lines), + self.file_to_string(file_path, max_lines=max_lines).result, code_theme=self.theme, hyperlinks=True, ) @@ -198,6 +201,7 @@ def watch_linenos(self, linenos: bool) -> None: """ if isinstance(self.content, Syntax): self.content.line_numbers = linenos + self.refresh() def watch_theme(self, theme: str) -> None: """ @@ -216,16 +220,91 @@ def watch_theme(self, theme: str) -> None: elif isinstance(self.content, Markdown): self.content.code_theme = self.theme - def next_theme(self) -> str | None: + +class TextWindow(TextArea, BaseCodeWindow, ThemeVisibleMixin, LinenosVisibleMixin): + """ + A window that displays text using a TextArea. + """ + + theme: Reactive[str] = reactive(textarea_default_theme) + default_theme: ClassVar[str] = textarea_default_theme + + BINDINGS: ClassVar[list[Binding | tuple[str, str] | tuple[str, str, str]]] = [ + Binding("j", "cursor_down", "Down", show=False), + Binding("k", "cursor_up", "Up", show=False), + Binding("l", "cursor_right", "Right", show=False), + Binding("h", "cursor_left", "Left", show=False), + ] + + def __init__(self, **kwargs: Any) -> None: + super().__init__(read_only=True, **kwargs) + self.theme = self.default_theme + self.soft_wrap = False + self.display = False + + def watch_linenos(self, linenos: bool) -> None: """ - Switch to the next theme + Called when linenos is modified. """ - if not isinstance(self.content, (Syntax, Markdown)): - return None - current_index = favorite_themes.index(self.theme) - next_theme = favorite_themes[(current_index + 1) % len(favorite_themes)] - self.theme = next_theme - return next_theme + self.show_line_numbers = linenos + + def copy_selected_text(self) -> None: + """ + Copy the selected text to the clipboard. + """ + if self.selected_text: + pyperclip.copy(self.selected_text) + self.app.notify( + title="Copied", + message="Selected text copied to clipboard", + severity="information", + timeout=1, + ) + else: + self.app.notify( + title="No Selection", + message="No text selected to copy", + severity="warning", + timeout=1, + ) + + def apply_smart_theme(self, rich_theme: str) -> None: + """ + Apply a theme to the TextArea + """ + with contextlib.suppress(RuntimeError, AttributeError): + if not getattr(self.app, "dark", True): + if self.theme != "github_light": + self.theme = "github_light" + return + target = textarea_theme_map.get(rich_theme, self.default_theme) + if target in self.available_themes and self.theme != target: + self.theme = target + + def load_file(self, text: str, file_path: UPath) -> None: + """ + Load text and detect language + """ + self.load_text(text) + self.detect_language(file_path) + + def detect_language(self, file_path: str | UPath) -> None: + """ + Detect the language from the file path + """ + if isinstance(file_path, str): + file_path = UPath(file_path) + file_name = file_path.name.lower() + if file_name in filename_map: + self.language = filename_map[file_name] + return + ext = file_path.suffix.lstrip(".").lower() + if ext in language_map: + self.language = language_map[ext] + elif ext in self.available_languages: + self.language = ext + else: + self.language = None class DataTableWindow(VimDataTable, BaseCodeWindow): @@ -285,7 +364,7 @@ def refresh_from_df( self.add_row(*row) -class WindowSwitcher(Container): +class WindowSwitcher(Container, ThemeVisibleMixin, LinenosVisibleMixin): """ A container that contains the file content windows """ @@ -307,20 +386,62 @@ def __init__( self, config_object: TextualAppContext, *args: Any, **kwargs: Any ) -> None: super().__init__(*args, **kwargs) + self.rendered_file: UPath | None = None self.config_object = config_object self.static_window = StaticWindow(expand=True, config_object=config_object) + self.text_window = TextWindow() self.datatable_window = DataTableWindow( zebra_stripes=True, show_header=True, show_cursor=True, id="table-view" ) self.datatable_window.display = False self.vim_scroll = VimScroll(self.static_window) - self.rendered_file: UPath | None = None + + def watch_linenos(self, linenos: bool) -> None: + """ + Called when linenos is modified. + """ + self.static_window.linenos = linenos + self.text_window.linenos = linenos + + def watch_theme(self, theme: str) -> None: + """ + Called when theme is modified. + """ + self.static_window.theme = theme + if self.text_window.display: + self.text_window.apply_smart_theme(theme) + self._update_subtitle() + + def _update_subtitle(self) -> None: + """ + Update the app subtitle + """ + if self.rendered_file is None: + return + active_widget = self.get_active_widget() + if active_widget is self.text_window: + display_theme = self.text_window.theme.replace("_", "-") + elif active_widget is self.vim_scroll: + display_theme = self.static_window.theme + else: + self.app.sub_title = str(self.rendered_file) + return + self.app.sub_title = str(self.rendered_file) + f" [{display_theme}]" + + def watch_dark(self, _dark: bool) -> None: + """ + Called when dark mode is modified. + """ + self.text_window.apply_smart_theme(self.theme) + self.static_window.refresh() + self._update_subtitle() def compose(self) -> ComposeResult: """ Compose the widget """ yield self.vim_scroll + yield self.text_window yield self.datatable_window def get_active_widget(self) -> Widget: # type: ignore[return] @@ -329,6 +450,8 @@ def get_active_widget(self) -> Widget: # type: ignore[return] """ if self.vim_scroll.display: return self.vim_scroll + elif self.text_window.display: + return self.text_window elif self.datatable_window.display: return self.datatable_window @@ -336,51 +459,44 @@ def switch_window(self, window: BaseCodeWindow) -> None: """ Switch to the window """ - screens: dict[Widget, Widget] = { + window_map: dict[Widget, Widget] = { self.static_window: self.vim_scroll, + self.text_window: self.text_window, self.datatable_window: self.datatable_window, } - for window_screen, _ in screens.items(): - if window is window_screen: - screens[window_screen].display = True - else: - screens[window_screen].display = False + for window_widget, container_widget in window_map.items(): + container_widget.display = window is window_widget + self._update_subtitle() def render_file(self, file_path: UPath, scroll_home: bool = True) -> None: """ Render a file """ - switch_window = self.static_window - joined_suffixes = "".join(file_path.suffixes).lower() - if joined_suffixes in self.datatable_extensions: - self.datatable_window.refresh_from_file( - file_path=file_path, max_lines=self.config_object.max_lines - ) - switch_window = self.datatable_window # type: ignore[assignment] - elif file_path.suffix.lower() in self.image_extensions: - image = self.static_window.file_to_image(file_path=file_path) - self.static_window.update(image) - elif file_path.suffix.lower() in self.markdown_extensions: - markdown = self.static_window.file_to_markdown( - file_path=file_path, max_lines=self.config_object.max_lines - ) - self.static_window.update(markdown) - elif file_path.suffix.lower() in self.json_extensions: - json_str = self.static_window.file_to_json( - file_path=file_path, max_lines=self.config_object.max_lines - ) - json_syntax = self.static_window.text_to_syntax( - text=json_str, file_path=file_path + try: + file_info = get_file_info(file_path=file_path) + self.static_window.handle_file_size( + file_info=file_info, max_file_size=self.config_object.max_file_size ) - self.static_window.update(json_syntax) - switch_window = self.static_window - else: - string = self.static_window.file_to_string( - file_path=file_path, max_lines=self.config_object.max_lines + joined_suffixes = "".join(file_path.suffixes).lower() + if joined_suffixes in self.datatable_extensions: + switch_window = self._render_datatable(file_path) + elif file_path.suffix.lower() in self.image_extensions: + switch_window = self._render_image(file_path) + elif file_path.suffix.lower() in self.markdown_extensions: + switch_window = self._render_markdown(file_path) + elif file_path.suffix.lower() in self.json_extensions: + switch_window = self._render_json(file_path) + else: + switch_window = self._render_text(file_path) + except Exception as e: + error_message = self.static_window.handle_exception(exception=e) + error_syntax = self.static_window.text_to_syntax( + text=error_message, + file_path=file_path, ) - syntax = self.static_window.text_to_syntax(text=string, file_path=file_path) - self.static_window.update(syntax) + self.static_window.update(error_syntax) switch_window = self.static_window + self.switch_window(switch_window) active_widget = self.get_active_widget() if scroll_home: @@ -388,22 +504,82 @@ def render_file(self, file_path: UPath, scroll_home: bool = True) -> None: self.vim_scroll.scroll_home(animate=False) else: switch_window.scroll_home(animate=False) - if active_widget is self.vim_scroll: - self.app.sub_title = str(file_path) + f" [{self.static_window.theme}]" - else: - self.app.sub_title = str(file_path) self.rendered_file = file_path + self._update_subtitle() + + def _render_datatable(self, file_path: UPath) -> BaseCodeWindow: + """Render a datatable file""" + self.datatable_window.refresh_from_file( + file_path=file_path, max_lines=self.config_object.max_lines + ) + return self.datatable_window + + def _render_image(self, file_path: UPath) -> BaseCodeWindow: + """Render an image file""" + image = self.static_window.file_to_image(file_path=file_path) + self.static_window.update(image) + return self.static_window + + def _render_markdown(self, file_path: UPath) -> BaseCodeWindow: + """Render a markdown file""" + markdown = self.static_window.file_to_markdown( + file_path=file_path, max_lines=self.config_object.max_lines + ) + self.static_window.update(markdown) + return self.static_window + + def _render_json(self, file_path: UPath) -> BaseCodeWindow: + """Render a JSON file""" + json_str = self.static_window.file_to_json( + file_path=file_path, max_lines=self.config_object.max_lines + ) + self.text_window.load_file(json_str, file_path) + return self.text_window + + def _render_text(self, file_path: UPath) -> BaseCodeWindow: + """Render a text file""" + result = self.static_window.file_to_string( + file_path=file_path, max_lines=self.config_object.max_lines + ) + if result.error_occurred: + self.static_window.update(result.result) + return self.static_window + else: + self.text_window.load_file(result.result, file_path) + return self.text_window def next_theme(self) -> str | None: """ Switch to the next theme """ - if self.get_active_widget() is not self.vim_scroll: - return None - current_index = favorite_themes.index(self.static_window.theme) + active_widget = self.get_active_widget() + if active_widget is self.text_window: + return self._next_textarea_theme() + elif active_widget is self.vim_scroll: + return self._next_rich_theme() + return None + + def _next_textarea_theme(self) -> str: + """Switch to the next TextArea theme""" + themes = list(textarea_theme_map.values()) + unique_themes = list(dict.fromkeys(themes)) + try: + current_index = unique_themes.index(self.text_window.theme) + except ValueError: + current_index = -1 + next_theme = unique_themes[(current_index + 1) % len(unique_themes)] + self.text_window.theme = next_theme + self._update_subtitle() + return next_theme + + def _next_rich_theme(self) -> str: + """Switch to the next Rich theme""" + try: + current_index = favorite_themes.index(self.theme) + except ValueError: + current_index = -1 next_theme = favorite_themes[(current_index + 1) % len(favorite_themes)] - self.static_window.theme = next_theme - self.app.sub_title = str(self.rendered_file) + f" [{self.static_window.theme}]" + self.theme = next_theme return next_theme def action_toggle_files(self) -> None: diff --git a/pyproject.toml b/pyproject.toml index 71b648e..0611d36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "rich>=14,<15", "rich-click~=1.9.7", "rich-pixels~=3.0.1", - "textual>=8,<9", + "textual[syntax]>=8,<9", "textual-universal-directorytree~=1.6.0", "universal-pathlib~=0.2.6", "Pillow>=12.1.1", diff --git a/tests/__snapshots__/test_screenshots/test_github_screenshot.raw b/tests/__snapshots__/test_screenshots/test_github_screenshot.raw deleted file mode 100644 index ea22761..0000000 --- a/tests/__snapshots__/test_screenshots/test_github_screenshot.raw +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - browsr - - - - - - - - - - browsr — github://juftin:browsr@v1.6.0/README.md [monokai] -📂 browsrbrowsr -├── 📁 .github -├── 📁 browsr -├── 📁 docs🌆 browsr Version 🌆 PyPI 🌆 Testing Status 🌆 GitHub License -├── 📁 requirements -├── 📁 testsbrowsr is a TUI (text-based user interface) file browser for your terminal. It's a simple way to browse your files and take a  -├── 📄 .gitignorepeek at their contents. Plus it works on local and remote file systems.                                                        -├── 📄 .pre-commit-config.yaml -├── 📄 .releaserc.js -├── 📄 LICENSE -├── 📄 mkdocs.yaml -├── 📄 pyproject.tomlInstallation -└── 📄 README.md -The below command recommends pipx instead of pip. pipx installs the package in an isolated environment and makes it easy to    -uninstall. If you'd like to use pip instead, just replace pipx with pip in the below command.                                  - - -pipx install browsr - - -Extra Installation - -If you're looking to use browsr on remote file systems, like AWS S3, you'll need to install the remote extra. If you'd like to -browse parquet files, you'll need to install the parquet extra. Or, even simpler, you can install the all extra to get all the -extras.                                                                                                                        - - -pipx install "browsr[all]" - - -Usage - - -browsr ~/Downloads/ - - -Simply give browsr a path to a file/directory and it will open a browser window with a file browser. You can also give it a    -URL to a remote file system, like AWS S3.                                                                                      - - -browsr s3://my-bucket/my-file.parquet - -▆▆ -Check out the Documentation for more - -🗂️  GitHub  🗄️️  3KB  📂  browsr  💾  README.md - q Quit  f Files  t Theme  n Line Numbers  d Dark Mode  . Parent Directory  r Reload  c Copy Path  x Download File ^p palette - - - diff --git a/tests/__snapshots__/test_screenshots/test_github_screenshot_license.raw b/tests/__snapshots__/test_screenshots/test_github_screenshot_license.raw index 8122897..f2d02db 100644 --- a/tests/__snapshots__/test_screenshots/test_github_screenshot_license.raw +++ b/tests/__snapshots__/test_screenshots/test_github_screenshot_license.raw @@ -19,236 +19,237 @@ font-weight: 700; } - .terminal-2863109803-matrix { + .terminal-1537121438-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2863109803-title { + .terminal-1537121438-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2863109803-r1 { fill: #c5c8c6 } -.terminal-2863109803-r2 { fill: #e0e0e0 } -.terminal-2863109803-r3 { fill: #a0a3a6 } -.terminal-2863109803-r4 { fill: #f8f8f2 } -.terminal-2863109803-r5 { fill: #dfdfdf } -.terminal-2863109803-r6 { fill: #0d0d0d } -.terminal-2863109803-r7 { fill: #003054 } -.terminal-2863109803-r8 { fill: #e0bf91 } -.terminal-2863109803-r9 { fill: #ffa62b;font-weight: bold } -.terminal-2863109803-r10 { fill: #495259 } + .terminal-1537121438-r1 { fill: #c5c8c6 } +.terminal-1537121438-r2 { fill: #e0e0e0 } +.terminal-1537121438-r3 { fill: #a0a3a6 } +.terminal-1537121438-r4 { fill: #0d0d0d } +.terminal-1537121438-r5 { fill: #191919 } +.terminal-1537121438-r6 { fill: #cccccc } +.terminal-1537121438-r7 { fill: #1f1f1f } +.terminal-1537121438-r8 { fill: #003054 } +.terminal-1537121438-r9 { fill: #e0bf91 } +.terminal-1537121438-r10 { fill: #ffa62b;font-weight: bold } +.terminal-1537121438-r11 { fill: #495259 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - browsr + browsr - - - - browsr — github://juftin:browsr@v1.6.0/LICENSE [monokai] -MIT License - -Copyright (c) 2023-present Justin Flannery <justin.flannery@juftin.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 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 FO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -🗂️  GitHub  🗄️️  1KB  📂  browsr  💾  LICENSE - q Quit  f Files  t Theme  n Line Numbers  d Dark Mode  . Parent Directory  r Reload  c Copy Path  x Download File ^p palette + + + + browsr — github://juftin:browsr@v1.6.0/LICENSE [vscode-dark] +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +MIT License                                                                                                                                                  + +Copyright (c) 2023-present Justin Flannery <justin.flannery@juftin.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 i + +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  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +🗂️  GitHub  🗄️️  1KB  📂  browsr  💾  LICENSE + q Quit  f File Browser  t Toggle Theme  n Toggle Line Numbers  d Toggle Dark Mode  ? Shortcuts  x Download File ^p palette diff --git a/tests/__snapshots__/test_screenshots/test_mkdocs_screenshot.raw b/tests/__snapshots__/test_screenshots/test_mkdocs_screenshot.raw index c03896e..30c2d43 100644 --- a/tests/__snapshots__/test_screenshots/test_mkdocs_screenshot.raw +++ b/tests/__snapshots__/test_screenshots/test_mkdocs_screenshot.raw @@ -19,238 +19,241 @@ font-weight: 700; } - .terminal-2587919084-matrix { + .terminal-3320198052-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2587919084-title { + .terminal-3320198052-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2587919084-r1 { fill: #c5c8c6 } -.terminal-2587919084-r2 { fill: #e0e0e0 } -.terminal-2587919084-r3 { fill: #a0a3a6 } -.terminal-2587919084-r4 { fill: #959077 } -.terminal-2587919084-r5 { fill: #dfdfdf } -.terminal-2587919084-r6 { fill: #0d0d0d } -.terminal-2587919084-r7 { fill: #ff4689 } -.terminal-2587919084-r8 { fill: #f8f8f2 } -.terminal-2587919084-r9 { fill: #e6db74 } -.terminal-2587919084-r10 { fill: #e0bf91 } -.terminal-2587919084-r11 { fill: #ffa62b;font-weight: bold } -.terminal-2587919084-r12 { fill: #495259 } + .terminal-3320198052-r1 { fill: #c5c8c6 } +.terminal-3320198052-r2 { fill: #e0e0e0 } +.terminal-3320198052-r3 { fill: #a0a3a6 } +.terminal-3320198052-r4 { fill: #0d0d0d } +.terminal-3320198052-r5 { fill: #191919 } +.terminal-3320198052-r6 { fill: #6a9955 } +.terminal-3320198052-r7 { fill: #cccccc } +.terminal-3320198052-r8 { fill: #1f1f1f } +.terminal-3320198052-r9 { fill: #569cd6;font-weight: bold } +.terminal-3320198052-r10 { fill: #ce9178 } +.terminal-3320198052-r11 { fill: #000000 } +.terminal-3320198052-r12 { fill: #7daf9c } +.terminal-3320198052-r13 { fill: #e0bf91 } +.terminal-3320198052-r14 { fill: #ffa62b;font-weight: bold } +.terminal-3320198052-r15 { fill: #495259 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - browsr + browsr - - - - browsr — github://juftin:browsr@v1.6.0/mkdocs.yaml [monokai] -# schema: https://squidfunk.github.io/mkdocs-material/schema.json - -site_namebrowsr                                                                                                                                              -nav: --index.md                                                                                                                                                 --Command Line Interface ⌨️cli.md                                                                                                                        --Contributing 🤝contributing.md                                                                                                                         --API Documentation 🤖reference/                                                                                                                         -theme: -faviconhttps://raw.githubusercontent.com/juftin/browsr/main/docs/_static/browsr_no_label.png                                                             -logohttps://raw.githubusercontent.com/juftin/browsr/main/docs/_static/browsr_no_label.png                                                                -namematerial                                                                                                                                             -features: --navigation.tracking                                                                                                                                  --content.code.annotate                                                                                                                                --content.code.copy                                                                                                                                    -palette: --media"(prefers-color-scheme:light)" -schemedefault                                                                                                                                      -accentpurple                                                                                                                                       -toggle: -iconmaterial/weather-sunny                                                                                                                     -nameSwitch to dark mode                                                                                                                        --media"(prefers-color-scheme:dark)" -schemeslate                                                                                                                                        -primaryblack                                                                                                                                       -toggle: -iconmaterial/weather-night                                                                                                                     -nameSwitch to light mode                                                                                                                       -repo_urlhttps://github.com/juftin/browsr                                                                                                                     -repo_namebrowsr                                                                                                                                              -edit_uriblob/main/docs/                                                                                                                                      -site_authorJustin Flannery                                                                                                                                   -remote_branchgh-pages                                                                                                                                        -copyrightCopyright © 2023 Justin Flannery                                                                                                                    -extra: -generatorfalse                                                                                                                                           -markdown_extensions: --toc: -permalink"#" --pymdownx.snippets                                                                                                                                        --pymdownx.magiclink                                                                                                                                       --attr_list                                                                                                                                                --md_in_html                                                                                                                                               --pymdownx.highlight: -🗂️  GitHub  🗄️️  2KB  📂  browsr  💾  mkdocs.yaml - q Quit  f Files  t Theme  n Line Numbers  d Dark Mode  . Parent Directory  r Reload  c Copy Path  x Download File ^p palette + + + + browsr — github://juftin:browsr@v1.6.0/mkdocs.yaml [vscode-dark] +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +# schema: https://squidfunk.github.io/mkdocs-material/schema.json + +site_namebrowsr +nav:                                                                                                                                                       +    - index.md +    - Command Line Interface ⌨️cli.md +    - Contributing 🤝contributing.md +    - API Documentation 🤖reference/ +theme:                                                                                                                                                     +faviconhttps://raw.githubusercontent.com/juftin/browsr/main/docs/_static/browsr_no_label.png +logohttps://raw.githubusercontent.com/juftin/browsr/main/docs/_static/browsr_no_label.png +namematerial +features:                                                                                                                                              +        - navigation.tracking +        - content.code.annotate +        - content.code.copy +palette:                                                                                                                                               +        - media"(prefers-color-scheme: light)" +schemedefault +accentpurple +toggle:                                                                                                                                          +iconmaterial/weather-sunny +nameSwitch to dark mode +        - media"(prefers-color-scheme: dark)" +schemeslate▂▂ +primaryblack +toggle:                                                                                                                                          +iconmaterial/weather-night +nameSwitch to light mode +repo_urlhttps://github.com/juftin/browsr +repo_namebrowsr +edit_uriblob/main/docs/ +site_authorJustin Flannery +remote_branchgh-pages +copyrightCopyright © 2023 Justin Flannery +extra:                                                                                                                                                     +generatorfalse +markdown_extensions:                                                                                                                                       +    - toc:                                                                                                                                                 +permalink"#" +    - pymdownx.snippets +    - pymdownx.magiclink +    - attr_list +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +🗂️  GitHub  🗄️️  2KB  📂  browsr  💾  mkdocs.yaml + q Quit  f File Browser  t Toggle Theme  n Toggle Line Numbers  d Toggle Dark Mode  ? Shortcuts  x Download File ^p palette diff --git a/tests/__snapshots__/test_screenshots/test_shortcuts_screenshot.raw b/tests/__snapshots__/test_screenshots/test_shortcuts_screenshot.raw new file mode 100644 index 0000000..79b17b9 --- /dev/null +++ b/tests/__snapshots__/test_screenshots/test_shortcuts_screenshot.raw @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + browsr + + + + + + + + + + browsr — github://juftin:browsr@v1.6.0/README.md [monokai] + + + + + + + + + + + +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +Keyboard Shortcuts + + Key      Description          + c        Copy Path            + shift+c  Copy Text            + x        Download File        + f        File Browser         + .        Parent Directory     + q        Quit                 + r        Reload               + ?        Shortcuts            + d        Toggle Dark Mode     + n        Toggle Line Numbers  + t        Toggle Theme         + w        Toggle Wrap          + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Close  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + + + + + + + + + +🗂️  GitHub  🗄️️  3KB  📂  browsr  💾  README.md▇▇ + q Quit  f File Browser  t Toggle Theme  n Toggle Line Numbers  d Toggle Dark Mode  ? Shortcuts  x Download File ^p palette + + + diff --git a/tests/cassettes/test_shortcuts_screenshot.yaml b/tests/cassettes/test_shortcuts_screenshot.yaml new file mode 100644 index 0000000..9d00b2a --- /dev/null +++ b/tests/cassettes/test_shortcuts_screenshot.yaml @@ -0,0 +1,225 @@ +interactions: + - request: + body: null + headers: + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.33.0 + method: GET + uri: https://api.github.com/repos/juftin/browsr/git/trees/v1.6.0 + response: + body: + string: '{"sha":"b80ea1937572041e2ed884f3d17919a7324b080b","url":"https://api.github.com/repos/juftin/browsr/git/trees/b80ea1937572041e2ed884f3d17919a7324b080b","tree":[{"path":".github","mode":"040000","type":"tree","sha":"a901aed96b47ecefdef6bf3530856065a76ff9f8","url":"https://api.github.com/repos/juftin/browsr/git/trees/a901aed96b47ecefdef6bf3530856065a76ff9f8"},{"path":".gitignore","mode":"100644","type":"blob","sha":"7e0d4d450a35a973ed5d4d1eeb749861ffb68ecf","size":2804,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/7e0d4d450a35a973ed5d4d1eeb749861ffb68ecf"},{"path":".pre-commit-config.yaml","mode":"100644","type":"blob","sha":"a9d8704a8c8511e6a430032ec2615c3dfeb7fb84","size":1379,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/a9d8704a8c8511e6a430032ec2615c3dfeb7fb84"},{"path":".releaserc.js","mode":"100644","type":"blob","sha":"71ebbd3ec251758bf9e3ac6b1c246808af9d289a","size":1574,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/71ebbd3ec251758bf9e3ac6b1c246808af9d289a"},{"path":"LICENSE","mode":"100644","type":"blob","sha":"3f1116db9ccaba831afbd47542a8dbef1ceb2189","size":1109,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/3f1116db9ccaba831afbd47542a8dbef1ceb2189"},{"path":"README.md","mode":"100644","type":"blob","sha":"1b7227281e1ac90b906c05a82ce968b6d5e3114c","size":2728,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/1b7227281e1ac90b906c05a82ce968b6d5e3114c"},{"path":"browsr","mode":"040000","type":"tree","sha":"dd4f0ba68388bc8382120b638a14497bafd3945b","url":"https://api.github.com/repos/juftin/browsr/git/trees/dd4f0ba68388bc8382120b638a14497bafd3945b"},{"path":"docs","mode":"040000","type":"tree","sha":"de31dc85a413f8f2cdd79130276faac5e93efcc9","url":"https://api.github.com/repos/juftin/browsr/git/trees/de31dc85a413f8f2cdd79130276faac5e93efcc9"},{"path":"mkdocs.yaml","mode":"100644","type":"blob","sha":"d5151fff1451011cdac254c3745e15e78af77064","size":2183,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/d5151fff1451011cdac254c3745e15e78af77064"},{"path":"pyproject.toml","mode":"100644","type":"blob","sha":"98be6018858bf4ccf1dafa7509d388ea08537ed7","size":4448,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/98be6018858bf4ccf1dafa7509d388ea08537ed7"},{"path":"requirements","mode":"040000","type":"tree","sha":"3b044dfacdcb0bd4b3d9b032363d135c5894e410","url":"https://api.github.com/repos/juftin/browsr/git/trees/3b044dfacdcb0bd4b3d9b032363d135c5894e410"},{"path":"tests","mode":"040000","type":"tree","sha":"c25588f3e19c20ee684b21551db7635b155b3cf9","url":"https://api.github.com/repos/juftin/browsr/git/trees/c25588f3e19c20ee684b21551db7635b155b3cf9"}],"truncated":false}' + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - public, max-age=60, s-maxage=60 + Content-Encoding: + - gzip + Content-Length: + - "728" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 01 Apr 2026 03:39:57 GMT + ETag: + - W/"91ee3d202828b3c28fcd9be69924424a4f792f5f17ff608d6d4ac2b1e19e5fcd" + Last-Modified: + - Tue, 31 Mar 2026 04:45:44 GMT + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept,Accept-Encoding, Accept, X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - EDC8:E0E0B:3F6066F:10681ED6:69CC938D + X-RateLimit-Limit: + - "60" + X-RateLimit-Remaining: + - "59" + X-RateLimit-Reset: + - "1775018397" + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - "1" + X-XSS-Protection: + - "0" + x-github-api-version-selected: + - "2022-11-28" + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.33.0 + method: GET + uri: https://api.github.com/repos/juftin/browsr/git/trees/v1.6.0 + response: + body: + string: '{"sha":"b80ea1937572041e2ed884f3d17919a7324b080b","url":"https://api.github.com/repos/juftin/browsr/git/trees/b80ea1937572041e2ed884f3d17919a7324b080b","tree":[{"path":".github","mode":"040000","type":"tree","sha":"a901aed96b47ecefdef6bf3530856065a76ff9f8","url":"https://api.github.com/repos/juftin/browsr/git/trees/a901aed96b47ecefdef6bf3530856065a76ff9f8"},{"path":".gitignore","mode":"100644","type":"blob","sha":"7e0d4d450a35a973ed5d4d1eeb749861ffb68ecf","size":2804,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/7e0d4d450a35a973ed5d4d1eeb749861ffb68ecf"},{"path":".pre-commit-config.yaml","mode":"100644","type":"blob","sha":"a9d8704a8c8511e6a430032ec2615c3dfeb7fb84","size":1379,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/a9d8704a8c8511e6a430032ec2615c3dfeb7fb84"},{"path":".releaserc.js","mode":"100644","type":"blob","sha":"71ebbd3ec251758bf9e3ac6b1c246808af9d289a","size":1574,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/71ebbd3ec251758bf9e3ac6b1c246808af9d289a"},{"path":"LICENSE","mode":"100644","type":"blob","sha":"3f1116db9ccaba831afbd47542a8dbef1ceb2189","size":1109,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/3f1116db9ccaba831afbd47542a8dbef1ceb2189"},{"path":"README.md","mode":"100644","type":"blob","sha":"1b7227281e1ac90b906c05a82ce968b6d5e3114c","size":2728,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/1b7227281e1ac90b906c05a82ce968b6d5e3114c"},{"path":"browsr","mode":"040000","type":"tree","sha":"dd4f0ba68388bc8382120b638a14497bafd3945b","url":"https://api.github.com/repos/juftin/browsr/git/trees/dd4f0ba68388bc8382120b638a14497bafd3945b"},{"path":"docs","mode":"040000","type":"tree","sha":"de31dc85a413f8f2cdd79130276faac5e93efcc9","url":"https://api.github.com/repos/juftin/browsr/git/trees/de31dc85a413f8f2cdd79130276faac5e93efcc9"},{"path":"mkdocs.yaml","mode":"100644","type":"blob","sha":"d5151fff1451011cdac254c3745e15e78af77064","size":2183,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/d5151fff1451011cdac254c3745e15e78af77064"},{"path":"pyproject.toml","mode":"100644","type":"blob","sha":"98be6018858bf4ccf1dafa7509d388ea08537ed7","size":4448,"url":"https://api.github.com/repos/juftin/browsr/git/blobs/98be6018858bf4ccf1dafa7509d388ea08537ed7"},{"path":"requirements","mode":"040000","type":"tree","sha":"3b044dfacdcb0bd4b3d9b032363d135c5894e410","url":"https://api.github.com/repos/juftin/browsr/git/trees/3b044dfacdcb0bd4b3d9b032363d135c5894e410"},{"path":"tests","mode":"040000","type":"tree","sha":"c25588f3e19c20ee684b21551db7635b155b3cf9","url":"https://api.github.com/repos/juftin/browsr/git/trees/c25588f3e19c20ee684b21551db7635b155b3cf9"}],"truncated":false}' + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - public, max-age=60, s-maxage=60 + Content-Encoding: + - gzip + Content-Length: + - "728" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 01 Apr 2026 03:39:57 GMT + ETag: + - W/"91ee3d202828b3c28fcd9be69924424a4f792f5f17ff608d6d4ac2b1e19e5fcd" + Last-Modified: + - Tue, 31 Mar 2026 04:45:44 GMT + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept,Accept-Encoding, Accept, X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - EDC9:82E6C:3D5DD44:FD7C591:69CC938D + X-RateLimit-Limit: + - "60" + X-RateLimit-Remaining: + - "58" + X-RateLimit-Reset: + - "1775018397" + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - "2" + X-XSS-Protection: + - "0" + x-github-api-version-selected: + - "2022-11-28" + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.33.0 + method: GET + uri: https://api.github.com/repos/juftin/browsr/contents/README.md?ref=v1.6.0 + response: + body: + string: '{"name":"README.md","path":"README.md","sha":"1b7227281e1ac90b906c05a82ce968b6d5e3114c","size":2728,"url":"https://api.github.com/repos/juftin/browsr/contents/README.md?ref=v1.6.0","html_url":"https://github.com/juftin/browsr/blob/v1.6.0/README.md","git_url":"https://api.github.com/repos/juftin/browsr/git/blobs/1b7227281e1ac90b906c05a82ce968b6d5e3114c","download_url":"https://raw.githubusercontent.com/juftin/browsr/v1.6.0/README.md","type":"file","content":"IyBicm93c3IKCjxkaXYgYWxpZ249ImNlbnRlciIgaHJlZj0iaHR0cHM6Ly9n\naXRodWIuY29tL2p1ZnRpbi9icm93c3IiPgogICAgPGEgaHJlZj0iaHR0cHM6\nLy9naXRodWIuY29tL2p1ZnRpbi9icm93c3IiPgogICAgICA8aW1nIHNyYz1o\ndHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vanVmdGluL2Jyb3dz\nci9tYWluL2RvY3MvX3N0YXRpYy9icm93c3IucG5nCiAgICAgICAgd2lkdGg9\nIjQwMCIgYWx0PSJicm93c3IiPgogICAgPC9hPgo8L2Rpdj4KClshW2Jyb3dz\nciBWZXJzaW9uXShodHRwczovL2ltZy5zaGllbGRzLmlvL3B5cGkvdi9icm93\nc3I/Y29sb3I9Ymx1ZSZsYWJlbD1icm93c3IpXShodHRwczovL2dpdGh1Yi5j\nb20vanVmdGluL2Jyb3dzcikKWyFbUHlQSV0oaHR0cHM6Ly9pbWcuc2hpZWxk\ncy5pby9weXBpL3B5dmVyc2lvbnMvYnJvd3NyKV0oaHR0cHM6Ly9weXBpLnB5\ndGhvbi5vcmcvcHlwaS9icm93c3IvKQpbIVtUZXN0aW5nIFN0YXR1c10oaHR0\ncHM6Ly9naXRodWIuY29tL2p1ZnRpbi9icm93c3IvYWN0aW9ucy93b3JrZmxv\nd3MvdGVzdHMueWFtbC9iYWRnZS5zdmc/YnJhbmNoPW1haW4pXShodHRwczov\nL2dpdGh1Yi5jb20vanVmdGluL2Jyb3dzci9hY3Rpb25zL3dvcmtmbG93cy90\nZXN0cy55YW1sP3F1ZXJ5PWJyYW5jaCUzQW1haW4pClshW0dpdEh1YiBMaWNl\nbnNlXShodHRwczovL2ltZy5zaGllbGRzLmlvL2dpdGh1Yi9saWNlbnNlL2p1\nZnRpbi9icm93c3I/Y29sb3I9Ymx1ZSZsYWJlbD1MaWNlbnNlKV0oaHR0cHM6\nLy9naXRodWIuY29tL2p1ZnRpbi9icm93c3IvYmxvYi9tYWluL0xJQ0VOU0Up\nCgoqKmBicm93c3JgKiogaXMgYSBUVUkgKHRleHQtYmFzZWQgdXNlciBpbnRl\ncmZhY2UpIGZpbGUgYnJvd3NlciBmb3IgeW91ciB0ZXJtaW5hbC4KSXQncyBh\nIHNpbXBsZSB3YXkgdG8gYnJvd3NlIHlvdXIgZmlsZXMgYW5kIHRha2UgYSBw\nZWVrIGF0IHRoZWlyIGNvbnRlbnRzLiBQbHVzIGl0CndvcmtzIG9uIGxvY2Fs\nIGFuZCByZW1vdGUgZmlsZSBzeXN0ZW1zLgoKPGRldGFpbHMgb3Blbj48L3N1\nbW1hcnk+PC9zdW1tYXJ5PgoKPGJvZHk+CjxkaXYgc3R5bGU9ImRpc3BsYXk6\nIGdyaWQ7IGdyaWQtdGVtcGxhdGUtY29sdW1uczogcmVwZWF0KDIsIDFmcik7\nIGdyaWQtZ2FwOiAxMHB4OyI+CiAgICA8aW1nIHNyYz0iaHR0cHM6Ly9yYXcu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tL2p1ZnRpbi9icm93c3IvbWFpbi9kb2Nz\nL19zdGF0aWMvc2NyZWVuc2hvdF91dGlscy5wbmciIGFsdD0iSW1hZ2UgMSI+\nCiAgICA8aW1nIHNyYz0iaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQu\nY29tL2p1ZnRpbi9icm93c3IvbWFpbi9kb2NzL19zdGF0aWMvc2NyZWVuc2hv\ndF9kYXRhdGFibGUucG5nIiBhbHQ9IkltYWdlIDIiPgogICAgPGltZyBzcmM9\nImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qdWZ0aW4vYnJv\nd3NyL21haW4vZG9jcy9fc3RhdGljL3NjcmVlbnNob3RfbW9uYV9saXNhLnBu\nZyIgYWx0PSJJbWFnZSAzIj4KICAgIDxpbWcgc3JjPSJodHRwczovL3Jhdy5n\naXRodWJ1c2VyY29udGVudC5jb20vanVmdGluL2Jyb3dzci9tYWluL2RvY3Mv\nX3N0YXRpYy9zY3JlZW5zaG90X21hcmtkb3duLnBuZyIgYWx0PSJJbWFnZSA0\nIj4KPC9kaXY+CjwvYm9keT4KCjwvZGV0YWlscz4KCiMjIEluc3RhbGxhdGlv\nbgoKVGhlIGJlbG93IGNvbW1hbmQgcmVjb21tZW5kcyBbcGlweF0oaHR0cHM6\nLy9weXBhLmdpdGh1Yi5pby9waXB4LykgaW5zdGVhZCBvZiBwaXAuIGBwaXB4\nYCBpbnN0YWxscyB0aGUgcGFja2FnZSBpbgphbiBpc29sYXRlZCBlbnZpcm9u\nbWVudCBhbmQgbWFrZXMgaXQgZWFzeSB0byB1bmluc3RhbGwuIElmIHlvdSdk\nIGxpa2UgdG8gdXNlIGBwaXBgIGluc3RlYWQsIGp1c3QgcmVwbGFjZSBgcGlw\neGAKd2l0aCBgcGlwYCBpbiB0aGUgYmVsb3cgY29tbWFuZC4KCmBgYHNoZWxs\nCnBpcHggaW5zdGFsbCBicm93c3IKYGBgCgojIyBFeHRyYSBJbnN0YWxsYXRp\nb24KCklmIHlvdSdyZSBsb29raW5nIHRvIHVzZSAqKmBicm93c3JgKiogb24g\ncmVtb3RlIGZpbGUgc3lzdGVtcywgbGlrZSBBV1MgUzMsIHlvdSdsbCBuZWVk\nIHRvIGluc3RhbGwgdGhlIGByZW1vdGVgIGV4dHJhLgpJZiB5b3UnZCBsaWtl\nIHRvIGJyb3dzZSBwYXJxdWV0IGZpbGVzLCB5b3UnbGwgbmVlZCB0byBpbnN0\nYWxsIHRoZSBgcGFycXVldGAgZXh0cmEuIE9yLCBldmVuIHNpbXBsZXIsCnlv\ndSBjYW4gaW5zdGFsbCB0aGUgYGFsbGAgZXh0cmEgdG8gZ2V0IGFsbCB0aGUg\nZXh0cmFzLgoKYGBgc2hlbGwKcGlweCBpbnN0YWxsICJicm93c3JbYWxsXSIK\nYGBgCgojIyBVc2FnZQoKYGBgc2hlbGwKYnJvd3NyIH4vRG93bmxvYWRzLwpg\nYGAKClNpbXBseSBnaXZlICoqYGJyb3dzcmAqKiBhIHBhdGggdG8gYSBmaWxl\nL2RpcmVjdG9yeSBhbmQgaXQgd2lsbCBvcGVuIGEgYnJvd3NlciB3aW5kb3cK\nd2l0aCBhIGZpbGUgYnJvd3Nlci4gWW91IGNhbiBhbHNvIGdpdmUgaXQgYSBV\nUkwgdG8gYSByZW1vdGUgZmlsZSBzeXN0ZW0sIGxpa2UgQVdTIFMzLgoKYGBg\nc2hlbGwKYnJvd3NyIHMzOi8vbXktYnVja2V0L215LWZpbGUucGFycXVldApg\nYGAKCiMjIyBbQ2hlY2sgb3V0IHRoZSBEb2N1bWVudGF0aW9uXShodHRwczov\nL2p1ZnRpbi5jb20vYnJvd3NyLykgZm9yIG1vcmUKCiMjIExpY2Vuc2UKCioq\nYGJyb3dzcmAqKiBpcyBkaXN0cmlidXRlZCB1bmRlciB0aGUgdGVybXMgb2Yg\ndGhlIFtNSVQgbGljZW5zZV0oTElDRU5TRSkuCg==\n","encoding":"base64","_links":{"self":"https://api.github.com/repos/juftin/browsr/contents/README.md?ref=v1.6.0","git":"https://api.github.com/repos/juftin/browsr/git/blobs/1b7227281e1ac90b906c05a82ce968b6d5e3114c","html":"https://github.com/juftin/browsr/blob/v1.6.0/README.md"}}' + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - public, max-age=60, s-maxage=60 + Content-Length: + - "4505" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 01 Apr 2026 03:39:58 GMT + ETag: + - '"1b7227281e1ac90b906c05a82ce968b6d5e3114c"' + Last-Modified: + - Tue, 16 May 2023 03:59:55 GMT + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Vary: + - Accept, Accept-Encoding, Accept, X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - EDCA:2F48D8:4033BE0:10A44C84:69CC938D + X-RateLimit-Limit: + - "60" + X-RateLimit-Remaining: + - "57" + X-RateLimit-Reset: + - "1775018397" + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - "3" + X-XSS-Protection: + - "0" + x-github-api-version-selected: + - "2022-11-28" + x-internal-experiment-version-metadata: + - ZW1wdHlfcmVwb3NpdG9yeTpmYWxzZSxoZWFkX29pZDo5MDMzYzcwNGE5NmJkZDRiNDM1YTc2MWYwNDA5NzQ1NTIzYTc5NjkyLHJlZl9uYW1lOnYxLjYuMCxyZWZfY29tbWl0X29pZDpiODBlYTE5Mzc1NzIwNDFlMmVkODg0ZjNkMTc5MTlhNzMyNGIwODBiLHJvb3RfdHJlZV9lbnRyeV9vaWQ6M2U4NTA1ZTdmYjAyZDU4ZTgzNDQ5YTlmNTFlOGU2ZmQwM2Y5YmNkZSxwYXRoX29iamVjdF90eXBlOmJsb2IscGF0aF9vYmplY3Rfb2lkOjFiNzIyNzI4MWUxYWM5MGI5MDZjMDVhODJjZTk2OGI2ZDVlMzExNGMscGF0aF9vYmplY3RfbW9kZTozMzE4OCxwYXRoX29iamVjdF9zaXplOjI3Mjg= + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_screenshots.py b/tests/test_screenshots.py index cc0b56a..e48ba02 100644 --- a/tests/test_screenshots.py +++ b/tests/test_screenshots.py @@ -30,7 +30,7 @@ def terminal_size() -> tuple[int, int]: @cassette -def test_github_screenshot( +def test_shortcuts_screenshot( snap_compare: Callable[..., bool], tmp_path: UPath, app_file: str, @@ -38,11 +38,13 @@ def test_github_screenshot( terminal_size: tuple[int, int], ) -> None: """ - Snapshot a release of this repo + Snapshot the shortcuts window """ app_path = tmp_path / "app.py" app_path.write_text(app_file.format(file_path=str(github_release_path))) - assert snap_compare(app=app_path, terminal_size=terminal_size) + assert snap_compare( + app=app_path, terminal_size=terminal_size, press=["question_mark"] + ) @cassette diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py new file mode 100644 index 0000000..c564e0b --- /dev/null +++ b/tests/test_shortcuts.py @@ -0,0 +1,22 @@ +from typing import ClassVar + +import pytest +from textual.app import App +from textual.binding import Binding + +from browsr.widgets.shortcuts import ShortcutsPopUp + + +class MockApp(App): + BINDINGS: ClassVar[list[Binding]] = [Binding("q", "quit", "Quit")] + + +@pytest.mark.asyncio +async def test_shortcut_discovery(): + app = MockApp() + async with app.run_test(): + popup = ShortcutsPopUp() + await app.mount(popup) + # Note: the widget calls update_shortcuts on mount + table = popup.query_one("#shortcuts-table") + assert table.row_count > 0 diff --git a/tests/test_windows.py b/tests/test_windows.py new file mode 100644 index 0000000..d898c7f --- /dev/null +++ b/tests/test_windows.py @@ -0,0 +1,187 @@ +from unittest.mock import MagicMock, patch + +import pytest +from textual.widgets import TextArea +from textual_universal_directorytree import UPath + +from browsr.base import TextualAppContext +from browsr.widgets.windows import FileToStringResult, TextWindow, WindowSwitcher + + +def test_text_window_inheritance(): + window = TextWindow() + assert isinstance(window, TextArea) + assert window.read_only is True + assert window.theme == window.default_theme + + +def test_text_window_theme_mapping(): + window = TextWindow() + # Test with dark mode + mock_app = MagicMock() + mock_app.dark = True + with pytest.MonkeyPatch.context() as mp: + mp.setattr("textual.widget.Widget.app", mock_app, raising=False) + window.apply_smart_theme("monokai") + assert window.theme == "monokai" + window.apply_smart_theme("invalid-theme") + assert window.theme == "vscode_dark" # Default + + # Test with light mode + mock_app.dark = False + window.apply_smart_theme("monokai") + assert window.theme == "github_light" + + +def test_text_window_language_detection(): + window = TextWindow() + window.detect_language("test.py") + assert window.language == "python" + window.detect_language("test.json") + assert window.language == "json" + window.detect_language("test.yml") + assert window.language == "yaml" + window.detect_language("test.sh") + assert window.language == "bash" + window.detect_language("uv.lock") + assert window.language == "toml" + window.detect_language("file.something.json") + assert window.language == "json" + + +def test_text_window_linenos(): + window = TextWindow() + window.linenos = True + assert window.show_line_numbers is True + window.linenos = False + assert window.show_line_numbers is False + + +def test_text_window_copy_text(): + window = TextWindow() + window.text = "Hello World" + mock_app = MagicMock() + + with pytest.MonkeyPatch.context() as mp: + mp.setattr("textual.widget.Widget.app", mock_app, raising=False) + with patch("pyperclip.copy") as mock_copy: + # Test no selection + window.copy_selected_text() + mock_copy.assert_not_called() + mock_app.notify.assert_called_with( + title="No Selection", + message="No text selected to copy", + severity="warning", + timeout=1, + ) + + # Test selection + window.selection = ((0, 0), (0, 5)) # "Hello" + window.copy_selected_text() + mock_copy.assert_called_with("Hello") + mock_app.notify.assert_called_with( + title="Copied", + message="Selected text copied to clipboard", + severity="information", + timeout=1, + ) + + +@pytest.mark.asyncio +async def test_window_switcher_routing(): + mock_app = MagicMock() + # Patch the property on the class for the duration of the test + with pytest.MonkeyPatch.context() as mp: + mp.setattr("textual.widget.Widget.app", mock_app, raising=False) + context = TextualAppContext() + switcher = WindowSwitcher(config_object=context) + + # Ensure default theme + assert switcher.text_window.theme == switcher.text_window.default_theme + + # Mock UPath for a JSON file + mock_json = MagicMock(spec=UPath) + mock_json.suffix = ".json" + mock_json.suffixes = [".json"] + mock_json.read_text.return_value = '{"key": "value"}' + mock_json.__str__.return_value = "test.json" + mock_json.stat.return_value.st_size = 100 + + # Mock UPath for a Python file + mock_py = MagicMock(spec=UPath) + mock_py.suffix = ".py" + mock_py.suffixes = [".py"] + mock_py.read_text.return_value = "print('hello')" + mock_py.__str__.return_value = "test.py" + mock_py.stat.return_value.st_size = 100 + + # We need to patch various things to avoid NoActiveAppError and other issues + switcher.static_window.file_to_json = MagicMock( + return_value='{\n "key": "value"\n}' + ) + switcher.static_window.file_to_string = MagicMock( + return_value=FileToStringResult( + result="print('hello')", error_occurred=False + ) + ) + switcher.text_window.scroll_home = MagicMock() + switcher.vim_scroll.scroll_home = MagicMock() + + # Render JSON + switcher.render_file(mock_json) + # After rendering JSON, the theme should remain at the default theme + assert switcher.text_window.theme == switcher.text_window.default_theme + + # Manually change theme + switcher.text_window.theme = "monokai" + + # Render Python - should PERSIST theme + switcher.render_file(mock_py) + assert switcher.text_window.theme == "monokai" + + +def test_window_switcher_linenos_sync(): + mock_app = MagicMock() + with pytest.MonkeyPatch.context() as mp: + mp.setattr("textual.widget.Widget.app", mock_app, raising=False) + context = TextualAppContext() + switcher = WindowSwitcher(config_object=context) + switcher.linenos = True + assert switcher.static_window.linenos is True + assert switcher.text_window.linenos is True + switcher.linenos = False + assert switcher.static_window.linenos is False + assert switcher.text_window.linenos is False + + +def test_window_switcher_theme_sync(): + mock_app = MagicMock() + mock_app.dark = True + mock_app.sub_title = "" + + with pytest.MonkeyPatch.context() as mp: + mp.setattr("textual.widget.Widget.app", mock_app, raising=False) + context = TextualAppContext() + switcher = WindowSwitcher(config_object=context) + switcher.rendered_file = "test.py" + + # Test StaticWindow theme cycling + switcher.switch_window(switcher.static_window) + initial_theme = switcher.theme + switcher.next_theme() + assert switcher.theme != initial_theme + assert switcher.static_window.theme == switcher.theme + + # Test TextWindow theme cycling + switcher.switch_window(switcher.text_window) + initial_text_theme = switcher.text_window.theme + switcher.next_theme() + assert switcher.text_window.theme != initial_text_theme + # Global switcher theme should NOT have changed + assert switcher.theme == switcher.static_window.theme + + mock_app.dark = False + # Trigger the watch_dark + switcher.watch_dark(False) + # TextWindow forces light theme when app is light + assert switcher.text_window.theme == "github_light" diff --git a/uv.lock b/uv.lock index 69440c4..82cffd3 100644 --- a/uv.lock +++ b/uv.lock @@ -314,7 +314,7 @@ dependencies = [ { name = "rich" }, { name = "rich-click" }, { name = "rich-pixels" }, - { name = "textual" }, + { name = "textual", extra = ["syntax"] }, { name = "textual-universal-directorytree" }, { name = "universal-pathlib" }, ] @@ -377,7 +377,7 @@ requires-dist = [ { name = "rich", specifier = ">=14,<15" }, { name = "rich-click", specifier = "~=1.9.7" }, { name = "rich-pixels", specifier = "~=3.0.1" }, - { name = "textual", specifier = ">=8,<9" }, + { name = "textual", extras = ["syntax"], specifier = ">=8,<9" }, { name = "textual-universal-directorytree", specifier = "~=1.6.0" }, { name = "textual-universal-directorytree", extras = ["remote"], marker = "extra == 'all'", specifier = "~=1.6.0" }, { name = "textual-universal-directorytree", extras = ["remote"], marker = "extra == 'remote'", specifier = "~=1.6.0" }, @@ -2538,27 +2538,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, - { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, - { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, - { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, - { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, - { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, - { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, - { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, +version = "0.15.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, + { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, ] [[package]] @@ -2613,6 +2613,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/09/c6f000c2e3702036e593803319af02feee58a662528d0d5728a37e1cf81b/textual-8.2.1-py3-none-any.whl", hash = "sha256:746cbf947a8ca875afc09779ef38cadbc7b9f15ac886a5090f7099fef5ade990", size = 723871, upload-time = "2026-03-29T03:57:34.334Z" }, ] +[package.optional-dependencies] +syntax = [ + { name = "tree-sitter" }, + { name = "tree-sitter-bash" }, + { name = "tree-sitter-css" }, + { name = "tree-sitter-go" }, + { name = "tree-sitter-html" }, + { name = "tree-sitter-java" }, + { name = "tree-sitter-javascript" }, + { name = "tree-sitter-json" }, + { name = "tree-sitter-markdown" }, + { name = "tree-sitter-python" }, + { name = "tree-sitter-regex" }, + { name = "tree-sitter-rust" }, + { name = "tree-sitter-sql" }, + { name = "tree-sitter-toml" }, + { name = "tree-sitter-xml" }, + { name = "tree-sitter-yaml" }, +] + [[package]] name = "textual-dev" version = "1.4.0" @@ -2691,6 +2711,284 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[package]] +name = "tree-sitter" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961, upload-time = "2025-09-25T17:37:59.751Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/d4/f7ffb855cb039b7568aba4911fbe42e4c39c0e4398387c8e0d8251489992/tree_sitter-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72a510931c3c25f134aac2daf4eb4feca99ffe37a35896d7150e50ac3eee06c7", size = 146749, upload-time = "2025-09-25T17:37:16.475Z" }, + { url = "https://files.pythonhosted.org/packages/9a/58/f8a107f9f89700c0ab2930f1315e63bdedccbb5fd1b10fcbc5ebadd54ac8/tree_sitter-0.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44488e0e78146f87baaa009736886516779253d6d6bac3ef636ede72bc6a8234", size = 137766, upload-time = "2025-09-25T17:37:18.138Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/357158d39f01699faea466e8fd5a849f5a30252c68414bddc20357a9ac79/tree_sitter-0.25.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2f8e7d6b2f8489d4a9885e3adcaef4bc5ff0a275acd990f120e29c4ab3395c5", size = 599809, upload-time = "2025-09-25T17:37:19.169Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a4/68ae301626f2393a62119481cb660eb93504a524fc741a6f1528a4568cf6/tree_sitter-0.25.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b570690f87f1da424cd690e51cc56728d21d63f4abd4b326d382a30353acc7", size = 627676, upload-time = "2025-09-25T17:37:20.715Z" }, + { url = "https://files.pythonhosted.org/packages/69/fe/4c1bef37db5ca8b17ca0b3070f2dff509468a50b3af18f17665adcab42b9/tree_sitter-0.25.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a0ec41b895da717bc218a42a3a7a0bfcfe9a213d7afaa4255353901e0e21f696", size = 624281, upload-time = "2025-09-25T17:37:21.823Z" }, + { url = "https://files.pythonhosted.org/packages/d4/30/3283cb7fa251cae2a0bf8661658021a789810db3ab1b0569482d4a3671fd/tree_sitter-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:7712335855b2307a21ae86efe949c76be36c6068d76df34faa27ce9ee40ff444", size = 127295, upload-time = "2025-09-25T17:37:22.977Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/ceb05e6de281aebe82b68662890619580d4ffe09283ebd2ceabcf5df7b4a/tree_sitter-0.25.2-cp310-cp310-win_arm64.whl", hash = "sha256:a925364eb7fbb9cdce55a9868f7525a1905af512a559303bd54ef468fd88cb37", size = 113991, upload-time = "2025-09-25T17:37:23.854Z" }, + { url = "https://files.pythonhosted.org/packages/7c/22/88a1e00b906d26fa8a075dd19c6c3116997cb884bf1b3c023deb065a344d/tree_sitter-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ca72d841215b6573ed0655b3a5cd1133f9b69a6fa561aecad40dca9029d75b", size = 146752, upload-time = "2025-09-25T17:37:24.775Z" }, + { url = "https://files.pythonhosted.org/packages/57/1c/22cc14f3910017b7a76d7358df5cd315a84fe0c7f6f7b443b49db2e2790d/tree_sitter-0.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc0351cfe5022cec5a77645f647f92a936b38850346ed3f6d6babfbeeeca4d26", size = 137765, upload-time = "2025-09-25T17:37:26.103Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0c/d0de46ded7d5b34631e0f630d9866dab22d3183195bf0f3b81de406d6622/tree_sitter-0.25.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1799609636c0193e16c38f366bda5af15b1ce476df79ddaae7dd274df9e44266", size = 604643, upload-time = "2025-09-25T17:37:27.398Z" }, + { url = "https://files.pythonhosted.org/packages/34/38/b735a58c1c2f60a168a678ca27b4c1a9df725d0bf2d1a8a1c571c033111e/tree_sitter-0.25.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e65ae456ad0d210ee71a89ee112ac7e72e6c2e5aac1b95846ecc7afa68a194c", size = 632229, upload-time = "2025-09-25T17:37:28.463Z" }, + { url = "https://files.pythonhosted.org/packages/32/f6/cda1e1e6cbff5e28d8433578e2556d7ba0b0209d95a796128155b97e7693/tree_sitter-0.25.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:49ee3c348caa459244ec437ccc7ff3831f35977d143f65311572b8ba0a5f265f", size = 629861, upload-time = "2025-09-25T17:37:29.593Z" }, + { url = "https://files.pythonhosted.org/packages/f9/19/427e5943b276a0dd74c2a1f1d7a7393443f13d1ee47dedb3f8127903c080/tree_sitter-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:56ac6602c7d09c2c507c55e58dc7026b8988e0475bd0002f8a386cce5e8e8adc", size = 127304, upload-time = "2025-09-25T17:37:30.549Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d9/eef856dc15f784d85d1397a17f3ee0f82df7778efce9e1961203abfe376a/tree_sitter-0.25.2-cp311-cp311-win_arm64.whl", hash = "sha256:b3d11a3a3ac89bb8a2543d75597f905a9926f9c806f40fcca8242922d1cc6ad5", size = 113990, upload-time = "2025-09-25T17:37:31.852Z" }, + { url = "https://files.pythonhosted.org/packages/3c/9e/20c2a00a862f1c2897a436b17edb774e831b22218083b459d0d081c9db33/tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960", size = 146941, upload-time = "2025-09-25T17:37:34.813Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/8512e2062e652a1016e840ce36ba1cc33258b0dcc4e500d8089b4054afec/tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c", size = 137699, upload-time = "2025-09-25T17:37:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/47/8a/d48c0414db19307b0fb3bb10d76a3a0cbe275bb293f145ee7fba2abd668e/tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99", size = 607125, upload-time = "2025-09-25T17:37:37.725Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/b95f545e9fc5001b8a78636ef942a4e4e536580caa6a99e73dd0a02e87aa/tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9", size = 635418, upload-time = "2025-09-25T17:37:38.922Z" }, + { url = "https://files.pythonhosted.org/packages/de/4d/b734bde3fb6f3513a010fa91f1f2875442cdc0382d6a949005cd84563d8f/tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac", size = 631250, upload-time = "2025-09-25T17:37:40.039Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/5f654994f36d10c64d50a192239599fcae46677491c8dd53e7579c35a3e3/tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897", size = 127156, upload-time = "2025-09-25T17:37:41.132Z" }, + { url = "https://files.pythonhosted.org/packages/67/23/148c468d410efcf0a9535272d81c258d840c27b34781d625f1f627e2e27d/tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5", size = 113984, upload-time = "2025-09-25T17:37:42.074Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/67492014ce32729b63d7ef318a19f9cfedd855d677de5773476caf771e96/tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd", size = 146926, upload-time = "2025-09-25T17:37:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9c/a278b15e6b263e86c5e301c82a60923fa7c59d44f78d7a110a89a413e640/tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601", size = 137712, upload-time = "2025-09-25T17:37:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/423bba15d2bf6473ba67846ba5244b988cd97a4b1ea2b146822162256794/tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053", size = 607873, upload-time = "2025-09-25T17:37:45.477Z" }, + { url = "https://files.pythonhosted.org/packages/ed/4c/b430d2cb43f8badfb3a3fa9d6cd7c8247698187b5674008c9d67b2a90c8e/tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614", size = 636313, upload-time = "2025-09-25T17:37:46.68Z" }, + { url = "https://files.pythonhosted.org/packages/9d/27/5f97098dbba807331d666a0997662e82d066e84b17d92efab575d283822f/tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae", size = 631370, upload-time = "2025-09-25T17:37:47.993Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3c/87caaed663fabc35e18dc704cd0e9800a0ee2f22bd18b9cbe7c10799895d/tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b", size = 127157, upload-time = "2025-09-25T17:37:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/d5/23/f8467b408b7988aff4ea40946a4bd1a2c1a73d17156a9d039bbaff1e2ceb/tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8", size = 113975, upload-time = "2025-09-25T17:37:49.922Z" }, + { url = "https://files.pythonhosted.org/packages/07/e3/d9526ba71dfbbe4eba5e51d89432b4b333a49a1e70712aa5590cd22fc74f/tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0", size = 146776, upload-time = "2025-09-25T17:37:50.898Z" }, + { url = "https://files.pythonhosted.org/packages/42/97/4bd4ad97f85a23011dd8a535534bb1035c4e0bac1234d58f438e15cff51f/tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87", size = 137732, upload-time = "2025-09-25T17:37:51.877Z" }, + { url = "https://files.pythonhosted.org/packages/b6/19/1e968aa0b1b567988ed522f836498a6a9529a74aab15f09dd9ac1e41f505/tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab", size = 609456, upload-time = "2025-09-25T17:37:52.925Z" }, + { url = "https://files.pythonhosted.org/packages/48/b6/cf08f4f20f4c9094006ef8828555484e842fc468827ad6e56011ab668dbd/tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358", size = 636772, upload-time = "2025-09-25T17:37:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/57/e2/d42d55bf56360987c32bc7b16adb06744e425670b823fb8a5786a1cea991/tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0", size = 631522, upload-time = "2025-09-25T17:37:55.833Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/af9604ebe275a9345d88c3ace0cf2a1341aa3f8ef49dd9fc11662132df8a/tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721", size = 130864, upload-time = "2025-09-25T17:37:57.453Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6e/e64621037357acb83d912276ffd30a859ef117f9c680f2e3cb955f47c680/tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f", size = 117470, upload-time = "2025-09-25T17:37:58.431Z" }, +] + +[[package]] +name = "tree-sitter-bash" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/0e/f0108be910f1eef6499eabce517e79fe3b12057280ed398da67ce2426cba/tree_sitter_bash-0.25.1.tar.gz", hash = "sha256:bfc0bdaa77bc1e86e3c6652e5a6e140c40c0a16b84185c2b63ad7cd809b88f14", size = 419703, upload-time = "2025-12-02T17:01:08.849Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/8e/37e7364d9c9c58da89e05c510671d8c45818afd7b31c6939ab72f8dc6c04/tree_sitter_bash-0.25.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0e6235f59e366d220dde7d830196bed597d01e853e44d8ccd1a82c5dd2500acf", size = 194160, upload-time = "2025-12-02T17:00:59.047Z" }, + { url = "https://files.pythonhosted.org/packages/23/bb/2d2cfbb1f89aaeb1ec892624f069d92d058d06bb66f16b9ec9fb5873ab60/tree_sitter_bash-0.25.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f4a34a6504c7c5b2a9b8c5c4065531dea19ca2c35026e706cf2eeeebe2c92512", size = 202659, upload-time = "2025-12-02T17:01:00.275Z" }, + { url = "https://files.pythonhosted.org/packages/25/f0/1bb25519be27460255d3899db677313cfa1e6306988fbf456a3d7e211bbb/tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e76c4cfb20b076552406782b7f8c2a3946835993df0a44df006de54b7030c7dc", size = 230596, upload-time = "2025-12-02T17:01:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/d7/22/9f70bc3d3b942ab9fc0f89c1dc9e087519a3a94f64ae6b7377aae3a7a0f0/tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f484c4bb8796cde7a87ca351e6116f09653edac0eb3c6d238566359dd28b117", size = 231981, upload-time = "2025-12-02T17:01:02.859Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c3/f1540e42cd41b323c6821e45e52e1aed6ed386209aad52db996f05703963/tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5e76af6df46d958c7f5b6d5884c9743218e3902a00ccb493ec92728b1084430b", size = 228364, upload-time = "2025-12-02T17:01:03.997Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/c3050a6277dfcac8c480f514dc4fe49f3f65f0eac68b4702cbaca2584e85/tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a3332d71c7b7d5f78259b19d02d0ea111fcb82b72712ee4a93aaa5b226d3f0a8", size = 230074, upload-time = "2025-12-02T17:01:05.05Z" }, + { url = "https://files.pythonhosted.org/packages/71/0f/203fe6b27211387f4b9ba8c4a321567ca4ded2624dae6ccdbd2b6e940e17/tree_sitter_bash-0.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:52a6802d9218f86278aa3e8b459c3abdad67eed0fde1f9f13aca5b6c634217a6", size = 195574, upload-time = "2025-12-02T17:01:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/47/75/4ca1a9fabd8fb5aea78cea70f7837ce4dbf2afae115f62051e5fa99cba1c/tree_sitter_bash-0.25.1-cp310-abi3-win_arm64.whl", hash = "sha256:59115057ec2bae319e8082ff29559861045002964c3431ccb0fc92aa4bc9bccb", size = 191196, upload-time = "2025-12-02T17:01:07.486Z" }, +] + +[[package]] +name = "tree-sitter-css" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/37/7d60171240d4c5ba330f05b725dfb5e5fd5b7cbe0aa98ef9e77f77f868f5/tree_sitter_css-0.25.0.tar.gz", hash = "sha256:2fc996bf05b04e06061e88ee4c60837783dc4e62a695205acbc262ee30454138", size = 43232, upload-time = "2025-09-28T11:37:13.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/a9/69e556f15ca774638bd79005369213dfbd41995bf032ce81cf3ffe086b8a/tree_sitter_css-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ddce6f84eeb0bb2877b4587b07bffb0753040c44d811ed9ab2af978c313beda8", size = 29933, upload-time = "2025-09-28T11:37:07.703Z" }, + { url = "https://files.pythonhosted.org/packages/4d/28/ebcbcbba812d3e407f2f393747330eb8843e0c69d159024e33460b622aab/tree_sitter_css-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:5a2a9c875037ef5f9da57697fb8075086476d42a49d25a88dcca60dfc09bd092", size = 31097, upload-time = "2025-09-28T11:37:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/86/a2/6f9658c723f3a857367c198bd4f50d854aa9468783b418407492c9634a44/tree_sitter_css-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4f5e1135bfd01bce24e2fc7bca1381f52bdd6c6282ee28f7aa77185340bcd135", size = 41713, upload-time = "2025-09-28T11:37:09.101Z" }, + { url = "https://files.pythonhosted.org/packages/85/bb/f74eea6839cb1ff6b5851c6ed33b18e65309eb347bbbe027c93e70e6c691/tree_sitter_css-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b6d0084536828c733a66524a43c9df89f335971d5b1b973e9d1c42ba9dd426b", size = 42312, upload-time = "2025-09-28T11:37:09.757Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fd/031ef1a5938441c98342faf70bb30998683b2130d4b55c282d76b2083f4a/tree_sitter_css-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8a83825daf538656cb88f4f7a0dd9963e3f204e83e7f8d92131f17e5bd712a77", size = 41585, upload-time = "2025-09-28T11:37:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/96/74/9f269bb3644a0511c1c263135e32d38a7f2af39cbba24d59a1633a5ebbc1/tree_sitter_css-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b486c097d250a598fba5f1f46f62697c7f4428252c8bdaad696a907ee913421d", size = 41490, upload-time = "2025-09-28T11:37:11.134Z" }, + { url = "https://files.pythonhosted.org/packages/04/9f/d4f1d3164b692b97266274dad6437586e0614f75080b7795fc7bfa5bf8ff/tree_sitter_css-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:fe319e4ad1b8327afbd9758b3ae22b09226d6c28dc9b022bcadabdaf6ea3716c", size = 32416, upload-time = "2025-09-28T11:37:11.808Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/fa62d70cb324788bcced741b5e19864ccf4c51ca31766a9f56a6b46a5cf6/tree_sitter_css-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:4fc2c82645cd593f1c695b4d6b678d71e633212ca030f26dedee4f92434bfe21", size = 31057, upload-time = "2025-09-28T11:37:12.734Z" }, +] + +[[package]] +name = "tree-sitter-go" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/05/727308adbbc79bcb1c92fc0ea10556a735f9d0f0a5435a18f59d40f7fd77/tree_sitter_go-0.25.0.tar.gz", hash = "sha256:a7466e9b8d94dda94cae8d91629f26edb2d26166fd454d4831c3bf6dfa2e8d68", size = 93890, upload-time = "2025-08-29T06:20:25.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/aa/0984707acc2b9bb461fe4a41e7e0fc5b2b1e245c32820f0c83b3c602957c/tree_sitter_go-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b852993063a3429a443e7bd0aa376dd7dd329d595819fabf56ac4cf9d7257b54", size = 47117, upload-time = "2025-08-29T06:20:14.286Z" }, + { url = "https://files.pythonhosted.org/packages/32/16/dd4cb124b35e99239ab3624225da07d4cb8da4d8564ed81d03fcb3a6ba9f/tree_sitter_go-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:503b81a2b4c31e302869a1de3a352ad0912ccab3df9ac9950197b0a9ceeabd8f", size = 48674, upload-time = "2025-08-29T06:20:17.557Z" }, + { url = "https://files.pythonhosted.org/packages/86/fb/b30d63a08044115d8b8bd196c6c2ab4325fb8db5757249a4ef0563966e2e/tree_sitter_go-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04b3b3cb4aff18e74e28d49b716c6f24cb71ddfdd66768987e26e4d0fa812f74", size = 66418, upload-time = "2025-08-29T06:20:18.345Z" }, + { url = "https://files.pythonhosted.org/packages/26/21/d3d88a30ad007419b2c97b3baeeef7431407faf9f686195b6f1cad0aedf9/tree_sitter_go-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:148255aca2f54b90d48c48a9dbb4c7faad6cad310a980b2c5a5a9822057ed145", size = 72006, upload-time = "2025-08-29T06:20:19.14Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d0/0dd6442353ced8a88bbda9e546f4ea29e381b59b5a40b122e5abb586bb6c/tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4d338116cdf8a6c6ff990d2441929b41323ef17c710407abe0993c13417d6aad", size = 70603, upload-time = "2025-08-29T06:20:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/01/e2/ee5e09f63504fc286539535d374d2eaa0e7d489b80f8f744bb3962aff22a/tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5608e089d2a29fa8d2b327abeb2ad1cdb8e223c440a6b0ceab0d3fa80bdeebae", size = 66088, upload-time = "2025-08-29T06:20:22.336Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b6/d9142583374720e79aca9ccb394b3795149a54c012e1dfd80738df2d984e/tree_sitter_go-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:30d4ada57a223dfc2c32d942f44d284d40f3d1215ddcf108f96807fd36d53022", size = 48152, upload-time = "2025-08-29T06:20:23.089Z" }, + { url = "https://files.pythonhosted.org/packages/9e/00/9a2638e7339236f5b01622952a4d71c1474dd3783d1982a89555fc1f03b1/tree_sitter_go-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:d5d62362059bf79997340773d47cc7e7e002883b527a05cca829c46e40b70ded", size = 46752, upload-time = "2025-08-29T06:20:24.235Z" }, +] + +[[package]] +name = "tree-sitter-html" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/06/ad1c53c79da15bef85939aa022d72301e12a9773e9bb9a5e6a6f65b7753a/tree_sitter_html-0.23.2.tar.gz", hash = "sha256:bc9922defe23144d9146bc1509fcd00d361bf6b3303f9effee6532c6a0296961", size = 13977, upload-time = "2024-11-11T05:58:07.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/27/b846852b567601c4df765bcb4636085a3260e9f03ae21e0ef2e7c7f957fc/tree_sitter_html-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e1641d5edf5568a246c6c47b947ed524b5bf944664e6473b21d4ae568e28ee9", size = 14787, upload-time = "2024-11-11T05:57:58.684Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/827c315deb156bb8cac541da800c4bd62878f50a28b7498fbb722bddd225/tree_sitter_html-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:3d0a83dd6cd1c7d4bcf6287b5145c92140f0194f8516f329ae8b9e952fbfa8ff", size = 15232, upload-time = "2024-11-11T05:58:00.139Z" }, + { url = "https://files.pythonhosted.org/packages/91/cb/2028fe446d0e18edf3737d91edcb6430f2c97f2296b8cd760702dfa13d90/tree_sitter_html-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b3775732fffc0abd275a419ef018fd4c1ad4044b2a2e422f3378d93c30eded", size = 39109, upload-time = "2024-11-11T05:58:00.986Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/b24f5e66be51447cf7e9bcce3d9440a6b4f17021da85779a51566646a7c7/tree_sitter_html-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bdaa7ac5030d416aea0c512d4810ef847bbbd62d61e3d213f370b64ce147293", size = 39630, upload-time = "2024-11-11T05:58:02.424Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d5/31b46cb362ad9679af21ff8b75d846fb7522ecf949beea4fddc86e97815d/tree_sitter_html-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d2e9631b66041a4fd792d7f79a0c4128adb3bfc71f3dcb7e1a3eab5dbee77d67", size = 37440, upload-time = "2024-11-11T05:58:03.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/03910b7c037105f33166439f0518dd0aa4f1b7ef8c9d7367c6e9cc6b5681/tree_sitter_html-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:85095f49f9e57f0ac9087a3e830783352c8447fdda55b1c1139aa47e5eaa0e21", size = 17765, upload-time = "2024-11-11T05:58:05.163Z" }, + { url = "https://files.pythonhosted.org/packages/20/32/63761055b03c69202a0e67b6e9a5cb3578da23aeefb62ee3e7ec2c1b0ff2/tree_sitter_html-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:0f65ed9e877144d0f04ade5644e5b0e88bf98a9e60bce65235c99905623e2f1a", size = 15576, upload-time = "2024-11-11T05:58:06.577Z" }, +] + +[[package]] +name = "tree-sitter-java" +version = "0.23.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/dc/eb9c8f96304e5d8ae1663126d89967a622a80937ad2909903569ccb7ec8f/tree_sitter_java-0.23.5.tar.gz", hash = "sha256:f5cd57b8f1270a7f0438878750d02ccc79421d45cca65ff284f1527e9ef02e38", size = 138121, upload-time = "2024-12-21T18:24:26.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/21/b3399780b440e1567a11d384d0ebb1aea9b642d0d98becf30fa55c0e3a3b/tree_sitter_java-0.23.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:355ce0308672d6f7013ec913dee4a0613666f4cda9044a7824240d17f38209df", size = 58926, upload-time = "2024-12-21T18:24:12.53Z" }, + { url = "https://files.pythonhosted.org/packages/57/ef/6406b444e2a93bc72a04e802f4107e9ecf04b8de4a5528830726d210599c/tree_sitter_java-0.23.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:24acd59c4720dedad80d548fe4237e43ef2b7a4e94c8549b0ca6e4c4d7bf6e69", size = 62288, upload-time = "2024-12-21T18:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6c/74b1c150d4f69c291ab0b78d5dd1b59712559bbe7e7daf6d8466d483463f/tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9401e7271f0b333df39fc8a8336a0caf1b891d9a2b89ddee99fae66b794fc5b7", size = 85533, upload-time = "2024-12-21T18:24:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/29/09/e0d08f5c212062fd046db35c1015a2621c2631bc8b4aae5740d7adb276ad/tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370b204b9500b847f6d0c5ad584045831cee69e9a3e4d878535d39e4a7e4c4f1", size = 84033, upload-time = "2024-12-21T18:24:18.758Z" }, + { url = "https://files.pythonhosted.org/packages/43/56/7d06b23ddd09bde816a131aa504ee11a1bbe87c6b62ab9b2ed23849a3382/tree_sitter_java-0.23.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aae84449e330363b55b14a2af0585e4e0dae75eb64ea509b7e5b0e1de536846a", size = 82564, upload-time = "2024-12-21T18:24:20.493Z" }, + { url = "https://files.pythonhosted.org/packages/da/d6/0528c7e1e88a18221dbd8ccee3825bf274b1fa300f745fd74eb343878043/tree_sitter_java-0.23.5-cp39-abi3-win_amd64.whl", hash = "sha256:1ee45e790f8d31d416bc84a09dac2e2c6bc343e89b8a2e1d550513498eedfde7", size = 60650, upload-time = "2024-12-21T18:24:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/72/57/5bab54d23179350356515526fff3cc0f3ac23bfbc1a1d518a15978d4880e/tree_sitter_java-0.23.5-cp39-abi3-win_arm64.whl", hash = "sha256:402efe136104c5603b429dc26c7e75ae14faaca54cfd319ecc41c8f2534750f4", size = 59059, upload-time = "2024-12-21T18:24:24.934Z" }, +] + +[[package]] +name = "tree-sitter-javascript" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/e0/e63103c72a9d3dfd89a31e02e660263ad84b7438e5f44ee82e443e65bbde/tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38", size = 132338, upload-time = "2025-09-01T07:13:44.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/df/5106ac250cd03661ebc3cc75da6b3d9f6800a3606393a0122eca58038104/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc", size = 64052, upload-time = "2025-09-01T07:13:36.865Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/6b4b2bc90d8ab3955856ce852cc9d1e82c81d7ab9646385f0e75ffd5b5d3/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1", size = 66440, upload-time = "2025-09-01T07:13:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c4/7da74ecdcd8a398f88bd003a87c65403b5fe0e958cdd43fbd5fd4a398fcf/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75", size = 99728, upload-time = "2025-09-01T07:13:38.755Z" }, + { url = "https://files.pythonhosted.org/packages/96/c8/97da3af4796495e46421e9344738addb3602fa6426ea695be3fcbadbee37/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b", size = 106072, upload-time = "2025-09-01T07:13:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/13/be/c964e8130be08cc9bd6627d845f0e4460945b158429d39510953bbcb8fcc/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc", size = 104388, upload-time = "2025-09-01T07:13:40.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/89/9b773dee0f8961d1bb8d7baf0a204ab587618df19897c1ef260916f318ec/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54", size = 98377, upload-time = "2025-09-01T07:13:41.838Z" }, + { url = "https://files.pythonhosted.org/packages/3b/dc/d90cb1790f8cec9b4878d278ad9faf7c8f893189ce0f855304fd704fc274/tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c", size = 62975, upload-time = "2025-09-01T07:13:42.828Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1f/f9eba1038b7d4394410f3c0a6ec2122b590cd7acb03f196e52fa57ebbe72/tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b", size = 61668, upload-time = "2025-09-01T07:13:43.803Z" }, +] + +[[package]] +name = "tree-sitter-json" +version = "0.24.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/29/e92df6dca3a6b2ab1c179978be398059817e1173fbacd47e832aaff3446b/tree_sitter_json-0.24.8.tar.gz", hash = "sha256:ca8486e52e2d261819311d35cf98656123d59008c3b7dcf91e61d2c0c6f3120e", size = 8155, upload-time = "2024-11-11T06:05:00.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/41/84866232980fb3cf0cff46f5af2dbb9bfa3324b32614c6a9af3d08926b72/tree_sitter_json-0.24.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:59ac06c6db1877d0e2076bce54a5fddcdd2fc38ca778905662e80fa9ffcea2ab", size = 8718, upload-time = "2024-11-11T06:04:49.779Z" }, + { url = "https://files.pythonhosted.org/packages/5c/31/102c15948d97b135611d6a995c97a3933c0e9745f25737723977f58e142c/tree_sitter_json-0.24.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:62b4c45b561db31436a81a3f037f71ec29049f4fc9bf5269b6ec3ebaaa35a1cd", size = 9163, upload-time = "2024-11-11T06:04:51.275Z" }, + { url = "https://files.pythonhosted.org/packages/28/64/aa44ea2f3d2e76ec086ce83902eb26b2ed0a92d3fd5e2714c9cb007e90d1/tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8627f7d375fda9fc193ebee368c453f374f65c2f25c58b6fea4e6b49a7fccbc", size = 17726, upload-time = "2024-11-11T06:04:52.732Z" }, + { url = "https://files.pythonhosted.org/packages/77/08/10001992526670e0d6f24c571b179f0ece90e5e014a4b98a3ce076884f32/tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85cca779872f7278f3a74eb38533d34b9c4de4fd548615e3361fa64fe350ad0a", size = 17236, upload-time = "2024-11-11T06:04:54.189Z" }, + { url = "https://files.pythonhosted.org/packages/92/64/908e9e0bd84fe3c81c564115d3bbe0e49b0e152784bbaf153d749d00bbe6/tree_sitter_json-0.24.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:deeb45850dcc52990fbb52c80196492a099e3fa3512d928a390a91cf061068cc", size = 16071, upload-time = "2024-11-11T06:04:55.628Z" }, + { url = "https://files.pythonhosted.org/packages/53/df/31daab1eedb445bef208a04fc35428de3afe2b37075fec84d7737e1c69de/tree_sitter_json-0.24.8-cp39-abi3-win_amd64.whl", hash = "sha256:e4849a03cd7197267b2688a4506a90a13568a8e0e8588080bd0212fcb38974e3", size = 11457, upload-time = "2024-11-11T06:04:57.698Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3d/902d2f3125b6b90cebf404b63ca775bc6d82071ccc76c0d10fabfeb2febe/tree_sitter_json-0.24.8-cp39-abi3-win_arm64.whl", hash = "sha256:591e0096c882d12668b88f30d3ca6f85b9db3406910eaaab6afb6b17d65367dd", size = 10174, upload-time = "2024-11-11T06:04:59.309Z" }, +] + +[[package]] +name = "tree-sitter-markdown" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/87/8f705d8f99337c8a691bcc8c22d89ddd323eb2b860a78ae2e894b9f7ade1/tree_sitter_markdown-0.5.1.tar.gz", hash = "sha256:6c69d7270a7e09be8988ced44584c09a6a4f541cea0dc394dd1c1a5ac3b5601d", size = 250138, upload-time = "2025-09-16T17:12:11.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/73/b5f88217a526f61080ddd71d554cff6a01ea23fffa584ad9de41ee8d1fe5/tree_sitter_markdown-0.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f00ce3f48f127377983859fcb93caf0693cbc7970f8c41f1e2bd21e4d56bdfd8", size = 139706, upload-time = "2025-09-16T17:12:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9b/65eb5e6a8d7791174644854437d35849d9b4e4ed034d54d2c78810eaf1a6/tree_sitter_markdown-0.5.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1ec4cc5d7b0d188bad22247501ab13663bb1bf1a60c2c020a22877fabce8daa9", size = 147540, upload-time = "2025-09-16T17:12:04.955Z" }, + { url = "https://files.pythonhosted.org/packages/24/d5/4152d00829c8643243f65b67a5485248661824f15e1868e14e54f03c2069/tree_sitter_markdown-0.5.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727242a70c46222092eba86c102301646f21ba32aee221f4b1f70e2020755e81", size = 187851, upload-time = "2025-09-16T17:12:05.813Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c1/994001c5a51d09e9da7236e01a855d3d49437a47fa8669f1d5e9ed60e64f/tree_sitter_markdown-0.5.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0b2fde19e692bb90e300d9788887528c624b659c794de6337f8193396de4399", size = 187563, upload-time = "2025-09-16T17:12:06.929Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d1/1f2ba1ae11568639f133c45c7a697e4e9277d6cc26a66c0caee62c11d1c2/tree_sitter_markdown-0.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:13da82db04cec7910b6afd4a67d02da9ef402df8d56fc6ed85e00584af1730ee", size = 185478, upload-time = "2025-09-16T17:12:08.126Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c8/8218482d56b78755cdc20816a28754145cb1767e1e7e0ddde5988547ab86/tree_sitter_markdown-0.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8a8a04a5d942c177cc590ec40074fcf3658f3a7c0a3388a8575990003665d8c", size = 184922, upload-time = "2025-09-16T17:12:08.937Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ca/423600960b91c3aba6f2202ad4c430b5401e652d51a73a59769375c2b4ea/tree_sitter_markdown-0.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:b1b0e4cbcf5a7b85005f1e9266fc2ed9b649b41a6048f3b1abae3612368d97a6", size = 142519, upload-time = "2025-09-16T17:12:10.027Z" }, + { url = "https://files.pythonhosted.org/packages/93/f5/327dd7fa42ae39796a8853685c40a8ac968585260094c581047270cbc851/tree_sitter_markdown-0.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:2296ef53a757d8f5b848616706d0518e04d487bc7748bd05755d4a3a65711542", size = 137166, upload-time = "2025-09-16T17:12:10.858Z" }, +] + +[[package]] +name = "tree-sitter-python" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845, upload-time = "2025-09-11T06:47:58.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790, upload-time = "2025-09-11T06:47:47.652Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691, upload-time = "2025-09-11T06:47:49.038Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133, upload-time = "2025-09-11T06:47:50.499Z" }, + { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603, upload-time = "2025-09-11T06:47:51.985Z" }, + { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998, upload-time = "2025-09-11T06:47:53.046Z" }, + { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268, upload-time = "2025-09-11T06:47:54.388Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073, upload-time = "2025-09-11T06:47:55.773Z" }, + { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169, upload-time = "2025-09-11T06:47:56.747Z" }, +] + +[[package]] +name = "tree-sitter-regex" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/92/1767b833518d731b97c07cf616ea15495dcc0af584aa0381657be4ec446d/tree_sitter_regex-0.25.0.tar.gz", hash = "sha256:5d29111b3f27d4afb31496476d392d1f562fe0bfe954e8968f1d8683424fc331", size = 22156, upload-time = "2025-09-13T05:00:18.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/b4/12e9ba02bab4ce13d1875f6585c3f2a5816233104d1507ea118950a4f7eb/tree_sitter_regex-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3fa11bbd76b29ac8ca2dbf85ad082f9b18ae6352251d805eb2d4191e1706a9d5", size = 13267, upload-time = "2025-09-13T05:00:10.847Z" }, + { url = "https://files.pythonhosted.org/packages/71/06/6b4f995f61952572a94bcfce12d43fc580226551fab9dd0aac4e94465f38/tree_sitter_regex-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:df5713649b89c5758649398053c306c41565f22a6f267cb5ec25596504bcf012", size = 13646, upload-time = "2025-09-13T05:00:12.149Z" }, + { url = "https://files.pythonhosted.org/packages/43/61/d94d889ee415805e5d64fc5163e7e2996975bb2c40d13f547efae3e7e37d/tree_sitter_regex-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cdd92400fd9d8229e584c55e12410251561f0d47eea49db17805e2f64a8b2490", size = 24691, upload-time = "2025-09-13T05:00:13.037Z" }, + { url = "https://files.pythonhosted.org/packages/00/a8/09dd698a9ac2b3d3139a936742b41ec1263f0b86d32ad68f4695871c8860/tree_sitter_regex-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cceab1c14deeec9c5899babcb2b7942f0607b4355e66eab4083514f644f1bd52", size = 26741, upload-time = "2025-09-13T05:00:14.182Z" }, + { url = "https://files.pythonhosted.org/packages/d7/bf/985e226c9a9f5ae895ff1a2cbc69531589a7d74acac49b2710ec89d53d80/tree_sitter_regex-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:253436be178150ca4a0603720e0c246e08b5bdd2dc6df313667d97e6c0fce846", size = 25758, upload-time = "2025-09-13T05:00:14.994Z" }, + { url = "https://files.pythonhosted.org/packages/b8/89/c6a6817e94a7deb61770a21e590a46791778ceed053ba4afbfb095488a23/tree_sitter_regex-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:883eacc46fd7eaffc328efd5865f1fe8825711892d3a89fccc2c414b061e806d", size = 24575, upload-time = "2025-09-13T05:00:16.081Z" }, + { url = "https://files.pythonhosted.org/packages/d0/5e/04e87eb155875f27355703ac7ab703090e30ad9aac6e003ef5c40820ee98/tree_sitter_regex-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:f0f2ebf9a6bb5d0d0da2a8ac51d7e5a985b87cdb24d86db5ddc6a58baf115d5d", size = 15684, upload-time = "2025-09-13T05:00:16.865Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d3/1f37c79dc18cc3c7521fdb51b614d29a36628d2afdc2cac2680967e703a6/tree_sitter_regex-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:d5a36150daa452f8aec1c2d6d1f2d26255dc05d1490f9618b14c12a6a648cda4", size = 14525, upload-time = "2025-09-13T05:00:17.673Z" }, +] + +[[package]] +name = "tree-sitter-rust" +version = "0.24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/87/75cbd22b927267d310f76cca1ab3c1d9d41035dfa3eb9cc95f96ee199440/tree_sitter_rust-0.24.2.tar.gz", hash = "sha256:54fb02a5911e345308b405174465112479f56dc39e3f1e7744d7568595f00db9", size = 339341, upload-time = "2026-03-27T21:08:55.629Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/24/2b2d33af5e27c84a4fde4e8cd2594bb4ab1e1cf48756a9f40dadc84956cc/tree_sitter_rust-0.24.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3620cfd12340efa43082d45df76349ff511893a9c361da2f8d6d51e307020a59", size = 129507, upload-time = "2026-03-27T21:08:47.585Z" }, + { url = "https://files.pythonhosted.org/packages/78/2a/cf39f881a545360b5a86bb1accba1f4acc713daab01fb9edd35b6e84f473/tree_sitter_rust-0.24.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01a46622735498493f29f3e628a90de95c96a07bfbeb88996243eb986b1cee36", size = 136812, upload-time = "2026-03-27T21:08:48.761Z" }, + { url = "https://files.pythonhosted.org/packages/ca/45/a051bbd3045a61182dde25b93ae9a33d2677c935b16952283e12eaf46051/tree_sitter_rust-0.24.2-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e033c5a93b57c88e0a835880de39fc802909ff69f57aaff6000211c196ea5190", size = 164706, upload-time = "2026-03-27T21:08:49.605Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f6/a5a146df5c0a5daea3ffcd5d7245775fe7f084357770d5a313dd6245ae78/tree_sitter_rust-0.24.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d76d1208c3638b871236090759dfc13d478921320653a6c9da5336e7c58f65a", size = 170310, upload-time = "2026-03-27T21:08:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/95/a8/f85b1ca75e01361ca5f92d226593ca4857cea49551b9f6c8fa6fc08ea917/tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87930163a462408c49ab62c667e74029bc26b4cc7123dd1bdc7352215786c64a", size = 168668, upload-time = "2026-03-27T21:08:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e1/3519f866a4679ca36acd9f5a06a779ecb8a92b18887c5546458d521df557/tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da2b86099028fd42c6cd32878b7b16b01f8aac0f7b0e98742b7fa6bc3cf09b89", size = 162403, upload-time = "2026-03-27T21:08:52.588Z" }, + { url = "https://files.pythonhosted.org/packages/34/71/7ef609894dbfe5699eb16f7471f9b8af1d958d8ba3e29c238d7607e8cb47/tree_sitter_rust-0.24.2-cp39-abi3-win_amd64.whl", hash = "sha256:4529c125d928882ddfb879fdc6bc0704913261ecc078b6fa7902559e0daf200d", size = 129422, upload-time = "2026-03-27T21:08:54.031Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d8/050a781172745bc345f98abb7c56e72022ea0790f8e793de981c83c2ef15/tree_sitter_rust-0.24.2-cp39-abi3-win_arm64.whl", hash = "sha256:66ba90f61bd54f4c4f5d30434957daf64507c16b0313df76becb37d63f70a227", size = 128245, upload-time = "2026-03-27T21:08:54.803Z" }, +] + +[[package]] +name = "tree-sitter-sql" +version = "0.3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5c/3d10387f779f36835486167253682f61d5f4fd8336b7001da1ac7d78f31c/tree_sitter_sql-0.3.11.tar.gz", hash = "sha256:700b93be2174c3c83d174ec3e10b682f72a4fb451f0076c7ce5012f1d5a76cbc", size = 834454, upload-time = "2025-10-01T13:44:15.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/68/bb80073915dfe1b38935451bc0d65528666c126b2d5878e7140ef9bf9f8a/tree_sitter_sql-0.3.11-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cf1b0c401756940bf47544ad7c4cc97373fc0dac118f821820953e7015a115e3", size = 322035, upload-time = "2025-10-01T13:44:07.497Z" }, + { url = "https://files.pythonhosted.org/packages/05/45/b2bd5f9919ea15c4ae90a156999101ebd4caa4036babe54efaf9d3e77d55/tree_sitter_sql-0.3.11-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a33cd6880ab2debef036f80365c32becb740ec79946805598488732b6c515fff", size = 341635, upload-time = "2025-10-01T13:44:08.961Z" }, + { url = "https://files.pythonhosted.org/packages/8e/96/7cee5661aa897e5d1a67499944ea5cf8a148953c1dc07a3059a50db8cb56/tree_sitter_sql-0.3.11-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:344e99b59c8c8d72f7154041e9d054400f4a3fccc16c2c96ac106dde0e7f8d0c", size = 381217, upload-time = "2025-10-01T13:44:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c1/eec7c09a9c94436ea4c56d096feba815e42b209b3d41a17532f99ecf0c67/tree_sitter_sql-0.3.11-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5128b12f71ac0f5ebcc607f67a62cdc56a187c1a5ba7553feeb9c5f6f9bc3c72", size = 380606, upload-time = "2025-10-01T13:44:11.135Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/06e9598799bd119e56f6e431d42c2f3a5c6dee858a5b6ad7633cc4d670aa/tree_sitter_sql-0.3.11-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:03cc164fcf7b1f711e7d939aeb4d1f62c76f4162e081c70b860b4fcd91806a38", size = 380862, upload-time = "2025-10-01T13:44:12.072Z" }, + { url = "https://files.pythonhosted.org/packages/52/e9/a7afd7f68ce165c040ce50e67bb05553784a8e17f37e057405d693fc869d/tree_sitter_sql-0.3.11-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0e22ea8de690dd9960d8c0c36c4cd25417b084e1e29c91ac0235fbdb3abb4664", size = 379447, upload-time = "2025-10-01T13:44:13.062Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b3/57ff42dadd33c06fabe6c725de50e1625e1060f1571cc21a9260febadc1f/tree_sitter_sql-0.3.11-cp310-abi3-win_amd64.whl", hash = "sha256:c57b877702d218c0856592d33320c02b2dc8411d8820b3bf7b81be86c54fa0bb", size = 343550, upload-time = "2025-10-01T13:44:13.988Z" }, + { url = "https://files.pythonhosted.org/packages/77/60/f10b8551f435d57a4748820ee30e66df2682820b2972375c2b89d2e5fb10/tree_sitter_sql-0.3.11-cp310-abi3-win_arm64.whl", hash = "sha256:8a1e42f0a2c9b01b23074708ecf5b8d21b9a0440e3dff279d8cf466cdf1a877e", size = 333547, upload-time = "2025-10-01T13:44:14.893Z" }, +] + +[[package]] +name = "tree-sitter-toml" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/b9/03ee757ac375e77186ea112c14fcf31e0ca70b27b6388d93dcceef61f029/tree_sitter_toml-0.7.0.tar.gz", hash = "sha256:29e257612fa8f0c1fcbc4e7e08ddc561169f1725265302e64d81086354144a70", size = 16803, upload-time = "2024-12-03T05:03:46.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/4d/1e00a5cd8dba09e340b25aa60a3eaeae584ff5bc5d93b0777169d6741ee5/tree_sitter_toml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b9ae5c3e7c5b6bb05299dd73452ceafa7fa0687d5af3012332afa7757653b676", size = 14755, upload-time = "2024-12-03T05:03:39.973Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/ac8a20805339105fe0bbb6beaa99dbbd1159647760ddd786142364e0b7f2/tree_sitter_toml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:18be09538e9775cddc0290392c4e2739de2201260af361473ca60b5c21f7bd22", size = 15201, upload-time = "2024-12-03T05:03:40.871Z" }, + { url = "https://files.pythonhosted.org/packages/36/cf/7bae8e20310e7cc763ae407599e6130b819f91ad5197e210a56f697f15d8/tree_sitter_toml-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a045e0acfcf91b7065066f7e51ea038ed7385c1e35e7e8fae18f252d3f8adb8c", size = 30855, upload-time = "2024-12-03T05:03:41.83Z" }, + { url = "https://files.pythonhosted.org/packages/7d/49/51f2fa25a3ff4d45af1be8cbf7a3d733fb6a390b2763cfa00892fffe90bf/tree_sitter_toml-0.7.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a2f8cf9d73f07b6628093b35e5c5fbac039247e32cb075eaa5289a5914e73af", size = 29741, upload-time = "2024-12-03T05:03:42.577Z" }, + { url = "https://files.pythonhosted.org/packages/4d/30/dd94ed1ab0bc3198e16ed2140a6f4d2474c1cd561d8c6847ab269af73654/tree_sitter_toml-0.7.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:860ffa4513b2dc3083d8e412bd815a350b0a9490624b37e7c8f6ed5c6f9ce63c", size = 30498, upload-time = "2024-12-03T05:03:43.447Z" }, + { url = "https://files.pythonhosted.org/packages/a2/dd/0681d43aa09dd161565858bcfdd4402c8d10259f142de734448f5ce17418/tree_sitter_toml-0.7.0-cp39-abi3-win_amd64.whl", hash = "sha256:2760a04f06937b01b1562a2135cd7e8207e399e73ef75bbebc77e37b1ad3b15d", size = 16756, upload-time = "2024-12-03T05:03:44.227Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/cce587001e620f1972e70aeabc1b38893a85681be9ec5a64e4be9ce17410/tree_sitter_toml-0.7.0-cp39-abi3-win_arm64.whl", hash = "sha256:fd00fd8a51c65aa19c40539431cb1773d87c30af5757b4041fa6c229058420b4", size = 15651, upload-time = "2024-12-03T05:03:45.261Z" }, +] + +[[package]] +name = "tree-sitter-xml" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/ba/77a92dbb4dfb374fb99863a07f938de7509ceeaa74139933ac2bd306eeb1/tree_sitter_xml-0.7.0.tar.gz", hash = "sha256:ab0ff396f20230ad8483d968151ce0c35abe193eb023b20fbd8b8ce4cf9e9f61", size = 54635, upload-time = "2024-11-13T17:27:01.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/1d/6b8974c493973c0c9df2bbf220a1f0a96fa785da81a5a13461faafd1441c/tree_sitter_xml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc3e516d4c1e0860fb22172c172148debb825ba638971bc48bad15b22e5b0bae", size = 35404, upload-time = "2024-11-13T17:26:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/75/f5/31013d04c4e3b9a55e90168cc222a601c84235ba4953a5a06b5cdf8353c4/tree_sitter_xml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0674fdf4cc386e4d323cb287d3b072663de0f20a9e9af5d5e09821aae56a9e5c", size = 35488, upload-time = "2024-11-13T17:26:53.526Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e6/e7493217f950a7c5969e3f3f057664142fa948abefd2dba5acea25719d55/tree_sitter_xml-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c0fe5f2d6cc09974c8375c8ea9b24909f493b5bf04aacdc4c694b5d2ae6b040", size = 74199, upload-time = "2024-11-13T17:26:55.069Z" }, + { url = "https://files.pythonhosted.org/packages/94/27/1dd6815592489de51fa7b5fffc1160cd385ade7fa06f07b998742ac18020/tree_sitter_xml-0.7.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd3209516a4d84dff90bc91d2ad2ce246de8504cede4358849687fa8e71536e7", size = 76244, upload-time = "2024-11-13T17:26:56.655Z" }, + { url = "https://files.pythonhosted.org/packages/20/10/2e4e84c50b2175cb53d255ef154aa893cb82cc9d035d7a1a73be9d2d2db4/tree_sitter_xml-0.7.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:87578e15fa55f44ecd9f331233b6f8a2cbde3546b354c830ecb862a632379455", size = 75112, upload-time = "2024-11-13T17:26:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/ae/91/77c348568bccb179eca21062c923f6f54026900b09fe0cf1aae89d78a0c8/tree_sitter_xml-0.7.0-cp39-abi3-win_amd64.whl", hash = "sha256:9ba2dafc6ce9feaf4ccc617d3aeea57f8e0ca05edad34953e788001ebff79133", size = 36558, upload-time = "2024-11-13T17:26:58.702Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/6b4de230770d7be87b2a415583121ac565ce1ff7d9a1ad7fec11f8e613fc/tree_sitter_xml-0.7.0-cp39-abi3-win_arm64.whl", hash = "sha256:fc759f710a8fd7a01c23e2d7cb013679199045bea3dc0e5151650a11322aaf40", size = 34610, upload-time = "2024-11-13T17:27:00.187Z" }, +] + +[[package]] +name = "tree-sitter-yaml" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/b6/941d356ac70c90b9d2927375259e3a4204f38f7499ec6e7e8a95b9664689/tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c", size = 84882, upload-time = "2025-10-07T14:40:36.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/29/c0b8dbff302c49ff4284666ffb6f2f21145006843bb4c3a9a85d0ec0b7ae/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f", size = 43898, upload-time = "2025-10-07T14:40:29.486Z" }, + { url = "https://files.pythonhosted.org/packages/18/0d/15a5add06b3932b5e4ce5f5e8e179197097decfe82a0ef000952c8b98216/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870", size = 44691, upload-time = "2025-10-07T14:40:30.369Z" }, + { url = "https://files.pythonhosted.org/packages/72/92/c4b896c90d08deb8308fadbad2210fdcc4c66c44ab4292eac4e80acb4b61/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41", size = 91430, upload-time = "2025-10-07T14:40:31.16Z" }, + { url = "https://files.pythonhosted.org/packages/89/59/61f1fed31eb6d46ff080b8c0d53658cf29e10263f41ef5fe34768908037a/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a", size = 92428, upload-time = "2025-10-07T14:40:31.954Z" }, + { url = "https://files.pythonhosted.org/packages/e3/62/a33a04d19b7f9a0ded780b9c9fcc6279e37c5d00b89b00425bb807a22cc2/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06", size = 90580, upload-time = "2025-10-07T14:40:33.037Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e7/9525defa7b30792623f56b1fba9bbba361752348875b165b8975b87398fd/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752", size = 90455, upload-time = "2025-10-07T14:40:33.778Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d6/8d1e1ace03db3b02e64e91daf21d1347941d1bbecc606a5473a1a605250d/tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186", size = 45514, upload-time = "2025-10-07T14:40:34.562Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c7/dcf3ea1c4f5da9b10353b9af4455d756c92d728a8f58f03c480d3ef0ead5/tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4", size = 44065, upload-time = "2025-10-07T14:40:35.337Z" }, +] + [[package]] name = "typing-extensions" version = "4.14.1"