# Path: porcupine/tabs.py
# def tabs(self) -> tuple[Tab, ...]:
#     """Return a tuple of tabs in the tab manager.
# 
#     This returns a tuple instead of a list for compatibility with
#     tkinter.
#     """
#     # tkinter has a bug that makes the original tabs() return widget name
#     # strings instead of widget objects
#     return tuple(self.nametowidget(tab) for tab in super().tabs())
# 
# Path: porcupine/utils.py
# _T = TypeVar("_T")
# _LIKELY_PROJECT_ROOT_THINGS = [".editorconfig"] + [
#     readme + extension
#     for readme in ["README", "readme", "Readme", "ReadMe"]
#     for extension in ["", ".txt", ".md", ".rst"]
# ]
#     def quote(string: str) -> str:
# def copy_type(f: _T) -> Callable[[Any], _T]:
# def format_command(command: str, substitutions: dict[str, Any]) -> list[str]:
# def file_url_to_path(file_url: str) -> Path:
# def find_project_root(project_file_path: Path) -> Path:
#     def __init__(self, *args: Any, **kwargs: Any) -> None:
#     def _update_colors(self, junk_event: object = None) -> None:
# def is_bright(color: str) -> bool:
# def invert_color(color: str, *, black_or_white: bool = False) -> str:
# def mix_colors(color1: str, color2: str, color1_amount: float) -> str:
# def _format_binding(binding: str, menu: bool) -> str:
# def get_binding(virtual_event: str, *, menu: bool = False, many: bool = False) -> str:
# def tkinter_safe_string(string: str, *, hide_unsupported_chars: bool = False) -> str:
#     def __str__(self) -> str:
#     def data_class(self, T: Type[_T]) -> _T:
#     def __repr__(self) -> str:
# def bind_with_data(
#     widget: tkinter.Misc,
#     sequence: str,
#     callback: Callable[[EventWithData], str | None],
#     add: bool = False,
# ) -> str:
#     def run_the_callback(data_string: str) -> str | None:
# def add_scroll_command(
#     widget: tkinter.Text,
#     option: Literal["xscrollcommand", "yscrollcommand"],
#     callback: Callable[[], None],
# ) -> None:
# def bind_tab_key(
#     widget: tkinter.Widget, on_tab: Callable[["tkinter.Event[Any]", bool], Any], **bind_kwargs: Any
# ) -> None:
#     def callback(shifted: bool, event: tkinter.Event[tkinter.Misc]) -> Any:
# def ask_encoding(text: str, old_encoding: str) -> str | None:
#     def select_encoding() -> None:
#     def validate_encoding(*junk: object) -> None:
# def run_in_thread(
#     blocking_function: Callable[[], _T],
#     done_callback: Callable[[bool, str | _T], None],
#     *,
#     check_interval_ms: int = 100,
#     daemon: bool = True,
# ) -> None:
#     def thread_target() -> None:
#     def check() -> None:
# def backup_open(file: Any, *args: Any, **kwargs: Any) -> Any:
# class PanedWindow(tkinter.PanedWindow):
# class EventDataclass:
# class EventWithData(_Event):


import dataclasses
import os
from pathlib import Path
from typing import Dict, List, Optional
from porcupine import tabs, utils

@dataclasses.dataclass
class Command:
    command_format: str
    cwd_format: str
    external_terminal: bool
    substitutions: Dict[str, str]

    def format_cwd(self) -> Path:
        return Path(self.cwd_format.format(**self.substitutions))

    def format_command(self) -> str:
        return self.command_format.format(
            **{name: utils.quote(value) for name, value in self.substitutions.items()}
        )


@dataclasses.dataclass
class ExampleCommand:
    command: str
    windows_command: Optional[str] = None
    macos_command: Optional[str] = None
    working_directory: str = "{folder_path}"
    external_terminal: bool = True


class Context:
    def __init__(self, tab: tabs.FileTab, key_id: int):
        assert tab.path is not None
        self.file_path = tab.path

