Skip to main content

Clickable file links for Textual applications.

Project description

textual-filelink

CI PyPI Python Versions Downloads License

Clickable file links for Textual applications. Open files in your editor directly from your TUI.

Features

  • 🔗 Clickable file links that open in your preferred editor (VSCode, vim, nano, etc.)
  • ☑️ Toggle controls for selecting/deselecting files
  • Remove buttons for file management interfaces
  • 🎨 Status icons with unicode support for visual feedback (optionally clickable)
  • 🎯 Jump to specific line and column in your editor
  • 🔧 Customizable command builders for any editor
  • 🎭 Flexible layouts - show/hide controls as needed
  • 💬 Tooltips for toggle, status icon, and remove button

Installation

pip install textual-filelink

Or with PDM:

pdm add textual-filelink

Quick Start

Basic FileLink

from pathlib import Path
from textual.app import App, ComposeResult
from textual_filelink import FileLink

class MyApp(App):
    def compose(self) -> ComposeResult:
        yield FileLink(Path("README.md"), line=10, column=5)
    
    def on_file_link_clicked(self, event: FileLink.Clicked):
        self.notify(f"Opened {event.path.name} at line {event.line}")

if __name__ == "__main__":
    MyApp().run()

ToggleableFileLink with Status Icons

from textual_filelink import ToggleableFileLink

class MyApp(App):
    def compose(self) -> ComposeResult:
        yield ToggleableFileLink(
            Path("script.py"),
            initial_toggle=True,
            show_toggle=True,
            show_remove=True,
            status_icon="✓",
            status_icon_clickable=True,
            toggle_tooltip="Toggle selection",
            status_tooltip="File status",
            remove_tooltip="Remove file",
            line=42
        )
    
    def on_toggleable_file_link_toggled(self, event: ToggleableFileLink.Toggled):
        self.notify(f"{event.path.name} toggled: {event.is_toggled}")
    
    def on_toggleable_file_link_removed(self, event: ToggleableFileLink.Removed):
        self.notify(f"{event.path.name} removed")
    
    def on_toggleable_file_link_status_icon_clicked(self, event: ToggleableFileLink.StatusIconClicked):
        self.notify(f"Status icon '{event.icon}' clicked for {event.path.name}")

if __name__ == "__main__":
    MyApp().run()

FileLink API

Constructor

FileLink(
    path: Path | str,
    *,
    line: int | None = None,
    column: int | None = None,
    command_builder: Callable | None = None,
    name: str | None = None,
    id: str | None = None,
    classes: str | None = None,
)

Parameters:

  • path: Full path to the file
  • line: Optional line number to jump to
  • column: Optional column number to jump to
  • command_builder: Custom function to build the editor command

Properties

  • path: Path - The file path
  • line: int | None - The line number
  • column: int | None - The column number

Messages

FileLink.Clicked

Posted when the link is clicked.

Attributes:

  • path: Path
  • line: int | None
  • column: int | None

ToggleableFileLink API

Constructor

ToggleableFileLink(
    path: Path | str,
    *,
    initial_toggle: bool = False,
    show_toggle: bool = True,
    show_remove: bool = True,
    status_icon: str | None = None,
    status_icon_clickable: bool = False,
    line: int | None = None,
    column: int | None = None,
    command_builder: Callable | None = None,
    disable_on_untoggle: bool = False,
    toggle_tooltip: str | None = None,
    status_tooltip: str | None = None,
    remove_tooltip: str | None = None,
    name: str | None = None,
    id: str | None = None,
    classes: str | None = None,
)

Parameters:

  • path: Full path to the file
  • initial_toggle: Whether the item starts toggled (checked)
  • show_toggle: Whether to display the toggle control (☐/✓)
  • show_remove: Whether to display the remove button (×)
  • status_icon: Optional unicode icon to display before the filename
  • status_icon_clickable: Whether the status icon is clickable (posts StatusIconClicked message)
  • line: Optional line number to jump to
  • column: Optional column number to jump to
  • command_builder: Custom function to build the editor command
  • disable_on_untoggle: If True, dim/disable the link when untoggled
  • toggle_tooltip: Optional tooltip text for the toggle button
  • status_tooltip: Optional tooltip text for the status icon
  • remove_tooltip: Optional tooltip text for the remove button

Properties

  • path: Path - The file path
  • is_toggled: bool - Current toggle state
  • status_icon: str | None - Current status icon

Methods

set_status_icon(icon: str | None, tooltip: str | None = None)

Update the status icon and optionally its tooltip.

link.set_status_icon("⚠", tooltip="Warning: File modified")  # Warning
link.set_status_icon("✓", tooltip="Validated")  # Success
link.set_status_icon("⏳", tooltip="Processing...")  # In progress
link.set_status_icon(None) # Hide icon

set_toggle_tooltip(tooltip: str | None)

Update the toggle button tooltip.

set_remove_tooltip(tooltip: str | None)

Update the remove button tooltip.

Messages

ToggleableFileLink.Toggled

Posted when the toggle state changes.

Attributes:

  • path: Path
  • is_toggled: bool

ToggleableFileLink.Removed

Posted when the remove button is clicked.

Attributes:

  • path: Path

ToggleableFileLink.StatusIconClicked

Posted when the status icon is clicked (if status_icon_clickable=True).

Attributes:

  • path: Path
  • icon: str

Custom Editor Commands

Using Built-in Command Builders

from textual_filelink import FileLink

# Set default for all FileLink instances
FileLink.default_command_builder = FileLink.vim_command

# Or per instance
link = FileLink(path, command_builder=FileLink.nano_command)

Available builders:

  • FileLink.vscode_command - VSCode (default)
  • FileLink.vim_command - Vim
  • FileLink.nano_command - Nano
  • FileLink.eclipse_command - Eclipse
  • FileLink.copy_path_command - Copy path to clipboard

Custom Command Builder

def my_editor_command(path: Path, line: int | None, column: int | None) -> list[str]:
    """Build command for my custom editor."""
    cmd = ["myeditor"]
    if line:
        cmd.extend(["--line", str(line)])
    if column:
        cmd.extend(["--column", str(column)])
    cmd.append(str(path))
    return cmd

link = FileLink(path, command_builder=my_editor_command)

Layout Configurations

Toggle Only

ToggleableFileLink(path, show_toggle=True, show_remove=False)

Display: ☐ filename.txt

Remove Only

ToggleableFileLink(path, show_toggle=False, show_remove=True)

Display: filename.txt ×

Both Controls

ToggleableFileLink(path, show_toggle=True, show_remove=True)

Display: ☐ filename.txt ×

Neither (Plain Link with Status)

ToggleableFileLink(path, show_toggle=False, show_remove=False, status_icon="📄")

Display: 📄 filename.txt

Status Icons

Common unicode icons you can use:

# Status indicators
"✓"  # Success/Complete
"⚠"  # Warning
"✗"  # Error/Failed
"⏳"  # In progress
"🔒"  # Locked
"📝"  # Modified
"➕"  # New/Added
"➖"  # Deleted
"🔄"  # Syncing

# File types
"📄"  # Document
"📁"  # Folder
"🐍"  # Python file
"📊"  # Data file
"⚙️"  # Config file

Complete Example

from pathlib import Path
from textual.app import App, ComposeResult
from textual.containers import Vertical, Horizontal
from textual.widgets import Header, Footer, Static
from textual_filelink import ToggleableFileLink

class FileManagerApp(App):
    CSS = """
    Screen {
        align: center middle;
    }
    Vertical {
        width: 80;
        height: auto;
        border: solid green;
        padding: 1;
    }
    Static {
        width: 100%;
        content-align: center middle;
        text-style: bold;
    }
    """
    
    def __init__(self):
        super().__init__()
        self.selected_files = set()
    
    def compose(self) -> ComposeResult:
        yield Header()
        
        with Vertical():
            yield Static("📁 Project Files")
            
            files = [
                ("main.py", "✓", True, "Validated", True),
                ("config.json", "⚠", False, "Needs review", True),
                ("README.md", "📝", False, "Draft", False),
                ("tests.py", "⏳", False, "Running tests", False),
            ]
            
            for filename, icon, toggled, tooltip, clickable in files:
                yield ToggleableFileLink(
                    Path(filename),
                    initial_toggle=toggled,
                    show_toggle=True,
                    show_remove=True,
                    status_icon=icon,
                    status_icon_clickable=clickable,
                    toggle_tooltip="Toggle selection",
                    status_tooltip=tooltip,
                    remove_tooltip="Remove file",
                    line=1,
                )
        
        yield Footer()
    
    def on_toggleable_file_link_toggled(self, event: ToggleableFileLink.Toggled):
        if event.is_toggled:
            self.selected_files.add(event.path)
            self.notify(f"✓ Selected {event.path.name}")
        else:
            self.selected_files.discard(event.path)
            self.notify(f"☐ Deselected {event.path.name}")
    
    def on_toggleable_file_link_removed(self, event: ToggleableFileLink.Removed):
        self.selected_files.discard(event.path)
        # Find and remove the widget
        for child in self.query(ToggleableFileLink):
            if child.path == event.path:
                child.remove()
        self.notify(f"❌ Removed {event.path.name}", severity="warning")
    
    def on_toggleable_file_link_status_icon_clicked(self, event: ToggleableFileLink.StatusIconClicked):
        self.notify(f"Clicked status icon '{event.icon}' for {event.path.name}")
    
    def on_file_link_clicked(self, event):
        self.notify(f"Opening {event.path.name}...")

if __name__ == "__main__":
    FileManagerApp().run()

Development

# Clone the repository
git clone https://github.com/eyecantell/textual-filelink.git
cd textual-filelink

# Install with dev dependencies
pdm install -d

# Run tests
pdm run pytest

# Run tests with coverage
pdm run pytest --cov

# Lint
pdm run ruff check .

# Format
pdm run ruff format .

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Built with Textual by Textualize
  • Inspired by the need for better file navigation in terminal applications

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

textual_filelink-0.1.0.tar.gz (17.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

textual_filelink-0.1.0-py3-none-any.whl (10.7 kB view details)

Uploaded Python 3

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page