# Path: porcupine/tabs.py
# def tabs(self) -> tuple[Tab, ...]:
#     """Return a tuple of tabs in the tab manager.
# 
#     This returns a tuple instead of a list for compatibility with
#     tkinter.
#     """
#     # tkinter has a bug that makes the original tabs() return widget name
#     # strings instead of widget objects
#     return tuple(self.nametowidget(tab) for tab in super().tabs())
# 
# Path: porcupine/utils.py
# _T = TypeVar("_T")
# _LIKELY_PROJECT_ROOT_THINGS = [".editorconfig"] + [
#     readme + extension
#     for readme in ["README", "readme", "Readme", "ReadMe"]
#     for extension in ["", ".txt", ".md", ".rst"]
# ]
#     def quote(string: str) -> str:
# def copy_type(f: _T) -> Callable[[Any], _T]:
# def format_command(command: str, substitutions: dict[str, Any]) -> list[str]:
# def file_url_to_path(file_url: str) -> Path:
# def find_project_root(project_file_path: Path) -> Path:
#     def __init__(self, *args: Any, **kwargs: Any) -> None:
#     def _update_colors(self, junk_event: object = None) -> None:
# def is_bright(color: str) -> bool:
# def invert_color(color: str, *, black_or_white: bool = False) -> str:
# def mix_colors(color1: str, color2: str, color1_amount: float) -> str:
# def _format_binding(binding: str, menu: bool) -> str:
# def get_binding(virtual_event: str, *, menu: bool = False, many: bool = False) -> str:
# def tkinter_safe_string(string: str, *, hide_unsupported_chars: bool = False) -> str:
#     def __str__(self) -> str:
#     def data_class(self, T: Type[_T]) -> _T:
#     def __repr__(self) -> str:
# def bind_with_data(
#     widget: tkinter.Misc,
#     sequence: str,
#     callback: Callable[[EventWithData], str | None],
#     add: bool = False,
# ) -> str:
#     def run_the_callback(data_string: str) -> str | None:
# def add_scroll_command(
#     widget: tkinter.Text,
#     option: Literal["xscrollcommand", "yscrollcommand"],
#     callback: Callable[[], None],
# ) -> None:
# def bind_tab_key(
#     widget: tkinter.Widget, on_tab: Callable[["tkinter.Event[Any]", bool], Any], **bind_kwargs: Any
# ) -> None:
#     def callback(shifted: bool, event: tkinter.Event[tkinter.Misc]) -> Any:
# def ask_encoding(text: str, old_encoding: str) -> str | None:
#     def select_encoding() -> None:
#     def validate_encoding(*junk: object) -> None:
# def run_in_thread(
#     blocking_function: Callable[[], _T],
#     done_callback: Callable[[bool, str | _T], None],
#     *,
#     check_interval_ms: int = 100,
#     daemon: bool = True,
# ) -> None:
#     def thread_target() -> None:
#     def check() -> None:
# def backup_open(file: Any, *args: Any, **kwargs: Any) -> Any:
# class PanedWindow(tkinter.PanedWindow):
# class EventDataclass:
# class EventWithData(_Event):


# Path: porcupine/plugins/run/common.py
import dataclasses
import os
from pathlib import Path
from typing import Dict, List, Optional
from porcupine import tabs, utils


@dataclasses.dataclass
class Command:
    command_format: str
    cwd_format: str
    external_terminal: bool
    substitutions: Dict[str, str]

    def format_cwd(self) -> Path:
        return Path(self.cwd_format.format(**self.substitutions))

    def format_command(self) -> str:
        return self.command_format.format(
            **{name: utils.quote(value) for name, value in self.substitutions.items()}
        )


@dataclasses.dataclass
class ExampleCommand:
    command: str
    windows_command: Optional[str] = None
    macos_command: Optional[str] = None
    working_directory: str = "{folder_path}"
    external_terminal: bool = True


class Context:
    def __init__(self, tab: tabs.FileTab, key_id: int):
        assert tab.path is not None
        self.file_path = tab.path

        self.project_path = utils.find_project_root(tab.path)